From b2f29d3283cfcb245558b4c0ccd0ca9afd397553 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 31 Jul 2026 08:48:44 +0800 Subject: [PATCH 1/3] chore: housekeeping --- pnpm-lock.yaml | 168 ++++++++++--------------------------------------- 1 file changed, 33 insertions(+), 135 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ed09efd..14af8dd9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -282,7 +282,7 @@ importers: dependencies: '@better-auth/electron': specifier: 1.7.0-rc.1 - version: 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.0-rc.1(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))(react-dom@19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728))(react@19.3.0-canary-96fcba90-20260728)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(electron@43.2.0) + version: 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.0-rc.1(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))(react-dom@19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728))(react@19.3.0-canary-96fcba90-20260728)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(electron@43.2.0) '@linkcode/common': specifier: workspace:* version: link:../../packages/foundation/common @@ -3818,15 +3818,9 @@ packages: '@posthog/browser-common@0.2.3': resolution: {integrity: sha512-LJOUcBLLjFOqw6ZZsjrCuojliJHFU/GYeOV88T+lXyePXJQnxFzuAouDtwwHFHtoaaOjfQ0OvDMR7J468Nkomw==} - '@posthog/core@1.44.0': - resolution: {integrity: sha512-uE+mdKvetxNQC6gWQf4MHIH8bGt+JN6z7ho0A4G3t9G1VGQTx9wHYHNwkKf3gzi+1oAXS9ej2VVY4pjWUU2PWg==} - '@posthog/core@1.45.1': resolution: {integrity: sha512-tLtvzomavb2PPWdGYKsusyIzIeL2Px47v348Smibkay7sMy/83TyPk+Ptsp2NdeOgJsbuwSxWkR2+XA0aSCAaA==} - '@posthog/types@1.397.1': - resolution: {integrity: sha512-W/LpWbKVaaUnfZKuFuHa+Dg03D+fC87cM+PQbG+59JcSPW8F0JcBtSoXmpfrqbpuxUToMo+gktutrUkAb/KQBw==} - '@posthog/types@1.398.0': resolution: {integrity: sha512-sJMkl4k+u8yS/0fjHsKqE9xTdsAh30a2WvgChiptellnVoE0e8QJKFgqOMD2sk8FaEArPdeFklAhXvmENAt3Sg==} @@ -7090,8 +7084,8 @@ packages: peerDependencies: eslint: '>=0.8.0' - eslint-plugin-sukka@9.18.1: - resolution: {integrity: sha512-0h0Vv4rsGchCWivVGSyQq69KqZhjOb1FL/4EXiNgwaA3CcfFubgxGV8T/9K1brj+lYqHrxL9RxYXDKamkhMXFg==} + eslint-plugin-sukka@9.18.2: + resolution: {integrity: sha512-RQa5CedBfRsd6QGBt2sZ7aXctnha+WGp1D1Ym/A6BfIgPeHJxdnZCuvcdv1gk3Gu4MAF+9d4XuG0wwROHcq3sg==} peerDependencies: eslint: '>=9.29.0' typescript: '*' @@ -7105,8 +7099,8 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-plugin-vibe-proof@1.1.0: - resolution: {integrity: sha512-Fbewo0PLM63jH68eewOg3sgAFcCGzeLBG0Ge2a/xyeBigNGXttZn9RDwboLqLGflrjFi37l83nWNKcQHJqaU5g==} + eslint-plugin-vibe-proof@1.5.0: + resolution: {integrity: sha512-mYTEIZseSf16d3SdjRILwHIaF6IOvdOPm85VePzxiS7EIcB/qmVv9WFGZ6b5MFiKCm2GMQoqCYyb8ijpMpFKZQ==} peerDependencies: eslint: '>=9.29.0' typescript: '*' @@ -8029,10 +8023,6 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - iconv-lite@0.7.3: resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} @@ -8118,10 +8108,6 @@ packages: resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} engines: {node: '>=12.22.0'} - ip-address@10.2.0: - resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} - engines: {node: '>= 12'} - ip-address@10.3.1: resolution: {integrity: sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==} engines: {node: '>= 12'} @@ -8246,9 +8232,6 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@6.2.3: - resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} - jose@6.2.4: resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} @@ -9099,11 +9082,6 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.15: - resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@3.3.16: resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -11194,18 +11172,6 @@ packages: utf-8-validate: optional: true - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.21.1: resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} engines: {node: '>=10.0.0'} @@ -12219,20 +12185,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0)': - dependencies: - '@better-auth/utils': 0.4.2 - '@better-fetch/fetch': 1.3.1 - '@opentelemetry/semantic-conventions': 1.41.1 - '@standard-schema/spec': 1.1.0 - better-call: 1.3.7(zod@4.4.3) - jose: 6.2.3 - kysely: 0.29.2 - nanostores: 1.4.0 - zod: 4.4.3 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0)': dependencies: '@better-auth/utils': 0.4.2 @@ -12247,13 +12199,6 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))': - dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) - '@better-auth/utils': 0.4.2 - optionalDependencies: - drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2) - '@better-auth/drizzle-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))': dependencies: '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) @@ -12261,9 +12206,9 @@ snapshots: optionalDependencies: drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2) - '@better-auth/electron@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.0-rc.1(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))(react-dom@19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728))(react@19.3.0-canary-96fcba90-20260728)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(electron@43.2.0)': + '@better-auth/electron@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.0-rc.1(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))(react-dom@19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728))(react@19.3.0-canary-96fcba90-20260728)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(electron@43.2.0)': dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 better-auth: 1.7.0-rc.1(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))(react-dom@19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728))(react@19.3.0-canary-96fcba90-20260728)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0))) @@ -12285,13 +12230,6 @@ snapshots: expo-network: 57.0.1(expo@57.0.4)(react@19.2.3) expo-web-browser: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) - '@better-auth/kysely-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(kysely@0.29.2)': - dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) - '@better-auth/utils': 0.4.2 - optionalDependencies: - kysely: 0.29.2 - '@better-auth/kysely-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(kysely@0.29.2)': dependencies: '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) @@ -12299,42 +12237,21 @@ snapshots: optionalDependencies: kysely: 0.29.2 - '@better-auth/memory-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': - dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) - '@better-auth/utils': 0.4.2 - '@better-auth/memory-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': dependencies: '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': - dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) - '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': dependencies: '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) '@better-auth/utils': 0.4.2 - '@better-auth/prisma-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': - dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) - '@better-auth/utils': 0.4.2 - '@better-auth/prisma-adapter@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)': dependencies: '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) '@better-auth/utils': 0.4.2 - '@better-auth/telemetry@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': - dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) - '@better-auth/utils': 0.4.2 - '@better-fetch/fetch': 1.3.1 - '@better-auth/telemetry@1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': dependencies: '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) @@ -12548,7 +12465,7 @@ snapshots: dependencies: '@types/ws': 8.18.1 effect: 4.0.0-beta.98 - ws: 8.21.0 + ws: 8.21.1 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -12896,7 +12813,7 @@ snapshots: dependencies: '@eslint-sukka/shared': 8.14.2(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) eslint: 10.7.0(jiti@2.7.0) - eslint-plugin-sukka: 9.18.1(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) + eslint-plugin-sukka: 9.18.2(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) optionalDependencies: typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: @@ -12996,7 +12913,7 @@ snapshots: '@expo/router-server': 57.0.2(@expo/metro-runtime@57.0.3)(expo-constants@57.0.3)(expo-font@57.0.0)(expo-router@57.0.4)(expo-server@57.0.0)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@expo/schema-utils': 57.0.1 '@expo/spawn-async': 1.8.0 - '@expo/ws-tunnel': 2.0.0(ws@8.21.0) + '@expo/ws-tunnel': 2.0.0(ws@8.21.1) '@expo/xcpretty': 4.4.4 '@react-native/dev-middleware': 0.86.0 accepts: 1.3.8 @@ -13033,7 +12950,7 @@ snapshots: terminal-link: 2.1.1 toqr: 0.1.1 wrap-ansi: 7.0.0 - ws: 8.21.0 + ws: 8.21.1 zod: 3.25.76 optionalDependencies: expo-router: 57.0.4(a892d21e44c1d5a25ab382e32d303fb9) @@ -13349,9 +13266,9 @@ snapshots: - '@types/react' - '@types/react-dom' - '@expo/ws-tunnel@2.0.0(ws@8.21.0)': + '@expo/ws-tunnel@2.0.0(ws@8.21.1)': dependencies: - ws: 8.21.0 + ws: 8.21.1 '@expo/xcpretty@4.4.4': dependencies: @@ -13405,7 +13322,7 @@ snapshots: google-auth-library: 10.7.0 p-retry: 4.6.2 protobufjs: 7.6.5 - ws: 8.21.0 + ws: 8.21.1 optionalDependencies: '@modelcontextprotocol/sdk': 1.30.0(zod@4.4.3) transitivePeerDependencies: @@ -13847,7 +13764,7 @@ snapshots: '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/semantic-conventions': 1.41.1 - ws: 8.21.0 + ws: 8.21.1 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) optionalDependencies: @@ -14190,16 +14107,10 @@ snapshots: '@posthog/core': 1.45.1 '@posthog/types': 1.398.0 - '@posthog/core@1.44.0': - dependencies: - '@posthog/types': 1.397.1 - '@posthog/core@1.45.1': dependencies: '@posthog/types': 1.398.0 - '@posthog/types@1.397.1': {} - '@posthog/types@1.398.0': {} '@preact/signals-core@1.14.3': {} @@ -16207,7 +16118,7 @@ snapshots: better-auth@1.7.0-rc.1(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0))): dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) '@better-auth/drizzle-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2)) '@better-auth/kysely-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(kysely@0.29.2) '@better-auth/memory-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) @@ -16220,7 +16131,7 @@ snapshots: '@noble/hashes': 2.2.0 better-call: 1.3.7(zod@4.4.3) defu: 6.1.7 - jose: 6.2.3 + jose: 6.2.4 kysely: 0.29.2 nanostores: 1.4.0 zod: 4.4.3 @@ -16237,20 +16148,20 @@ snapshots: better-auth@1.7.0-rc.1(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2))(react-dom@19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728))(react@19.3.0-canary-96fcba90-20260728)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0))): dependencies: - '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0) - '@better-auth/drizzle-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2)) - '@better-auth/kysely-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(kysely@0.29.2) - '@better-auth/memory-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) - '@better-auth/mongo-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) - '@better-auth/prisma-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) - '@better-auth/telemetry': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/core': 1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0) + '@better-auth/drizzle-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.2)) + '@better-auth/kysely-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(kysely@0.29.2) + '@better-auth/memory-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.7.0-rc.1(@better-auth/core@1.7.0-rc.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.2)(nanostores@1.4.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@noble/ciphers': 2.2.0 '@noble/hashes': 2.2.0 better-call: 1.3.7(zod@4.4.3) defu: 6.1.7 - jose: 6.2.3 + jose: 6.2.4 kysely: 0.29.2 nanostores: 1.4.0 zod: 4.4.3 @@ -17468,12 +17379,12 @@ snapshots: eslint: 10.7.0(jiti@2.7.0) globals: 13.24.0 - eslint-plugin-sukka@9.18.1(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)): + eslint-plugin-sukka@9.18.2(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)): dependencies: '@typescript-eslint/types': 8.65.0 '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) eslint: 10.7.0(jiti@2.7.0) - eslint-plugin-vibe-proof: 1.1.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) + eslint-plugin-vibe-proof: 1.5.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) foxts: 5.8.1 typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: @@ -17485,7 +17396,7 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)))(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) - eslint-plugin-vibe-proof@1.1.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)): + eslint-plugin-vibe-proof@1.5.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)): dependencies: '@typescript-eslint/types': 8.65.0 '@typescript-eslint/utils': 8.65.0(@typescript/typescript6@6.0.2)(eslint@10.7.0(jiti@2.7.0)) @@ -17824,7 +17735,7 @@ snapshots: expo-symbols: 57.0.0(expo-font@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) fast-deep-equal: 3.1.3 invariant: 2.2.4 - nanoid: 3.3.15 + nanoid: 3.3.16 query-string: 7.1.3 react: 19.2.3 react-fast-compare: 3.2.2 @@ -18696,11 +18607,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - optional: true - iconv-lite@0.7.3: dependencies: safer-buffer: 2.1.2 @@ -18784,8 +18690,6 @@ snapshots: transitivePeerDependencies: - supports-color - ip-address@10.2.0: {} - ip-address@10.3.1: {} ipaddr.js@1.9.1: {} @@ -18892,8 +18796,6 @@ snapshots: jiti@2.7.0: {} - jose@6.2.3: {} - jose@6.2.4: {} joycon@3.1.1: {} @@ -19925,7 +19827,7 @@ snapshots: minipass-sized: 2.0.0 minizlib: 3.1.0 optionalDependencies: - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 minipass-flush@1.0.7: dependencies: @@ -20010,8 +19912,6 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.15: {} - nanoid@3.3.16: {} nanoid@5.1.16: {} @@ -20368,8 +20268,8 @@ snapshots: posthog-react-native@4.58.0(expo-application@57.0.2(expo@57.0.4))(expo-device@57.0.0(expo@57.0.4))(expo-file-system@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)))(expo-localization@57.0.1(expo@57.0.4)(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)): dependencies: - '@posthog/core': 1.44.0 - '@posthog/types': 1.397.1 + '@posthog/core': 1.45.1 + '@posthog/types': 1.398.0 optionalDependencies: expo-application: 57.0.2(expo@57.0.4) expo-device: 57.0.0(expo@57.0.4) @@ -21466,7 +21366,7 @@ snapshots: socks@2.8.9: dependencies: - ip-address: 10.2.0 + ip-address: 10.3.1 smart-buffer: 4.2.0 sonic-boom@4.2.1: @@ -22317,8 +22217,6 @@ snapshots: ws@7.5.11: {} - ws@8.21.0: {} - ws@8.21.1: {} xcode@3.0.1: From 5465e8641361a71c7fd9db8ed035f2b6b6f77e65 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 31 Jul 2026 09:21:59 +0800 Subject: [PATCH 2/3] chore: make eslint happy --- apps/daemon/e2e/startup.e2e.ts | 4 +- apps/daemon/scripts/dev-clean.mts | 3 +- apps/daemon/scripts/package-daemon.mts | 11 ++- apps/daemon/src/__tests__/ai-gateway.test.ts | 10 ++- .../src/__tests__/secrets-vault.test.ts | 5 +- apps/daemon/src/ai-gateway.ts | 3 +- apps/daemon/src/config.ts | 7 +- apps/daemon/src/diagnostic-sanitizer.ts | 13 +++- apps/daemon/src/index.ts | 4 +- apps/daemon/src/logger.ts | 8 ++- apps/daemon/src/loop-store.ts | 3 +- apps/daemon/src/pty/bench-throughput.mts | 4 +- apps/daemon/src/pty/sidecar.ts | 13 +++- apps/daemon/src/schedule-store.ts | 3 +- .../src/secrets/provider-credentials.ts | 4 +- apps/daemon/src/secrets/vault.ts | 4 +- apps/daemon/src/session-store.ts | 7 +- apps/desktop/e2e/browser-broker.e2e.mts | 8 +-- apps/desktop/e2e/browser-tabs.e2e.mts | 3 +- apps/desktop/e2e/simulator-live.mts | 4 +- apps/desktop/e2e/simulator-panel.e2e.mts | 10 ++- apps/desktop/scripts/build.mts | 4 +- apps/desktop/scripts/dev.mts | 6 +- apps/desktop/scripts/package-app.mts | 13 ++-- apps/desktop/scripts/stage-sidecar.mts | 3 +- apps/desktop/scripts/verify-artifacts.mts | 35 ++++++--- .../main/__tests__/daemon-supervisor.test.ts | 5 +- apps/desktop/src/main/editors.ts | 12 +++- .../src/renderer/src/shell/desktop-shell.tsx | 3 +- .../src/shell/layout/sash-drag-style.ts | 5 +- .../scripts/generate-terminal-themes.mjs | 37 +++++----- .../components/terminal-renderer.native.tsx | 3 +- .../src/runtime/use-seeded-conversation.ts | 5 +- .../data-table/core/use-table-sort.ts | 12 +++- .../src/__tests__/conversation-perf.bench.ts | 29 ++++++-- .../conversation-perf.report.test.ts | 21 ++++-- .../core/src/__tests__/conversation.test.ts | 11 ++- packages/client/core/src/client.ts | 5 +- .../client/core/src/client/event-buffer.ts | 7 +- .../client/core/src/client/loop-log-buffer.ts | 13 +++- .../core/src/client/pending-registry.ts | 8 ++- .../core/src/client/terminal-channel.ts | 17 ++++- .../client/core/src/conversation-store.ts | 12 +++- packages/client/core/src/conversation.ts | 5 +- packages/client/core/src/react.tsx | 10 ++- .../workbench/src/agent-runtime/onboarding.ts | 12 ++-- packages/client/workbench/src/files/locate.ts | 16 +++-- .../client/workbench/src/history/imported.ts | 3 +- .../src/history/use-bulk-history-import.ts | 6 +- .../src/history/use-history-import-surface.ts | 11 ++- .../src/history/use-provider-history.ts | 8 ++- .../workbench/src/mock/dev-mock-host.ts | 13 ++-- .../workbench/src/palette/command-palette.tsx | 4 +- .../providers/__tests__/capability.test.ts | 4 +- .../src/settings/providers/add-flow.tsx | 13 +++- .../src/settings/providers/catalog.ts | 3 +- .../src/settings/providers/default-models.ts | 3 +- .../workbench/src/settings/providers/view.ts | 8 ++- .../client/workbench/src/sidebar/ordering.ts | 3 +- .../__tests__/agent-activity.test.ts | 11 +-- .../simulator/__tests__/panel-store.test.ts | 10 +-- .../src/simulator/background-streams.ts | 5 +- .../src/surface/startup-selection.ts | 3 +- .../src/surface/use-seeded-conversation.ts | 5 +- .../src/surface/use-workbench-sessions.ts | 3 +- .../workbench/src/surface/workbench.tsx | 9 ++- .../integration/dev-mock-transport.test.ts | 6 +- .../__tests__/executable-locations.test.ts | 4 +- .../common/src/node/executable-locations.ts | 4 +- .../foundation/common/src/sentry/index.ts | 12 +++- .../common/src/telemetry-config/index.ts | 2 +- .../schema/tests/contract/wire/loop.test.ts | 3 +- .../tests/contract/wire/schedule.test.ts | 3 +- .../transport/src/__tests__/tunnel.test.ts | 18 +++-- packages/foundation/transport/src/hub.ts | 26 +++++-- .../foundation/transport/src/preview-proxy.ts | 2 +- .../foundation/transport/src/tunnel-client.ts | 22 ++++-- .../tests/integration/preview-proxy.test.ts | 3 +- .../__tests__/claude-code-commands.test.ts | 5 +- .../__tests__/claude-code-subagent.test.ts | 10 ++- .../src/__tests__/codex-app-server.test.ts | 5 +- .../src/__tests__/codex-auth.test.ts | 3 +- .../src/__tests__/pi-approval.test.ts | 4 +- packages/host/agent-adapter/src/credential.ts | 6 +- .../host/agent-adapter/src/history-util.ts | 8 ++- .../agent-adapter/src/native/claude-code.ts | 72 +++++++++++++------ .../agent-adapter/src/native/codex/adapter.ts | 35 ++++++--- .../src/native/codex/history-tools.ts | 3 +- .../agent-adapter/src/native/codex/history.ts | 21 ++++-- .../src/native/codex/unified-diff.ts | 3 +- .../src/native/grok-build/adapter.ts | 2 +- .../src/native/opencode/adapter.ts | 26 +++++-- .../src/native/opencode/history.ts | 6 +- .../agent-adapter/src/native/pi/history.ts | 6 +- .../agent-adapter/src/plugins/claude-code.ts | 6 +- packages/host/agent-adapter/src/probe/base.ts | 7 +- packages/host/agent-adapter/src/util.ts | 3 +- .../host/assets/src/__tests__/catalog.test.ts | 22 ++++-- .../assets/src/__tests__/system-proxy.test.ts | 8 ++- packages/host/assets/src/catalog.ts | 25 +++---- packages/host/assets/src/closure-gen.ts | 11 ++- packages/host/assets/src/download.ts | 3 +- packages/host/assets/src/gc.ts | 3 +- packages/host/assets/src/install.ts | 14 ++-- packages/host/assets/src/registry-client.ts | 3 +- .../host/assets/src/system-proxy/index.ts | 4 +- .../host/assets/src/system-proxy/win32.ts | 8 ++- .../assets/tests/integration/manager.test.ts | 4 +- .../host/assets/tests/support/fixtures.ts | 8 ++- .../src/__tests__/engine-file-suggest.test.ts | 5 +- .../engine/src/__tests__/file-service.test.ts | 5 +- .../__tests__/request-handler-effects.test.ts | 5 +- .../src/__tests__/script-service.test.ts | 10 ++- .../src/__tests__/simulator-service.test.ts | 4 +- .../src/__tests__/terminal-service.test.ts | 20 ++++-- .../src/__tests__/workspace-registry.test.ts | 5 +- .../src/automation/loop-iteration-runner.ts | 3 +- .../engine/src/automation/loop-service.ts | 8 ++- .../automation/schedule-run-coordinator.ts | 3 +- .../engine/src/automation/schedule-store.ts | 5 +- .../host/engine/src/browser/repl-rewrite.ts | 13 ++-- packages/host/engine/src/engine.ts | 4 +- packages/host/engine/src/git/branches.ts | 4 +- packages/host/engine/src/git/diff.ts | 15 ++-- packages/host/engine/src/git/github.ts | 3 +- packages/host/engine/src/git/status.ts | 4 +- packages/host/engine/src/json-equal.ts | 8 ++- packages/host/engine/src/scripts/config.ts | 4 +- .../engine/src/scripts/script-port-plan.ts | 6 +- .../host/engine/src/session/ask-response.ts | 6 +- .../engine/src/session/attachment-guard.ts | 3 +- .../engine/src/session/history-service.ts | 6 +- .../src/session/session-record-registry.ts | 8 ++- packages/host/engine/src/simulator/consent.ts | 5 +- packages/host/engine/src/terminal/replay.ts | 3 +- .../src/workspace/file-suggest-service.ts | 11 +-- .../src/workspace/workspace-registry.ts | 3 +- .../engine/src/worktree/worktree-service.ts | 13 +++- .../tests/integration/engine-worktree.test.ts | 5 +- .../integration/file-suggest-service.test.ts | 5 +- .../engine/tests/integration/git-diff.test.ts | 15 +++- .../tests/integration/git-status.test.ts | 5 +- .../integration/worktree-service.test.ts | 5 +- packages/host/sim/src/__tests__/codec.test.ts | 5 +- packages/host/sim/src/client.ts | 3 +- packages/integrations/im-render/src/blocks.ts | 3 +- packages/integrations/im-render/src/render.ts | 13 ++-- packages/presentation/i18n/src/index.ts | 3 +- .../__tests__/conversation-view-perf.bench.ts | 2 +- .../conversation-view-perf.report.test.ts | 8 ++- .../src/chat/__tests__/activity-run.test.tsx | 13 ++-- .../src/chat/__tests__/card-radius.test.tsx | 4 +- .../chat/__tests__/file-preview-card.test.tsx | 6 +- .../ui/src/chat/__tests__/link-icon.test.tsx | 3 +- .../__tests__/smooth-text-controller.test.ts | 6 +- .../chat/__tests__/turn-diff-summary.test.tsx | 6 +- .../ui/src/chat/activity-groups.ts | 3 +- .../presentation/ui/src/chat/activity-run.tsx | 3 +- .../ui/src/chat/activity-summary.ts | 6 +- .../ui/src/chat/artifacts/registry.ts | 3 +- .../ui/src/chat/conversation-prompts.ts | 3 +- .../ui/src/chat/conversation-text.ts | 9 ++- .../presentation/ui/src/chat/diff-utils.ts | 7 +- .../ui/src/chat/file-tool-presentation.ts | 3 +- .../presentation/ui/src/chat/markdown.tsx | 4 +- .../ui/src/chat/stack-trace-parser.ts | 8 ++- .../presentation/ui/src/chat/subagents.ts | 6 +- .../ui/src/chat/tool-result-content.ts | 3 +- .../presentation/ui/src/chat/tool-utils.ts | 4 +- .../presentation/ui/src/chat/turn-edits.ts | 16 +++-- .../ui/src/chat/use-timeline-model.ts | 7 +- .../src/keyboard/__tests__/registry.test.ts | 11 ++- .../presentation/ui/src/keyboard/hooks.ts | 13 ++-- .../presentation/ui/src/keyboard/registry.ts | 10 ++- packages/presentation/ui/src/lib/file-icon.ts | 9 ++- .../__tests__/composer-command-menu.test.tsx | 14 +++- .../composer-directive-chips.test.tsx | 12 ++-- .../presentation/ui/src/shell/agent-models.ts | 4 +- .../ui/src/shell/composer-command.tsx | 9 ++- .../ui/src/shell/composer-controls.tsx | 7 +- .../__tests__/composer-editor.test.ts | 27 +++---- .../ui/src/shell/composer-editor/editor.tsx | 5 +- .../ui/src/shell/composer-editor/serialize.ts | 10 ++- .../ui/src/shell/composer-editor/tokenize.ts | 4 +- .../presentation/ui/src/shell/composer.tsx | 6 +- .../presentation/ui/src/shell/history/sort.ts | 3 +- .../ui/src/shell/new-session-surface.tsx | 3 +- .../ui/src/shell/permission-prompt.tsx | 7 +- .../src/shell/simulator/device-compositor.ts | 6 +- .../ui/src/shell/terminal/fonts.ts | 3 +- .../ui/src/shell/threads-view.tsx | 13 ++-- packages/presentation/ui/src/thread-groups.ts | 3 +- .../system-plane/ipc/src/electron-main.ts | 3 +- 193 files changed, 1113 insertions(+), 470 deletions(-) diff --git a/apps/daemon/e2e/startup.e2e.ts b/apps/daemon/e2e/startup.e2e.ts index a843129d..98181341 100644 --- a/apps/daemon/e2e/startup.e2e.ts +++ b/apps/daemon/e2e/startup.e2e.ts @@ -120,7 +120,9 @@ async function main(): Promise { .map((row) => (row as { name: string }).name), ); sqlite.close(); - for (const table of ['__drizzle_migrations', 'sessions', 'workspaces', 'schedules', 'loops']) { + const expectedTables = ['__drizzle_migrations', 'sessions', 'workspaces', 'schedules', 'loops']; + for (let i = 0, len = expectedTables.length; i < len; i++) { + const table = expectedTables[i]; assert(tables.has(table), `missing migrated table ${table}`); } diff --git a/apps/daemon/scripts/dev-clean.mts b/apps/daemon/scripts/dev-clean.mts index ae90d3fe..4a1e574e 100644 --- a/apps/daemon/scripts/dev-clean.mts +++ b/apps/daemon/scripts/dev-clean.mts @@ -3,4 +3,5 @@ import { databasePath, runtimeFilePath } from '../src/config'; // Resolves through config.ts so a fork's renamed state dir, the resolved channel, or an active // LINKCODE_PROFILE cleans the same universe the dev daemon will actually use. -for (const path of [databasePath(), runtimeFilePath()]) rmSync(path, { force: true }); +const paths = [databasePath(), runtimeFilePath()]; +for (let i = 0, len = paths.length; i < len; i++) rmSync(paths[i], { force: true }); diff --git a/apps/daemon/scripts/package-daemon.mts b/apps/daemon/scripts/package-daemon.mts index 2e0dbda8..3f26ecda 100644 --- a/apps/daemon/scripts/package-daemon.mts +++ b/apps/daemon/scripts/package-daemon.mts @@ -60,10 +60,13 @@ run('pnpm', ['--filter', '@linkcode/daemon', '--prod', 'deploy', '--legacy', out rmSync(join(outDir, 'dist'), { recursive: true, force: true }); cpSync(join(daemonDir, 'dist'), join(outDir, 'dist'), { recursive: true }); -for (const [scope, prefix] of PRUNE) { +for (let i = 0, len = PRUNE.length; i < len; i++) { + const [scope, prefix] = PRUNE[i]; const scopeDir = join(outDir, 'node_modules', scope); if (!existsSync(scopeDir)) continue; - for (const entry of readdirSync(scopeDir)) { + const entries = readdirSync(scopeDir); + for (let entryIndex = 0, entryCount = entries.length; entryIndex < entryCount; entryIndex++) { + const entry = entries[entryIndex]; if (entry.startsWith(prefix)) rmSync(join(scopeDir, entry), { recursive: true, force: true }); } } @@ -73,7 +76,9 @@ console.log(`daemon packaged at ${outDir} (${Math.round(bytes / 1e6)} MB)`); function dirSize(dir: string): number { let total = 0; - for (const entry of readdirSync(dir, { withFileTypes: true })) { + const entries = readdirSync(dir, { withFileTypes: true }); + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; const full = join(dir, entry.name); if (entry.isDirectory()) total += dirSize(full); else if (entry.isFile()) total += statSync(full).size; diff --git a/apps/daemon/src/__tests__/ai-gateway.test.ts b/apps/daemon/src/__tests__/ai-gateway.test.ts index 65b5fa8b..084f7e69 100644 --- a/apps/daemon/src/__tests__/ai-gateway.test.ts +++ b/apps/daemon/src/__tests__/ai-gateway.test.ts @@ -30,10 +30,16 @@ class FakeChild implements SidecarChildProcess { this.killed = true; } emitStdout(text: string): void { - for (const listener of this.dataListeners) listener(text); + for (let i = 0, len = this.dataListeners.length; i < len; i++) { + const listener = this.dataListeners[i]; + listener(text); + } } emitExit(code: number | null): void { - for (const listener of this.exitListeners) listener(code); + for (let i = 0, len = this.exitListeners.length; i < len; i++) { + const listener = this.exitListeners[i]; + listener(code); + } } } diff --git a/apps/daemon/src/__tests__/secrets-vault.test.ts b/apps/daemon/src/__tests__/secrets-vault.test.ts index c3a0d655..de421991 100644 --- a/apps/daemon/src/__tests__/secrets-vault.test.ts +++ b/apps/daemon/src/__tests__/secrets-vault.test.ts @@ -25,8 +25,9 @@ const noKeyring = (): MasterKey | null => null; * Secrets are only reachable through a namespace, so every case goes through one. On disk the same * entry is the full `cloud:session` ref — asserting against that is what proves the prefixing. */ -const open = (file: string, loadKey: () => MasterKey | null): SecretStore => - createSecretVault(file, loadKey).namespace('cloud'); +function open(file: string, loadKey: () => MasterKey | null): SecretStore { + return createSecretVault(file, loadKey).namespace('cloud'); +} let file: string; const realPlatform = process.platform; diff --git a/apps/daemon/src/ai-gateway.ts b/apps/daemon/src/ai-gateway.ts index cb0a8e41..1e14f709 100644 --- a/apps/daemon/src/ai-gateway.ts +++ b/apps/daemon/src/ai-gateway.ts @@ -136,7 +136,8 @@ export function createAiGatewaySidecar(options: AiGatewaySidecarOptions = {}): T const pending = [...running.values()]; running.clear(); const settled = await Promise.allSettled(pending); - for (const result of settled) { + for (let i = 0, len = settled.length; i < len; i++) { + const result = settled[i]; if (result.status === 'fulfilled') result.value.child.kill('SIGTERM'); } }, diff --git a/apps/daemon/src/config.ts b/apps/daemon/src/config.ts index b69b7498..ecbd70e9 100644 --- a/apps/daemon/src/config.ts +++ b/apps/daemon/src/config.ts @@ -187,7 +187,8 @@ function parseAccounts(store: SecretStore, raw: unknown): Parsed { } const accounts: Accounts = []; let migrated = false; - for (const value of raw) { + for (let i = 0, len = raw.length; i < len; i++) { + const value = raw[i]; // The credential secret lives in the vault (CODE-371); merge it back before validating, so a // secret that is gone fails the schema and lands in the same drop-and-log path as a malformed one. const attached = withAccountSecret(store, value); @@ -214,7 +215,9 @@ function parseProviders(store: SecretStore, raw: unknown): Parsed): unknown seen.set(value, sanitized); const cause = Reflect.get(value, 'cause'); if (cause !== undefined) sanitized.cause = sanitizeValue(cause, seen); - for (const [key, entry] of Object.entries(value)) { + const entries = Object.entries(value); + for (let i = 0, len = entries.length; i < len; i++) { + const [key, entry] = entries[i]; sanitized[key] = SENSITIVE_KEY.test(key) ? REDACTED : sanitizeValue(entry, seen); } return sanitized; @@ -49,7 +51,10 @@ function sanitizeValue(value: unknown, seen: WeakMap): unknown if (Array.isArray(value)) { const sanitized: unknown[] = []; seen.set(value, sanitized); - for (const entry of value) sanitized.push(sanitizeValue(entry, seen)); + for (let i = 0, len = value.length; i < len; i++) { + const entry = value[i]; + sanitized.push(sanitizeValue(entry, seen)); + } return sanitized; } @@ -59,7 +64,9 @@ function sanitizeValue(value: unknown, seen: WeakMap): unknown const sanitized: Record = {}; seen.set(value, sanitized); - for (const [key, entry] of Object.entries(value)) { + const entries = Object.entries(value); + for (let i = 0, len = entries.length; i < len; i++) { + const [key, entry] = entries[i]; sanitized[key] = SENSITIVE_KEY.test(key) ? REDACTED : sanitizeValue(entry, seen); } return sanitized; diff --git a/apps/daemon/src/index.ts b/apps/daemon/src/index.ts index a0dfaed4..8c8cc114 100644 --- a/apps/daemon/src/index.ts +++ b/apps/daemon/src/index.ts @@ -276,7 +276,9 @@ async function main(): Promise { const engine = yield* EngineService; void agentRuntimesReady .then((agentRuntimes) => { - for (const kind of agentsToRefresh(consentedAgents, agentRuntimes, assets)) { + const agents = agentsToRefresh(consentedAgents, agentRuntimes, assets); + for (let i = 0, len = agents.length; i < len; i++) { + const kind = agents[i]; void assets .ensure(managedAgentAssetId(kind)) .catch((err) => { diff --git a/apps/daemon/src/logger.ts b/apps/daemon/src/logger.ts index b1447530..423969c5 100644 --- a/apps/daemon/src/logger.ts +++ b/apps/daemon/src/logger.ts @@ -56,7 +56,7 @@ export function createDaemonLogger( name: 'linkcode-daemon', hooks: { logMethod(args, method) { - for (let index = 0; index < args.length; index += 1) { + for (let index = 0, len = args.length; index < len; index += 1) { args[index] = sanitizeDiagnostic(args[index]); } method.apply(this, args); @@ -78,9 +78,11 @@ export const logger = createDaemonLogger(); function effectBindings(messages: readonly unknown[]): Record { const bindings: Record = { source: 'effect' }; - for (const message of messages) { + for (let i = 0, len = messages.length; i < len; i++) { + const message = messages[i]; if (typeof message !== 'object' || message === null) continue; - for (const key of EFFECT_BINDING_KEYS) { + for (let keyIndex = 0, keyCount = EFFECT_BINDING_KEYS.length; keyIndex < keyCount; keyIndex++) { + const key = EFFECT_BINDING_KEYS[keyIndex]; const value = Reflect.get(message, key); if (typeof value === 'string' || typeof value === 'number') bindings[key] = value; } diff --git a/apps/daemon/src/loop-store.ts b/apps/daemon/src/loop-store.ts index 82d74949..27f130b8 100644 --- a/apps/daemon/src/loop-store.ts +++ b/apps/daemon/src/loop-store.ts @@ -77,7 +77,8 @@ export function createLoopStore(dbPath: string): LoopStore { /** Parse each row through its schema; drop and log a row that fails rather than failing the load. */ function parseAll(rows: Row[], parse: (row: Row) => T): T[] { const parsed: T[] = []; - for (const row of rows) { + for (let i = 0, len = rows.length; i < len; i++) { + const row = rows[i]; try { parsed.push(parse(row)); } catch { diff --git a/apps/daemon/src/pty/bench-throughput.mts b/apps/daemon/src/pty/bench-throughput.mts index 874e3905..578db364 100644 --- a/apps/daemon/src/pty/bench-throughput.mts +++ b/apps/daemon/src/pty/bench-throughput.mts @@ -67,7 +67,9 @@ async function runLinkCodePty( const pending = new Map(); child.stdout.on('data', (chunk: Buffer) => { - for (const frame of decoder.feed(chunk)) { + const frames = decoder.feed(chunk); + for (let i = 0, len = frames.length; i < len; i++) { + const frame = frames[i]; switch (frame.type) { case OPENED: case OUTPUT: diff --git a/apps/daemon/src/pty/sidecar.ts b/apps/daemon/src/pty/sidecar.ts index 392af938..ffe20167 100644 --- a/apps/daemon/src/pty/sidecar.ts +++ b/apps/daemon/src/pty/sidecar.ts @@ -135,7 +135,8 @@ export class SidecarPtyBackend implements PtyBackend { this.child = child; child.stdout.on('data', (chunk: Buffer) => { try { - for (const frame of this.decoder.feed(chunk)) this.handleFrame(frame); + const frames = this.decoder.feed(chunk); + for (let i = 0, len = frames.length; i < len; i++) this.handleFrame(frames[i]); } catch (err) { logger.error( { @@ -219,7 +220,10 @@ export class SidecarPtyBackend implements PtyBackend { const unsub = terminal.exited ? noop : terminal.data.add(cb); if (!terminal.dataSubscribed) { terminal.dataSubscribed = true; - for (const data of terminal.bufferedData) cb(data); + for (let i = 0, len = terminal.bufferedData.length; i < len; i++) { + const data = terminal.bufferedData[i]; + cb(data); + } terminal.bufferedData.length = 0; } return unsub; @@ -271,7 +275,10 @@ export class SidecarPtyBackend implements PtyBackend { } this.pending.clear(); const terminalIds = Array.from(this.terminals.keys()); - for (const terminalId of terminalIds) this.finish(terminalId, null, false); + for (let i = 0, len = terminalIds.length; i < len; i++) { + const terminalId = terminalIds[i]; + this.finish(terminalId, null, false); + } } } diff --git a/apps/daemon/src/schedule-store.ts b/apps/daemon/src/schedule-store.ts index d69b4fd4..c1a67760 100644 --- a/apps/daemon/src/schedule-store.ts +++ b/apps/daemon/src/schedule-store.ts @@ -92,7 +92,8 @@ export function createScheduleStore(dbPath: string): ScheduleStore { /** Parse each row through its schema; drop and log a row that fails rather than failing the load. */ function parseAll(rows: Row[], parse: (row: Row) => T): T[] { const parsed: T[] = []; - for (const row of rows) { + for (let i = 0, len = rows.length; i < len; i++) { + const row = rows[i]; try { parsed.push(parse(row)); } catch { diff --git a/apps/daemon/src/secrets/provider-credentials.ts b/apps/daemon/src/secrets/provider-credentials.ts index bb3acb8d..9e9cae8a 100644 --- a/apps/daemon/src/secrets/provider-credentials.ts +++ b/apps/daemon/src/secrets/provider-credentials.ts @@ -76,7 +76,9 @@ export function detachProviderSecrets( const stripped: ProvidersConfig = {}; const secrets = new Map(); - for (const [kind, config] of Object.entries(providers)) { + const entries = Object.entries(providers); + for (let i = 0, len = entries.length; i < len; i++) { + const [kind, config] = entries[i]; const { apiKey, ...rest } = config; if (apiKey !== undefined) secrets.set(kind, apiKey); Reflect.set(stripped, kind, rest); diff --git a/apps/daemon/src/secrets/vault.ts b/apps/daemon/src/secrets/vault.ts index ac132eee..075ca4e8 100644 --- a/apps/daemon/src/secrets/vault.ts +++ b/apps/daemon/src/secrets/vault.ts @@ -231,7 +231,9 @@ function parseEntries(raw: unknown, file: string): Map { return new Map(); } const secrets = new Map(); - for (const [ref, value] of Object.entries(raw)) { + const entries = Object.entries(raw); + for (let i = 0, len = entries.length; i < len; i++) { + const [ref, value] = entries[i]; if (typeof value === 'string') secrets.set(ref, value); else logger.warn({ operation: 'secrets.vault', ref }, 'Dropping a malformed stored secret'); } diff --git a/apps/daemon/src/session-store.ts b/apps/daemon/src/session-store.ts index 0e4364ac..72cd0ef2 100644 --- a/apps/daemon/src/session-store.ts +++ b/apps/daemon/src/session-store.ts @@ -30,7 +30,9 @@ function reconcileMigrationLedger(sqlite: Sqlite.Database, migrationsFolder: str 'UPDATE __drizzle_migrations SET created_at = ? WHERE hash = ? AND created_at <> ?', ); sqlite.transaction(() => { - for (const migration of readMigrationFiles({ migrationsFolder })) { + const migrations = readMigrationFiles({ migrationsFolder }); + for (let i = 0, len = migrations.length; i < len; i++) { + const migration = migrations[i]; realign.run(migration.folderMillis, migration.hash, migration.folderMillis); } })(); @@ -59,7 +61,8 @@ export function createSessionStore(dbPath: string): SessionStore { .orderBy(asc(sessionRuns.sessionId), asc(sessionRuns.seq)) .all(); const runsBySession = new Map(); - for (const run of runRows) { + for (let i = 0, len = runRows.length; i < len; i++) { + const run = runRows[i]; const bucket = runsBySession.get(run.sessionId); if (bucket) bucket.push(run); else runsBySession.set(run.sessionId, [run]); diff --git a/apps/desktop/e2e/browser-broker.e2e.mts b/apps/desktop/e2e/browser-broker.e2e.mts index 13b542f3..7777c4a8 100644 --- a/apps/desktop/e2e/browser-broker.e2e.mts +++ b/apps/desktop/e2e/browser-broker.e2e.mts @@ -61,11 +61,11 @@ function createWireClient(): { } { const socket = io(`http://127.0.0.1:${PORT}`, { transports: ['websocket'] }); let seq = 0; - const backlog: Record[] = []; - const waiters: { + const backlog: Array> = []; + const waiters: Array<{ predicate: (payload: Record) => boolean; resolve: (payload: Record) => void; - }[] = []; + }> = []; socket.on('frame', (frame: WireFrame) => { const index = waiters.findIndex((waiter) => waiter.predicate(frame.payload)); if (index === -1) { @@ -160,7 +160,7 @@ async function main(): Promise { url: 'https://example.com/', }); if (opened.ok !== true) fail(`tabs.open failed: ${JSON.stringify(opened)}`); - const tabs = opened.data as { id: string; url: string | null; active: boolean }[]; + const tabs = opened.data as Array<{ id: string; url: string | null; active: boolean }>; const active = tabs.find((tab) => tab.active); if (active?.url !== 'https://example.com/') { fail(`expected an active example.com tab, got ${JSON.stringify(tabs)}`); diff --git a/apps/desktop/e2e/browser-tabs.e2e.mts b/apps/desktop/e2e/browser-tabs.e2e.mts index 25bb0ead..672d3eeb 100644 --- a/apps/desktop/e2e/browser-tabs.e2e.mts +++ b/apps/desktop/e2e/browser-tabs.e2e.mts @@ -91,7 +91,8 @@ function logPageErrors(page: Page): void { } function watchAppErrors(app: ElectronApplication): void { - for (const page of app.windows()) logPageErrors(page); + const pages = app.windows(); + for (let i = 0, len = pages.length; i < len; i++) logPageErrors(pages[i]); app.on('window', logPageErrors); } diff --git a/apps/desktop/e2e/simulator-live.mts b/apps/desktop/e2e/simulator-live.mts index 7117f83c..62e7559f 100644 --- a/apps/desktop/e2e/simulator-live.mts +++ b/apps/desktop/e2e/simulator-live.mts @@ -148,7 +148,9 @@ async function main(): Promise { await win.waitForTimeout(1200); } const plus = win.locator('button[aria-label="Open window"]:visible'); - for (const candidate of await plus.all()) { + const candidates = await plus.all(); + for (let i = 0, len = candidates.length; i < len; i++) { + const candidate = candidates[i]; try { await candidate.click({ timeout: 2000 }); break; diff --git a/apps/desktop/e2e/simulator-panel.e2e.mts b/apps/desktop/e2e/simulator-panel.e2e.mts index af5f860f..dfbb9741 100644 --- a/apps/desktop/e2e/simulator-panel.e2e.mts +++ b/apps/desktop/e2e/simulator-panel.e2e.mts @@ -243,7 +243,9 @@ async function run( let plusTarget = null as Awaited>[number] | null; const plusDeadline = Date.now() + 15000; while (plusTarget === null && Date.now() < plusDeadline) { - for (const candidate of await plus.all()) { + const candidates = await plus.all(); + for (let i = 0, len = candidates.length; i < len; i++) { + const candidate = candidates[i]; try { await candidate.click({ trial: true, timeout: 1000 }); plusTarget = candidate; @@ -259,7 +261,9 @@ async function run( await win.screenshot({ path: shot }); console.error(`screenshot: ${shot}`); console.error(`toggle aria-pressed: ${await toggle.getAttribute('aria-pressed')}`); - for (const candidate of await plus.all()) { + const candidates = await plus.all(); + for (let i = 0, len = candidates.length; i < len; i++) { + const candidate = candidates[i]; console.error(`plus box: ${JSON.stringify(await candidate.boundingBox())}`); } fail('the section strip + trigger never became clickable'); @@ -382,7 +386,7 @@ async function run( ctx.drawImage(source, 0, 0, 32, 64); const data = ctx.getImageData(0, 0, 32, 64).data; let hash = 0; - for (let i = 0; i < data.length; i += 16) { + for (let i = 0, len = data.length; i < len; i += 16) { hash = ((hash * 31 + data[i]) & 0xff_ff_ff) >>> 0; } return hash; diff --git a/apps/desktop/scripts/build.mts b/apps/desktop/scripts/build.mts index 84da46d1..1200d4c8 100644 --- a/apps/desktop/scripts/build.mts +++ b/apps/desktop/scripts/build.mts @@ -13,7 +13,9 @@ async function main(): Promise { const desktopDir = resolve(import.meta.dirname, '..'); // Main goes first: its closeBundle stages out/daemon + out/drizzle, matching electron-vite's // main → preload → renderer order. - for (const target of ['main', 'preload', 'renderer']) { + const targets = ['main', 'preload', 'renderer']; + for (let i = 0, len = targets.length; i < len; i++) { + const target = targets[i]; await build({ configFile: resolve(desktopDir, `vite.${target}.config.ts`), mode }); } } diff --git a/apps/desktop/scripts/dev.mts b/apps/desktop/scripts/dev.mts index dbf645db..4c37c972 100644 --- a/apps/desktop/scripts/dev.mts +++ b/apps/desktop/scripts/dev.mts @@ -13,7 +13,7 @@ async function main(): Promise { const args = process.argv.slice(2); const electronArgs: string[] = []; let mode = 'development'; - for (let i = 0; i < args.length; i++) { + for (let i = 0, len = args.length; i < len; i++) { const arg = args[i]; if (arg === '--') continue; if (arg === '--mode') { @@ -30,7 +30,9 @@ async function main(): Promise { process.env.NODE_ENV ??= 'development'; const desktopDir = resolve(import.meta.dirname, '..'); - for (const target of ['main', 'preload']) { + const targets = ['main', 'preload']; + for (let i = 0, len = targets.length; i < len; i++) { + const target = targets[i]; await build({ configFile: resolve(desktopDir, `vite.${target}.config.ts`), mode }); } diff --git a/apps/desktop/scripts/package-app.mts b/apps/desktop/scripts/package-app.mts index 63ffeccb..5c2afa03 100644 --- a/apps/desktop/scripts/package-app.mts +++ b/apps/desktop/scripts/package-app.mts @@ -80,7 +80,9 @@ function materializeStaging(): void { ); // deploy's file selection skips .gitignore'd paths inconsistently across pnpm versions; sync the // build outputs in explicitly so `files: out/**` and `extraResources: sidecar/${arch}` resolve. - for (const dir of ['out', 'sidecar']) { + const buildOutputDirs = ['out', 'sidecar']; + for (let i = 0, len = buildOutputDirs.length; i < len; i++) { + const dir = buildOutputDirs[i]; const dest = join(stagingDir, dir); rmSync(dest, { recursive: true, force: true }); cpSync(join(desktopDir, dir), dest, { recursive: true }); @@ -103,10 +105,12 @@ const MARKDOWN_RE = /\.(?:md|markdown)$/i; function pruneStaging(): void { let files = 0; let bytes = 0; - for (const entry of readdirSync(join(stagingDir, 'node_modules'), { + const entries = readdirSync(join(stagingDir, 'node_modules'), { recursive: true, withFileTypes: true, - })) { + }); + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; if (!entry.isFile()) continue; const prunable = entry.name.endsWith('.map') || @@ -134,7 +138,8 @@ function stagedArches(): string[] { ); if (staged.length === 0) throw new Error('no staged sidecar arch; run stage:host-runtime first'); const arches = requestedArches.length === 0 ? staged : requestedArches; - for (const arch of arches) { + for (let i = 0, len = arches.length; i < len; i++) { + const arch = arches[i]; if (!staged.includes(arch)) throw new Error(`sidecar/${arch} is not staged`); } return arches; diff --git a/apps/desktop/scripts/stage-sidecar.mts b/apps/desktop/scripts/stage-sidecar.mts index c7c9e706..423b8172 100644 --- a/apps/desktop/scripts/stage-sidecar.mts +++ b/apps/desktop/scripts/stage-sidecar.mts @@ -44,7 +44,8 @@ function stage(arch: string, cross?: CrossBuild): void { }); const destDir = join(desktopDir, 'sidecar', arch); mkdirSync(destDir, { recursive: true }); - for (const crate of crates) { + for (let i = 0, len = crates.length; i < len; i++) { + const crate = crates[i]; const binary = process.platform === 'win32' ? `${crate}.exe` : crate; const built = join(repoRoot, 'target', ...(cross ? [cross.target] : []), 'release', binary); cpSync(built, join(destDir, binary)); diff --git a/apps/desktop/scripts/verify-artifacts.mts b/apps/desktop/scripts/verify-artifacts.mts index 97ca4ddf..2816f5c4 100644 --- a/apps/desktop/scripts/verify-artifacts.mts +++ b/apps/desktop/scripts/verify-artifacts.mts @@ -187,7 +187,8 @@ function verifyNativeBindings(platform: string, resourceDir: string, problems: s const bindings: Array<[label: string, path: string]> = [['better-sqlite3', NATIVE_BINDING]]; if (keyring !== null) bindings.push(['@napi-rs/keyring', keyring]); - for (const [label, relative] of bindings) { + for (let i = 0, len = bindings.length; i < len; i++) { + const [label, relative] = bindings[i]; const binding = join(RELEASE_DIR, resourceDir, 'app.asar.unpacked', relative); if (!existsSync(binding)) { problems.push(`${resourceDir}: missing native binding ${relative} (${label})`); @@ -208,7 +209,8 @@ function verifyNativeBindings(platform: string, resourceDir: string, problems: s function verifyHostRuntime(resourceDir: string, problems: string[]): void { const asarPath = join(RELEASE_DIR, resourceDir, 'app.asar'); let missing = false; - for (const inner of ASAR_HOST_RUNTIME) { + for (let i = 0, len = ASAR_HOST_RUNTIME.length; i < len; i++) { + const inner = ASAR_HOST_RUNTIME[i]; try { // asar's lookup splits on the platform separator — normalize or Windows never matches. statFile(asarPath, inner.replaceAll('/', sep)); @@ -240,7 +242,9 @@ function verifyNoAgentBinaries(resourceDir: string, asarPath: string, problems: ); } const shipped: string[] = []; - for (const raw of listPackage(asarPath, { isPack: false })) { + const packageEntries = listPackage(asarPath, { isPack: false }); + for (let i = 0, len = packageEntries.length; i < len; i++) { + const raw = packageEntries[i]; const normalized = raw.replaceAll('\\', '/'); const entry = normalized[0] === '/' ? normalized.slice(1) : normalized; if (EXCLUDED_MODULE_PREFIXES.some((prefix) => entry.startsWith(prefix))) shipped.push(entry); @@ -250,7 +254,8 @@ function verifyNoAgentBinaries(resourceDir: string, asarPath: string, problems: `${resourceDir}/app.asar: agent platform packages shipped: ${shipped[0]} (+${shipped.length - 1} more)`, ); } - for (const prefix of EXCLUDED_MODULE_PREFIXES) { + for (let i = 0, len = EXCLUDED_MODULE_PREFIXES.length; i < len; i++) { + const prefix = EXCLUDED_MODULE_PREFIXES[i]; const dir = join( RELEASE_DIR, resourceDir, @@ -260,7 +265,9 @@ function verifyNoAgentBinaries(resourceDir: string, asarPath: string, problems: if (!existsSync(dir)) continue; const base = prefix.split('/').at(-1)!; - for (const entry of readdirSync(dir)) { + const entries = readdirSync(dir); + for (let entryIndex = 0, entryCount = entries.length; entryIndex < entryCount; entryIndex++) { + const entry = entries[entryIndex]; if (entry.startsWith(base)) { problems.push(`${resourceDir}/app.asar.unpacked: agent platform package shipped: ${entry}`); } @@ -277,7 +284,9 @@ interface FeedEntry { function parseFeedEntries(text: string): FeedEntry[] { const entries: FeedEntry[] = []; let current: FeedEntry | null = null; - for (const line of text.split('\n')) { + const lines = text.split('\n'); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; const url = FEED_URL_LINE.exec(line); const sha = FEED_SHA_LINE.exec(line); if (url) entries.push((current = { url: url[1].trim(), sha512: '' })); @@ -313,7 +322,8 @@ async function verifyFeed(feed: string, archTokens: string[], problems: string[] if (!text.includes(`version: ${version}`)) problems.push(`${feed}: version is not ${version}`); const entries = parseFeedEntries(text); if (entries.length === 0) problems.push(`${feed}: no file entries parsed`); - for (const token of archTokens) { + for (let i = 0, len = archTokens.length; i < len; i++) { + const token = archTokens[i]; if (!entries.some((entry) => entry.url.includes(token))) { problems.push(`${feed}: no entry for arch "${token}"`); } @@ -344,7 +354,8 @@ async function main(): Promise { } const problems: string[] = []; - for (const name of expected.artifacts) { + for (let i = 0, len = expected.artifacts.length; i < len; i++) { + const name = expected.artifacts[i]; let size: number; try { size = statSync(join(RELEASE_DIR, name)).size; @@ -363,13 +374,17 @@ async function main(): Promise { verifyFeed(feed, archTokens, problems), ), ); - for (const resourceDir of expected.resourceDirs) { + for (let i = 0, len = expected.resourceDirs.length; i < len; i++) { + const resourceDir = expected.resourceDirs[i]; verifyHostRuntime(resourceDir, problems); verifyNativeBindings(platform, resourceDir, problems); } if (problems.length > 0) { - for (const problem of problems) console.error(`✗ ${problem}`); + for (let i = 0, len = problems.length; i < len; i++) { + const problem = problems[i]; + console.error(`✗ ${problem}`); + } return 1; } console.log( diff --git a/apps/desktop/src/main/__tests__/daemon-supervisor.test.ts b/apps/desktop/src/main/__tests__/daemon-supervisor.test.ts index 27947597..d350c5a1 100644 --- a/apps/desktop/src/main/__tests__/daemon-supervisor.test.ts +++ b/apps/desktop/src/main/__tests__/daemon-supervisor.test.ts @@ -74,7 +74,10 @@ function fakeUtilityProcess(): FakeUtilityProcess { return this.on(event, listener); }, emitExit(code) { - for (const listener of exitListeners) listener(code); + for (let i = 0, len = exitListeners.length; i < len; i++) { + const listener = exitListeners[i]; + listener(code); + } }, }; } diff --git a/apps/desktop/src/main/editors.ts b/apps/desktop/src/main/editors.ts index 71695b50..fa8f04d2 100644 --- a/apps/desktop/src/main/editors.ts +++ b/apps/desktop/src/main/editors.ts @@ -135,12 +135,16 @@ export function editorTargets( ): EditorTarget[] { const targets: EditorTarget[] = []; if (platform !== 'win32' && candidate.cli !== undefined) { - for (const file of executableSearchLocations(candidate.cli)) { + const locations = executableSearchLocations(candidate.cli); + for (let i = 0, len = locations.length; i < len; i++) { + const file = locations[i]; targets.push({ kind: 'executable', file }); } } if (platform === 'darwin' && candidate.macApp !== undefined) { - for (const root of ['/Applications', join(homedir(), 'Applications')]) { + const roots = ['/Applications', join(homedir(), 'Applications')]; + for (let i = 0, len = roots.length; i < len; i++) { + const root = roots[i]; targets.push({ kind: 'mac-app', bundle: join(root, candidate.macApp), @@ -149,7 +153,9 @@ export function editorTargets( } } if (platform === 'win32' && candidate.windowsExe !== undefined) { - for (const root of windowsProgramRoots()) { + const roots = windowsProgramRoots(); + for (let i = 0, len = roots.length; i < len; i++) { + const root = roots[i]; targets.push({ kind: 'executable', file: join(root, candidate.windowsExe) }); } } diff --git a/apps/desktop/src/renderer/src/shell/desktop-shell.tsx b/apps/desktop/src/renderer/src/shell/desktop-shell.tsx index 816b72e2..63b43211 100644 --- a/apps/desktop/src/renderer/src/shell/desktop-shell.tsx +++ b/apps/desktop/src/renderer/src/shell/desktop-shell.tsx @@ -555,7 +555,8 @@ export function DesktopShell({ // Browser webviews live here permanently: unmounting or DOM-moving a webview // reloads it, so section and tab switches only toggle visibility. const activeIsBrowser = rightPanel.activeSection === 'browser'; - for (const tab of rightPanel.browser.tabs) { + for (let i = 0, len = rightPanel.browser.tabs.length; i < len; i++) { + const tab = rightPanel.browser.tabs[i]; items.push({ id: tab.id, active: activeIsBrowser && tab.id === rightPanel.browser.activeTabId, diff --git a/apps/desktop/src/renderer/src/shell/layout/sash-drag-style.ts b/apps/desktop/src/renderer/src/shell/layout/sash-drag-style.ts index 4acf3eb9..5cc95e5c 100644 --- a/apps/desktop/src/renderer/src/shell/layout/sash-drag-style.ts +++ b/apps/desktop/src/renderer/src/shell/layout/sash-drag-style.ts @@ -82,7 +82,10 @@ export function createSashDragStyleWriter( }; const restore = (): void => { - for (const [style, property, value] of touched) style.setProperty(property, value); + for (let i = 0, len = touched.length; i < len; i++) { + const [style, property, value] = touched[i]; + style.setProperty(property, value); + } touched.length = 0; }; diff --git a/apps/mobile/scripts/generate-terminal-themes.mjs b/apps/mobile/scripts/generate-terminal-themes.mjs index ce336e9f..6b237fef 100644 --- a/apps/mobile/scripts/generate-terminal-themes.mjs +++ b/apps/mobile/scripts/generate-terminal-themes.mjs @@ -21,10 +21,11 @@ const SCHEMES = [ 'Gruvbox Dark', ]; -const hex = (color) => - color +function hex(color) { + return color ? `#${[color.r, color.g, color.b].map((v) => v.toString(16).padStart(2, '0')).join('')}` : undefined; +} async function main() { const require = createRequire(import.meta.url); @@ -32,24 +33,20 @@ async function main() { const resttyVersion = require(join(dirname(resttyEntry), '..', 'package.json')).version; const { getBuiltinTheme } = await import(resttyEntry); - const themes = Object.fromEntries( - SCHEMES.map((name) => { - const { colors } = getBuiltinTheme(name); - return [ - name, - { - background: hex(colors.background), - foreground: hex(colors.foreground), - cursorColor: hex(colors.cursor), - selectionBackground: hex(colors.selectionBackground), - selectionForeground: hex(colors.selectionForeground), - // ANSI 0-15 only: entries 16-255 are the standard xterm cube ghostty - // derives itself. - palette: colors.palette.slice(0, 16).map(hex), - }, - ]; - }), - ); + const themes = SCHEMES.reduce((result, name) => { + const { colors } = getBuiltinTheme(name); + result[name] = { + background: hex(colors.background), + foreground: hex(colors.foreground), + cursorColor: hex(colors.cursor), + selectionBackground: hex(colors.selectionBackground), + selectionForeground: hex(colors.selectionForeground), + // ANSI 0-15 only: entries 16-255 are the standard xterm cube ghostty + // derives itself. + palette: colors.palette.slice(0, 16).map(hex), + }; + return result; + }, {}); const out = `// Generated by scripts/generate-terminal-themes.mjs from restty@${resttyVersion} // builtin themes — do not edit by hand; re-run the script after a restty bump. diff --git a/apps/mobile/src/components/terminal-renderer.native.tsx b/apps/mobile/src/components/terminal-renderer.native.tsx index ed263e31..c91a3003 100644 --- a/apps/mobile/src/components/terminal-renderer.native.tsx +++ b/apps/mobile/src/components/terminal-renderer.native.tsx @@ -41,7 +41,8 @@ export default function TerminalRenderer({ events(events) { const terminal = terminalRef.current; if (!terminal) return; - for (const event of events) { + for (let i = 0, len = events.length; i < len; i++) { + const event = events[i]; // Remote resizes are not applied: the ghostty grid derives from the // local view size, so only 'write' payloads reach the surface. if (event.type === 'write') terminal.writeText(event.data).catch(reportError); diff --git a/apps/mobile/src/runtime/use-seeded-conversation.ts b/apps/mobile/src/runtime/use-seeded-conversation.ts index 32930639..60f4068f 100644 --- a/apps/mobile/src/runtime/use-seeded-conversation.ts +++ b/apps/mobile/src/runtime/use-seeded-conversation.ts @@ -51,7 +51,10 @@ export function useSeededConversation( cursor, forceRefresh: page === 0, }); - for (const entry of result.events) events.push({ event: entry.event, ts: entry.ts }); + for (let i = 0, len = result.events.length; i < len; i++) { + const entry = result.events[i]; + events.push({ event: entry.event, ts: entry.ts }); + } cursor = result.cursor; if (cursor === undefined) break; } diff --git a/apps/webview/src/components/data-table/core/use-table-sort.ts b/apps/webview/src/components/data-table/core/use-table-sort.ts index 520a3d56..45bc23e5 100644 --- a/apps/webview/src/components/data-table/core/use-table-sort.ts +++ b/apps/webview/src/components/data-table/core/use-table-sort.ts @@ -96,7 +96,8 @@ export function useTableSort({ } else if (mode === 'single') { // activating a column replaces the current sort — evict the rest atomically const payload: TableSortsState = { [columnId]: direction }; - for (const id of prevOrder) { + for (let i = 0, len = prevOrder.length; i < len; i++) { + const id = prevOrder[i]; if (id !== columnId) payload[id] = undefined; } setSortsState(payload); @@ -135,7 +136,9 @@ export function useTableSort({ // getter reads the tracked `sorts` at access time, keeping the preview live. const nextSorts = useMemo(() => { const result: TableSortsState = {}; - for (const column of table?.columns ?? []) { + const columns = table?.columns ?? []; + for (let i = 0, len = columns.length; i < len; i++) { + const column = columns[i]; const { id } = column; Object.defineProperty(result, id, { enumerable: true, get: () => computeNextSort(id) }); } @@ -151,7 +154,10 @@ export function useTableSort({ const someOtherActive = orderState.order.some((id) => id !== columnId); if (!someOtherActive) { const reset: TableSortsState = {}; - for (const key of Object.keys(sorts)) reset[key] = undefined; + const keys = Object.keys(sorts); + for (let i = 0, len = keys.length; i < len; i++) { + reset[keys[i]] = undefined; + } setSortsState({ ...reset, ...defaultSorts }); setOrderState({ order: Object.keys(defaultSorts) }); onAfterSortChange(); diff --git a/packages/client/core/src/__tests__/conversation-perf.bench.ts b/packages/client/core/src/__tests__/conversation-perf.bench.ts index d81dc42b..3c1090a5 100644 --- a/packages/client/core/src/__tests__/conversation-perf.bench.ts +++ b/packages/client/core/src/__tests__/conversation-perf.bench.ts @@ -133,15 +133,23 @@ function streamTail(shape: LoadShape): AgentEvent[] { function foldAll(events: readonly AgentEvent[]): void { const builder = createConversationBuilder(); - for (const event of events) builder.advance(event); + for (let i = 0, len = events.length; i < len; i++) { + const event = events[i]; + builder.advance(event); + } builder.snapshot(); } function foldStreamingCost(shape: LoadShape): void { const history = seedHistory(shape); const builder = createConversationBuilder(); - for (const event of history) builder.advance(event); - for (const event of streamTail(shape)) { + for (let i = 0, len = history.length; i < len; i++) { + const event = history[i]; + builder.advance(event); + } + const stream = streamTail(shape); + for (let i = 0, len = stream.length; i < len; i++) { + const event = stream[i]; builder.advance(event); // Each live chunk invalidates the cache and forces a fresh items shallow-copy. builder.snapshot(); @@ -160,14 +168,19 @@ function storeStreamingCost(shape: LoadShape): void { const sessionId = 'sess-perf' as SessionId; const history = seedHistory(shape); const buffer = new EventBuffer(); - for (const event of history) buffer.ingest(sessionId, event); + for (let i = 0, len = history.length; i < len; i++) { + const event = history[i]; + buffer.ingest(sessionId, event); + } // createConversationStore only needs the event surface used by sync(). const store = createConversationStore(bufferClient(buffer), sessionId); // Seed fold via first getSnapshot. store.getSnapshot(); - for (const event of streamTail(shape)) { + const stream = streamTail(shape); + for (let i = 0, len = stream.length; i < len; i++) { + const event = stream[i]; buffer.ingest(sessionId, event); store.getSnapshot(); } @@ -176,8 +189,10 @@ function storeStreamingCost(shape: LoadShape): void { function eventBufferSnapshotCost(shape: LoadShape): void { const sessionId = 'sess-buf' as SessionId; const buffer = new EventBuffer(); - for (const event of seedHistory(shape)) buffer.ingest(sessionId, event); - for (const event of streamTail(shape)) buffer.ingest(sessionId, event); + const history = seedHistory(shape); + for (let i = 0, len = history.length; i < len; i++) buffer.ingest(sessionId, history[i]); + const stream = streamTail(shape); + for (let i = 0, len = stream.length; i < len; i++) buffer.ingest(sessionId, stream[i]); // snapshot copies the full buffer array when the cache is cold; force cold each time. buffer.ingest(sessionId, textChunk('force', '.')); buffer.snapshot(sessionId); diff --git a/packages/client/core/src/__tests__/conversation-perf.report.test.ts b/packages/client/core/src/__tests__/conversation-perf.report.test.ts index 0a01a1be..5461f926 100644 --- a/packages/client/core/src/__tests__/conversation-perf.report.test.ts +++ b/packages/client/core/src/__tests__/conversation-perf.report.test.ts @@ -154,7 +154,10 @@ function fmt(ms: number): string { function foldHistory(history: readonly AgentEvent[]): Conversation { const builder = createConversationBuilder(); - for (const event of history) builder.advance(event); + for (let i = 0, len = history.length; i < len; i++) { + const event = history[i]; + builder.advance(event); + } return builder.snapshot(); } @@ -178,8 +181,12 @@ function reportShape(shape: LoadShape): string { const streamFold = measure(() => { const builder = createConversationBuilder(); - for (const event of history) builder.advance(event); - for (const event of stream) { + for (let i = 0, len = history.length; i < len; i++) { + const event = history[i]; + builder.advance(event); + } + for (let i = 0, len = stream.length; i < len; i++) { + const event = stream[i]; builder.advance(event); builder.snapshot(); } @@ -188,10 +195,14 @@ function reportShape(shape: LoadShape): string { const storeStream = measure(() => { const sessionId = 'sess-perf' as SessionId; const buffer = new EventBuffer(); - for (const event of history) buffer.ingest(sessionId, event); + for (let i = 0, len = history.length; i < len; i++) { + const event = history[i]; + buffer.ingest(sessionId, event); + } const store = createConversationStore(bufferClient(buffer), sessionId); store.getSnapshot(); - for (const event of stream) { + for (let i = 0, len = stream.length; i < len; i++) { + const event = stream[i]; buffer.ingest(sessionId, event); store.getSnapshot(); } diff --git a/packages/client/core/src/__tests__/conversation.test.ts b/packages/client/core/src/__tests__/conversation.test.ts index 4f9f08b9..cdcd8b1d 100644 --- a/packages/client/core/src/__tests__/conversation.test.ts +++ b/packages/client/core/src/__tests__/conversation.test.ts @@ -780,7 +780,10 @@ describe('createConversationBuilder', () => { it('advancing event-by-event equals a single fold', () => { const builder = createConversationBuilder(); - for (const event of scenario) builder.advance(event); + for (let i = 0, len = scenario.length; i < len; i++) { + const event = scenario[i]; + builder.advance(event); + } expect(builder.snapshot()).toEqual(buildConversation(scenario)); }); @@ -795,11 +798,13 @@ describe('createConversationBuilder', () => { it('never mutates previously returned snapshots (copy-on-write)', () => { const builder = createConversationBuilder(); - for (const event of scenario.slice(0, 5)) builder.advance(event); + const initialEvents = scenario.slice(0, 5); + for (let i = 0, len = initialEvents.length; i < len; i++) builder.advance(initialEvents[i]); const before = builder.snapshot(); const frozen = structuredClone(before); - for (const event of scenario.slice(5)) builder.advance(event); + const remainingEvents = scenario.slice(5); + for (let i = 0, len = remainingEvents.length; i < len; i++) builder.advance(remainingEvents[i]); builder.snapshot(); // The earlier snapshot still shows the pending tool call and the streaming message. expect(before).toEqual(frozen); diff --git a/packages/client/core/src/client.ts b/packages/client/core/src/client.ts index f53a597b..28200fe0 100644 --- a/packages/client/core/src/client.ts +++ b/packages/client/core/src/client.ts @@ -308,7 +308,10 @@ export class LinkCodeClient { if (wasReady) { const subscribers = [...this.connectionCloseSubs]; this.connectionCloseSubs.clear(); - for (const cb of subscribers) cb(error); + for (let i = 0, len = subscribers.length; i < len; i++) { + const cb = subscribers[i]; + cb(error); + } } } diff --git a/packages/client/core/src/client/event-buffer.ts b/packages/client/core/src/client/event-buffer.ts index 38c8f08e..1917f742 100644 --- a/packages/client/core/src/client/event-buffer.ts +++ b/packages/client/core/src/client/event-buffer.ts @@ -66,7 +66,12 @@ export class EventBuffer { set.add(cb); // Replay buffered events with their original seqs so a late subscriber sees the full timeline. const buf = this.events.get(sessionId); - if (buf) for (const { event, seq } of buf) cb(event, seq); + if (buf) { + for (let i = 0, len = buf.length; i < len; i++) { + const { event, seq } = buf[i]; + cb(event, seq); + } + } return () => set.delete(cb); } diff --git a/packages/client/core/src/client/loop-log-buffer.ts b/packages/client/core/src/client/loop-log-buffer.ts index ab83b4ff..7bd47c97 100644 --- a/packages/client/core/src/client/loop-log-buffer.ts +++ b/packages/client/core/src/client/loop-log-buffer.ts @@ -26,8 +26,12 @@ export class LoopLogBuffer { seen.add(entry.seq); merged.push(entry); }; - for (const entry of logs) add(entry); - for (const entry of this.entries.get(loopId) ?? []) add(entry); + for (let i = 0, len = logs.length; i < len; i++) { + const entry = logs[i]; + add(entry); + } + const existing = this.entries.get(loopId) ?? []; + for (let i = 0, len = existing.length; i < len; i++) add(existing[i]); merged.sort((a, b) => a.seq - b.seq); this.commit(loopId, merged, seen); } @@ -79,7 +83,10 @@ export class LoopLogBuffer { private commit(loopId: LoopId, list: LoopLogEntry[], seen: Set): void { if (list.length > MAX_LOG_ENTRIES) { const dropped = list.splice(0, list.length - MAX_LOG_ENTRIES); - for (const entry of dropped) seen.delete(entry.seq); + for (let i = 0, len = dropped.length; i < len; i++) { + const entry = dropped[i]; + seen.delete(entry.seq); + } } this.entries.set(loopId, list); this.seen.set(loopId, seen); diff --git a/packages/client/core/src/client/pending-registry.ts b/packages/client/core/src/client/pending-registry.ts index f24adf32..d2a5a3e3 100644 --- a/packages/client/core/src/client/pending-registry.ts +++ b/packages/client/core/src/client/pending-registry.ts @@ -197,7 +197,9 @@ export class PendingRegistry { /** Reject a pending request by id alone — a `request.failed` reply doesn't carry which kind it was. */ reject(id: string, err: Error): boolean { - for (const map of Object.values(this.maps)) { + const maps = Object.values(this.maps); + for (let i = 0, len = maps.length; i < len; i++) { + const map = maps[i]; const pending = map.get(id); if (pending) { map.delete(id); @@ -210,7 +212,9 @@ export class PendingRegistry { /** Reject every in-flight request across every kind, so awaiters get an error instead of hanging forever. */ failAll(err: Error): void { - for (const map of Object.values(this.maps)) { + const maps = Object.values(this.maps); + for (let i = 0, len = maps.length; i < len; i++) { + const map = maps[i]; for (const pending of map.values()) pending.reject(err); map.clear(); } diff --git a/packages/client/core/src/client/terminal-channel.ts b/packages/client/core/src/client/terminal-channel.ts index b527caf0..89973815 100644 --- a/packages/client/core/src/client/terminal-channel.ts +++ b/packages/client/core/src/client/terminal-channel.ts @@ -319,7 +319,10 @@ export class TerminalChannel { set.add(cb); const replay = this.replay.get(terminalId); if (replay) { - for (const event of replay) if (event.type === 'write') cb(event.data); + for (let i = 0, len = replay.length; i < len; i++) { + const event = replay[i]; + if (event.type === 'write') cb(event.data); + } } return () => set.delete(cb); } @@ -332,7 +335,12 @@ export class TerminalChannel { } set.add(cb); const replay = this.replay.get(terminalId); - if (replay) for (const event of replay) cb(event); + if (replay) { + for (let i = 0, len = replay.length; i < len; i++) { + const event = replay[i]; + cb(event); + } + } return () => set.delete(cb); } @@ -445,7 +453,10 @@ export class TerminalChannel { ): { terminal: TerminalMetadata; truncated: boolean } { this.adoptAttachment(terminal, state); if (truncated) this.markReplayTruncated(terminal.terminalId); - for (const event of replay) this.ingestEvent(terminal.terminalId, event); + for (let i = 0, len = replay.length; i < len; i++) { + const event = replay[i]; + this.ingestEvent(terminal.terminalId, event); + } this.lastSeq.set( terminal.terminalId, Math.max(this.lastSeq.get(terminal.terminalId) ?? 0, cutoffSeq), diff --git a/packages/client/core/src/conversation-store.ts b/packages/client/core/src/conversation-store.ts index 5d398140..33a75402 100644 --- a/packages/client/core/src/conversation-store.ts +++ b/packages/client/core/src/conversation-store.ts @@ -94,7 +94,8 @@ export function createConversationStore( const seedToolIds = new Set(); const seedUserMessageCounts = new Map(); if (seed) { - for (const { event } of seed.events) { + for (let i = 0, len = seed.events.length; i < len; i++) { + const { event } = seed.events[i]; switch (event.type) { case 'agent-message': case 'agent-message-chunk': @@ -125,11 +126,16 @@ export function createConversationStore( const sync = (): void => { if (!seeded) { seeded = true; - if (seed) for (const entry of seed.events) builder.advance(entry.event, entry.ts); + if (seed) { + for (let i = 0, len = seed.events.length; i < len; i++) { + const entry = seed.events[i]; + builder.advance(entry.event, entry.ts); + } + } } if (client.eventSeq(sessionId) <= consumedSeq) return; const events = client.eventsSnapshot(sessionId); - for (let i = firstIndexAfter(events, consumedSeq); i < events.length; i += 1) { + for (let i = firstIndexAfter(events, consumedSeq), len = events.length; i < len; i += 1) { const { event, seq, receivedAt } = events[i]; if ( seq > uptoSeq || diff --git a/packages/client/core/src/conversation.ts b/packages/client/core/src/conversation.ts index ec460b6c..6239a8ef 100644 --- a/packages/client/core/src/conversation.ts +++ b/packages/client/core/src/conversation.ts @@ -709,7 +709,10 @@ export function createConversationBuilder(): ConversationBuilder { /** Build a structured Conversation from the flat, append-only agent event stream. Pure & deterministic. */ export function buildConversation(events: readonly AgentEvent[]): Conversation { const builder = createConversationBuilder(); - for (const event of events) builder.advance(event); + for (let i = 0, len = events.length; i < len; i++) { + const event = events[i]; + builder.advance(event); + } return builder.snapshot(); } diff --git a/packages/client/core/src/react.tsx b/packages/client/core/src/react.tsx index 600c727a..8bf87180 100644 --- a/packages/client/core/src/react.tsx +++ b/packages/client/core/src/react.tsx @@ -145,9 +145,15 @@ export function useSessions(): SessionsApi { const list = await client.listSessions(); setSessions((local) => { const byId = new Map(); - for (const s of list) byId.set(s.sessionId, s); + for (let i = 0, len = list.length; i < len; i++) { + const s = list[i]; + byId.set(s.sessionId, s); + } // Keep optimistic locals the snapshot doesn't know about yet. - for (const s of local) if (!byId.has(s.sessionId)) byId.set(s.sessionId, s); + for (let i = 0, len = local.length; i < len; i++) { + const s = local[i]; + if (!byId.has(s.sessionId)) byId.set(s.sessionId, s); + } return [...byId.values()].sort((a, b) => a.createdAt - b.createdAt); }); setLoading(false); diff --git a/packages/client/workbench/src/agent-runtime/onboarding.ts b/packages/client/workbench/src/agent-runtime/onboarding.ts index 04ee345a..b3af8da8 100644 --- a/packages/client/workbench/src/agent-runtime/onboarding.ts +++ b/packages/client/workbench/src/agent-runtime/onboarding.ts @@ -58,9 +58,9 @@ export function dropConfirmedInstalls( runtimes: AgentRuntimes, ): AssetActivityMap { const next: AssetActivityMap = {}; - for (const [kind, assetId] of Object.entries(AGENT_ASSET_IDS) as Array< - [AgentKind, ManagedAssetId] - >) { + const agentAssets = Object.entries(AGENT_ASSET_IDS) as Array<[AgentKind, ManagedAssetId]>; + for (let i = 0, len = agentAssets.length; i < len; i++) { + const [kind, assetId] = agentAssets[i]; const key = managedAssetKey(assetId); const entry = activity[key]; if (!entry) continue; @@ -103,9 +103,9 @@ export function deriveAgentRuntimeCues( ): AgentRuntimeCues { const cues: AgentRuntimeCues = {}; if (!runtimes) return cues; - for (const [kind, runtime] of Object.entries(runtimes) as Array< - [AgentKind, AgentRuntimeAvailability] - >) { + const entries = Object.entries(runtimes) as Array<[AgentKind, AgentRuntimeAvailability]>; + for (let i = 0, len = entries.length; i < len; i++) { + const [kind, runtime] = entries[i]; const cue = deriveCue(kind, runtime, assets, activity, acknowledged, loginActivity, providers); if (cue) cues[kind] = cue; } diff --git a/packages/client/workbench/src/files/locate.ts b/packages/client/workbench/src/files/locate.ts index 337cde94..2f70377c 100644 --- a/packages/client/workbench/src/files/locate.ts +++ b/packages/client/workbench/src/files/locate.ts @@ -26,13 +26,20 @@ export function fileArtifactCandidates( const suffix = `/${relative}`; const exactHits: string[] = []; const touchedDirs: string[] = []; - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind !== 'tool') continue; const paths = (item.toolCall.locations ?? []).map((location) => location.path); - for (const content of item.toolCall.content) { + for ( + let contentIndex = 0, contentCount = item.toolCall.content.length; + contentIndex < contentCount; + contentIndex++ + ) { + const content = item.toolCall.content[contentIndex]; if (content.type === 'diff') paths.push(content.path); } - for (const touched of paths) { + for (let pathIndex = 0, pathCount = paths.length; pathIndex < pathCount; pathIndex++) { + const touched = paths[pathIndex]; // Relative locations (adapter-dependent) have no reliable anchor; skip them. if (!isAbsolute(touched)) continue; const normalized = normalize(touched); @@ -64,7 +71,8 @@ export async function locateFileArtifact( ): Promise { const candidates = fileArtifactCandidates(requestPath, cwd, items); if (candidates.length > 1) { - for (const candidate of candidates) { + for (let i = 0, len = candidates.length; i < len; i++) { + const candidate = candidates[i]; try { // eslint-disable-next-line no-await-in-loop -- candidates must be probed in priority order and stop at the first readable path. await readWorkspaceFile({ cwd, path: candidate }); diff --git a/packages/client/workbench/src/history/imported.ts b/packages/client/workbench/src/history/imported.ts index 7c7ec40c..378ac6ef 100644 --- a/packages/client/workbench/src/history/imported.ts +++ b/packages/client/workbench/src/history/imported.ts @@ -14,7 +14,8 @@ export function importedSessionByHistoryId( sessions: readonly SessionInfo[], ): Map { const map = new Map(); - for (const session of sessions) { + for (let i = 0, len = sessions.length; i < len; i++) { + const session = sessions[i]; if (session.historyId !== undefined) { map.set(importedSessionKey(session.kind, session.historyId), session.sessionId); } diff --git a/packages/client/workbench/src/history/use-bulk-history-import.ts b/packages/client/workbench/src/history/use-bulk-history-import.ts index 3b2ac734..18ff15c3 100644 --- a/packages/client/workbench/src/history/use-bulk-history-import.ts +++ b/packages/client/workbench/src/history/use-bulk-history-import.ts @@ -70,7 +70,8 @@ export function useBulkHistoryImport(): BulkHistoryImportSurface { const result = await historyByKind[group.kind].importGroup(group.cwd, group.entries); if (!result) return null; const importedIds = new Set(result.imported); - for (const entry of group.entries) { + for (let i = 0, len = group.entries.length; i < len; i++) { + const entry = group.entries[i]; if (importedIds.has(entry.historyId)) { importedKeys.add(importedSessionKey(entry.kind, entry.historyId)); } @@ -111,7 +112,8 @@ export function useBulkHistoryImport(): BulkHistoryImportSurface { function groupByProviderDirectory(entries: readonly AgentHistorySession[]) { const groups = new Map(); - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; const key = `${entry.kind}\0${entry.cwd ?? ''}`; const group = groups.get(key); if (group) group.push(entry); diff --git a/packages/client/workbench/src/history/use-history-import-surface.ts b/packages/client/workbench/src/history/use-history-import-surface.ts index 12148f13..f711deec 100644 --- a/packages/client/workbench/src/history/use-history-import-surface.ts +++ b/packages/client/workbench/src/history/use-history-import-surface.ts @@ -59,7 +59,8 @@ export function useHistoryImportSurface( const imported = importedSessionByHistoryId(sessions.sessions); const importErrors = new Map(); const groupImportFailures = new Map(); - for (const entry of history.entries) { + for (let i = 0, len = history.entries.length; i < len; i++) { + const entry = history.entries[i]; const entryError = importErrorsByKey.get(historyStateKey(entry.kind, entry.historyId)); if (entryError !== undefined) importErrors.set(entry.historyId, entryError); if (!entry.cwd) continue; @@ -130,7 +131,8 @@ export function useHistoryImportSurface( if (result.failures.length === 0) return; setImportErrorsByKey((current) => { const next = new Map(current); - for (const failure of result.failures) { + for (let i = 0, len = result.failures.length; i < len; i++) { + const failure = result.failures[i]; next.set(historyStateKey(kind, failure.historyId), errorMessage(failure.error)); } return next; @@ -198,7 +200,10 @@ function withoutMapKey(map: ReadonlyMap, key: K): ReadonlyMap function withoutMapKeys(map: ReadonlyMap, keys: readonly K[]): ReadonlyMap { if (!keys.some((key) => map.has(key))) return map; const next = new Map(map); - for (const key of keys) next.delete(key); + for (let i = 0, len = keys.length; i < len; i++) { + const key = keys[i]; + next.delete(key); + } return next; } diff --git a/packages/client/workbench/src/history/use-provider-history.ts b/packages/client/workbench/src/history/use-provider-history.ts index 5e14863d..9fcbf27e 100644 --- a/packages/client/workbench/src/history/use-provider-history.ts +++ b/packages/client/workbench/src/history/use-provider-history.ts @@ -59,7 +59,7 @@ export async function importHistoryGroup({ inFlight.add(inFlightKey); try { const settled: Array> = []; - for (let start = 0; start < entries.length; start += GROUP_IMPORT_CONCURRENCY) { + for (let start = 0, len = entries.length; start < len; start += GROUP_IMPORT_CONCURRENCY) { const batch = entries.slice(start, start + GROUP_IMPORT_CONCURRENCY); appendArrayInPlace( settled, @@ -169,7 +169,8 @@ function currentImportingIds( importingKeys: ReadonlySet, ): ReadonlySet { const ids = new Set(); - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; if (importingKeys.has(importKey(entry.kind, entry.historyId))) ids.add(entry.historyId); } return ids; @@ -181,7 +182,8 @@ function currentImportingCwds( importingGroupKeys: ReadonlySet, ): ReadonlySet { const cwds = new Set(); - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; if (entry.cwd && importingGroupKeys.has(importKey(kind, entry.cwd))) cwds.add(entry.cwd); } return cwds; diff --git a/packages/client/workbench/src/mock/dev-mock-host.ts b/packages/client/workbench/src/mock/dev-mock-host.ts index da0e1e57..9d45558a 100644 --- a/packages/client/workbench/src/mock/dev-mock-host.ts +++ b/packages/client/workbench/src/mock/dev-mock-host.ts @@ -242,7 +242,9 @@ export class DevMockHost { /** Staged download: throttled progress → settled → correlated reply → runtime re-probe push. */ private async ensureAsset(clientReqId: string, id: ManagedAssetId): Promise { const totalBytes = 66 * 1_048_576; - for (const fraction of [0.04, 0.19, 0.42, 0.68, 0.91]) { + const progressFractions = [0.04, 0.19, 0.42, 0.68, 0.91]; + for (let i = 0, len = progressFractions.length; i < len; i++) { + const fraction = progressFractions[i]; this.send({ kind: 'asset.progress', id, @@ -279,7 +281,8 @@ export class DevMockHost { void this.handle(msg); }); const now = Date.now(); - for (const { ageMs, ...seed } of SEED_SESSIONS) { + for (let i = 0, len = SEED_SESSIONS.length; i < len; i++) { + const { ageMs, ...seed } = SEED_SESSIONS[i]; const createdAt = now - ageMs; this.addSession({ ...seed, createdAt, updatedAt: createdAt }); this.touchWorkspace(seed.cwd, createdAt); @@ -1165,9 +1168,10 @@ export class DevMockHost { ...toolEvents(bursts.activityRun.afterTask), ]; - for (const event of script) { + for (let i = 0, len = script.length; i < len; i++) { // eslint-disable-next-line no-await-in-loop -- the showcase script emits step by step on purpose. if (!(await waitForShowcaseStep(session, epoch))) return false; + const event = script[i]; this.emit(session.sessionId, event); } if (!(await waitForShowcaseStep(session, epoch))) return false; @@ -1177,7 +1181,8 @@ export class DevMockHost { toolCall: SHOWCASE_QUESTION.toolCall, }); if (!(await this.emitShowcaseEvent(session, epoch, SHOWCASE_QUESTION))) return false; - for (const permission of SHOWCASE_PERMISSIONS) { + for (let i = 0, len = SHOWCASE_PERMISSIONS.length; i < len; i++) { + const permission = SHOWCASE_PERMISSIONS[i]; this.permissions.set(permission.requestId, { sessionId: session.sessionId, toolCall: permission.toolCall, diff --git a/packages/client/workbench/src/palette/command-palette.tsx b/packages/client/workbench/src/palette/command-palette.tsx index 9fd6a890..7ca0a7e7 100644 --- a/packages/client/workbench/src/palette/command-palette.tsx +++ b/packages/client/workbench/src/palette/command-palette.tsx @@ -97,7 +97,9 @@ function OpenCommandPalette({ sessions }: WorkbenchCommandPaletteProps): React.R : []), ]; let targetWorkspace: WorkspaceRecord | null = null; - for (const workspace of workspaces ?? []) { + const availableWorkspaces = workspaces ?? []; + for (let i = 0, len = availableWorkspaces.length; i < len; i++) { + const workspace = availableWorkspaces[i]; if (workspaceKind(workspace) === 'project') { targetWorkspace = workspace; break; diff --git a/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts b/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts index e3a83195..d4f3b428 100644 --- a/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts +++ b/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts @@ -82,7 +82,9 @@ describe('bindingAvailability', () => { it('keeps protocol-unknown legacy accounts bindable everywhere', () => { const legacy = account({}); - for (const kind of ['claude-code', 'codex', 'opencode', 'pi', 'grok-build'] as const) { + const agentKinds = ['claude-code', 'codex', 'opencode', 'pi', 'grok-build'] as const; + for (let i = 0, len = agentKinds.length; i < len; i++) { + const kind = agentKinds[i]; expect(bindingAvailability(legacy, kind)).toEqual({ tier: 'native' }); } }); diff --git a/packages/client/workbench/src/settings/providers/add-flow.tsx b/packages/client/workbench/src/settings/providers/add-flow.tsx index e1dda878..1753fc88 100644 --- a/packages/client/workbench/src/settings/providers/add-flow.tsx +++ b/packages/client/workbench/src/settings/providers/add-flow.tsx @@ -26,7 +26,10 @@ const GROUPS: ServiceGroup[] = ['subscription', 'direct', 'gateway', 'custom']; const SERVICES_BY_GROUP = new Map( GROUPS.map((group) => [group, []]), ); -for (const service of SERVICE_CATALOG) SERVICES_BY_GROUP.get(service.group)?.push(service); +for (let i = 0, len = SERVICE_CATALOG.length; i < len; i++) { + const service = SERVICE_CATALOG[i]; + SERVICES_BY_GROUP.get(service.group)?.push(service); +} /** Account constructors live at module scope: `Date.now` may not run in a component body. */ function newAccountBase(label: string): Pick { @@ -50,7 +53,9 @@ function catalogAccount( draft: CatalogDraft, ): Account { const trimmed: Record = {}; - for (const key of templatePlaceholders(variant.baseUrl)) { + const placeholders = templatePlaceholders(variant.baseUrl); + for (let i = 0, len = placeholders.length; i < len; i++) { + const key = placeholders[i]; const value = Object.hasOwn(draft.placeholders, key) ? draft.placeholders[key] : ''; trimmed[key] = value.trim(); } @@ -320,7 +325,9 @@ type CatalogDraft = z.infer; function catalogDraftSchema(variant: ServiceVariant): typeof CatalogDraftSchema { return CatalogDraftSchema.superRefine((draft, ctx) => { - for (const key of templatePlaceholders(variant.baseUrl)) { + const placeholders = templatePlaceholders(variant.baseUrl); + for (let i = 0, len = placeholders.length; i < len; i++) { + const key = placeholders[i]; const value = Object.hasOwn(draft.placeholders, key) ? draft.placeholders[key] : ''; if (!value.trim()) { ctx.addIssue({ code: 'custom', path: ['placeholders', key], message: 'required' }); diff --git a/packages/client/workbench/src/settings/providers/catalog.ts b/packages/client/workbench/src/settings/providers/catalog.ts index 5fbfc98b..7fe4d833 100644 --- a/packages/client/workbench/src/settings/providers/catalog.ts +++ b/packages/client/workbench/src/settings/providers/catalog.ts @@ -187,7 +187,8 @@ export function detectedLoginSuggestions( runtimes: AgentRuntimes | undefined, ): DetectedLoginSuggestion[] { const suggestions: DetectedLoginSuggestion[] = []; - for (const service of SERVICE_CATALOG) { + for (let i = 0, len = SERVICE_CATALOG.length; i < len; i++) { + const service = SERVICE_CATALOG[i]; if (service.kind !== 'oauth') continue; const auth = runtimes?.[service.agent]?.auth; if (auth?.loggedIn !== true) continue; diff --git a/packages/client/workbench/src/settings/providers/default-models.ts b/packages/client/workbench/src/settings/providers/default-models.ts index 78877678..6cbe2999 100644 --- a/packages/client/workbench/src/settings/providers/default-models.ts +++ b/packages/client/workbench/src/settings/providers/default-models.ts @@ -10,7 +10,8 @@ export function configuredDefaultModels( accounts: Accounts | undefined, ): Partial> { const defaults: Partial> = {}; - for (const kind of AgentKindSchema.options) { + for (let i = 0, len = AgentKindSchema.options.length; i < len; i++) { + const kind = AgentKindSchema.options[i]; const provider = providers?.[kind]; const account = accounts?.find((candidate) => candidate.id === provider?.activeAccountId); const model = account?.model ?? provider?.defaultModel; diff --git a/packages/client/workbench/src/settings/providers/view.ts b/packages/client/workbench/src/settings/providers/view.ts index 93563669..7bd6ea22 100644 --- a/packages/client/workbench/src/settings/providers/view.ts +++ b/packages/client/workbench/src/settings/providers/view.ts @@ -43,7 +43,10 @@ export function accountConfigSnippet( ): string { const bound = boundAgentKinds(providers, accountId); const slice: Record = {}; - for (const kind of bound) slice[kind] = providers?.[kind]; + for (let i = 0, len = bound.length; i < len; i++) { + const kind = bound[i]; + slice[kind] = providers?.[kind]; + } return JSON.stringify({ providers: slice }, null, 2); } @@ -240,7 +243,8 @@ export function withModel( export function withoutAccount(providers: ProvidersConfig, accountId: string): ProvidersConfig { let changed = false; const next: ProvidersConfig = {}; - for (const kind of AGENT_KINDS) { + for (let i = 0, len = AGENT_KINDS.length; i < len; i++) { + const kind = AGENT_KINDS[i]; const entry = providers[kind]; if (entry === undefined) continue; if (entry.activeAccountId === accountId) { diff --git a/packages/client/workbench/src/sidebar/ordering.ts b/packages/client/workbench/src/sidebar/ordering.ts index 83c3a4c0..30e3f3d9 100644 --- a/packages/client/workbench/src/sidebar/ordering.ts +++ b/packages/client/workbench/src/sidebar/ordering.ts @@ -15,7 +15,8 @@ export function orderGroups( const unlisted: ThreadGroup[] = []; const fixed: ThreadGroup[] = []; - for (const group of groups) { + for (let i = 0, len = groups.length; i < len; i++) { + const group = groups[i]; if (group.isChat || group.workspace === null) fixed.push(group); else if (rank.has(group.collapseKey)) listed.push(group); else unlisted.push(group); diff --git a/packages/client/workbench/src/simulator/__tests__/agent-activity.test.ts b/packages/client/workbench/src/simulator/__tests__/agent-activity.test.ts index 8898f401..5a2222f1 100644 --- a/packages/client/workbench/src/simulator/__tests__/agent-activity.test.ts +++ b/packages/client/workbench/src/simulator/__tests__/agent-activity.test.ts @@ -23,13 +23,16 @@ function fakeClient(): SimulatorActivityClient & { emit: (activity: Activity) => }; } -const started = ( +function started( udid: string | undefined, tool = 'sim_tap', at?: { x: number; y: number }, -): Activity => ({ sessionId: 's1', udid, tool, phase: 'started', ...at }) as Activity; -const settled = (udid: string | undefined, tool = 'sim_tap'): Activity => - ({ sessionId: 's1', udid, tool, phase: 'settled' }) as Activity; +): Activity { + return { sessionId: 's1', udid, tool, phase: 'started', ...at } as Activity; +} +function settled(udid: string | undefined, tool = 'sim_tap'): Activity { + return { sessionId: 's1', udid, tool, phase: 'settled' } as Activity; +} describe('useSimulatorAgentActivity', () => { beforeEach(() => { diff --git a/packages/client/workbench/src/simulator/__tests__/panel-store.test.ts b/packages/client/workbench/src/simulator/__tests__/panel-store.test.ts index cd4f8bbc..ac4e91f2 100644 --- a/packages/client/workbench/src/simulator/__tests__/panel-store.test.ts +++ b/packages/client/workbench/src/simulator/__tests__/panel-store.test.ts @@ -5,10 +5,12 @@ import { selectDeviceTabs, useSimulatorPanelStore } from '../panel-store'; const A = 'thread-a'; const B = 'thread-b'; -const tabsOf = (key: string): { udids: readonly string[]; activeUdid: string | null } => - selectDeviceTabs(useSimulatorPanelStore.getState(), key); -const open = (key: string, udid: string): void => - useSimulatorPanelStore.getState().openDevice(key, udid); +function tabsOf(key: string): { udids: readonly string[]; activeUdid: string | null } { + return selectDeviceTabs(useSimulatorPanelStore.getState(), key); +} +function open(key: string, udid: string): void { + return useSimulatorPanelStore.getState().openDevice(key, udid); +} describe('simulator device tabs', () => { beforeEach(() => { diff --git a/packages/client/workbench/src/simulator/background-streams.ts b/packages/client/workbench/src/simulator/background-streams.ts index ac7a276a..bb889e22 100644 --- a/packages/client/workbench/src/simulator/background-streams.ts +++ b/packages/client/workbench/src/simulator/background-streams.ts @@ -36,7 +36,10 @@ export function useBackgroundSimulatorStreams( return lease; }); return () => { - for (const lease of leases) lease.release(); + for (let i = 0, len = leases.length; i < len; i++) { + const lease = leases[i]; + lease.release(); + } }; }, [client, sessionId, key, scale, codec]); } diff --git a/packages/client/workbench/src/surface/startup-selection.ts b/packages/client/workbench/src/surface/startup-selection.ts index 853d6c85..729d969a 100644 --- a/packages/client/workbench/src/surface/startup-selection.ts +++ b/packages/client/workbench/src/surface/startup-selection.ts @@ -19,7 +19,8 @@ export function reflectedStartupSelection( ): NewSessionSelection { let reflectedModel: string | undefined; let reflectedEffort: EffortLevel | undefined; - for (const { event } of events) { + for (let i = 0, len = events.length; i < len; i++) { + const { event } = events[i]; if (event.type === 'model-update') reflectedModel = event.model; else if (event.type === 'effort-update') reflectedEffort = event.effort; } diff --git a/packages/client/workbench/src/surface/use-seeded-conversation.ts b/packages/client/workbench/src/surface/use-seeded-conversation.ts index c2318c1a..396248f3 100644 --- a/packages/client/workbench/src/surface/use-seeded-conversation.ts +++ b/packages/client/workbench/src/surface/use-seeded-conversation.ts @@ -33,7 +33,10 @@ async function readConversationSeed( cursor, forceRefresh: page === 0, }); - for (const entry of data.events) events.push({ event: entry.event, ts: entry.ts }); + for (let i = 0, len = data.events.length; i < len; i++) { + const entry = data.events[i]; + events.push({ event: entry.event, ts: entry.ts }); + } cursor = data.cursor; if (cursor === undefined) break; } diff --git a/packages/client/workbench/src/surface/use-workbench-sessions.ts b/packages/client/workbench/src/surface/use-workbench-sessions.ts index 0500ae31..1b6fa659 100644 --- a/packages/client/workbench/src/surface/use-workbench-sessions.ts +++ b/packages/client/workbench/src/surface/use-workbench-sessions.ts @@ -260,7 +260,8 @@ function sessionById( sessionId: SessionId | null, ): SessionInfo | null { if (!sessionId) return null; - for (const session of sessions) { + for (let i = 0, len = sessions.length; i < len; i++) { + const session = sessions[i]; if (session.sessionId === sessionId) return session; } return null; diff --git a/packages/client/workbench/src/surface/workbench.tsx b/packages/client/workbench/src/surface/workbench.tsx index f711bfc4..b893d9b3 100644 --- a/packages/client/workbench/src/surface/workbench.tsx +++ b/packages/client/workbench/src/surface/workbench.tsx @@ -224,11 +224,13 @@ function WorkbenchSessionSurface({ const [respondingRequestIds, addRespondingRequest, removeRespondingRequest] = useSet(); const [responseErrors, setResponseErrors] = useState(() => new Map()); const visibleResponseErrors = new Map(); - for (const requestId of conversation.pendingPermissionIds) { + for (let i = 0, len = conversation.pendingPermissionIds.length; i < len; i++) { + const requestId = conversation.pendingPermissionIds[i]; const message = responseErrors.get(requestId); if (message) visibleResponseErrors.set(requestId, message); } - for (const requestId of conversation.pendingQuestionIds) { + for (let i = 0, len = conversation.pendingQuestionIds.length; i < len; i++) { + const requestId = conversation.pendingQuestionIds[i]; const message = responseErrors.get(requestId); if (message) visibleResponseErrors.set(requestId, message); } @@ -520,7 +522,8 @@ function WorkbenchSessionSurface({ const workspacesById = new Map(); let chatWorkspace: WorkspaceRecord | null = null; const projectWorkspaces: WorkspaceRecord[] = []; - for (const workspace of allWorkspaces) { + for (let i = 0, len = allWorkspaces.length; i < len; i++) { + const workspace = allWorkspaces[i]; const kind = workspaceKind(workspace); if (kind === 'worktree') continue; workspacesById.set(workspace.workspaceId, workspace); diff --git a/packages/client/workbench/tests/integration/dev-mock-transport.test.ts b/packages/client/workbench/tests/integration/dev-mock-transport.test.ts index a60368cf..9dfd926e 100644 --- a/packages/client/workbench/tests/integration/dev-mock-transport.test.ts +++ b/packages/client/workbench/tests/integration/dev-mock-transport.test.ts @@ -500,7 +500,8 @@ describe('dev mock transport', () => { }, source: 'user', }); - for (const request of permissionRequests) { + for (let i = 0, len = permissionRequests.length; i < len; i++) { + const request = permissionRequests[i]; // eslint-disable-next-line no-await-in-loop -- answer the asks in order, like a user would. await expect( client.respondPermission(showcase.sessionId, request.requestId, { @@ -632,7 +633,8 @@ describe('dev mock transport', () => { }), ).rejects.toThrow('Unknown question request'); - for (const request of permissionRequests) { + for (let i = 0, len = permissionRequests.length; i < len; i++) { + const request = permissionRequests[i]; // eslint-disable-next-line no-await-in-loop -- assert each cancelled ask reaches its own tool snapshot. const cancelledTool = await eventually(() => toolCalls(events).find( diff --git a/packages/foundation/common/src/node/__tests__/executable-locations.test.ts b/packages/foundation/common/src/node/__tests__/executable-locations.test.ts index 32171e8f..e242dec2 100644 --- a/packages/foundation/common/src/node/__tests__/executable-locations.test.ts +++ b/packages/foundation/common/src/node/__tests__/executable-locations.test.ts @@ -32,7 +32,9 @@ describe('executableSearchLocations', () => { it('yields only absolute candidate paths', () => { vi.stubEnv('PATH', ['relative/bin', ''].join(delimiter)); - for (const location of executableSearchLocations('tool')) { + const locations = executableSearchLocations('tool'); + for (let i = 0, len = locations.length; i < len; i++) { + const location = locations[i]; expect(isAbsolute(location)).toBe(true); } }); diff --git a/packages/foundation/common/src/node/executable-locations.ts b/packages/foundation/common/src/node/executable-locations.ts index 3080f810..ad16b29f 100644 --- a/packages/foundation/common/src/node/executable-locations.ts +++ b/packages/foundation/common/src/node/executable-locations.ts @@ -17,7 +17,9 @@ import { delimiter, isAbsolute, join } from 'node:path'; */ function pathInstallLocations(binary: string): string[] { const locations: string[] = []; - for (const entry of (process.env.PATH ?? '').split(delimiter)) { + const entries = (process.env.PATH ?? '').split(delimiter); + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; // Windows PATH entries with spaces are conventionally double-quoted. const dir = entry.replaceAll('"', ''); if (dir.length > 0 && isAbsolute(dir)) locations.push(join(dir, binary)); diff --git a/packages/foundation/common/src/sentry/index.ts b/packages/foundation/common/src/sentry/index.ts index 1ffe2992..658b39c5 100644 --- a/packages/foundation/common/src/sentry/index.ts +++ b/packages/foundation/common/src/sentry/index.ts @@ -105,7 +105,9 @@ export function sanitizeSentryTransaction( ? sanitizeProfileContext(event.contexts.profile) : undefined; const spans: TransactionSpan[] = []; - for (const span of event.spans ?? []) { + const eventSpans = event.spans ?? []; + for (let i = 0, len = eventSpans.length; i < len; i++) { + const span = eventSpans[i]; const sanitizedSpan = sanitizeSpan(span, options); if (sanitizedSpan) spans.push(sanitizedSpan); } @@ -122,7 +124,10 @@ export function sanitizeSentryTransaction( measurements: sanitizeMeasurements(event.measurements, options.safeMeasurementNames), }; - for (const key of Object.keys(event)) Reflect.deleteProperty(event, key); + const eventKeys = Object.keys(event); + for (let i = 0, len = eventKeys.length; i < len; i++) { + Reflect.deleteProperty(event, eventKeys[i]); + } Object.assign(event, sanitized); return event; } @@ -187,7 +192,8 @@ function sanitizeMeasurements( if (!isRecord(measurements) || !safeNames?.length) return undefined; const sanitized: Record = {}; let hasMeasurement = false; - for (const name of safeNames) { + for (let i = 0, len = safeNames.length; i < len; i++) { + const name = safeNames[i]; const measurement = measurements[name]; if (!isRecord(measurement)) continue; const value = finiteNumber(measurement.value); diff --git a/packages/foundation/common/src/telemetry-config/index.ts b/packages/foundation/common/src/telemetry-config/index.ts index bf256cab..07eac769 100644 --- a/packages/foundation/common/src/telemetry-config/index.ts +++ b/packages/foundation/common/src/telemetry-config/index.ts @@ -57,7 +57,7 @@ export function parseTelemetryConfig(value: unknown): TelemetryConfig | null { /** Fetches public sampling configuration without delaying telemetry initialization on failure. */ export async function fetchTelemetryConfig( - fetcher: TelemetryConfigFetcher = globalThis.fetch, + fetcher: TelemetryConfigFetcher = fetch, ): Promise { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 3000); diff --git a/packages/foundation/schema/tests/contract/wire/loop.test.ts b/packages/foundation/schema/tests/contract/wire/loop.test.ts index 7c01c50f..9e4a2fe6 100644 --- a/packages/foundation/schema/tests/contract/wire/loop.test.ts +++ b/packages/foundation/schema/tests/contract/wire/loop.test.ts @@ -63,7 +63,8 @@ describe('loop wire variants', () => { { kind: 'loop.iteration', iteration }, { kind: 'loop.log', loopId: loop.loopId, entry: log }, ]; - for (const payload of payloads) { + for (let i = 0, len = payloads.length; i < len; i++) { + const payload = payloads[i]; expect(parseWireMessage(envelope(payload)).success, payload.kind).toBe(true); } }); diff --git a/packages/foundation/schema/tests/contract/wire/schedule.test.ts b/packages/foundation/schema/tests/contract/wire/schedule.test.ts index a6d34ec6..a07886ff 100644 --- a/packages/foundation/schema/tests/contract/wire/schedule.test.ts +++ b/packages/foundation/schema/tests/contract/wire/schedule.test.ts @@ -54,7 +54,8 @@ describe('schedule wire variants', () => { { kind: 'schedule.removed', scheduleId: schedule.scheduleId }, { kind: 'schedule.run', run }, ]; - for (const payload of payloads) { + for (let i = 0, len = payloads.length; i < len; i++) { + const payload = payloads[i]; expect(parseWireMessage(envelope(payload)).success, payload.kind).toBe(true); } }); diff --git a/packages/foundation/transport/src/__tests__/tunnel.test.ts b/packages/foundation/transport/src/__tests__/tunnel.test.ts index f9dbab2a..0474b4ca 100644 --- a/packages/foundation/transport/src/__tests__/tunnel.test.ts +++ b/packages/foundation/transport/src/__tests__/tunnel.test.ts @@ -106,7 +106,9 @@ describe('TunnelTransportServer', () => { const inbound: unknown[] = []; connection.onMessage((message) => inbound.push(message)); const request = createWireMessage({ kind: 'session.list', clientReqId: 'r1' }); - for (const data of new TunnelChunkEncoder(1).encode(JSON.stringify(request))) { + const chunks = new TunnelChunkEncoder(1).encode(JSON.stringify(request)); + for (let i = 0, len = chunks.length; i < len; i++) { + const data = chunks[i]; socket.emit('message', { data: encodeTunnelPeerFrame({ kind: 'peer.data', peerId: 'peer-1', data }), }); @@ -115,7 +117,8 @@ describe('TunnelTransportServer', () => { connection.send(createWireMessage({ kind: 'request.succeeded', replyTo: 'r1' })); let outbound: TunnelPeerFrame | null = null; - for (const sent of socket.sent) { + for (let i = 0, len = socket.sent.length; i < len; i++) { + const sent = socket.sent[i]; if (!(sent instanceof ArrayBuffer)) continue; const frame = decodeTunnelPeerFrame(sent); if (frame?.kind === 'peer.data') { @@ -330,7 +333,9 @@ describe('TunnelTransportServer', () => { candidate.emit('message', { data: encodeTunnelPeerFrame({ kind: 'peer.join', peerId: 'peer-1' }), }); - for (const data of chunks.slice(1)) { + const remainingChunks = chunks.slice(1); + for (let i = 0, len = remainingChunks.length; i < len; i++) { + const data = remainingChunks[i]; candidate.emit('message', { data: encodeTunnelPeerFrame({ kind: 'peer.data', peerId: 'peer-1', data }), }); @@ -346,7 +351,8 @@ describe('TunnelTransportServer', () => { expect(inbound).toEqual([request]); await vi.waitFor(() => { let outbound = 0; - for (const sent of candidate.sent) { + for (let i = 0, len = candidate.sent.length; i < len; i++) { + const sent = candidate.sent[i]; if (!(sent instanceof ArrayBuffer)) continue; if (decodeTunnelPeerFrame(sent)?.kind === 'peer.data') outbound += 1; } @@ -401,7 +407,9 @@ describe('TunnelTransportServer', () => { candidate.emit('message', { data: encodeTunnelPeerFrame({ kind: 'peer.join', peerId: 'peer-1' }), }); - for (const data of new TunnelChunkEncoder(2).encode(JSON.stringify(request))) { + const chunks = new TunnelChunkEncoder(2).encode(JSON.stringify(request)); + for (let i = 0, len = chunks.length; i < len; i++) { + const data = chunks[i]; candidate.emit('message', { data: encodeTunnelPeerFrame({ kind: 'peer.data', peerId: 'peer-1', data }), }); diff --git a/packages/foundation/transport/src/hub.ts b/packages/foundation/transport/src/hub.ts index 924918d4..d1d7dac5 100644 --- a/packages/foundation/transport/src/hub.ts +++ b/packages/foundation/transport/src/hub.ts @@ -124,14 +124,26 @@ export class Hub implements Transport { bestEffort(() => conn.send(createWireMessage({ kind: 'pong' }))); return; } - if (p.kind === 'session.attach') subscription.attached.add(p.sessionId); - else if (p.kind === 'session.detach') subscription.attached.delete(p.sessionId); - else if (p.kind === 'terminal.detach') { - const attachments = subscription.terminals.get(p.terminalId); - if (attachments?.get(p.attachmentId) === p.attachmentSecret) { - attachments.delete(p.attachmentId); - if (attachments.size === 0) subscription.terminals.delete(p.terminalId); + switch (p.kind) { + case 'session.attach': { + subscription.attached.add(p.sessionId); + break; } + case 'session.detach': { + subscription.attached.delete(p.sessionId); + break; + } + case 'terminal.detach': { + const attachments = subscription.terminals.get(p.terminalId); + if (attachments?.get(p.attachmentId) === p.attachmentSecret) { + attachments.delete(p.attachmentId); + if (attachments.size === 0) subscription.terminals.delete(p.terminalId); + } + + break; + } + default: + break; } } if ('clientReqId' in p) { diff --git a/packages/foundation/transport/src/preview-proxy.ts b/packages/foundation/transport/src/preview-proxy.ts index 3cadff99..7356be53 100644 --- a/packages/foundation/transport/src/preview-proxy.ts +++ b/packages/foundation/transport/src/preview-proxy.ts @@ -198,7 +198,7 @@ function proxyUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, port: upstream.on('connect', () => { const lines = [`${req.method ?? 'GET'} ${req.url ?? '/'} HTTP/1.1`]; - for (let i = 0; i < req.rawHeaders.length; i += 2) { + for (let i = 0, len = req.rawHeaders.length; i < len; i += 2) { lines.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`); } upstream.write(`${lines.join('\r\n')}\r\n\r\n`); diff --git a/packages/foundation/transport/src/tunnel-client.ts b/packages/foundation/transport/src/tunnel-client.ts index bc98d211..7cc248e8 100644 --- a/packages/foundation/transport/src/tunnel-client.ts +++ b/packages/foundation/transport/src/tunnel-client.ts @@ -101,7 +101,8 @@ export class TunnelClient { throw new Error('TunnelClient: host messages must be sent through a peer'); } const ws = this.openSocket(); - for (const frame of this.encoder.encode(message)) ws.send(frame); + const frames = this.encoder.encode(message); + for (let i = 0, len = frames.length; i < len; i++) ws.send(frames[i]); } onMessage(cb: (message: string) => void): () => void { @@ -155,7 +156,10 @@ export class TunnelClient { throw failure; } if (this.candidateWs === ws) this.candidateWs = null; - for (const event of prepared.buffered) this.handleMessage(ws, event); + for (let i = 0, len = prepared.buffered.length; i < len; i++) { + const event = prepared.buffered[i]; + this.handleMessage(ws, event); + } if (this.closedByUser || ws !== this.ws) { throw new Error('TunnelClient: socket closed during handshake'); } @@ -277,7 +281,12 @@ export class TunnelClient { this.adopt(ws, prepared); const queued = this.handoffQueue; this.handoffQueue = null; - if (queued) for (const frame of queued) ws.send(frame); + if (queued) { + for (let i = 0, len = queued.length; i < len; i++) { + const frame = queued[i]; + ws.send(frame); + } + } if (current !== ws && current.readyState === current.OPEN) current.close(1000); } catch { if (handoffStarted) this.abortHandoff(current); @@ -288,7 +297,9 @@ export class TunnelClient { private sendToPeer(peerId: string, message: string): void { if (!this.peers.has(peerId)) throw new Error('TunnelPeer: connection closed'); const ws = this.handoffQueue ? null : this.openSocket(); - for (const data of this.encoder.encode(message)) { + const chunks = this.encoder.encode(message); + for (let i = 0, len = chunks.length; i < len; i++) { + const data = chunks[i]; const frame = encodeTunnelPeerFrame({ kind: 'peer.data', peerId, data }); if (this.handoffQueue) this.handoffQueue.push(frame); else ws?.send(frame); @@ -358,7 +369,8 @@ export class TunnelClient { private reconcilePeers(buffered: readonly MessageEvent[]): void { const successorPeers = new Set(); - for (const event of buffered) { + for (let i = 0, len = buffered.length; i < len; i++) { + const event = buffered[i]; if (!(event.data instanceof ArrayBuffer)) continue; const frame = decodeTunnelPeerFrame(event.data); if (frame?.kind === 'peer.join') successorPeers.add(frame.peerId); diff --git a/packages/foundation/transport/tests/integration/preview-proxy.test.ts b/packages/foundation/transport/tests/integration/preview-proxy.test.ts index c24f6ae6..e61f96b5 100644 --- a/packages/foundation/transport/tests/integration/preview-proxy.test.ts +++ b/packages/foundation/transport/tests/integration/preview-proxy.test.ts @@ -9,8 +9,9 @@ import { WebSocket, WebSocketServer } from 'ws'; const cleanups: Array<() => Promise | void> = []; afterAll(async () => { + const reversedCleanups = cleanups.reverse(); // eslint-disable-next-line no-await-in-loop -- teardown must run one at a time in LIFO order - for (const cleanup of cleanups.reverse()) await cleanup(); + for (let i = 0, len = reversedCleanups.length; i < len; i++) await reversedCleanups[i](); }); /** An upstream dev-server stand-in: echoes the request path and Host, and echoes WS frames. */ diff --git a/packages/host/agent-adapter/src/__tests__/claude-code-commands.test.ts b/packages/host/agent-adapter/src/__tests__/claude-code-commands.test.ts index d908d8a6..f9eb8603 100644 --- a/packages/host/agent-adapter/src/__tests__/claude-code-commands.test.ts +++ b/packages/host/agent-adapter/src/__tests__/claude-code-commands.test.ts @@ -186,7 +186,10 @@ class FakeQuery { } function pushMessages(query: FakeQuery, ...messages: WireMessage[]): void { - for (const message of messages) query.push(message); + for (let i = 0, len = messages.length; i < len; i++) { + const message = messages[i]; + query.push(message); + } } const queries: FakeQuery[] = []; diff --git a/packages/host/agent-adapter/src/__tests__/claude-code-subagent.test.ts b/packages/host/agent-adapter/src/__tests__/claude-code-subagent.test.ts index 2914aaa6..2443eb80 100644 --- a/packages/host/agent-adapter/src/__tests__/claude-code-subagent.test.ts +++ b/packages/host/agent-adapter/src/__tests__/claude-code-subagent.test.ts @@ -317,7 +317,8 @@ describe('ClaudeCodeAdapter readHistory subagent splice', () => { 'agent-message:u3', ]); - for (const e of result.events) { + for (let i = 0, len = result.events.length; i < len; i++) { + const e = result.events[i]; if (e.event.type === 'tool-call' && e.event.toolCall.toolCallId === 'toolu_sub') { expect(e.event.toolCall.parentToolCallId).toBe(TASK_ID); } @@ -416,10 +417,7 @@ describe('ClaudeCodeAdapter readHistory subagent splice', () => { message: { content: [{ type: 'tool_result', tool_use_id: EDIT_ID, content: 'updated' }] }, tool_use_result: toolUseResult, }); - const liveSettle = live - .tools() - .filter((t) => t.toolCallId === EDIT_ID) - .at(-1); + const liveSettle = live.tools().findLast((t) => t.toolCallId === EDIT_ID); // History: `getSubagentMessages` strips `tool_use_result`, so the patch has to come from the // subagent's own raw transcript instead. @@ -444,7 +442,7 @@ describe('ClaudeCodeAdapter readHistory subagent splice', () => { }), ); const result = await adapter.readHistory({ historyId: asHistoryId(SESSION) }); - const replayedSettle = result.events.filter((e) => e.itemId === EDIT_ID).at(-1)?.event; + const replayedSettle = result.events.findLast((e) => e.itemId === EDIT_ID)?.event; expect(replayedSettle?.type).toBe('tool-call'); if (replayedSettle?.type !== 'tool-call') return; diff --git a/packages/host/agent-adapter/src/__tests__/codex-app-server.test.ts b/packages/host/agent-adapter/src/__tests__/codex-app-server.test.ts index c123de33..4eac6f0b 100644 --- a/packages/host/agent-adapter/src/__tests__/codex-app-server.test.ts +++ b/packages/host/agent-adapter/src/__tests__/codex-app-server.test.ts @@ -31,7 +31,10 @@ function fakeChild() { buffered += chunk.toString(); const lines = buffered.split('\n'); buffered = lines.pop() ?? ''; - for (const line of lines) writes.push(JSON.parse(line) as Record); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; + writes.push(JSON.parse(line) as Record); + } }); return { child, diff --git a/packages/host/agent-adapter/src/__tests__/codex-auth.test.ts b/packages/host/agent-adapter/src/__tests__/codex-auth.test.ts index 348b3f87..3a0d4ce1 100644 --- a/packages/host/agent-adapter/src/__tests__/codex-auth.test.ts +++ b/packages/host/agent-adapter/src/__tests__/codex-auth.test.ts @@ -34,7 +34,8 @@ class FakeCodexServer { close(): void { this.closed = true; const rejections = this.held.splice(0); - for (const reject of rejections) { + for (let i = 0, len = rejections.length; i < len; i++) { + const reject = rejections[i]; reject(new Error('codex: app-server connection is closed')); } } diff --git a/packages/host/agent-adapter/src/__tests__/pi-approval.test.ts b/packages/host/agent-adapter/src/__tests__/pi-approval.test.ts index 0ad3d505..fd0bfcd0 100644 --- a/packages/host/agent-adapter/src/__tests__/pi-approval.test.ts +++ b/packages/host/agent-adapter/src/__tests__/pi-approval.test.ts @@ -38,7 +38,9 @@ vi.mock('@earendil-works/pi-coding-agent', async () => { private readonly options: { extensionFactories?: Array<(api: unknown) => void> }, ) {} reload() { - for (const factory of this.options.extensionFactories ?? []) { + const factories = this.options.extensionFactories ?? []; + for (let i = 0, len = factories.length; i < len; i++) { + const factory = factories[i]; factory({ on(_name: string, handler: Gate) { sdk.gate = handler; diff --git a/packages/host/agent-adapter/src/credential.ts b/packages/host/agent-adapter/src/credential.ts index 8850ed35..a2d9f5e2 100644 --- a/packages/host/agent-adapter/src/credential.ts +++ b/packages/host/agent-adapter/src/credential.ts @@ -86,6 +86,10 @@ function readString(value: unknown): string | undefined { function readStringRecord(value: unknown): Record | undefined { if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined; const out: Record = {}; - for (const [key, val] of Object.entries(value)) if (typeof val === 'string') out[key] = val; + const entries = Object.entries(value); + for (let i = 0, len = entries.length; i < len; i++) { + const [key, val] = entries[i]; + if (typeof val === 'string') out[key] = val; + } return isObjectEmpty(out) ? undefined : out; } diff --git a/packages/host/agent-adapter/src/history-util.ts b/packages/host/agent-adapter/src/history-util.ts index 139963ea..328b742e 100644 --- a/packages/host/agent-adapter/src/history-util.ts +++ b/packages/host/agent-adapter/src/history-util.ts @@ -41,7 +41,8 @@ export function cursorFromTotal( function eventAttachmentLength(event: AgentHistoryEvent): number { if (event.event.type !== 'user-message') return 0; let total = 0; - for (const block of event.event.content) { + for (let i = 0, len = event.event.content.length; i < len; i++) { + const block = event.event.content[i]; if (block.type === 'image' || block.type === 'audio') total += block.data.length; else if (block.type === 'resource' && 'blob' in block.resource) { total += block.resource.blob.length; @@ -64,13 +65,16 @@ export function sliceHistoryEventPage( ): { events: AgentHistoryEvent[]; cursor: string | undefined } { const page: AgentHistoryEvent[] = []; let payloadLength = 0; - for (let index = offset; index < events.length && page.length < limit; index += 1) { + const eventCount = events.length; + let index = offset; + while (index < eventCount && page.length < limit) { const attachmentLength = eventAttachmentLength(events[index]); if (page.length > 0 && payloadLength + attachmentLength > MAX_ATTACHMENT_TOTAL_BASE64_LENGTH) { break; } payloadLength += attachmentLength; page.push(events[index]); + index += 1; } const next = offset + page.length; return { events: page, cursor: next < events.length ? String(next) : undefined }; diff --git a/packages/host/agent-adapter/src/native/claude-code.ts b/packages/host/agent-adapter/src/native/claude-code.ts index 96adac44..8030af57 100644 --- a/packages/host/agent-adapter/src/native/claude-code.ts +++ b/packages/host/agent-adapter/src/native/claude-code.ts @@ -42,6 +42,7 @@ import type { StartOptions, StopReason, SupportedAttachmentImageMimeType, + TokenUsage, ToolCall, ToolCallContent, ToolCallLocation, @@ -167,7 +168,8 @@ async function settingsDefaultMode(cwd: string): Promise | undefined { if (!servers?.length) return undefined; const out: Record = {}; - for (const server of servers) { + for (let i = 0, len = servers.length; i < len; i++) { + const server = servers[i]; out[server.name] = server.type === 'http' ? { type: 'http', url: server.url, ...(server.headers && { headers: server.headers }) } @@ -318,7 +321,9 @@ function usageWindows( push('seven_day_oauth_apps', 10080, limits.seven_day_oauth_apps); push('seven_day_opus', 10080, limits.seven_day_opus); push('seven_day_sonnet', 10080, limits.seven_day_sonnet); - for (const bucket of limits.model_scoped ?? []) { + const modelBuckets = limits.model_scoped ?? []; + for (let i = 0, len = modelBuckets.length; i < len; i++) { + const bucket = modelBuckets[i]; windows.push({ label: bucket.display_name, utilization: bucket.utilization, @@ -360,17 +365,18 @@ export function mapClaudeUsageReport(raw: SDKControlGetUsageResponse): UsageRepo totalDurationMs: raw.session.total_duration_ms, totalLinesAdded: raw.session.total_lines_added, totalLinesRemoved: raw.session.total_lines_removed, - modelUsage: Object.fromEntries( - Object.entries(raw.session.model_usage).map(([model, usage]) => [ - model, - { + modelUsage: Object.entries(raw.session.model_usage).reduce>( + (modelUsage, [model, usage]) => { + modelUsage[model] = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens, cacheReadTokens: usage.cacheReadInputTokens, cacheCreationTokens: usage.cacheCreationInputTokens, totalCostUsd: usage.costUSD, - }, - ]), + }; + return modelUsage; + }, + {}, ), }, subscriptionType: raw.subscription_type, @@ -648,7 +654,10 @@ export class ClaudeCodeAdapter extends BaseAgentAdapter { const children = subagentEvents.get(event.event.toolCall.toolCallId); if (children) { subagentEvents.delete(event.event.toolCall.toolCallId); - for (const child of children) pushWithSubagents(child); + for (let i = 0, len = children.length; i < len; i++) { + const child = children[i]; + pushWithSubagents(child); + } } } }; @@ -660,8 +669,20 @@ export class ClaudeCodeAdapter extends BaseAgentAdapter { const returned = new Set(page.map((message) => message.uuid)); const dropped = offset === 0 ? supplement.droppedRows.filter((row) => !returned.has(row.uuid)) : []; - for (const message of [...dropped, ...page]) { - for (const event of mapper(message)) pushWithSubagents(event); + const pageMessages = [...dropped, ...page]; + for ( + let messageIndex = 0, messageCount = pageMessages.length; + messageIndex < messageCount; + messageIndex++ + ) { + const mappedEvents = mapper(pageMessages[messageIndex]); + for ( + let eventIndex = 0, eventCount = mappedEvents.length; + eventIndex < eventCount; + eventIndex++ + ) { + pushWithSubagents(mappedEvents[eventIndex]); + } } return { session: info @@ -1276,7 +1297,8 @@ export class ClaudeCodeAdapter extends BaseAgentAdapter { // (`init` fires only at Query creation, so it can't catch a live `setModel`). this.syncModel(message.model); let calledTool = false; - for (const block of message.content) { + for (let i = 0, len = message.content.length; i < len; i++) { + const block = message.content[i]; if (block.type === 'tool_use') { const diff = editDiffContent(block.name, block.input); // Announce the tool the moment Claude requests it; the matching tool_result settles it. @@ -1304,7 +1326,8 @@ export class ClaudeCodeAdapter extends BaseAgentAdapter { * cursors or calls `freshSegment()`, so a mid-turn subagent can't break the main streaming bubble. */ private handleSubagentAssistant(message: AssistantMessage, parent: string): void { - for (const block of message.content) { + for (let i = 0, len = message.content.length; i < len; i++) { + const block = message.content[i]; // eslint-disable-next-line sukka/unicorn/prefer-switch -- deliberately non-exhaustive (other block variants are ignored); the switch autofix then trips the error-level default-case rule if (block.type === 'tool_use') { const diff = editDiffContent(block.name, block.input); @@ -1337,14 +1360,16 @@ export class ClaudeCodeAdapter extends BaseAgentAdapter { const results = content.filter((block) => block.type === 'tool_result'); const envelope = results.length === 1 ? toolUseResultEnvelope(msg.tool_use_result) : undefined; const patched = results.length === 1 ? editResultDiffContent(msg.tool_use_result) : undefined; - for (const block of content) { + for (let i = 0, len = content.length; i < len; i++) { + const block = content[i]; if (block.type !== 'tool_result') continue; // Replace (not append) so the patch-bearing diff supersedes the announce-time fragment // instead of stacking a second card, and do it before the settle below: a completed tool is // terminal, so any content emitted after it is silently dropped. if (patched) this.emitTool({ toolCallId: block.tool_use_id, content: patched }); - for (const result of toolResultContent(block.content)) { - this.appendToolContent(block.tool_use_id, result); + const results = toolResultContent(block.content); + for (let i = 0, len = results.length; i < len; i++) { + this.appendToolContent(block.tool_use_id, results[i]); } this.emitTool({ toolCallId: block.tool_use_id, @@ -1490,7 +1515,9 @@ export function toolUseResultEnvelope(value: unknown): Record | if (!isRecord(value)) return undefined; const envelope: Record = {}; let fields = 0; - for (const [key, field] of Object.entries(value)) { + const entries = Object.entries(value); + for (let i = 0, len = entries.length; i < len; i++) { + const [key, field] = entries[i]; const scalar = typeof field === 'string' ? field.length > 0 && field.length <= TOOL_USE_RESULT_SCALAR_MAX @@ -1840,7 +1867,8 @@ export function createClaudeHistoryEventMapper( lastModel = model; events.push({ historyId, ts, event: { type: 'model-update', model } }); } - for (const block of blocks) { + for (let i = 0, len = blocks.length; i < len; i++) { + const block = blocks[i]; if (!isThinkingBlock(block)) continue; const thought = thoughtHistoryEvent( historyId, @@ -1853,7 +1881,8 @@ export function createClaudeHistoryEventMapper( } const text = textHistoryEvent(historyId, 'assistant', messageId, message.message, ts, parent); if (text) events.push(text); - for (const block of blocks) { + for (let i = 0, len = blocks.length; i < len; i++) { + const block = blocks[i]; if (!isToolUseBlock(block)) continue; events.push( toolEvent({ @@ -1871,7 +1900,8 @@ export function createClaudeHistoryEventMapper( } const results = blocks.filter((block) => isToolResultBlock(block)); - for (const block of results) { + for (let i = 0, len = results.length; i < len; i++) { + const block = results[i]; const existing = announced.get(block.tool_use_id); events.push( toolEvent({ diff --git a/packages/host/agent-adapter/src/native/codex/adapter.ts b/packages/host/agent-adapter/src/native/codex/adapter.ts index e00797bb..886c681b 100644 --- a/packages/host/agent-adapter/src/native/codex/adapter.ts +++ b/packages/host/agent-adapter/src/native/codex/adapter.ts @@ -85,9 +85,15 @@ function resolveCodexEnvironment(cwd?: string): Promise { export function codexSkillCommands(response: unknown): CodexSkillCommand[] { if (!isRecord(response) || !Array.isArray(response.data)) return []; const commands = new Map(); - for (const entry of response.data) { + for (let i = 0, len = response.data.length; i < len; i++) { + const entry = response.data[i]; if (!isRecord(entry) || !Array.isArray(entry.skills)) continue; - for (const skill of entry.skills) { + for ( + let skillIndex = 0, skillCount = entry.skills.length; + skillIndex < skillCount; + skillIndex++ + ) { + const skill = entry.skills[skillIndex]; if (!isRecord(skill) || skill.enabled !== true) continue; const name = stringField(skill, 'name'); const path = stringField(skill, 'path'); @@ -118,7 +124,8 @@ interface CodexModelCatalog { function codexModelCatalog(response: unknown): CodexModelCatalog { const catalog: CodexModelCatalog = { defaultModel: undefined, models: [] }; if (!isRecord(response) || !Array.isArray(response.data)) return catalog; - for (const candidate of response.data) { + for (let i = 0, len = response.data.length; i < len; i++) { + const candidate = response.data[i]; if (!isRecord(candidate)) continue; const model = stringField(candidate, 'model') ?? stringField(candidate, 'id'); if (!model) continue; @@ -126,7 +133,8 @@ function codexModelCatalog(response: unknown): CodexModelCatalog { let effortLevels: EffortLevel[] | undefined; if (Array.isArray(advertised)) { const supported = new Set(); - for (const option of advertised) { + for (let i = 0, len = advertised.length; i < len; i++) { + const option = advertised[i]; if (!isRecord(option)) continue; const effort = EffortLevelSchema.safeParse(stringField(option, 'reasoningEffort')); if (effort.success && effort.data !== 'ultracode') supported.add(effort.data); @@ -237,7 +245,8 @@ export function codexMcpConfigOverrides( const out: Record = {}; if (!servers?.length) return out; let hasHttp = false; - for (const server of servers) { + for (let i = 0, len = servers.length; i < len; i++) { + const server = servers[i]; const prefix = `mcp_servers.${server.name}`; if (server.type === 'http') { if (server.headers && !isObjectEmpty(server.headers)) { @@ -467,7 +476,8 @@ export class CodexAdapter extends BaseAgentAdapter { // live-verified against codex app-server 0.144.1; nothing documents it (codex has no .d.ts) — // verify again if the app-server pin moves. const input: CodexTurnInput[] = []; - for (const block of content) { + for (let i = 0, len = content.length; i < len; i++) { + const block = content[i]; if (block.type === 'text') { const previous = input.at(-1); if (previous?.type === 'text') previous.text += `\n${block.text}`; @@ -857,7 +867,10 @@ export class CodexAdapter extends BaseAgentAdapter { (skill) => skill.name !== COMPACT_COMMAND.name, ); this.skillCommands.clear(); - for (const skill of skills) this.skillCommands.set(skill.name, skill); + for (let i = 0, len = skills.length; i < len; i++) { + const skill = skills[i]; + this.skillCommands.set(skill.name, skill); + } this.emitCommands([COMPACT_COMMAND, ...skills.map(({ path: _path, ...command }) => command)]); } catch { if (this.server === server && generation === this.skillsRefreshGeneration) { @@ -1151,7 +1164,10 @@ export class CodexAdapter extends BaseAgentAdapter { this.emitTool(toolCall); break; } - for (const content of toolCall.content) this.appendToolContent(id, content); + for (let i = 0, len = toolCall.content.length; i < len; i++) { + const content = toolCall.content[i]; + this.appendToolContent(id, content); + } this.emitTool({ ...toolCall, content: undefined }); break; } @@ -1159,7 +1175,8 @@ export class CodexAdapter extends BaseAgentAdapter { const changes = Array.isArray(item.changes) ? item.changes.filter(isRecord) : []; const locations: Array<{ path: string }> = []; const content: ToolCallContent[] = []; - for (const change of changes) { + for (let i = 0, len = changes.length; i < len; i++) { + const change = changes[i]; const path = stringField(change, 'path'); if (!path) continue; // An update kind can carry a rename: `kind: {type:'update', move_path}` with `path` diff --git a/packages/host/agent-adapter/src/native/codex/history-tools.ts b/packages/host/agent-adapter/src/native/codex/history-tools.ts index 7ff67542..0e820dc6 100644 --- a/packages/host/agent-adapter/src/native/codex/history-tools.ts +++ b/packages/host/agent-adapter/src/native/codex/history-tools.ts @@ -254,7 +254,8 @@ export function applyPatchToolView(input: string): ApplyPatchView | null { const lines = input.split('\n'); // An envelope ending in '\n' splits into a trailing '' that is not a content line. if (lines.at(-1) === '') lines.pop(); - for (const line of lines) { + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; if (line.startsWith('*** Update File: ')) { flush(); updatePath = line.slice('*** Update File: '.length); diff --git a/packages/host/agent-adapter/src/native/codex/history.ts b/packages/host/agent-adapter/src/native/codex/history.ts index 1f085a45..0bd2ed44 100644 --- a/packages/host/agent-adapter/src/native/codex/history.ts +++ b/packages/host/agent-adapter/src/native/codex/history.ts @@ -125,7 +125,8 @@ export function isSyntheticCodexUserPayload( /** The texts codex echoed as `event_msg`/`user_message` rows — the real prompts of the rollout. */ export function collectCodexPromptTexts(rows: JsonRecord[]): Set { const texts = new Set(); - for (const row of rows) { + for (let i = 0, len = rows.length; i < len; i++) { + const row = rows[i]; if (stringField(row, 'type') !== 'event_msg') continue; const payload = recordField(row, 'payload'); if (!payload || stringField(payload, 'type') !== 'user_message') continue; @@ -228,7 +229,8 @@ export function codexHome(environment: NodeJS.ProcessEnv = env): string { export async function readCodexIndex(home = codexHome()): Promise> { const rows = await readJsonlFile(join(home, 'session_index.jsonl')); const index = new Map(); - for (const row of rows) { + for (let i = 0, len = rows.length; i < len; i++) { + const row = rows[i]; const id = stringField(row, 'id'); if (!id) continue; index.set(id, { @@ -271,12 +273,16 @@ async function collectJsonlFiles(root: string, depth = 8): Promise { } const files: string[] = []; const pendingDirs: Array> = []; - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; const path = join(root, entry.name); if (entry.isDirectory()) pendingDirs.push(collectJsonlFiles(path, depth - 1)); else if (entry.isFile() && entry.name.endsWith('.jsonl')) files.push(path); } - for (const nestedFiles of await Promise.all(pendingDirs)) appendArrayInPlace(files, nestedFiles); + const nestedFileGroups = await Promise.all(pendingDirs); + for (let i = 0, len = nestedFileGroups.length; i < len; i++) { + appendArrayInPlace(files, nestedFileGroups[i]); + } return files; } @@ -288,7 +294,9 @@ export async function readJsonlFile(path: string): Promise { return []; } const rows: JsonRecord[] = []; - for (const line of raw.split('\n')) { + const lines = raw.split('\n'); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; if (line.trim().length === 0) continue; try { const parsed: unknown = JSON.parse(line); @@ -322,7 +330,8 @@ async function readCodexTranscriptSummary( let modelProvider: string | undefined; let gitBranch: string | undefined; - for (const row of rows) { + for (let i = 0, len = rows.length; i < len; i++) { + const row = rows[i]; const rowType = stringField(row, 'type'); const rowTs = timestampMs(row.timestamp); if (rowTs !== undefined) { diff --git a/packages/host/agent-adapter/src/native/codex/unified-diff.ts b/packages/host/agent-adapter/src/native/codex/unified-diff.ts index 147532a2..306dc87e 100644 --- a/packages/host/agent-adapter/src/native/codex/unified-diff.ts +++ b/packages/host/agent-adapter/src/native/codex/unified-diff.ts @@ -27,7 +27,8 @@ export function diffContentFromUnified( const newLines: string[] = []; let inHunk = false; - for (const line of lines) { + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; if (HUNK_HEADER_RE.test(line)) { inHunk = true; continue; diff --git a/packages/host/agent-adapter/src/native/grok-build/adapter.ts b/packages/host/agent-adapter/src/native/grok-build/adapter.ts index 626e9a69..684c8b48 100644 --- a/packages/host/agent-adapter/src/native/grok-build/adapter.ts +++ b/packages/host/agent-adapter/src/native/grok-build/adapter.ts @@ -28,7 +28,7 @@ const POLICIES = [ name: 'Bypass permissions', description: 'All tools run without approval prompts; this adapter cannot change it.', }, -] as const satisfies ReadonlyArray; +] as const satisfies readonly ApprovalPolicy[]; /** * Grok Build adapter — drives the local `grok` CLI in **headless** mode (`grok -p`), not ACP. diff --git a/packages/host/agent-adapter/src/native/opencode/adapter.ts b/packages/host/agent-adapter/src/native/opencode/adapter.ts index adbc2be4..1c2f0dda 100644 --- a/packages/host/agent-adapter/src/native/opencode/adapter.ts +++ b/packages/host/agent-adapter/src/native/opencode/adapter.ts @@ -79,7 +79,8 @@ export function opencodeMcpConfig( ): Record | undefined { if (!servers?.length) return undefined; const out: Record = {}; - for (const server of servers) { + for (let i = 0, len = servers.length; i < len; i++) { + const server = servers[i]; out[server.name] = server.type === 'http' ? { @@ -150,7 +151,12 @@ type OpencodeClient = ReturnType; type OpencodeProviderList = NonNullable< Awaited>['data'] >; -type OpencodeAgentSummary = { name: string; mode: string; hidden?: boolean; description?: string }; +interface OpencodeAgentSummary { + name: string; + mode: string; + hidden?: boolean; + description?: string; +} /** Reachable models: every connected (or key-less `api`-source) provider's models, narrowed to the * credential-injected provider when one is in play. Pre-session reads pass null — the injection is @@ -161,13 +167,16 @@ function opencodeModelOptions( ): AgentModelOption[] { const connected = new Set(listed.connected); const models: AgentModelOption[] = []; - for (const provider of listed.all) { + for (let i = 0, len = listed.all.length; i < len; i++) { + const provider = listed.all[i]; if (credentialProviderId) { if (provider.id !== credentialProviderId) continue; } else if (!connected.has(provider.id) && provider.source !== 'api') { continue; } - for (const [modelId, model] of Object.entries(provider.models)) { + const entries = Object.entries(provider.models); + for (let i = 0, len = entries.length; i < len; i++) { + const [modelId, model] = entries[i]; models.push({ id: `${provider.id}/${modelId}`, label: model.name || modelId, @@ -183,7 +192,7 @@ function opencodeModelOptions( * the first primary, else the first selectable. Null when nothing is selectable, so the axis stays * hidden rather than advertising an empty list. */ function opencodeAgentPolicies( - agents: ReadonlyArray, + agents: readonly OpencodeAgentSummary[], ): { policies: ApprovalPolicy[]; defaultPolicyId: string } | null { const selectable = agents.filter( (agent) => (agent.mode === 'primary' || agent.mode === 'all') && agent.hidden !== true, @@ -762,7 +771,7 @@ export class OpenCodeAdapter extends BaseAgentAdapter { * recorded agent wins while it is still selectable. Nothing selectable → the axis stays hidden * (an empty `availablePolicies` is never emitted). */ private adoptAgentCatalog( - agents: ReadonlyArray, + agents: readonly OpencodeAgentSummary[], preferredAgent: string | null, ): void { const catalog = opencodeAgentPolicies(agents); @@ -1128,7 +1137,10 @@ export class OpenCodeAdapter extends BaseAgentAdapter { // cumulative part updates do not retransmit an ever-growing content array. const toolCall = toolCallFromPart(part); if (toolCall.status === 'completed' || toolCall.status === 'failed') { - for (const content of toolCall.content) this.appendToolContent(part.id, content); + for (let i = 0, len = toolCall.content.length; i < len; i++) { + const content = toolCall.content[i]; + this.appendToolContent(part.id, content); + } this.emitTool({ ...toolCall, content: undefined }); } else { this.emitTool(toolCall); diff --git a/packages/host/agent-adapter/src/native/opencode/history.ts b/packages/host/agent-adapter/src/native/opencode/history.ts index 8ec28b43..9e4b8676 100644 --- a/packages/host/agent-adapter/src/native/opencode/history.ts +++ b/packages/host/agent-adapter/src/native/opencode/history.ts @@ -108,7 +108,8 @@ export function mapOpencodeHistoryEvents( messages: OpencodeMessageWithParts[], ): AgentHistoryEvent[] { const events: AgentHistoryEvent[] = []; - for (const { info, parts } of messages) { + for (let i = 0, len = messages.length; i < len; i++) { + const { info, parts } = messages[i]; const ts = info.time.created; if (info.role === 'user') { const text = parts @@ -123,7 +124,8 @@ export function mapOpencodeHistoryEvents( if (event) events.push(event); continue; } - for (const part of parts) { + for (let partIndex = 0, partCount = parts.length; partIndex < partCount; partIndex++) { + const part = parts[partIndex]; switch (part.type) { case 'text': { const event = textHistoryEvent(historyId, 'assistant', part.id, part.text, ts); diff --git a/packages/host/agent-adapter/src/native/pi/history.ts b/packages/host/agent-adapter/src/native/pi/history.ts index 69d1964d..6c570a2e 100644 --- a/packages/host/agent-adapter/src/native/pi/history.ts +++ b/packages/host/agent-adapter/src/native/pi/history.ts @@ -137,7 +137,8 @@ export function mapPiHistoryEvents( event: { type: 'tool-call', toolCall: value }, }); }; - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; const ts = timestampMs(entry.timestamp); if (entry.type === 'compaction') { events.push({ @@ -247,7 +248,8 @@ export function lastPiModelChange(entries: SessionEntry[]) { return null; } function firstUser(entries: SessionEntry[]): string | undefined { - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; if (entry.type === 'message' && isRecord(entry.message) && entry.message.role === 'user') { const content = entry.message.content; const text = diff --git a/packages/host/agent-adapter/src/plugins/claude-code.ts b/packages/host/agent-adapter/src/plugins/claude-code.ts index 5fc09938..79924f47 100644 --- a/packages/host/agent-adapter/src/plugins/claude-code.ts +++ b/packages/host/agent-adapter/src/plugins/claude-code.ts @@ -131,14 +131,16 @@ async function normalizeClaudePlugins( marketplaces.map((marketplace) => [marketplace.name, marketplace]), ); const records = new Map(); - for (const available of catalog.available) { + for (let i = 0, len = catalog.available.length; i < len; i++) { + const available = catalog.available[i]; records.set(available.pluginId, { id: available.pluginId, available, installations: [], }); } - for (const installed of catalog.installed) { + for (let i = 0, len = catalog.installed.length; i < len; i++) { + const installed = catalog.installed[i]; const record = records.get(installed.id) ?? { id: installed.id, installations: [] }; record.installations.push(installed); records.set(installed.id, record); diff --git a/packages/host/agent-adapter/src/probe/base.ts b/packages/host/agent-adapter/src/probe/base.ts index bca37f91..ab67a93f 100644 --- a/packages/host/agent-adapter/src/probe/base.ts +++ b/packages/host/agent-adapter/src/probe/base.ts @@ -56,7 +56,8 @@ export abstract class AgentCliProbe { sdkPlatformBinaryPath(): string | undefined { const scope = this.sdkPackage.split('/', 1)[0]; const paths = createRequire(import.meta.url).resolve.paths(this.sdkPackage) ?? []; - for (const dir of paths) { + for (let i = 0, len = paths.length; i < len; i++) { + const dir = paths[i]; const candidate = join(dir, scope, this.platformPackageBase(), this.binaryName()); if (existsSync(candidate)) return candidate; } @@ -100,7 +101,9 @@ export abstract class AgentCliProbe { /** Probe the known install locations in order; the first verified install wins. */ async detect(): Promise { - for (const location of this.knownLocations()) { + const locations = this.knownLocations(); + for (let i = 0, len = locations.length; i < len; i++) { + const location = locations[i]; // eslint-disable-next-line no-await-in-loop -- locations are a precedence list; the first verified install wins const runtime = await this.probeAt(location); if (runtime) return runtime; diff --git a/packages/host/agent-adapter/src/util.ts b/packages/host/agent-adapter/src/util.ts index caf7da2f..3d5378ad 100644 --- a/packages/host/agent-adapter/src/util.ts +++ b/packages/host/agent-adapter/src/util.ts @@ -37,7 +37,8 @@ const PATH_INPUT_KEYS = ['file_path', 'path', 'notebook_path', 'filePath'] as co export function locationsFromToolInput(input: unknown): ToolCallLocation[] | undefined { if (input === null || typeof input !== 'object') return undefined; const record = input as Record; - for (const key of PATH_INPUT_KEYS) { + for (let i = 0, len = PATH_INPUT_KEYS.length; i < len; i++) { + const key = PATH_INPUT_KEYS[i]; const value = record[key]; if (typeof value === 'string' && value.length > 0) return [{ path: value }]; } diff --git a/packages/host/assets/src/__tests__/catalog.test.ts b/packages/host/assets/src/__tests__/catalog.test.ts index 1bce2da7..02cc7270 100644 --- a/packages/host/assets/src/__tests__/catalog.test.ts +++ b/packages/host/assets/src/__tests__/catalog.test.ts @@ -49,8 +49,15 @@ describe('CATALOG', () => { }); it('declares a verifiable baked artifact for every baked platform entry', () => { - for (const descriptor of descriptors) { - for (const source of Object.values(descriptor.artifacts)) { + for (let i = 0, len = descriptors.length; i < len; i++) { + const descriptor = descriptors[i]; + const sources = Object.values(descriptor.artifacts); + for ( + let sourceIndex = 0, sourceCount = sources.length; + sourceIndex < sourceCount; + sourceIndex++ + ) { + const source = sources[sourceIndex]; if (source.kind !== 'baked') continue; expect(source.url).toMatch(HTTPS); expect(source.size).toBeGreaterThan(0); @@ -60,8 +67,11 @@ describe('CATALOG', () => { }); it('gives windows entries .exe members and posix entries bare members', () => { - for (const descriptor of descriptors) { - for (const [key, source] of Object.entries(descriptor.artifacts)) { + for (let i = 0, len = descriptors.length; i < len; i++) { + const descriptor = descriptors[i]; + const entries = Object.entries(descriptor.artifacts); + for (let entryIndex = 0, entryCount = entries.length; entryIndex < entryCount; entryIndex++) { + const [key, source] = entries[entryIndex]; expect(source.member.endsWith('.exe')).toBe(key.startsWith('win32')); } } @@ -77,7 +87,9 @@ describe('CATALOG', () => { }); it('ships the codex Windows sandbox helpers next to the win32 binaries only', () => { - for (const [key, source] of Object.entries(binary(CODEX_ID).artifacts)) { + const entries = Object.entries(binary(CODEX_ID).artifacts); + for (let i = 0, len = entries.length; i < len; i++) { + const [key, source] = entries[i]; if (source.kind !== 'npm') throw new Error('expected npm source'); if (!key.startsWith('win32')) { expect(source.extraMembers).toBeUndefined(); diff --git a/packages/host/assets/src/__tests__/system-proxy.test.ts b/packages/host/assets/src/__tests__/system-proxy.test.ts index 98c20a19..586e0187 100644 --- a/packages/host/assets/src/__tests__/system-proxy.test.ts +++ b/packages/host/assets/src/__tests__/system-proxy.test.ts @@ -34,7 +34,10 @@ beforeEach(() => { fetchMock.mockReset(); win32Mock.mockReset(); darwinMock.mockReset(); - for (const key of PROXY_ENV_KEYS) delete process.env[key]; + for (let i = 0, len = PROXY_ENV_KEYS.length; i < len; i++) { + const key = PROXY_ENV_KEYS[i]; + delete process.env[key]; + } setPlatform('win32'); }); @@ -43,7 +46,8 @@ afterEach(() => { }); afterAll(() => { - for (const key of PROXY_ENV_KEYS) { + for (let i = 0, len = PROXY_ENV_KEYS.length; i < len; i++) { + const key = PROXY_ENV_KEYS[i]; const value = originalProxyEnv.get(key); if (value === undefined) delete process.env[key]; else process.env[key] = value; diff --git a/packages/host/assets/src/catalog.ts b/packages/host/assets/src/catalog.ts index d8b9ad07..7c9a022b 100644 --- a/packages/host/assets/src/catalog.ts +++ b/packages/host/assets/src/catalog.ts @@ -70,24 +70,21 @@ export function isClosureDescriptor( return 'closure' in descriptor; } -const PLATFORM_KEYS: PlatformKey[] = [ - 'darwin-arm64', - 'darwin-x64', - 'linux-arm64', - 'linux-x64', - 'win32-arm64', - 'win32-x64', -]; - function exe(key: PlatformKey): string { return key.startsWith('win32') ? '.exe' : ''; } -function forAllPlatforms(source: (key: PlatformKey) => ArtifactSource) { - return Object.fromEntries(PLATFORM_KEYS.map((key) => [key, source(key)])) as Record< - PlatformKey, - ArtifactSource - >; +function forAllPlatforms( + source: (key: PlatformKey) => ArtifactSource, +): Record { + return { + 'darwin-arm64': source('darwin-arm64'), + 'darwin-x64': source('darwin-x64'), + 'linux-arm64': source('linux-arm64'), + 'linux-x64': source('linux-x64'), + 'win32-arm64': source('win32-arm64'), + 'win32-x64': source('win32-x64'), + }; } /** Rust target triples embedded in the codex tarball's `vendor/` tree, per platform. */ diff --git a/packages/host/assets/src/closure-gen.ts b/packages/host/assets/src/closure-gen.ts index 76aacaea..cb41c4bc 100644 --- a/packages/host/assets/src/closure-gen.ts +++ b/packages/host/assets/src/closure-gen.ts @@ -79,8 +79,15 @@ function collectNodes(lockfile: Lockfile, rootKey: string): Map(); - for (const source of [snapshot.dependencies, snapshot.optionalDependencies]) { - for (const [depName, depVersion] of Object.entries(source ?? {})) { + const dependencyGroups = [snapshot.dependencies, snapshot.optionalDependencies]; + for ( + let groupIndex = 0, groupCount = dependencyGroups.length; + groupIndex < groupCount; + groupIndex++ + ) { + const entries = Object.entries(dependencyGroups[groupIndex] ?? {}); + for (let entryIndex = 0, entryCount = entries.length; entryIndex < entryCount; entryIndex++) { + const [depName, depVersion] = entries[entryIndex]; const childKey = `${depName}@${depVersion}`; deps.set(depName, childKey); queue.push(childKey); diff --git a/packages/host/assets/src/download.ts b/packages/host/assets/src/download.ts index 5d64da1a..5812baaf 100644 --- a/packages/host/assets/src/download.ts +++ b/packages/host/assets/src/download.ts @@ -41,7 +41,8 @@ export async function downloadVerified( ): Promise { const failures: string[] = []; let integrityFailed = false; - for (const url of artifact.urls) { + for (let i = 0, len = artifact.urls.length; i < len; i++) { + const url = artifact.urls[i]; try { // eslint-disable-next-line no-await-in-loop -- urls are an ordered fallback list await downloadFrom(url, destFile, artifact, options); diff --git a/packages/host/assets/src/gc.ts b/packages/host/assets/src/gc.ts index d6ca3cf4..86daad04 100644 --- a/packages/host/assets/src/gc.ts +++ b/packages/host/assets/src/gc.ts @@ -36,7 +36,8 @@ export function collectGarbage( } // Version dirs publish via atomic rename, so presence = a complete install. const wantedInstalled = entries.includes(version); - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; if (entry === version) continue; if (!wantedInstalled && !entry.startsWith('.tmp-')) continue; const target = join(dir, entry); diff --git a/packages/host/assets/src/install.ts b/packages/host/assets/src/install.ts index b991ac8e..1589cd3e 100644 --- a/packages/host/assets/src/install.ts +++ b/packages/host/assets/src/install.ts @@ -121,7 +121,9 @@ async function doInstall( artifact.member, join(stage, binaryName(descriptor)), ); - for (const member of artifact.extraMembers ?? []) { + const extraMembers = artifact.extraMembers ?? []; + for (let i = 0, len = extraMembers.length; i < len; i++) { + const member = extraMembers[i]; await extractMember(archive, artifact.format, member, join(stage, posix.basename(member))); } } @@ -152,7 +154,9 @@ async function stageClosure( options: InstallOptions, ): Promise { const byTarball = new Map(); - for (const pkg of closurePackagesForHost(descriptor.closure, process.platform, process.arch)) { + const packages = closurePackagesForHost(descriptor.closure, process.platform, process.arch); + for (let i = 0, len = packages.length; i < len; i++) { + const pkg = packages[i]; const key = `${pkg.name}@${pkg.version}`; const targets = byTarball.get(key); if (targets) targets.push(pkg); @@ -181,7 +185,8 @@ async function stageClosure( }, }, ); - for (const target of targets) { + for (let i = 0, len = targets.length; i < len; i++) { + const target = targets[i]; // eslint-disable-next-line no-await-in-loop -- same archive, sequential extract targets await extractPackageTree(archive, join(stage, target.path)); } @@ -204,7 +209,8 @@ function publish(stage: string, dest: string, expected: readonly string[]): void renameSync(stage, dest); } catch (error) { if (!existsSync(dest)) throw error; - for (const file of expected) { + for (let i = 0, len = expected.length; i < len; i++) { + const file = expected[i]; const target = join(dest, file); if (existsSync(target)) continue; try { diff --git a/packages/host/assets/src/registry-client.ts b/packages/host/assets/src/registry-client.ts index 36c61159..fd6f42bf 100644 --- a/packages/host/assets/src/registry-client.ts +++ b/packages/host/assets/src/registry-client.ts @@ -43,7 +43,8 @@ export async function fetchNpmDist( const registries = options.registries ?? DEFAULT_REGISTRIES; const retry = options.retry ?? DEFAULT_RETRY; const failures: string[] = []; - for (const registry of registries) { + for (let i = 0, len = registries.length; i < len; i++) { + const registry = registries[i]; // eslint-disable-next-line no-await-in-loop -- registries are an ordered fallback list const dist = await fetchDist(`${registry}/${pkg}/${version}`, retry, failures); if (dist) return dist; diff --git a/packages/host/assets/src/system-proxy/index.ts b/packages/host/assets/src/system-proxy/index.ts index 172103ab..c2717980 100644 --- a/packages/host/assets/src/system-proxy/index.ts +++ b/packages/host/assets/src/system-proxy/index.ts @@ -84,7 +84,9 @@ function mergeEnvNoProxy(osNoProxy: string[]): string[] { ([key, value]) => key.toLowerCase() === 'no_proxy' && !!value, )?.[1]; const envNoProxy: string[] = []; - for (const host of env?.split(',') ?? []) { + const hosts = env?.split(',') ?? []; + for (let i = 0, len = hosts.length; i < len; i++) { + const host = hosts[i]; const trimmed = host.trim(); if (trimmed) envNoProxy.push(trimmed); } diff --git a/packages/host/assets/src/system-proxy/win32.ts b/packages/host/assets/src/system-proxy/win32.ts index dfba1d7f..06f7a4ba 100644 --- a/packages/host/assets/src/system-proxy/win32.ts +++ b/packages/host/assets/src/system-proxy/win32.ts @@ -35,7 +35,9 @@ export function getWin32SystemProxy(): SystemProxyDetection | undefined { */ export function parseRegQueryOutput(output: string): Map { const values = new Map(); - for (const line of output.split(NEWLINE)) { + const lines = output.split(NEWLINE); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; const row = VALUE_ROW.exec(line); if (!row) continue; const [, name, type, data] = row; @@ -94,7 +96,9 @@ function parseProxyServer(config: string): string { function parseProxyOverride(override: string | number | undefined): string[] { if (typeof override !== 'string') return []; const noProxy: string[] = []; - for (const entry of override.split(';')) { + const entries = override.split(';'); + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; const host = entry.trim(); if (!host) continue; if (host === '') noProxy.push('localhost', '127.0.0.1', '::1'); diff --git a/packages/host/assets/tests/integration/manager.test.ts b/packages/host/assets/tests/integration/manager.test.ts index 4a24a221..9e40c1cf 100644 --- a/packages/host/assets/tests/integration/manager.test.ts +++ b/packages/host/assets/tests/integration/manager.test.ts @@ -71,7 +71,9 @@ function closureDescriptor(sdkPackage = 'fake-pi-sdk'): NpmClosureAssetDescripto /** A pinFrom anchor whose node_modules holds the given packages. */ function anchorWith(packages: Record): string { const root = mkdtempSync(join(tmpdir(), 'closure-pin-')); - for (const [name, version] of Object.entries(packages)) { + const entries = Object.entries(packages); + for (let i = 0, len = entries.length; i < len; i++) { + const [name, version] = entries[i]; const dir = join(root, 'node_modules', name); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, 'package.json'), JSON.stringify({ name, version })); diff --git a/packages/host/assets/tests/support/fixtures.ts b/packages/host/assets/tests/support/fixtures.ts index 3155f300..835d3fd4 100644 --- a/packages/host/assets/tests/support/fixtures.ts +++ b/packages/host/assets/tests/support/fixtures.ts @@ -19,7 +19,9 @@ export function makeTgz( ): TgzFixture { const root = mkdtempSync(join(tmpdir(), 'tgz-fixture-')); const members = { [member]: content, ...extraMembers }; - for (const [path, data] of Object.entries(members)) { + const entries = Object.entries(members); + for (let i = 0, len = entries.length; i < len; i++) { + const [path, data] = entries[i]; const file = join(root, path); mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, data, { mode: 0o644 }); @@ -37,7 +39,9 @@ export function makeTgz( /** Build an npm-shaped tgz: every file under the standard `package/` root. */ export function makePackageTgz(files: Record): TgzFixture { const root = mkdtempSync(join(tmpdir(), 'pkg-fixture-')); - for (const [name, content] of Object.entries(files)) { + const entries = Object.entries(files); + for (let i = 0, len = entries.length; i < len; i++) { + const [name, content] = entries[i]; const file = join(root, 'package', name); mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, content, { mode: 0o644 }); diff --git a/packages/host/engine/src/__tests__/engine-file-suggest.test.ts b/packages/host/engine/src/__tests__/engine-file-suggest.test.ts index 0c476ea6..0892ccde 100644 --- a/packages/host/engine/src/__tests__/engine-file-suggest.test.ts +++ b/packages/host/engine/src/__tests__/engine-file-suggest.test.ts @@ -18,7 +18,10 @@ import { createTestEngine } from './fixtures/test-engine'; const tempRoots: string[] = []; afterAll(() => { - for (const dir of tempRoots) rmSync(dir, { recursive: true, force: true }); + for (let i = 0, len = tempRoots.length; i < len; i++) { + const dir = tempRoots[i]; + rmSync(dir, { recursive: true, force: true }); + } }); function fakeAdapter(): AgentAdapter { diff --git a/packages/host/engine/src/__tests__/file-service.test.ts b/packages/host/engine/src/__tests__/file-service.test.ts index 7b53e75b..386818a7 100644 --- a/packages/host/engine/src/__tests__/file-service.test.ts +++ b/packages/host/engine/src/__tests__/file-service.test.ts @@ -14,7 +14,10 @@ function makeTempDir(): string { } afterAll(() => { - for (const root of roots) rmSync(root, { recursive: true, force: true }); + for (let i = 0, len = roots.length; i < len; i++) { + const root = roots[i]; + rmSync(root, { recursive: true, force: true }); + } }); function runReadWorkspaceFile(cwd: string, path: string) { diff --git a/packages/host/engine/src/__tests__/request-handler-effects.test.ts b/packages/host/engine/src/__tests__/request-handler-effects.test.ts index b686beaf..b4791f28 100644 --- a/packages/host/engine/src/__tests__/request-handler-effects.test.ts +++ b/packages/host/engine/src/__tests__/request-handler-effects.test.ts @@ -18,7 +18,10 @@ import { WireResponder } from '../wire/responder'; const roots: string[] = []; afterAll(() => { - for (const root of roots) rmSync(root, { recursive: true, force: true }); + for (let i = 0, len = roots.length; i < len; i++) { + const root = roots[i]; + rmSync(root, { recursive: true, force: true }); + } }); class RejectingPtyBackend implements PtyBackend { diff --git a/packages/host/engine/src/__tests__/script-service.test.ts b/packages/host/engine/src/__tests__/script-service.test.ts index d2b3e12c..64db7117 100644 --- a/packages/host/engine/src/__tests__/script-service.test.ts +++ b/packages/host/engine/src/__tests__/script-service.test.ts @@ -30,7 +30,10 @@ function makeWorkspace(config?: unknown): string { } afterAll(() => { - for (const root of roots) rmSync(root, { recursive: true, force: true }); + for (let i = 0, len = roots.length; i < len; i++) { + const root = roots[i]; + rmSync(root, { recursive: true, force: true }); + } }); class FakePtyProcess implements PtyProcess { @@ -58,7 +61,10 @@ class FakePtyProcess implements PtyProcess { this.exit(0); } exit(code: number | null): void { - for (const cb of this.exitCbs) cb(code); + for (let i = 0, len = this.exitCbs.length; i < len; i++) { + const cb = this.exitCbs[i]; + cb(code); + } } } diff --git a/packages/host/engine/src/__tests__/simulator-service.test.ts b/packages/host/engine/src/__tests__/simulator-service.test.ts index 3dcecd53..3039caf2 100644 --- a/packages/host/engine/src/__tests__/simulator-service.test.ts +++ b/packages/host/engine/src/__tests__/simulator-service.test.ts @@ -90,7 +90,9 @@ describe('SimulatorService', () => { const backend = fakeBackend([]); const service = new SimulatorService(backend); - for (const udid of ['A', 'B', 'C', 'D']) { + const udids = ['A', 'B', 'C', 'D']; + for (let i = 0, len = udids.length; i < len; i++) { + const udid = udids[i]; await service.openUrl(S1, udid, 'https://example.com'); } await expect(service.openUrl(S1, 'E', 'https://example.com')).rejects.toMatchObject({ diff --git a/packages/host/engine/src/__tests__/terminal-service.test.ts b/packages/host/engine/src/__tests__/terminal-service.test.ts index a2c6cb67..70ef7216 100644 --- a/packages/host/engine/src/__tests__/terminal-service.test.ts +++ b/packages/host/engine/src/__tests__/terminal-service.test.ts @@ -30,7 +30,10 @@ class FakePtyProcess implements PtyProcess { } write(data: string): void { this.writes.push(data); - for (const cb of this.dataCbs) cb(data); + for (let i = 0, len = this.dataCbs.length; i < len; i++) { + const cb = this.dataCbs[i]; + cb(data); + } } resize(cols: number, rows: number): void { this.resizes.push({ cols, rows }); @@ -40,15 +43,24 @@ class FakePtyProcess implements PtyProcess { } kill(): void { this.killed = true; - for (const cb of this.exitCbs) cb(0); + for (let i = 0, len = this.exitCbs.length; i < len; i++) { + const cb = this.exitCbs[i]; + cb(0); + } } emitData(data: string): void { - for (const cb of this.dataCbs) cb(data); + for (let i = 0, len = this.dataCbs.length; i < len; i++) { + const cb = this.dataCbs[i]; + cb(data); + } } emitExit(exitCode: number | null): void { - for (const cb of this.exitCbs) cb(exitCode); + for (let i = 0, len = this.exitCbs.length; i < len; i++) { + const cb = this.exitCbs[i]; + cb(exitCode); + } } } diff --git a/packages/host/engine/src/__tests__/workspace-registry.test.ts b/packages/host/engine/src/__tests__/workspace-registry.test.ts index 138fc306..e893f761 100644 --- a/packages/host/engine/src/__tests__/workspace-registry.test.ts +++ b/packages/host/engine/src/__tests__/workspace-registry.test.ts @@ -29,7 +29,10 @@ function makeTempDir(): string { } afterAll(() => { - for (const dir of tempRoots) rmSync(dir, { recursive: true, force: true }); + for (let i = 0, len = tempRoots.length; i < len; i++) { + const dir = tempRoots[i]; + rmSync(dir, { recursive: true, force: true }); + } }); beforeEach(() => { diff --git a/packages/host/engine/src/automation/loop-iteration-runner.ts b/packages/host/engine/src/automation/loop-iteration-runner.ts index ac3c5f71..ab4615b6 100644 --- a/packages/host/engine/src/automation/loop-iteration-runner.ts +++ b/packages/host/engine/src/automation/loop-iteration-runner.ts @@ -151,7 +151,8 @@ export class LoopIterationRunner { const { reporter } = this; const saveEffect = this.saveEffect.bind(this); return Effect.gen(function* () { - for (const command of loop.spec.verifyChecks) { + for (let i = 0, len = loop.spec.verifyChecks.length; i < len; i++) { + const command = loop.spec.verifyChecks[i]; const result = yield* runShellCheck(command, { cwd: loop.spec.cwd, timeoutMs: loop.spec.turnTimeoutMs, diff --git a/packages/host/engine/src/automation/loop-service.ts b/packages/host/engine/src/automation/loop-service.ts index eac08cda..c862d41d 100644 --- a/packages/host/engine/src/automation/loop-service.ts +++ b/packages/host/engine/src/automation/loop-service.ts @@ -57,12 +57,16 @@ export class LoopService { } async start(): Promise { - for (const loop of await this.store.load()) { + const loops = await this.store.load(); + for (let i = 0, len = loops.length; i < len; i++) { + const loop = loops[i]; this.loops.set(loop.loopId, loop); } // A loop is a single bounded job; a restart cannot resume its worker sessions, so mark any that // were mid-run as stopped rather than pretending to continue. - for (const loop of await this.store.loadRunning()) { + const running = await this.store.loadRunning(); + for (let i = 0, len = running.length; i < len; i++) { + const loop = running[i]; loop.status = 'stopped'; loop.error = 'daemon restarted before the loop finished'; loop.endedAt = this.now(); diff --git a/packages/host/engine/src/automation/schedule-run-coordinator.ts b/packages/host/engine/src/automation/schedule-run-coordinator.ts index 433fb3f1..05104b39 100644 --- a/packages/host/engine/src/automation/schedule-run-coordinator.ts +++ b/packages/host/engine/src/automation/schedule-run-coordinator.ts @@ -92,7 +92,8 @@ export class ScheduleRunCoordinator { 'Failed to recover schedule runs', () => store.loadRunningRuns(), ); - for (const run of running) { + for (let i = 0, len = running.length; i < len; i++) { + const run = running[i]; yield* saveRun({ ...run, status: 'failed', diff --git a/packages/host/engine/src/automation/schedule-store.ts b/packages/host/engine/src/automation/schedule-store.ts index 3e2ee323..c5d3f2dc 100644 --- a/packages/host/engine/src/automation/schedule-store.ts +++ b/packages/host/engine/src/automation/schedule-store.ts @@ -71,7 +71,10 @@ export class InMemoryScheduleStore implements ScheduleStore { pruneRuns(scheduleId: ScheduleId, keep: number): Promise { const runs = this.runsForSchedule(scheduleId); runs.sort((a, b) => b.startedAt - a.startedAt); - for (const run of runs.slice(keep)) this.runs.delete(run.runId); + const prunedRuns = runs.slice(keep); + for (let i = 0, len = prunedRuns.length; i < len; i++) { + this.runs.delete(prunedRuns[i].runId); + } return Promise.resolve(); } } diff --git a/packages/host/engine/src/browser/repl-rewrite.ts b/packages/host/engine/src/browser/repl-rewrite.ts index 4e1de599..9a5edf09 100644 --- a/packages/host/engine/src/browser/repl-rewrite.ts +++ b/packages/host/engine/src/browser/repl-rewrite.ts @@ -28,7 +28,8 @@ export function rewriteForPersistentRepl(code: string): string { const hoisted: string[] = []; const edits: Array<{ start: number; end: number; text: string }> = []; - for (const node of program.body) { + for (let i = 0, len = program.body.length; i < len; i++) { + const node = program.body[i]; switch (node.type) { case 'VariableDeclaration': { if (node.kind === 'using' || node.kind === 'await using') break; @@ -58,7 +59,9 @@ export function rewriteForPersistentRepl(code: string): string { } let rewritten = code; - for (const edit of edits.reverse()) { + const reversedEdits = edits.reverse(); + for (let i = 0, len = reversedEdits.length; i < len; i++) { + const edit = reversedEdits[i]; rewritten = rewritten.slice(0, edit.start) + edit.text + rewritten.slice(edit.end); } const prelude = hoisted.length > 0 ? `var ${hoisted.join(', ')};\n` : ''; @@ -75,12 +78,14 @@ function collectPatternNames(pattern: Pattern, names: string[]): void { names.push(pattern.name); break; case 'ObjectPattern': - for (const property of pattern.properties) { + for (let i = 0, len = pattern.properties.length; i < len; i++) { + const property = pattern.properties[i]; collectPatternNames(property.type === 'RestElement' ? property : property.value, names); } break; case 'ArrayPattern': - for (const element of pattern.elements) { + for (let i = 0, len = pattern.elements.length; i < len; i++) { + const element = pattern.elements[i]; if (element) collectPatternNames(element, names); } break; diff --git a/packages/host/engine/src/engine.ts b/packages/host/engine/src/engine.ts index 003e2383..bfdb552d 100644 --- a/packages/host/engine/src/engine.ts +++ b/packages/host/engine/src/engine.ts @@ -241,7 +241,9 @@ export const createEngineRuntime = Effect.fn('Engine.create')(function* ( yield* tryOperation('store', 'workspaces.load', 'Failed to load workspaces', () => workspaces.start(), ); - for (const workspace of workspaces.list()) { + const workspaceRecords = workspaces.list(); + for (let i = 0, len = workspaceRecords.length; i < len; i++) { + const workspace = workspaceRecords[i]; if (workspace.kind === 'worktree' && !worktrees.hasPath(workspace.cwd)) { yield* tryOperation( 'store', diff --git a/packages/host/engine/src/git/branches.ts b/packages/host/engine/src/git/branches.ts index ea323b6f..d0fa3539 100644 --- a/packages/host/engine/src/git/branches.ts +++ b/packages/host/engine/src/git/branches.ts @@ -25,7 +25,9 @@ export const readGitBranches = Effect.fn('Git.readBranches')(function* (cwd: str if (result.exitCode !== 0) return { isRepo: false } as const; const branches: GitBranch[] = []; - for (const line of result.stdout.split('\n')) { + const lines = result.stdout.split('\n'); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; if (line.length === 0) continue; const [name, head, committedAt] = line.split(FIELD_SEPARATOR); if (!name) continue; diff --git a/packages/host/engine/src/git/diff.ts b/packages/host/engine/src/git/diff.ts index e570b891..d759eba6 100644 --- a/packages/host/engine/src/git/diff.ts +++ b/packages/host/engine/src/git/diff.ts @@ -47,7 +47,9 @@ const resolveBaseRef = Effect.fn('Git.resolveBaseRef')(function* (cwd: string) { const base = symref.stdout.trim().replace(REMOTES_REF_PREFIX, ''); if (base.length > 0) return base; } - for (const candidate of ['origin/main', 'origin/master']) { + const candidates = ['origin/main', 'origin/master']; + for (let i = 0, len = candidates.length; i < len; i++) { + const candidate = candidates[i]; const verify = yield* git(cwd, 'rev-parse', '--verify', '--quiet', `refs/remotes/${candidate}`); if (verify.exitCode === 0) return candidate; } @@ -115,7 +117,9 @@ const readUntrackedDiff = Effect.fn('Git.readUntrackedDiff')(function* (cwd: str if (status.exitCode !== 0) return { patch: '', truncated: false }; const paths: string[] = []; - for (const entry of status.stdout.split('\0')) { + const statusEntries = status.stdout.split('\0'); + for (let i = 0, len = statusEntries.length; i < len; i++) { + const entry = statusEntries[i]; if (entry.startsWith('?? ')) paths.push(entry.slice(3)); } const truncated = paths.length > MAX_UNTRACKED_FILES; @@ -164,7 +168,8 @@ function capToFileBoundary(patch: string, capBytes: number): { patch: string; tr const fileEnds = [...findFileBoundaries(patch).slice(1), patch.length]; let cut = 0; - for (const end of fileEnds) { + for (let i = 0, len = fileEnds.length; i < len; i++) { + const end = fileEnds[i]; if (Buffer.byteLength(patch.slice(0, end), 'utf8') > capBytes) break; cut = end; } @@ -176,7 +181,9 @@ function computeStat(patch: string): GitDiffStat { let files = 0; let additions = 0; let deletions = 0; - for (const line of patch.split('\n')) { + const lines = patch.split('\n'); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; if (line.startsWith('diff --git ')) files += 1; else if (line.startsWith('+++') || line.startsWith('---')) continue; else if (line[0] === '+') additions += 1; diff --git a/packages/host/engine/src/git/github.ts b/packages/host/engine/src/git/github.ts index ccc6f5fa..2f2b8cd8 100644 --- a/packages/host/engine/src/git/github.ts +++ b/packages/host/engine/src/git/github.ts @@ -69,7 +69,8 @@ export function rollUpChecks(rollup: readonly GhCheck[] | null | undefined): Git if (!rollup || rollup.length === 0) return 'none'; let sawFailing = false; let sawPending = false; - for (const check of rollup) { + for (let i = 0, len = rollup.length; i < len; i++) { + const check = rollup[i]; const outcome = check.state ?? (check.status === 'COMPLETED' ? check.conclusion : null); if (outcome === 'EXPECTED' || outcome === 'PENDING' || outcome == null) sawPending = true; else if (FAILING_OUTCOMES.has(outcome)) sawFailing = true; diff --git a/packages/host/engine/src/git/status.ts b/packages/host/engine/src/git/status.ts index c3e039ee..bfd6fd40 100644 --- a/packages/host/engine/src/git/status.ts +++ b/packages/host/engine/src/git/status.ts @@ -25,7 +25,9 @@ const readDirtyFileCount = Effect.fn('Git.readDirtyFileCount')(function* (cwd: s const result = yield* git(cwd, 'status', '--porcelain'); if (result.exitCode !== 0) return 0; let count = 0; - for (const line of result.stdout.split('\n')) { + const lines = result.stdout.split('\n'); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; if (line.length > 0) count += 1; } return count; diff --git a/packages/host/engine/src/json-equal.ts b/packages/host/engine/src/json-equal.ts index 64acd9c9..b27eadc7 100644 --- a/packages/host/engine/src/json-equal.ts +++ b/packages/host/engine/src/json-equal.ts @@ -9,10 +9,14 @@ export function jsonValueEqual(a: unknown, b: unknown): boolean { if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false; const left = a as Record; const right = b as Record; - for (const key of Object.keys(left)) { + const leftKeys = Object.keys(left); + for (let i = 0, len = leftKeys.length; i < len; i++) { + const key = leftKeys[i]; if (!jsonValueEqual(left[key], right[key])) return false; } - for (const key of Object.keys(right)) { + const rightKeys = Object.keys(right); + for (let i = 0, len = rightKeys.length; i < len; i++) { + const key = rightKeys[i]; if (!(key in left) && right[key] !== undefined) return false; } return true; diff --git a/packages/host/engine/src/scripts/config.ts b/packages/host/engine/src/scripts/config.ts index 2d514224..15bd097e 100644 --- a/packages/host/engine/src/scripts/config.ts +++ b/packages/host/engine/src/scripts/config.ts @@ -35,7 +35,9 @@ export function readWorkspaceScripts(cwd: string): ScriptDeclaration[] { if (scripts === null || typeof scripts !== 'object') return []; const declarations: ScriptDeclaration[] = []; - for (const [name, raw] of Object.entries(scripts as Record)) { + const entries = Object.entries(scripts as Record); + for (let i = 0, len = entries.length; i < len; i++) { + const [name, raw] = entries[i]; const entry = ScriptEntrySchema.safeParse(raw); if (!entry.success || name.trim().length === 0) continue; const type = entry.data.type ?? 'task'; diff --git a/packages/host/engine/src/scripts/script-port-plan.ts b/packages/host/engine/src/scripts/script-port-plan.ts index de151a16..ea8e5ced 100644 --- a/packages/host/engine/src/scripts/script-port-plan.ts +++ b/packages/host/engine/src/scripts/script-port-plan.ts @@ -54,7 +54,8 @@ export class ScriptPortPlan { const url = proxyUrl(hostnameFor(declaration.name)); if (url) env.LINKCODE_URL = url; } - for (const sibling of declarations) { + for (let i = 0, len = declarations.length; i < len; i++) { + const sibling = declarations[i]; if (sibling.type !== 'service') continue; const port = this.ports.get(sibling.name); if (port === undefined) continue; @@ -67,7 +68,8 @@ export class ScriptPortPlan { } private async allocateMissing(declarations: ScriptDeclaration[]): Promise { - for (const declaration of declarations) { + for (let i = 0, len = declarations.length; i < len; i++) { + const declaration = declarations[i]; // eslint-disable-next-line no-await-in-loop -- ports are allocated one at a time on purpose const port = declaration.preferredPort ?? (await this.allocate()); this.ensureAccepting(); diff --git a/packages/host/engine/src/session/ask-response.ts b/packages/host/engine/src/session/ask-response.ts index 4960dd3b..e2badbc1 100644 --- a/packages/host/engine/src/session/ask-response.ts +++ b/packages/host/engine/src/session/ask-response.ts @@ -38,7 +38,8 @@ export function validateAskResponse(request: AskEvent, input: AskResponseInput): throw invalidRequest(`Question request ${input.requestId} has duplicate question IDs`); } const answers = new Map(); - for (const answer of input.outcome.answers) { + for (let i = 0, len = input.outcome.answers.length; i < len; i++) { + const answer = input.outcome.answers[i]; if (answers.has(answer.questionId)) { throw invalidRequest(`Duplicate answer for question: ${answer.questionId}`); } @@ -48,7 +49,8 @@ export function validateAskResponse(request: AskEvent, input: AskResponseInput): answers.set(answer.questionId, answer); } - for (const question of request.questions) { + for (let i = 0, len = request.questions.length; i < len; i++) { + const question = request.questions[i]; const answer = answers.get(question.questionId); if (!answer) throw invalidRequest(`Missing answer: ${question.questionId}`); if (answer.customText !== undefined) { diff --git a/packages/host/engine/src/session/attachment-guard.ts b/packages/host/engine/src/session/attachment-guard.ts index 22daa452..b7c95da9 100644 --- a/packages/host/engine/src/session/attachment-guard.ts +++ b/packages/host/engine/src/session/attachment-guard.ts @@ -24,7 +24,8 @@ function attachmentData(block: ContentBlock): string | undefined { * keeps a prompt's base64 under the transport's frame buffer, whose overflow kills the connection. */ export function assertAttachmentContentAllowed(content: ContentBlock[]): void { let totalBytes = 0; - for (const block of content) { + for (let i = 0, len = content.length; i < len; i++) { + const block = content[i]; if (block.type === 'image' && !isSupportedAttachmentImageMimeType(block.mimeType)) { throw new RequestError({ code: 'invalid_request', diff --git a/packages/host/engine/src/session/history-service.ts b/packages/host/engine/src/session/history-service.ts index 4a3d50a0..b55175e5 100644 --- a/packages/host/engine/src/session/history-service.ts +++ b/packages/host/engine/src/session/history-service.ts @@ -84,7 +84,8 @@ export class HistoryService { Effect.tap((result) => Effect.sync(() => { this.invalidateEventCacheFromList(kind, result.sessions); - for (const session of result.sessions) { + for (let i = 0, len = result.sessions.length; i < len; i++) { + const session = result.sessions[i]; const historyKey = eventCacheKey(kind, session.historyId); if (opts.cwd) this.historyCwdById.set(historyKey, opts.cwd); else this.historyCwdById.delete(historyKey); @@ -176,7 +177,8 @@ export class HistoryService { } private invalidateEventCacheFromList(kind: AgentKind, sessions: AgentHistorySession[]): void { - for (const session of sessions) { + for (let i = 0, len = sessions.length; i < len; i++) { + const session = sessions[i]; const key = eventCacheKey(kind, session.historyId); const cached = this.eventCache.get(key); if (cached && cached.fingerprint !== sessionFingerprint(session)) this.eventCache.delete(key); diff --git a/packages/host/engine/src/session/session-record-registry.ts b/packages/host/engine/src/session/session-record-registry.ts index 8cc62c08..996108a7 100644 --- a/packages/host/engine/src/session/session-record-registry.ts +++ b/packages/host/engine/src/session/session-record-registry.ts @@ -31,7 +31,10 @@ export class SessionRecordRegistry { ), Effect.tap((records) => Effect.sync(() => { - for (const record of records) this.records.set(record.sessionId, record); + for (let i = 0, len = records.length; i < len; i++) { + const record = records[i]; + this.records.set(record.sessionId, record); + } }), ), Effect.asVoid, @@ -179,7 +182,8 @@ function latestHistoryId(record: SessionRecord): AgentHistoryId | undefined { const WHITESPACE_RUN_RE = /\s+/g; function titleFromContent(content: ContentBlock[]): string | undefined { - for (const block of content) { + for (let i = 0, len = content.length; i < len; i++) { + const block = content[i]; if (block.type !== 'text') continue; const text = block.text.trim().replaceAll(WHITESPACE_RUN_RE, ' '); if (text.length === 0) continue; diff --git a/packages/host/engine/src/simulator/consent.ts b/packages/host/engine/src/simulator/consent.ts index b4b53ef2..ea0a31a0 100644 --- a/packages/host/engine/src/simulator/consent.ts +++ b/packages/host/engine/src/simulator/consent.ts @@ -57,7 +57,10 @@ export class SimulatorConsentService { async init(): Promise { const state = await this.store.load(); this.agentToolsEnabled = state.agentToolsEnabled; - for (const entry of state.entries) this.decisions.set(entry.udid, entry.decision); + for (let i = 0, len = state.entries.length; i < len; i++) { + const entry = state.entries[i]; + this.decisions.set(entry.udid, entry.decision); + } } /** diff --git a/packages/host/engine/src/terminal/replay.ts b/packages/host/engine/src/terminal/replay.ts index 0ded93a8..2a69f4f1 100644 --- a/packages/host/engine/src/terminal/replay.ts +++ b/packages/host/engine/src/terminal/replay.ts @@ -47,7 +47,8 @@ export class TerminalReplayJournal { * everything after it reaches that attachment as ordinary live frames. */ snapshotUpTo(seq: number): TerminalReplayEvent[] { const events: TerminalReplayEvent[] = []; - for (const { event } of this.entries) { + for (let i = 0, len = this.entries.length; i < len; i++) { + const { event } = this.entries[i]; if (event.seq > seq) break; events.push(event); } diff --git a/packages/host/engine/src/workspace/file-suggest-service.ts b/packages/host/engine/src/workspace/file-suggest-service.ts index fc3157f3..ad5550ce 100644 --- a/packages/host/engine/src/workspace/file-suggest-service.ts +++ b/packages/host/engine/src/workspace/file-suggest-service.ts @@ -44,14 +44,16 @@ const listGitFiles = Effect.fn('FileSuggestService.listGitFiles')(function* (cwd const walkFiles = Effect.fn('FileSuggestService.walkFiles')(function* (root: string) { const files: string[] = []; const queue: Array<{ dir: string; depth: number }> = [{ dir: root, depth: 0 }]; - for (let head = 0; head < queue.length && files.length < MAX_ENUMERATED_FILES; head++) { - const { dir, depth } = queue[head]; + let head = 0; + while (head < queue.length && files.length < MAX_ENUMERATED_FILES) { + const { dir, depth } = queue[head++]; const entries = yield* Effect.tryPromise(() => readdir(dir, { withFileTypes: true })).pipe( Effect.catch(() => Effect.succeed(null)), ); // Unreadable directory (permissions, races) — skip, don't fail the search. if (!entries) continue; - for (const entry of entries) { + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; if (entry.name[0] === '.') continue; const absolute = path.join(dir, entry.name); if (entry.isDirectory()) { @@ -121,7 +123,8 @@ export class FileSuggestService { function rankFiles(files: readonly string[], query: string): string[] { const needle = query.toLowerCase(); const ranked: Array<{ file: string; tier: number; depth: number }> = []; - for (const file of files) { + for (let i = 0, len = files.length; i < len; i++) { + const file = files[i]; const tier = matchTier(file, needle); if (tier === null) continue; ranked.push({ file, tier, depth: file.split('/').length }); diff --git a/packages/host/engine/src/workspace/workspace-registry.ts b/packages/host/engine/src/workspace/workspace-registry.ts index 1560e0e2..166f5c53 100644 --- a/packages/host/engine/src/workspace/workspace-registry.ts +++ b/packages/host/engine/src/workspace/workspace-registry.ts @@ -26,7 +26,8 @@ export class WorkspaceRegistry { const records = await storeOperation('workspace.load', 'Failed to load workspaces', () => this.store.load(), ); - for (const record of records) { + for (let i = 0, len = records.length; i < len; i++) { + const record = records[i]; this.index(record); } } diff --git a/packages/host/engine/src/worktree/worktree-service.ts b/packages/host/engine/src/worktree/worktree-service.ts index 424ba8c6..1087e32f 100644 --- a/packages/host/engine/src/worktree/worktree-service.ts +++ b/packages/host/engine/src/worktree/worktree-service.ts @@ -43,7 +43,8 @@ export class WorktreeService { ).pipe( Effect.tap((records) => Effect.sync(() => { - for (const record of records) { + for (let i = 0, len = records.length; i < len; i++) { + const record = records[i]; this.bySession.set(record.sessionId, record); this.byRepoBranch.set(repoBranchKey(record.repoRoot, record.branch), record); } @@ -261,9 +262,15 @@ export class WorktreeService { const root = this.root; return Effect.gen({ self: this }, function* () { const groups = yield* readChildDirectories(root); - for (const group of groups) { + for (let i = 0, len = groups.length; i < len; i++) { + const group = groups[i]; const candidates = yield* readChildDirectories(group); - for (const candidate of candidates) { + for ( + let candidateIndex = 0, candidateCount = candidates.length; + candidateIndex < candidateCount; + candidateIndex++ + ) { + const candidate = candidates[candidateIndex]; if (this.hasPath(candidate)) continue; const identity = yield* identifyManagedWorktree(candidate).pipe( Effect.catch((error) => diff --git a/packages/host/engine/tests/integration/engine-worktree.test.ts b/packages/host/engine/tests/integration/engine-worktree.test.ts index 8364b133..fea40ce0 100644 --- a/packages/host/engine/tests/integration/engine-worktree.test.ts +++ b/packages/host/engine/tests/integration/engine-worktree.test.ts @@ -56,7 +56,10 @@ class RejectingStartAdapter extends FakeAdapter { } afterEach(() => { - for (const root of tempRoots.splice(0)) rmSync(root, { recursive: true, force: true }); + const roots = tempRoots.splice(0); + for (let i = 0, len = roots.length; i < len; i++) { + rmSync(roots[i], { recursive: true, force: true }); + } }); describe('engine managed worktree sessions', () => { diff --git a/packages/host/engine/tests/integration/file-suggest-service.test.ts b/packages/host/engine/tests/integration/file-suggest-service.test.ts index 53507065..b698aaad 100644 --- a/packages/host/engine/tests/integration/file-suggest-service.test.ts +++ b/packages/host/engine/tests/integration/file-suggest-service.test.ts @@ -28,7 +28,10 @@ function git(cwd: string, ...args: string[]): void { } afterAll(() => { - for (const dir of roots) rmSync(dir, { recursive: true, force: true }); + for (let i = 0, len = roots.length; i < len; i++) { + const dir = roots[i]; + rmSync(dir, { recursive: true, force: true }); + } }); describe('FileSuggestService', () => { diff --git a/packages/host/engine/tests/integration/git-diff.test.ts b/packages/host/engine/tests/integration/git-diff.test.ts index f50fd7f3..e728b10b 100644 --- a/packages/host/engine/tests/integration/git-diff.test.ts +++ b/packages/host/engine/tests/integration/git-diff.test.ts @@ -68,7 +68,10 @@ function parseShortstat(output: string): { files: number; additions: number; del } afterAll(() => { - for (const dir of roots) rmSync(dir, { recursive: true, force: true }); + for (let i = 0, len = roots.length; i < len; i++) { + const dir = roots[i]; + rmSync(dir, { recursive: true, force: true }); + } }); describe('readGitDiff', () => { @@ -111,11 +114,17 @@ describe('readGitDiff', () => { it('truncates at the last complete file boundary once the patch exceeds the byte cap', async () => { const dir = makeRepo(); const fileNames = ['f0.txt', 'f1.txt', 'f2.txt', 'f3.txt', 'f4.txt']; - for (const name of fileNames) writeFileSync(join(dir, name), 'seed\n'); + for (let i = 0, len = fileNames.length; i < len; i++) { + const name = fileNames[i]; + writeFileSync(join(dir, name), 'seed\n'); + } git(dir, 'add', ...fileNames); commit(dir, 'init'); // ~500KB of unique content per file, ~2.5MB combined — comfortably over the 2MB cap. - for (const name of fileNames) writeFileSync(join(dir, name), bigLineBlock(500000, name)); + for (let i = 0, len = fileNames.length; i < len; i++) { + const name = fileNames[i]; + writeFileSync(join(dir, name), bigLineBlock(500000, name)); + } const diff = await Effect.runPromise(readGitDiff(dir, 'uncommitted')); expect(diff.truncated).toBe(true); diff --git a/packages/host/engine/tests/integration/git-status.test.ts b/packages/host/engine/tests/integration/git-status.test.ts index 49c5983f..c21774c2 100644 --- a/packages/host/engine/tests/integration/git-status.test.ts +++ b/packages/host/engine/tests/integration/git-status.test.ts @@ -80,7 +80,10 @@ afterAll(() => { else process.env.GIT_CONFIG_GLOBAL = previousGitConfigGlobal; if (previousGitConfigNoSystem === undefined) delete process.env.GIT_CONFIG_NOSYSTEM; else process.env.GIT_CONFIG_NOSYSTEM = previousGitConfigNoSystem; - for (const dir of roots) rmSync(dir, { recursive: true, force: true }); + for (let i = 0, len = roots.length; i < len; i++) { + const dir = roots[i]; + rmSync(dir, { recursive: true, force: true }); + } }); describe('readGitStatus', () => { diff --git a/packages/host/engine/tests/integration/worktree-service.test.ts b/packages/host/engine/tests/integration/worktree-service.test.ts index b813cc1b..d2c1abc4 100644 --- a/packages/host/engine/tests/integration/worktree-service.test.ts +++ b/packages/host/engine/tests/integration/worktree-service.test.ts @@ -57,7 +57,10 @@ async function managedWorktree(store: WorktreeStore = new InMemoryWorktreeStore( } afterEach(() => { - for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); + const removedRoots = roots.splice(0); + for (let i = 0, len = removedRoots.length; i < len; i++) { + rmSync(removedRoots[i], { recursive: true, force: true }); + } }); describe('WorktreeService', () => { diff --git a/packages/host/sim/src/__tests__/codec.test.ts b/packages/host/sim/src/__tests__/codec.test.ts index a79ad4ca..f1975ea1 100644 --- a/packages/host/sim/src/__tests__/codec.test.ts +++ b/packages/host/sim/src/__tests__/codec.test.ts @@ -24,7 +24,10 @@ describe('FrameDecoder', () => { ]); const frames = []; for (const byte of encoded) { - for (const decoded of decoder.feed(Buffer.from([byte]))) frames.push(decoded); + const decodedFrames = decoder.feed(Buffer.from([byte])); + for (let i = 0, len = decodedFrames.length; i < len; i++) { + frames.push(decodedFrames[i]); + } } expect(frames.map((f) => f.body.toString())).toEqual(['{"a":1}', '{"b":2}']); }); diff --git a/packages/host/sim/src/client.ts b/packages/host/sim/src/client.ts index 27cfddda..4454d13c 100644 --- a/packages/host/sim/src/client.ts +++ b/packages/host/sim/src/client.ts @@ -331,7 +331,8 @@ export class SimSidecarClient { child.stdout.on('data', (chunk: Buffer) => { if (this.child !== child) return; try { - for (const frame of this.decoder.feed(chunk)) this.handleFrame(frame); + const frames = this.decoder.feed(chunk); + for (let i = 0, len = frames.length; i < len; i++) this.handleFrame(frames[i]); } catch { // A corrupt stream cannot be resynchronized mid-flight; drop the child and start over. child.kill(); diff --git a/packages/integrations/im-render/src/blocks.ts b/packages/integrations/im-render/src/blocks.ts index 19fd0e04..f6c4caa0 100644 --- a/packages/integrations/im-render/src/blocks.ts +++ b/packages/integrations/im-render/src/blocks.ts @@ -28,7 +28,8 @@ export function contentBlockToMarkdown(block: ContentBlock): string { export function contentToMarkdown(blocks: readonly ContentBlock[]): string { const parts: string[] = []; - for (const block of blocks) { + for (let i = 0, len = blocks.length; i < len; i++) { + const block = blocks[i]; const text = contentBlockToMarkdown(block); if (text.length > 0) parts.push(text); } diff --git a/packages/integrations/im-render/src/render.ts b/packages/integrations/im-render/src/render.ts index f45e2d20..27f6bde6 100644 --- a/packages/integrations/im-render/src/render.ts +++ b/packages/integrations/im-render/src/render.ts @@ -90,7 +90,8 @@ function toolContentMarkdown(content: ToolCallContent, opts: RenderOptions): str export function toolCallMarkdown(toolCall: ToolCall, opts: RenderOptions = {}): string { const parts = [`${TOOL_STATUS_ICON[toolCall.status]} *${toolCall.title || toolCall.kind}*`]; - for (const content of toolCall.content) { + for (let i = 0, len = toolCall.content.length; i < len; i++) { + const content = toolCall.content[i]; const text = toolContentMarkdown(content, opts); if (text.length > 0) parts.push(text); } @@ -121,7 +122,8 @@ export function permissionMarkdown( /** The agent's structured ask (AskUserQuestion): each question with its options as bullets. */ export function questionMarkdown(questions: readonly Question[]): string { const parts: string[] = ['❓ **Question**']; - for (const question of questions) { + for (let i = 0, len = questions.length; i < len; i++) { + const question = questions[i]; parts.push(question.prompt); appendArrayInPlace( parts, @@ -164,7 +166,8 @@ export function renderTurns( opts: RenderOptions = {}, ): RenderedTurn[] { const turns: RenderedTurn[] = []; - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; let turn = turns.at(-1); if (turn?.turnId !== item.turnId) { turn = { turnId: item.turnId, userMarkdown: null, agentMarkdown: '', items: [] }; @@ -190,7 +193,9 @@ export function renderConversation( opts: RenderOptions = {}, ): string { const parts: string[] = []; - for (const turn of renderTurns(items, opts)) { + const turns = renderTurns(items, opts); + for (let i = 0, len = turns.length; i < len; i++) { + const turn = turns[i]; if (turn.userMarkdown !== null) parts.push(`👤 ${turn.userMarkdown}`); if (turn.agentMarkdown.length > 0) parts.push(turn.agentMarkdown); } diff --git a/packages/presentation/i18n/src/index.ts b/packages/presentation/i18n/src/index.ts index db2b5a2d..a00ad50c 100644 --- a/packages/presentation/i18n/src/index.ts +++ b/packages/presentation/i18n/src/index.ts @@ -19,7 +19,8 @@ export function getMessages(locale: Locale): Messages { export function resolveLocale(input: string | readonly string[] | undefined): Locale { const candidates = typeof input === 'string' ? [input] : (input ?? []); - for (const candidate of candidates) { + for (let i = 0, len = candidates.length; i < len; i++) { + const candidate = candidates[i]; const normalized = normalizeLocale(candidate); if (normalized) return normalized; } diff --git a/packages/presentation/ui/src/__tests__/conversation-view-perf.bench.ts b/packages/presentation/ui/src/__tests__/conversation-view-perf.bench.ts index 696cad0e..6a94e67d 100644 --- a/packages/presentation/ui/src/__tests__/conversation-view-perf.bench.ts +++ b/packages/presentation/ui/src/__tests__/conversation-view-perf.bench.ts @@ -183,7 +183,7 @@ function conversationViewPipeline(conversation: ConversationViewModel): number { const allSubagentChildren = partitionSubagentItems(items).childrenByParent; let entryCount = 0; - for (let index = 0; index < segments.length; index += 1) { + for (let index = 0, len = segments.length; index < len; index += 1) { const segment = segments[index]; const ended = index < segments.length - 1 || !isThinking; const { topLevel } = partitionSubagentItems(segment.items); diff --git a/packages/presentation/ui/src/__tests__/conversation-view-perf.report.test.ts b/packages/presentation/ui/src/__tests__/conversation-view-perf.report.test.ts index 67a95d01..ccc7cd41 100644 --- a/packages/presentation/ui/src/__tests__/conversation-view-perf.report.test.ts +++ b/packages/presentation/ui/src/__tests__/conversation-view-perf.report.test.ts @@ -181,7 +181,7 @@ function conversationViewPipeline(conversation: ConversationViewModel): number { ); const segments = splitTurnSegments(conversationFlowItems(items)); let entryCount = 0; - for (let index = 0; index < segments.length; index += 1) { + for (let index = 0, len = segments.length; index < len; index += 1) { const segment = segments[index]; const ended = index < segments.length - 1 || !isThinking; const { topLevel } = partitionSubagentItems(segment.items); @@ -198,7 +198,8 @@ function conversationViewPipelineNoEdits(conversation: ConversationViewModel): n declinedToolCallIds(items); const segments = splitTurnSegments(conversationFlowItems(items)); let entryCount = 0; - for (const segment of segments) { + for (let i = 0, len = segments.length; i < len; i++) { + const segment = segments[i]; const { topLevel } = partitionSubagentItems(segment.items); entryCount += groupTimeline(topLevel).length + (assistantTurnText(topLevel) ? 1 : 0); } @@ -207,7 +208,8 @@ function conversationViewPipelineNoEdits(conversation: ConversationViewModel): n function allEditDiffStats(conversation: ConversationViewModel): number { let total = 0; - for (const item of conversation.items) { + for (let i = 0, len = conversation.items.length; i < len; i++) { + const item = conversation.items[i]; if (item.kind !== 'tool' || item.toolCall.kind !== 'edit') continue; const stats = toolCallDiffStats(item.toolCall); total += stats.additions + stats.deletions; diff --git a/packages/presentation/ui/src/chat/__tests__/activity-run.test.tsx b/packages/presentation/ui/src/chat/__tests__/activity-run.test.tsx index 8f951ada..e3e34c89 100644 --- a/packages/presentation/ui/src/chat/__tests__/activity-run.test.tsx +++ b/packages/presentation/ui/src/chat/__tests__/activity-run.test.tsx @@ -462,7 +462,9 @@ describe('ActivityRun', () => { 'Made 2 file changes', 'Explored once', ]; - for (const label of ['An action failed', ...regularLabels]) { + const labels = ['An action failed', ...regularLabels]; + for (let i = 0, len = labels.length; i < len; i++) { + const label = labels[i]; expect(header.textContent.split(label)).toHaveLength(2); } const failureClause = within(header).getByText(RE_FAILURE); @@ -470,7 +472,8 @@ describe('ActivityRun', () => { expect(failureClause.className).toContain('font-medium'); expect(failureClause.className).toContain('opacity-100'); const regularClauses = regularLabels.map((label) => within(header).getByText(`· ${label}`)); - for (const clause of regularClauses) { + for (let i = 0, len = regularClauses.length; i < len; i++) { + const clause = regularClauses[i]; expect(clause.className).toContain('font-medium'); expect(clause.className).toContain('opacity-80'); expect(clause.className).not.toContain('text-label-tertiary'); @@ -483,14 +486,16 @@ describe('ActivityRun', () => { expect(header.querySelector('svg.lucide-circle-x')).toBeNull(); // A settled abstract never shimmers. expect(header.querySelector('.bg-clip-text')).toBeNull(); - for (const detail of [ + const details = [ 'first.ts', 'second.ts', 'Linear', 'git status', 'README.md', 'Reviewed the results', - ]) { + ]; + for (let i = 0, len = details.length; i < len; i++) { + const detail = details[i]; expect(header.textContent).not.toContain(detail); expect(header.getAttribute('aria-label')).not.toContain(detail); } diff --git a/packages/presentation/ui/src/chat/__tests__/card-radius.test.tsx b/packages/presentation/ui/src/chat/__tests__/card-radius.test.tsx index a7392ae1..0254c651 100644 --- a/packages/presentation/ui/src/chat/__tests__/card-radius.test.tsx +++ b/packages/presentation/ui/src/chat/__tests__/card-radius.test.tsx @@ -101,7 +101,9 @@ describe('Chat card chrome contract', () => { , ); - for (const testId of ['commit', 'stack-trace', 'step']) { + const roundedCards = ['commit', 'stack-trace', 'step']; + for (let i = 0, len = roundedCards.length; i < len; i++) { + const testId = roundedCards[i]; expect(screen.getByTestId(testId).classList.contains('rounded-2xl')).toBe(true); } diff --git a/packages/presentation/ui/src/chat/__tests__/file-preview-card.test.tsx b/packages/presentation/ui/src/chat/__tests__/file-preview-card.test.tsx index 2683ea39..78239bfc 100644 --- a/packages/presentation/ui/src/chat/__tests__/file-preview-card.test.tsx +++ b/packages/presentation/ui/src/chat/__tests__/file-preview-card.test.tsx @@ -15,10 +15,12 @@ vi.mock('use-intl', () => ({ useTranslations: () => translateKey })); /** jsdom lays nothing out, so both metrics read 0 and the card can never look overflowing. * Stub the pair the overflow check reads to drive each branch deterministically. */ function stubPanelMetrics(scrollHeight: number, clientHeight: number): void { - for (const [name, value] of [ + const metrics = [ ['scrollHeight', scrollHeight], ['clientHeight', clientHeight], - ] as const) { + ] as const; + for (let i = 0, len = metrics.length; i < len; i++) { + const [name, value] = metrics[i]; vi.spyOn(HTMLElement.prototype, name, 'get').mockReturnValue(value); } } diff --git a/packages/presentation/ui/src/chat/__tests__/link-icon.test.tsx b/packages/presentation/ui/src/chat/__tests__/link-icon.test.tsx index 8e479ce7..2a439d66 100644 --- a/packages/presentation/ui/src/chat/__tests__/link-icon.test.tsx +++ b/packages/presentation/ui/src/chat/__tests__/link-icon.test.tsx @@ -18,7 +18,8 @@ it('loads the destination and Google favicon concurrently with a local fallback' 'https://internal.example.test/favicon.ico', 'https://www.google.com/s2/favicons?domain=https%3A%2F%2Finternal.example.test&sz=32', ]); - for (const image of images) { + for (let i = 0, len = images.length; i < len; i++) { + const image = images[i]; expect(image.getAttribute('alt')).toBe(''); expect(image.getAttribute('decoding')).toBe('async'); expect(image.getAttribute('draggable')).toBe('false'); diff --git a/packages/presentation/ui/src/chat/__tests__/smooth-text-controller.test.ts b/packages/presentation/ui/src/chat/__tests__/smooth-text-controller.test.ts index 120aca90..96becad2 100644 --- a/packages/presentation/ui/src/chat/__tests__/smooth-text-controller.test.ts +++ b/packages/presentation/ui/src/chat/__tests__/smooth-text-controller.test.ts @@ -59,11 +59,13 @@ describe('smooth text controller', () => { expect(state.visible).toBe(source); expect(seen.every((visible) => source.startsWith(visible))).toBe(true); - for (const [initial, nextSource, firstStep] of [ + const cases = [ ['👨', '👨\u{200D}👩x', '👨\u{200D}👩'], ['🇺', '🇺🇸x', '🇺🇸'], ['e', 'éx', 'é'], - ]) { + ]; + for (let i = 0, len = cases.length; i < len; i++) { + const [initial, nextSource, firstStep] = cases[i]; const joined = reconcileSmoothText(createSmoothTextState(initial, false), nextSource, false); expect(advanceSmoothText(joined).visible).toBe(firstStep); } diff --git a/packages/presentation/ui/src/chat/__tests__/turn-diff-summary.test.tsx b/packages/presentation/ui/src/chat/__tests__/turn-diff-summary.test.tsx index 3d62bf6f..3a6e1a7d 100644 --- a/packages/presentation/ui/src/chat/__tests__/turn-diff-summary.test.tsx +++ b/packages/presentation/ui/src/chat/__tests__/turn-diff-summary.test.tsx @@ -47,8 +47,10 @@ describe('TurnDiffSummary', () => { ); expect(fileButtons).toHaveLength(FILES.length); // user-event queues pointer interactions, so these clicks must stay serial. - // eslint-disable-next-line no-await-in-loop -- preserve interaction ordering - for (const button of fileButtons) await user.click(button); + for (let i = 0, len = fileButtons.length; i < len; i++) { + const button = fileButtons[i]; + await user.click(button); + } await user.click(screen.getByRole('button', { name: 'review' })); expect(openFile.mock.calls).toEqual(FILES.map((file) => [file.path])); diff --git a/packages/presentation/ui/src/chat/activity-groups.ts b/packages/presentation/ui/src/chat/activity-groups.ts index 554cfc32..a86f3b6d 100644 --- a/packages/presentation/ui/src/chat/activity-groups.ts +++ b/packages/presentation/ui/src/chat/activity-groups.ts @@ -41,7 +41,8 @@ export function groupTimeline( ): TimelineEntry[] { // Policies may preserve approval-gated tools as standalone interaction points. const approvalGated = new Set(); - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind === 'approval') approvalGated.add(item.toolCall.toolCallId); } diff --git a/packages/presentation/ui/src/chat/activity-run.tsx b/packages/presentation/ui/src/chat/activity-run.tsx index cbb6e282..45383490 100644 --- a/packages/presentation/ui/src/chat/activity-run.tsx +++ b/packages/presentation/ui/src/chat/activity-run.tsx @@ -213,7 +213,8 @@ function ActivityRunIcon({ function primarySettledCategory( clauses: readonly ActivitySummaryClause[], ): SettledActivityCategory | undefined { - for (const clause of clauses) { + for (let i = 0, len = clauses.length; i < len; i++) { + const clause = clauses[i]; if (clause.category !== 'failure') return clause.category; } return undefined; diff --git a/packages/presentation/ui/src/chat/activity-summary.ts b/packages/presentation/ui/src/chat/activity-summary.ts index 98cfb17e..4e7f6c7b 100644 --- a/packages/presentation/ui/src/chat/activity-summary.ts +++ b/packages/presentation/ui/src/chat/activity-summary.ts @@ -70,7 +70,8 @@ export function settledActivityRunDescriptor( let failureCount = 0; let hasThinking = false; - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind === 'tool' && item.toolCall.status === 'failed') failureCount += 1; const category = activityCategory(item); if (category === 'thinking') { @@ -82,7 +83,8 @@ export function settledActivityRunDescriptor( const clauses: ActivitySummaryClause[] = []; if (failureCount > 0) clauses.push({ category: 'failure', count: failureCount }); - for (const category of SETTLED_CATEGORY_PRIORITY) { + for (let i = 0, len = SETTLED_CATEGORY_PRIORITY.length; i < len; i++) { + const category = SETTLED_CATEGORY_PRIORITY[i]; const count = categoryCounts.get(category); if (count !== undefined) clauses.push({ category, count }); } diff --git a/packages/presentation/ui/src/chat/artifacts/registry.ts b/packages/presentation/ui/src/chat/artifacts/registry.ts index 3b9b648a..33354f46 100644 --- a/packages/presentation/ui/src/chat/artifacts/registry.ts +++ b/packages/presentation/ui/src/chat/artifacts/registry.ts @@ -65,7 +65,8 @@ export interface ResolvedInlineArtifact { /** Run detectors over a fence (first match wins). Returns null — degrade to a plain * code block — when nothing matches or the matched kind cannot render inline. */ export function resolveFencedArtifact(block: FencedBlock): ResolvedInlineArtifact | null { - for (const detector of detectors) { + for (let i = 0, len = detectors.length; i < len; i++) { + const detector = detectors[i]; const artifact = detector.detectFence(block); if (!artifact) continue; const definition = kinds.get(artifact.kind); diff --git a/packages/presentation/ui/src/chat/conversation-prompts.ts b/packages/presentation/ui/src/chat/conversation-prompts.ts index f7d0a69e..c69ed430 100644 --- a/packages/presentation/ui/src/chat/conversation-prompts.ts +++ b/packages/presentation/ui/src/chat/conversation-prompts.ts @@ -72,7 +72,8 @@ export function selectPendingPromptItems( /** toolCallIds whose authoritative permission resolution declined the gated action. */ export function declinedToolCallIds(items: readonly ConversationItem[]): Set { const ids = new Set(); - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind !== 'approval') continue; if (!item.resolution) continue; const outcome = item.resolution.outcome; diff --git a/packages/presentation/ui/src/chat/conversation-text.ts b/packages/presentation/ui/src/chat/conversation-text.ts index 89dd508c..86a99b8b 100644 --- a/packages/presentation/ui/src/chat/conversation-text.ts +++ b/packages/presentation/ui/src/chat/conversation-text.ts @@ -4,7 +4,8 @@ import type { ConversationItem } from './types'; /** Plain-text projection of content blocks (non-text blocks are skipped). */ export function contentBlocksText(blocks: readonly ContentBlock[]): string { const parts: string[] = []; - for (const block of blocks) { + for (let i = 0, len = blocks.length; i < len; i++) { + const block = blocks[i]; if (block.type === 'text' && block.text) parts.push(block.text); } return parts.join('\n'); @@ -13,7 +14,8 @@ export function contentBlocksText(blocks: readonly ContentBlock[]): string { /** The turn's copyable reply: every assistant message's text, in order. */ export function assistantTurnText(items: readonly ConversationItem[]): string { const parts: string[] = []; - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind !== 'message' || item.role !== 'assistant') continue; const text = contentBlocksText(item.blocks); if (text) parts.push(text); @@ -33,7 +35,8 @@ export function turnModel(items: readonly ConversationItem[]): string | undefine /** Best-known time of the latest-stamped item — the turn's approximate end time. */ export function latestReceivedAt(items: readonly ConversationItem[]): number | undefined { let latest: number | undefined; - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.receivedAt !== undefined && (latest === undefined || item.receivedAt > latest)) { latest = item.receivedAt; } diff --git a/packages/presentation/ui/src/chat/diff-utils.ts b/packages/presentation/ui/src/chat/diff-utils.ts index f160d7d4..157e8255 100644 --- a/packages/presentation/ui/src/chat/diff-utils.ts +++ b/packages/presentation/ui/src/chat/diff-utils.ts @@ -76,7 +76,9 @@ export function diffLines(oldStr: string, newStr: string): DiffRow[] { export function patchLines(patch: string): DiffRow[] { const rows: DiffRow[] = []; let inHunk = false; - for (const line of patch.split('\n')) { + const lines = patch.split('\n'); + for (let i = 0, len = lines.length; i < len; i++) { + const line = lines[i]; if (line.startsWith('@@')) { inHunk = true; continue; @@ -135,7 +137,8 @@ export function diffContentStats(content: DiffToolCallContent): DiffStats { export function toolCallDiffStats(toolCall: Pick): DiffStats { let additions = 0; let deletions = 0; - for (const content of toolCall.content) { + for (let i = 0, len = toolCall.content.length; i < len; i++) { + const content = toolCall.content[i]; if (content.type !== 'diff') continue; const stats = diffContentStats(content); additions += stats.additions; diff --git a/packages/presentation/ui/src/chat/file-tool-presentation.ts b/packages/presentation/ui/src/chat/file-tool-presentation.ts index 74c50aa3..496f61f4 100644 --- a/packages/presentation/ui/src/chat/file-tool-presentation.ts +++ b/packages/presentation/ui/src/chat/file-tool-presentation.ts @@ -21,7 +21,8 @@ export interface ToolCallFilePresentation { function filePaths(toolCall: ToolCall): string[] { const paths = new Set(toolCall.locations?.map((location) => location.path)); - for (const content of toolCall.content) { + for (let i = 0, len = toolCall.content.length; i < len; i++) { + const content = toolCall.content[i]; if (content.type === 'diff') paths.add(content.path); } const fallback = toolCallFilePath(toolCall); diff --git a/packages/presentation/ui/src/chat/markdown.tsx b/packages/presentation/ui/src/chat/markdown.tsx index c87a120f..b8d42533 100644 --- a/packages/presentation/ui/src/chat/markdown.tsx +++ b/packages/presentation/ui/src/chat/markdown.tsx @@ -388,7 +388,9 @@ function allowMentionLinkProtocols(entry: RehypePlugins[number]): RehypePlugins[ // which would corrupt cwd-relative file mentions into absolute paths for openFile. const defaultRehypePluginValues: RehypePlugins = []; let rawRehypePluginIndex = -1; -for (const [name, entry] of Object.entries(defaultRehypePlugins)) { +const defaultRehypePluginEntries = Object.entries(defaultRehypePlugins); +for (let i = 0, len = defaultRehypePluginEntries.length; i < len; i++) { + const [name, entry] = defaultRehypePluginEntries[i]; if (name === 'harden') continue; if (name === 'raw') rawRehypePluginIndex = defaultRehypePluginValues.length; defaultRehypePluginValues.push(name === 'sanitize' ? allowMentionLinkProtocols(entry) : entry); diff --git a/packages/presentation/ui/src/chat/stack-trace-parser.ts b/packages/presentation/ui/src/chat/stack-trace-parser.ts index 4efec202..6174cf91 100644 --- a/packages/presentation/ui/src/chat/stack-trace-parser.ts +++ b/packages/presentation/ui/src/chat/stack-trace-parser.ts @@ -21,7 +21,9 @@ interface ParsedStackLocation { export function parseStackTrace(trace: string): ParsedStackTrace { const lines: string[] = []; - for (const line of trace.split('\n')) { + const traceLines = trace.split('\n'); + for (let i = 0, len = traceLines.length; i < len; i++) { + const line = traceLines[i]; const trimmed = line.trim(); if (trimmed.length > 0) lines.push(trimmed); } @@ -29,7 +31,9 @@ export function parseStackTrace(trace: string): ParsedStackTrace { const firstLine = lines[0]?.trim() ?? ''; const error = parseErrorLine(firstLine); const frames: ParsedStackFrame[] = []; - for (const line of lines.slice(1)) { + const frameLines = lines.slice(1); + for (let i = 0, len = frameLines.length; i < len; i++) { + const line = frameLines[i]; if (line.startsWith('at ')) frames.push(parseStackFrame(line)); } diff --git a/packages/presentation/ui/src/chat/subagents.ts b/packages/presentation/ui/src/chat/subagents.ts index 8f4a29e6..36239c24 100644 --- a/packages/presentation/ui/src/chat/subagents.ts +++ b/packages/presentation/ui/src/chat/subagents.ts @@ -16,7 +16,8 @@ function itemParentId(item: ConversationItem): string | undefined { export function partitionSubagentItems(items: readonly ConversationItem[]): SubagentPartition { const taskIds = new Set(); - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind === 'tool' && item.toolCall.kind === 'task') { taskIds.add(item.toolCall.toolCallId); } @@ -24,7 +25,8 @@ export function partitionSubagentItems(items: readonly ConversationItem[]): Suba const topLevel: ConversationItem[] = []; const childrenByParent = new Map(); - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; const parent = itemParentId(item); if (parent === undefined || !taskIds.has(parent)) { topLevel.push(item); diff --git a/packages/presentation/ui/src/chat/tool-result-content.ts b/packages/presentation/ui/src/chat/tool-result-content.ts index e1e9e480..7ac900e7 100644 --- a/packages/presentation/ui/src/chat/tool-result-content.ts +++ b/packages/presentation/ui/src/chat/tool-result-content.ts @@ -15,7 +15,8 @@ export function stringValue( keys: readonly string[], ): string | undefined { if (!record) return undefined; - for (const key of keys) { + for (let i = 0, len = keys.length; i < len; i++) { + const key = keys[i]; const value = record[key]; if (typeof value === 'string' && value.length > 0) return value; } diff --git a/packages/presentation/ui/src/chat/tool-utils.ts b/packages/presentation/ui/src/chat/tool-utils.ts index b433fc2e..63e3b7f7 100644 --- a/packages/presentation/ui/src/chat/tool-utils.ts +++ b/packages/presentation/ui/src/chat/tool-utils.ts @@ -153,7 +153,9 @@ function toolCallParamMetadata(toolCall: ToolCall): ToolMetadata[] { const input = recordValue(toolCall.rawInput); if (!input) return []; const metadata: ToolMetadata[] = []; - for (const [key, value] of Object.entries(input)) { + const entries = Object.entries(input); + for (let i = 0, len = entries.length; i < len; i++) { + const [key, value] = entries[i]; if (metadata.length >= PARAM_BADGE_LIMIT) break; if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') { continue; diff --git a/packages/presentation/ui/src/chat/turn-edits.ts b/packages/presentation/ui/src/chat/turn-edits.ts index a9cda9fc..b675fd5e 100644 --- a/packages/presentation/ui/src/chat/turn-edits.ts +++ b/packages/presentation/ui/src/chat/turn-edits.ts @@ -11,7 +11,8 @@ export function splitTurnSegments( items: readonly T[], ): Array> { const segments: Array> = []; - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; const current = segments.at(-1); // turnId is string | null, never undefined, so a match proves `current` exists. if (current?.turnId === item.turnId) current.items.push(item); @@ -43,7 +44,8 @@ export function advanceTurnSegments( const reused: Array> = []; let covered = 0; - for (const segment of prev.segments) { + for (let i = 0, len = prev.segments.length; i < len; i++) { + const segment = prev.segments[i]; if (covered + segment.items.length > stable) break; reused.push(segment); covered += segment.items.length; @@ -76,9 +78,15 @@ export interface TurnEdits { * or declined edit never landed). Null when the turn edited nothing. */ export function turnFileEdits(items: readonly ConversationItem[]): TurnEdits | null { const byPath = new Map(); - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind !== 'tool' || item.toolCall.status !== 'completed') continue; - for (const content of item.toolCall.content) { + for ( + let contentIndex = 0, contentCount = item.toolCall.content.length; + contentIndex < contentCount; + contentIndex++ + ) { + const content = item.toolCall.content[contentIndex]; if (content.type !== 'diff') continue; const stats = diffContentStats(content); const edit = byPath.get(content.path) ?? { path: content.path, additions: 0, deletions: 0 }; diff --git a/packages/presentation/ui/src/chat/use-timeline-model.ts b/packages/presentation/ui/src/chat/use-timeline-model.ts index 6ed27c19..7ee0cb0c 100644 --- a/packages/presentation/ui/src/chat/use-timeline-model.ts +++ b/packages/presentation/ui/src/chat/use-timeline-model.ts @@ -75,13 +75,16 @@ function advanceTimeline( const snapshotted = new Set(); const questions = new Map(); - for (const item of items) { + for (let i = 0, len = items.length; i < len; i++) { + const item = items[i]; if (item.kind === 'tool') snapshotted.add(item.toolCall.toolCallId); if (item.kind === 'question') questions.set(item.toolCall.toolCallId, item); } const awaiting = new Set(); const answering = new Set(); - for (const item of selectPendingPromptItems(conversation)) { + const pendingItems = selectPendingPromptItems(conversation); + for (let i = 0, len = pendingItems.length; i < len; i++) { + const item = pendingItems[i]; if (item.kind === 'approval') awaiting.add(item.toolCall.toolCallId); if (item.kind === 'question') answering.add(item.toolCall.toolCallId); } diff --git a/packages/presentation/ui/src/keyboard/__tests__/registry.test.ts b/packages/presentation/ui/src/keyboard/__tests__/registry.test.ts index 8dd9f748..e71c2a78 100644 --- a/packages/presentation/ui/src/keyboard/__tests__/registry.test.ts +++ b/packages/presentation/ui/src/keyboard/__tests__/registry.test.ts @@ -27,7 +27,9 @@ function keydown(overrides: Partial = {}): KeyboardEvent { stopImmediatePropagation: vi.fn(), ...overrides, }; - for (const [key, value] of Object.entries(properties)) { + const entries = Object.entries(properties); + for (let i = 0, len = entries.length; i < len; i++) { + const [key, value] = entries[i]; Object.defineProperty(event, key, { configurable: true, value }); } return event; @@ -182,7 +184,8 @@ describe('keyboard shortcut registry', () => { stopImmediatePropagation, }), ]; - for (const event of events) { + for (let i = 0, len = events.length; i < len; i++) { + const event = events[i]; expect(registry.dispatch(event, true)).toBe(false); } expect(preventDefault).not.toHaveBeenCalled(); @@ -229,7 +232,9 @@ describe('keyboard shortcut registry', () => { registry.setPlatform('mac'); expect(registry.dispatch(keydown({ code: 'KeyJ', metaKey: true }), true)).toBe(false); expect(getOwner).not.toHaveBeenCalled(); - for (currentOwner of [null, owner(false), owner(true, true)]) { + const inactiveOwners = [null, owner(false), owner(true, true)]; + for (let i = 0, len = inactiveOwners.length; i < len; i++) { + currentOwner = inactiveOwners[i]; expect(registry.dispatch(keydown({ code: 'KeyK', metaKey: true }), true)).toBe(false); } const ownerClosest = vi.fn(() => null); diff --git a/packages/presentation/ui/src/keyboard/hooks.ts b/packages/presentation/ui/src/keyboard/hooks.ts index 86a5e847..4c194070 100644 --- a/packages/presentation/ui/src/keyboard/hooks.ts +++ b/packages/presentation/ui/src/keyboard/hooks.ts @@ -1,6 +1,7 @@ +import { useEffect } from 'foxact/use-abortable-effect'; import { useLayoutEffect } from 'foxact/use-isomorphic-layout-effect'; import { useStableHandler } from 'foxact/use-stable-handler-only-when-you-know-what-you-are-doing-or-you-will-be-fired'; -import { useEffect, useEffectEvent, useSyncExternalStore } from 'react'; +import { useEffectEvent, useSyncExternalStore } from 'react'; import type { KeyboardPlatform, KeyboardShortcuts } from './registry'; import { createKeyboardShortcutRegistry } from './registry'; @@ -19,19 +20,15 @@ export function setKeyboardShortcutPlatform(platform: KeyboardPlatform): void { } export function useKeyboardShortcutListener(): void { - useEffect(() => { + useEffect((signal) => { const capture = (event: KeyboardEvent): void => { keyboardShortcutRegistry.dispatch(event, true); }; const bubble = (event: KeyboardEvent): void => { keyboardShortcutRegistry.dispatch(event, false); }; - window.addEventListener('keydown', capture, { capture: true }); - window.addEventListener('keydown', bubble); - return () => { - window.removeEventListener('keydown', capture, { capture: true }); - window.removeEventListener('keydown', bubble); - }; + window.addEventListener('keydown', capture, { capture: true, signal }); + window.addEventListener('keydown', bubble, { signal }); }, []); } diff --git a/packages/presentation/ui/src/keyboard/registry.ts b/packages/presentation/ui/src/keyboard/registry.ts index cafe61fd..cc6c6e8d 100644 --- a/packages/presentation/ui/src/keyboard/registry.ts +++ b/packages/presentation/ui/src/keyboard/registry.ts @@ -65,7 +65,8 @@ function matchesShortcut( shortcuts: readonly KeyboardShortcut[], platform: KeyboardPlatform, ) { - for (const shortcut of shortcuts) { + for (let i = 0, len = shortcuts.length; i < len; i++) { + const shortcut = shortcuts[i]; if (matchesChord(event, chordForPlatform(shortcut, platform), platform)) return true; } return false; @@ -173,7 +174,8 @@ export function createKeyboardShortcutRegistry(warnOnMultipleMatches = IS_DEVELO const refreshLabels = (): void => { const nextLabels = new Map(); if (platform !== undefined) { - for (const registration of registrations) { + for (let i = 0, len = registrations.length; i < len; i++) { + const registration = registrations[i]; nextLabels.set( registration.actionId, formatKeyboardShortcut(registration.shortcut, platform), @@ -247,7 +249,9 @@ export function createKeyboardShortcutRegistry(warnOnMultipleMatches = IS_DEVELO let claimed = false; let matches: string[] | undefined; - for (const registration of registrations) { + for (let i = 0, len = registrations.length; i < len; i++) { + if (i >= registrations.length) break; + const registration = registrations[i]; if (!isOwnerMatch(registration, event, currentPlatform)) continue; if (warnOnMultipleMatches) (matches ??= []).push(registration.actionId); if (claimed) continue; diff --git a/packages/presentation/ui/src/lib/file-icon.ts b/packages/presentation/ui/src/lib/file-icon.ts index ca981b3a..b456ea75 100644 --- a/packages/presentation/ui/src/lib/file-icon.ts +++ b/packages/presentation/ui/src/lib/file-icon.ts @@ -174,7 +174,8 @@ const KNOWN_EXTENSIONS = new Set([ export function hasKnownFileIdentity(name: string): boolean { const basename = fileBasename(name).toLowerCase(); if (iconForFileName(basename) !== undefined) return true; - for (const [compoundExtension] of MATERIAL_COMPOUND_EXTENSION_ENTRIES) { + for (let i = 0, len = MATERIAL_COMPOUND_EXTENSION_ENTRIES.length; i < len; i++) { + const [compoundExtension] = MATERIAL_COMPOUND_EXTENSION_ENTRIES[i]; if (basename.endsWith(`.${compoundExtension}`)) return true; } return KNOWN_EXTENSIONS.has(fileExtension(basename)); @@ -197,7 +198,8 @@ export function fileIconFor({ kind, mimeType, name }: FileIconInput): FileIconCo const fileNameIcon = iconForFileName(basename); if (fileNameIcon) return fileNameIcon; - for (const [compoundExtension, icon] of MATERIAL_COMPOUND_EXTENSION_ENTRIES) { + for (let i = 0, len = MATERIAL_COMPOUND_EXTENSION_ENTRIES.length; i < len; i++) { + const [compoundExtension, icon] = MATERIAL_COMPOUND_EXTENSION_ENTRIES[i]; if (icon && basename.endsWith(`.${compoundExtension}`)) return icon; } @@ -214,7 +216,8 @@ function iconForFileName(basename: string): FileIconComponent | undefined { const materialIcon = MATERIAL_FILE_NAME_ICONS[basename]; if (materialIcon) return materialIcon; - for (const [prefix, icon] of MATERIAL_FILE_NAME_PREFIX_ICONS) { + for (let i = 0, len = MATERIAL_FILE_NAME_PREFIX_ICONS.length; i < len; i++) { + const [prefix, icon] = MATERIAL_FILE_NAME_PREFIX_ICONS[i]; if (basename.startsWith(prefix)) return icon; } diff --git a/packages/presentation/ui/src/shell/__tests__/composer-command-menu.test.tsx b/packages/presentation/ui/src/shell/__tests__/composer-command-menu.test.tsx index 81643941..f4186534 100644 --- a/packages/presentation/ui/src/shell/__tests__/composer-command-menu.test.tsx +++ b/packages/presentation/ui/src/shell/__tests__/composer-command-menu.test.tsx @@ -191,13 +191,21 @@ describe('Composer command menu', () => { }); const initialActive = editor.getAttribute('aria-activedescendant'); - for (const key of ['ArrowDown', 'ArrowUp']) { - for (const modifier of [ + const keys = ['ArrowDown', 'ArrowUp']; + for (let keyIndex = 0, keyCount = keys.length; keyIndex < keyCount; keyIndex++) { + const key = keys[keyIndex]; + const modifiers = [ { altKey: true }, { ctrlKey: true }, { metaKey: true }, { shiftKey: true }, - ]) { + ]; + for ( + let modifierIndex = 0, modifierCount = modifiers.length; + modifierIndex < modifierCount; + modifierIndex++ + ) { + const modifier = modifiers[modifierIndex]; expect(fireEvent.keyDown(editor, { code: key, key, ...modifier })).toBe(true); expect(editor.getAttribute('aria-activedescendant')).toBe(initialActive); } diff --git a/packages/presentation/ui/src/shell/__tests__/composer-directive-chips.test.tsx b/packages/presentation/ui/src/shell/__tests__/composer-directive-chips.test.tsx index 2895edb6..b710c769 100644 --- a/packages/presentation/ui/src/shell/__tests__/composer-directive-chips.test.tsx +++ b/packages/presentation/ui/src/shell/__tests__/composer-directive-chips.test.tsx @@ -291,10 +291,12 @@ describe('Composer directive chips', () => { await pressInComposer('ArrowRight'); expect(hasNodeSelection(editor, chipKey)).toBe(true); - for (const [key, modifier] of [ + const modifiedKeys = [ ['ArrowLeft', { altKey: true }], ['ArrowRight', { shiftKey: true }], - ] as const) { + ] as const; + for (let i = 0, len = modifiedKeys.length; i < len; i++) { + const [key, modifier] = modifiedKeys[i]; expect(fireEvent.keyDown(composerTextbox(), { code: key, key, ...modifier })).toBe(true); expect(hasNodeSelection(editor, chipKey)).toBe(true); } @@ -459,9 +461,9 @@ describe('Composer directive chips', () => { rerender(composer({ disabled: true })); expect(screen.getByRole('button', { name: '/typo' }).disabled).toBe(true); - for (const action of within(screen.getByRole('status')).getAllByRole( - 'button', - )) { + const actions = within(screen.getByRole('status')).getAllByRole('button'); + for (let i = 0, len = actions.length; i < len; i++) { + const action = actions[i]; expect(action.disabled).toBe(true); } }); diff --git a/packages/presentation/ui/src/shell/agent-models.ts b/packages/presentation/ui/src/shell/agent-models.ts index bc51a32c..216f0951 100644 --- a/packages/presentation/ui/src/shell/agent-models.ts +++ b/packages/presentation/ui/src/shell/agent-models.ts @@ -24,7 +24,9 @@ export function groupModelsByProvider( ): ModelProviderGroups | null { const ungrouped: ModelOption[] = []; const byProvider = new Map(); - for (const option of options ?? []) { + const modelOptions = options ?? []; + for (let i = 0, len = modelOptions.length; i < len; i++) { + const option = modelOptions[i]; if (option.description === undefined) { ungrouped.push(option); continue; diff --git a/packages/presentation/ui/src/shell/composer-command.tsx b/packages/presentation/ui/src/shell/composer-command.tsx index 2def1add..08ba75d4 100644 --- a/packages/presentation/ui/src/shell/composer-command.tsx +++ b/packages/presentation/ui/src/shell/composer-command.tsx @@ -185,7 +185,8 @@ export function buildComposerCommandGroups({ ]; const commandItems: ComposerCommandEntry[] = []; if (commandSource === 'slash') { - for (const command of agentCommands) { + for (let i = 0, len = agentCommands.length; i < len; i++) { + const command = agentCommands[i]; // Aliases match too (typing /cost surfaces /usage); selection inserts the canonical name. if ( !matchesQuery(command.name, command.name, command.description, commandQuery) && @@ -205,13 +206,15 @@ export function buildComposerCommandGroups({ }); } } - for (const item of commandItemCandidates) { + for (let i = 0, len = commandItemCandidates.length; i < len; i++) { + const item = commandItemCandidates[i]; if (commandSource === 'slash' && item.source === 'plus') continue; if (matchesQuery(item.label, item.value, item.hint, commandQuery)) commandItems.push(item); } if (commandSource === 'plus' && modesEnabled) { - for (const mode of availableModes) { + for (let i = 0, len = availableModes.length; i < len; i++) { + const mode = availableModes[i]; if (!matchesQuery(mode.name, mode.modeId, mode.description, commandQuery)) continue; commandItems.push({ active: currentModeId === mode.modeId, diff --git a/packages/presentation/ui/src/shell/composer-controls.tsx b/packages/presentation/ui/src/shell/composer-controls.tsx index 7c96b5dc..8e692a88 100644 --- a/packages/presentation/ui/src/shell/composer-controls.tsx +++ b/packages/presentation/ui/src/shell/composer-controls.tsx @@ -38,7 +38,9 @@ function optionById( options: readonly T[] | undefined, id: string | null, ): T | undefined { - for (const option of options ?? []) { + const availableOptions = options ?? []; + for (let i = 0, len = availableOptions.length; i < len; i++) { + const option = availableOptions[i]; if (option.id === id) return option; } return undefined; @@ -48,7 +50,8 @@ function policyById( policies: readonly ApprovalPolicy[], policyId: string | null, ): ApprovalPolicy | undefined { - for (const policy of policies) { + for (let i = 0, len = policies.length; i < len; i++) { + const policy = policies[i]; if (policy.policyId === policyId) return policy; } return undefined; diff --git a/packages/presentation/ui/src/shell/composer-editor/__tests__/composer-editor.test.ts b/packages/presentation/ui/src/shell/composer-editor/__tests__/composer-editor.test.ts index bbdf37eb..804a7465 100644 --- a/packages/presentation/ui/src/shell/composer-editor/__tests__/composer-editor.test.ts +++ b/packages/presentation/ui/src/shell/composer-editor/__tests__/composer-editor.test.ts @@ -174,14 +174,9 @@ describe('directive tokenizer', () => { it('keeps mid-line slash and dollar-sign tokens as prose', () => { const editor = createEditor(); - for (const draft of [ - 'run /usage now', - 'run /typo now', - 'run $ now', - 'pay $5', - 'echo $HOME', - ' $ls', - ]) { + const drafts = ['run /usage now', 'run /typo now', 'run $ now', 'pay $5', 'echo $HOME', ' $ls']; + for (let i = 0, len = drafts.length; i < len; i++) { + const draft = drafts[i]; setDraft(editor, draft); expect(draftShape(editor)).toEqual(['text']); expect(draftText(editor)).toBe(draft); @@ -258,7 +253,9 @@ describe('directive tokenizer', () => { () => { const paragraph = $getRoot().getFirstChild(); if (!$isElementNode(paragraph)) throw new Error('expected paragraph'); - for (const child of paragraph.getChildren()) { + const children = paragraph.getChildren(); + for (let i = 0, len = children.length; i < len; i++) { + const child = children[i]; if (child.getType() !== 'composer-command') continue; const converted = $convertDirectiveToText(child.getKey()); if (converted) suppressed.add(converted); @@ -460,10 +457,12 @@ describe('$draftDirective', () => { it('moves a lone misplaced directive to the start without doubling separator whitespace', () => { const editor = createEditor(); - for (const [before, after, expected] of [ + const cases = [ ['please ', ' now', '/review please now'], [' ', ' now', '/review now'], - ]) { + ]; + for (let i = 0, len = cases.length; i < len; i++) { + const [before, after, expected] = cases[i]; setStructuredCommandDraft(editor, before, after); editor.update( () => { @@ -482,12 +481,14 @@ describe('$draftDirective', () => { it('normalizes boundary whitespace when removing a directive', () => { const editor = createEditor(); - for (const [before, after, expected] of [ + const cases = [ ['please ', ' now', 'please now'], ['', ' now', 'now'], ['please ', ' ', 'please'], ['', ' ', ''], - ]) { + ]; + for (let i = 0, len = cases.length; i < len; i++) { + const [before, after, expected] = cases[i]; setStructuredCommandDraft(editor, before, after); editor.update( () => { diff --git a/packages/presentation/ui/src/shell/composer-editor/editor.tsx b/packages/presentation/ui/src/shell/composer-editor/editor.tsx index d86045d2..5b2a10a5 100644 --- a/packages/presentation/ui/src/shell/composer-editor/editor.tsx +++ b/packages/presentation/ui/src/shell/composer-editor/editor.tsx @@ -297,7 +297,10 @@ export function ComposerEditor({ const analysis = $analyzeDirectives(); const placementIssues: Partial> = {}; if (analysis.composition.kind === 'blocked') { - for (const key of analysis.blockedKeys) placementIssues[key] = analysis.composition.issue; + for (let i = 0, len = analysis.blockedKeys.length; i < len; i++) { + const key = analysis.blockedKeys[i]; + placementIssues[key] = analysis.composition.issue; + } } return { caretOffset: $caretFlatOffset(), diff --git a/packages/presentation/ui/src/shell/composer-editor/serialize.ts b/packages/presentation/ui/src/shell/composer-editor/serialize.ts index c477dbca..4e16626b 100644 --- a/packages/presentation/ui/src/shell/composer-editor/serialize.ts +++ b/packages/presentation/ui/src/shell/composer-editor/serialize.ts @@ -55,7 +55,7 @@ function $pointFlatOffset(point: PointType): number { if (!$isElementNode(node)) throw new Error('Element selection point must reference an element'); let offset = $flatOffsetOfNode(node); const children = node.getChildren(); - for (let i = 0; i < point.offset && i < children.length; i++) { + for (let i = 0, len = children.length; i < point.offset && i < len; i++) { offset += flatTextContribution(children[i], i < children.length - 1); } return offset; @@ -209,7 +209,8 @@ function $collectDirectives(node: LexicalNode, output: EditorDirective[]): void return; } if (!$isElementNode(node)) return; - for (const child of node.getChildren()) $collectDirectives(child, output); + const children = node.getChildren(); + for (let i = 0, len = children.length; i < len; i++) $collectDirectives(children[i], output); } /** Classify directive placement independently from catalog validity. AgentInput can represent @@ -217,7 +218,10 @@ function $collectDirectives(node: LexicalNode, output: EditorDirective[]): void export function $analyzeDirectives(): DirectiveAnalysis { const root = $getRoot(); const directives: EditorDirective[] = []; - for (const child of root.getChildren()) $collectDirectives(child, directives); + const children = root.getChildren(); + for (let i = 0, len = children.length; i < len; i++) { + $collectDirectives(children[i], directives); + } if (directives.length === 0) { return { blockedKeys: [], composition: { kind: 'none' }, leading: null }; diff --git a/packages/presentation/ui/src/shell/composer-editor/tokenize.ts b/packages/presentation/ui/src/shell/composer-editor/tokenize.ts index 918e5244..f0390fe9 100644 --- a/packages/presentation/ui/src/shell/composer-editor/tokenize.ts +++ b/packages/presentation/ui/src/shell/composer-editor/tokenize.ts @@ -94,7 +94,9 @@ export function $normalizeDirectiveTokens( // `/name` inside command arguments or shell syntax as a second action. if ($hasLeadingDirective()) return; changed = false; - for (const node of $nodesOfType(TextNode)) { + const nodes = $nodesOfType(TextNode); + for (let i = 0, len = nodes.length; i < len; i++) { + const node = nodes[i]; const candidate = $findDirectiveCandidate(node, state, opts.force ?? false); if (!candidate) continue; $replaceCandidate(node, candidate); diff --git a/packages/presentation/ui/src/shell/composer.tsx b/packages/presentation/ui/src/shell/composer.tsx index fb259e64..1bd2be36 100644 --- a/packages/presentation/ui/src/shell/composer.tsx +++ b/packages/presentation/ui/src/shell/composer.tsx @@ -530,7 +530,8 @@ export function Composer({ return; } let total = attachmentPayloadBytes(attachments); - for (const file of files) { + for (let i = 0, len = files.length; i < len; i++) { + const file = files[i]; const validationError = isSupportedImageFile(file) ? file.size > MAX_ATTACHMENT_BYTES ? t('attachmentTooLarge') @@ -801,7 +802,8 @@ export function Composer({ // Workflow mode and approval policy are orthogonal axes (see session-modes.ts, approval-policy.ts). // The active mode is server-reflected; a rejected switch (error banner) leaves the previous active. let matchedMode: SessionMode | null = null; - for (const mode of workflowModes) { + for (let i = 0, len = workflowModes.length; i < len; i++) { + const mode = workflowModes[i]; if (mode.modeId === currentModeId) matchedMode = mode; } const activeMode = matchedMode; diff --git a/packages/presentation/ui/src/shell/history/sort.ts b/packages/presentation/ui/src/shell/history/sort.ts index f72815ff..025751ba 100644 --- a/packages/presentation/ui/src/shell/history/sort.ts +++ b/packages/presentation/ui/src/shell/history/sort.ts @@ -42,7 +42,8 @@ export function groupHistoryBrowserEntries( ): HistoryBrowserGroup[] { const groups: HistoryBrowserGroup[] = []; const byCwd = new Map(); - for (const entry of sorted) { + for (let i = 0, len = sorted.length; i < len; i++) { + const entry = sorted[i]; const existing = byCwd.get(entry.cwd); if (existing) { existing.entries.push(entry); diff --git a/packages/presentation/ui/src/shell/new-session-surface.tsx b/packages/presentation/ui/src/shell/new-session-surface.tsx index 3fd75c6c..6ca12e1e 100644 --- a/packages/presentation/ui/src/shell/new-session-surface.tsx +++ b/packages/presentation/ui/src/shell/new-session-surface.tsx @@ -127,7 +127,8 @@ function workspaceById( workspaceId: WorkspaceId | null, ): WorkspaceRecord | null { if (workspaceId === null) return null; - for (const workspace of workspaces) { + for (let i = 0, len = workspaces.length; i < len; i++) { + const workspace = workspaces[i]; if (workspace.workspaceId === workspaceId) return workspace; } return null; diff --git a/packages/presentation/ui/src/shell/permission-prompt.tsx b/packages/presentation/ui/src/shell/permission-prompt.tsx index b2eb636f..1b585faf 100644 --- a/packages/presentation/ui/src/shell/permission-prompt.tsx +++ b/packages/presentation/ui/src/shell/permission-prompt.tsx @@ -157,10 +157,13 @@ function permissionDetails(item: PermissionConversationItem): PermissionDetail[] const showRawArguments = toolCall.kind === 'other' && raw !== undefined; const files = new Set(); - for (const item of toolCall.content ?? []) { + const content = toolCall.content ?? []; + for (let i = 0, len = content.length; i < len; i++) { + const item = content[i]; if (item.type === 'diff') files.add(item.path); } - for (const location of toolCall.locations ?? []) files.add(location.path); + const locations = toolCall.locations ?? []; + for (let i = 0, len = locations.length; i < len; i++) files.add(locations[i].path); if (!showRawArguments) { const rawPath = stringField(raw, 'path') ?? stringField(raw, 'file_path'); if (rawPath) files.add(rawPath); diff --git a/packages/presentation/ui/src/shell/simulator/device-compositor.ts b/packages/presentation/ui/src/shell/simulator/device-compositor.ts index 70ed9c0b..604b1e48 100644 --- a/packages/presentation/ui/src/shell/simulator/device-compositor.ts +++ b/packages/presentation/ui/src/shell/simulator/device-compositor.ts @@ -57,12 +57,14 @@ export function paintChassis( // Side buttons first, so the chassis paints over their inner halves. context.fillStyle = RIM_COLOR; const buttonWidth = buttonDepth * 3; - for (const [top, size] of LEFT_BUTTONS) { + for (let i = 0, len = LEFT_BUTTONS.length; i < len; i++) { + const [top, size] = LEFT_BUTTONS[i]; context.beginPath(); context.roundRect(0, pad + top * screenH, buttonWidth, size * screenH, buttonDepth); context.fill(); } - for (const [top, size] of RIGHT_BUTTONS) { + for (let i = 0, len = RIGHT_BUTTONS.length; i < len; i++) { + const [top, size] = RIGHT_BUTTONS[i]; context.beginPath(); context.roundRect( width - buttonWidth, diff --git a/packages/presentation/ui/src/shell/terminal/fonts.ts b/packages/presentation/ui/src/shell/terminal/fonts.ts index 73593671..016fc239 100644 --- a/packages/presentation/ui/src/shell/terminal/fonts.ts +++ b/packages/presentation/ui/src/shell/terminal/fonts.ts @@ -56,7 +56,8 @@ const COLOR_EMOJI_FAMILIES = ['apple color emoji']; const MONO_FAMILIES = ['sf mono', 'menlo', 'monaco', 'consolas', 'dejavu sans mono']; function pickLocalFamily(fonts: readonly LocalFontData[], matchers: string[]): string | null { - for (const matcher of matchers) { + for (let i = 0, len = matchers.length; i < len; i++) { + const matcher = matchers[i]; const hit = fonts.find((font) => `${font.family} ${font.fullName} ${font.postscriptName}`.toLowerCase().includes(matcher), ); diff --git a/packages/presentation/ui/src/shell/threads-view.tsx b/packages/presentation/ui/src/shell/threads-view.tsx index 85cbbd78..b2d53436 100644 --- a/packages/presentation/ui/src/shell/threads-view.tsx +++ b/packages/presentation/ui/src/shell/threads-view.tsx @@ -98,7 +98,8 @@ export function ThreadsView({ const projectGroups: ThreadGroupViewModel[] = []; let pinnedGroup: ThreadGroupViewModel | undefined; let chatGroup: ThreadGroupViewModel | undefined; - for (const group of groups) { + for (let i = 0, len = groups.length; i < len; i++) { + const group = groups[i]; if (group.isPinned) pinnedGroup = group; else if (group.isChat) chatGroup = group; else projectGroups.push(group); @@ -165,8 +166,9 @@ export function ThreadsView({ className="flex flex-col gap-0.5" value={openSections} onValueChange={(next) => { - for (const section of changedAccordionValues(SIDEBAR_SECTIONS, openSections, next)) { - onToggleSectionCollapsed(section); + const changedSections = changedAccordionValues(SIDEBAR_SECTIONS, openSections, next); + for (let i = 0, len = changedSections.length; i < len; i++) { + onToggleSectionCollapsed(changedSections[i]); } }} > @@ -214,8 +216,9 @@ export function ThreadsView({ className="flex flex-col gap-0.5" value={openGroupKeys} onValueChange={(next) => { - for (const key of changedAccordionValues(projectGroupKeys, openGroupKeys, next)) { - onToggleGroupCollapsed(key); + const changedKeys = changedAccordionValues(projectGroupKeys, openGroupKeys, next); + for (let i = 0, len = changedKeys.length; i < len; i++) { + onToggleGroupCollapsed(changedKeys[i]); } }} > diff --git a/packages/presentation/ui/src/thread-groups.ts b/packages/presentation/ui/src/thread-groups.ts index 66ab60cf..631b5750 100644 --- a/packages/presentation/ui/src/thread-groups.ts +++ b/packages/presentation/ui/src/thread-groups.ts @@ -52,7 +52,8 @@ export function groupThreadsByWorkspace( const sessionsByWorkspaceId = new Map(); const unregistered: SessionInfo[] = []; - for (const session of sessions) { + for (let i = 0, len = sessions.length; i < len; i++) { + const session = sessions[i]; const matched = workspaceByCwdKey.get(normalizeCwdKey(session.cwd)); let workspace = matched; if (matched && workspaceKind(matched) === 'worktree') { diff --git a/packages/system-plane/ipc/src/electron-main.ts b/packages/system-plane/ipc/src/electron-main.ts index 6d8a6ed0..7025cd8a 100644 --- a/packages/system-plane/ipc/src/electron-main.ts +++ b/packages/system-plane/ipc/src/electron-main.ts @@ -85,7 +85,8 @@ export function bindElectronSystemIpc({ const dispose = (reason?: unknown): void => { if (disposed) return; disposed = true; - for (const removeHandler of Object.values(removeHandlers)) removeHandler(); + const handlers = Object.values(removeHandlers); + for (let i = 0, len = handlers.length; i < len; i++) handlers[i](); ipcMain.removeListener(SETTINGS_SNAPSHOT_CHANNEL, handleSnapshot); ipcMain.removeListener(DAEMON_URL_SNAPSHOT_CHANNEL, handleDaemonUrlSnapshot); window.off('maximize', emitMaximizedState); From 8e6720795c4cd1738753e06244be3bc4f11a0336 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 31 Jul 2026 10:24:36 +0800 Subject: [PATCH 3/3] chore: make ci happy (after housekeeping) --- packages/host/assets/src/pi-closure.gen.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/host/assets/src/pi-closure.gen.ts b/packages/host/assets/src/pi-closure.gen.ts index 42702498..52409729 100644 --- a/packages/host/assets/src/pi-closure.gen.ts +++ b/packages/host/assets/src/pi-closure.gen.ts @@ -241,13 +241,6 @@ const packages: NpmClosure['packages'] = [ 'sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==', path: 'node_modules/@google/genai', }, - { - name: 'ws', - version: '8.21.0', - integrity: - 'sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==', - path: 'node_modules/@google/genai/node_modules/ws', - }, { name: '@hono/node-server', version: '2.0.12', @@ -358,13 +351,6 @@ const packages: NpmClosure['packages'] = [ 'sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==', path: 'node_modules/@mistralai/mistralai', }, - { - name: 'ws', - version: '8.21.0', - integrity: - 'sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==', - path: 'node_modules/@mistralai/mistralai/node_modules/ws', - }, { name: '@modelcontextprotocol/sdk', version: '1.30.0', @@ -1622,7 +1608,7 @@ const packages: NpmClosure['packages'] = [ export const PI_CLOSURE: NpmClosure = { version: '0.80.6', - revision: 'b77bf10e9a04fe3f', + revision: '606dbeda05e086fb', entry: 'node_modules/@earendil-works/pi-coding-agent/dist/index.js', packages, };