From e4b9a8385d89f37b4096df75fffd7cbd5f997ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 17:46:02 +0200 Subject: [PATCH 01/16] feat(mcp): support resource_link content + bump @modelcontextprotocol/sdk to 1.26.0 [security] Port of Zoo-Code PR #351. Bumps @modelcontextprotocol/sdk 1.12.0 -> 1.26.0 (security) and adds the MCP resource_link content type (spec 2025-06-18): a new McpResourceLink type in @roo-code/types and markdown-link rendering for resource_link items in UseMcpToolTool. Co-authored-by: Elliott de Launay Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com> --- ...6-06-27_zoo-351-mcp-sdk-1-26-0-security.md | 62 ++++++ packages/types/src/mcp.ts | 19 ++ pnpm-lock.yaml | 205 +++++++++++++++--- src/core/tools/UseMcpToolTool.ts | 6 +- .../tools/__tests__/useMcpToolTool.spec.ts | 56 +++++ src/package.json | 2 +- 6 files changed, 316 insertions(+), 34 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-351-mcp-sdk-1-26-0-security.md diff --git a/ai_plans/2026-06-27_zoo-351-mcp-sdk-1-26-0-security.md b/ai_plans/2026-06-27_zoo-351-mcp-sdk-1-26-0-security.md new file mode 100644 index 000000000..454225e9d --- /dev/null +++ b/ai_plans/2026-06-27_zoo-351-mcp-sdk-1-26-0-security.md @@ -0,0 +1,62 @@ +# Port Zoo PR #351 — update @modelcontextprotocol/sdk to v1.26.0 [security] + MCP resource_link support + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #351, commit `09fedd62c`, merged 2026-06-06. +- Original author(s): Elliott de Launay, edelauna (renovate[bot] dropped as a bot). +- Commit trailers to add: + ``` + Co-authored-by: Elliott de Launay + Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com> + ``` + +## §1 What & why + +Two coupled changes: + +1. **Security bump** of `@modelcontextprotocol/sdk` `1.12.0 → 1.26.0` in `src/package.json` (+ lockfile). +2. **MCP `resource_link` content type** (MCP spec 2025-06-18): add the `McpResourceLink` + type to `packages/types/src/mcp.ts` and render it in `UseMcpToolTool`. + +Our fork is at the exact pre-PR state (sdk 1.12.0; no `McpResourceLink`; no `resource_link` +branch in `UseMcpToolTool.processToolContent`). MCP code paths exist here (McpHub, UseMcpToolTool), +so this is in-scope. No Roo/TTS/router/cloud entanglement. + +## §2 Edits (exact, adapted to our code) + +### 2a. `packages/types/src/mcp.ts` — add types before `McpToolCallResponse`, add to union + +Insert the `McpResourceLinkAnnotations` + `McpResourceLink` types and add `| McpResourceLink` +to the `McpToolCallResponse.content` union. (Keep our existing multi-line union formatting; +only add the new arm — do NOT reflow the existing arms to one-liners like upstream did; that's +churn with no behavior change.) + +### 2b. `src/core/tools/UseMcpToolTool.ts` + +- Add `McpResourceLink` to the type import from `@roo-code/types`. +- In `processToolContent`'s `.map`, after the `image` branch and before the trailing `return ""`, + add a `resource_link` branch rendering a markdown link. + +### 2c. `src/package.json` + +- `"@modelcontextprotocol/sdk": "1.12.0"` → `"1.26.0"`. + +### 2d. Lockfile + +- `pnpm install --lockfile-only` (or filtered install) to refresh `pnpm-lock.yaml`. + +## §3 Scope cuts (YAGNI) + +- Do NOT reflow the existing `McpToolCallResponse` union arms (upstream cosmetic change). +- Do NOT re-add TTS / router / cloud / Roo branding. +- No new tests required by upstream; add a focused type/render check only if cheap. + +## §4 Verify (binary acceptance) + +- `pnpm --filter @roo-code/types check-types` → passes. +- `cd src && npx tsc --noEmit` (or repo typecheck) → passes; `McpResourceLink` resolves. +- `grep '@modelcontextprotocol/sdk' src/package.json` shows `1.26.0`. +- `grep '@modelcontextprotocol/sdk@1.26.0' pnpm-lock.yaml` present; no `@1.12.0` left. +- Existing MCP tests (`UseMcpToolTool`, `McpHub`) still pass. + +## §5 Co-authors — see §0. diff --git a/packages/types/src/mcp.ts b/packages/types/src/mcp.ts index 7e9cd5974..8cbe0c522 100644 --- a/packages/types/src/mcp.ts +++ b/packages/types/src/mcp.ts @@ -115,6 +115,24 @@ export type McpResourceResponse = { }> } +export type McpResourceLinkAnnotations = { + audience?: ("user" | "assistant")[] + priority?: number + lastModified?: string +} + +// Defined in MCP spec 2025-06-18: https://modelcontextprotocol.io/specification/2025-06-18/server/tools +export type McpResourceLink = { + type: "resource_link" + uri: string + name: string + description?: string + mimeType?: string + title?: string + size?: number + annotations?: McpResourceLinkAnnotations +} + export type McpToolCallResponse = { _meta?: Record // eslint-disable-line @typescript-eslint/no-explicit-any content: Array< @@ -141,6 +159,7 @@ export type McpToolCallResponse = { blob?: string } } + | McpResourceLink > isError?: boolean } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffd10ac8a..c0ca7871f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -673,7 +673,7 @@ importers: version: 3.922.0 '@google/genai': specifier: ^1.29.1 - version: 1.29.1(@modelcontextprotocol/sdk@1.12.0) + version: 1.29.1(@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)) '@lmstudio/sdk': specifier: ^1.1.1 version: 1.2.0 @@ -681,8 +681,8 @@ importers: specifier: ^1.9.18 version: 1.9.18(zod@3.25.76) '@modelcontextprotocol/sdk': - specifier: 1.12.0 - version: 1.12.0 + specifier: 1.26.0 + version: 1.26.0(zod@3.25.76) '@qdrant/js-client-rest': specifier: ^1.14.0 version: 1.14.0(typescript@5.8.3) @@ -2227,6 +2227,12 @@ packages: '@modelcontextprotocol/sdk': optional: true + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@hookform/resolvers@5.1.1': resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==} peerDependencies: @@ -2580,9 +2586,15 @@ packages: '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} - '@modelcontextprotocol/sdk@1.12.0': - resolution: {integrity: sha512-m//7RlINx1F3sz3KqwY1WWzVgTcYX52HYk4bJ1hkBXV3zccAEth+jRvG8DBRrdaQuRsPAJOx2MH3zaHNCKL7Zg==} + '@modelcontextprotocol/sdk@1.26.0': + resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: 3.25.76 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true '@mswjs/interceptors@0.39.6': resolution: {integrity: sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==} @@ -4801,6 +4813,14 @@ packages: peerDependencies: zod: 3.25.76 + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -5060,8 +5080,8 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} boolbase@1.0.0: @@ -5417,6 +5437,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -6350,14 +6374,14 @@ packages: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - express-rate-limit@7.5.0: - resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} engines: {node: '>= 16'} peerDependencies: - express: ^4.11 || 5 || ^5.0.0-beta.1 + express: '>= 4.11' - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} extend-shallow@2.0.1: @@ -6847,6 +6871,10 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} + hono@4.12.27: + resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==} + engines: {node: '>=16.9.0'} + hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} @@ -6877,6 +6905,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -6932,6 +6964,10 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + identity-obj-proxy@3.0.0: resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} engines: {node: '>=4'} @@ -7025,6 +7061,10 @@ packages: resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==} engines: {node: '>=12.22.0'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -7337,6 +7377,9 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -7402,6 +7445,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -8849,6 +8895,10 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -8866,6 +8916,10 @@ packages: resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -9370,6 +9424,10 @@ packages: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + side-channel-map@1.0.1: resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} engines: {node: '>= 0.4'} @@ -9382,6 +9440,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -9526,6 +9588,10 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -9986,6 +10052,10 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -10879,7 +10949,7 @@ snapshots: dependencies: '@ai-sdk/provider': 3.0.8 '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 + eventsource-parser: 3.1.0 zod: 3.25.76 '@ai-sdk/provider-utils@4.0.27(zod@3.25.76)': @@ -12125,17 +12195,21 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@google/genai@1.29.1(@modelcontextprotocol/sdk@1.12.0)': + '@google/genai@1.29.1(@modelcontextprotocol/sdk@1.26.0(zod@3.25.76))': dependencies: google-auth-library: 10.5.0 ws: 8.18.3 optionalDependencies: - '@modelcontextprotocol/sdk': 1.12.0 + '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate + '@hono/node-server@1.19.14(hono@4.12.27)': + dependencies: + hono: 4.12.27 + '@hookform/resolvers@5.1.1(react-hook-form@7.57.0(react@18.3.1))': dependencies: '@standard-schema/utils': 0.3.0 @@ -12489,19 +12563,25 @@ snapshots: '@mixmark-io/domino@2.2.0': {} - '@modelcontextprotocol/sdk@1.12.0': + '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': dependencies: - ajv: 6.12.6 + '@hono/node-server': 1.19.14(hono@4.12.27) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 cors: 2.8.5 cross-spawn: 7.0.6 eventsource: 3.0.7 - express: 5.1.0 - express-rate-limit: 7.5.0(express@5.1.0) + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.27 + jose: 6.2.3 + json-schema-typed: 8.0.2 pkce-challenge: 5.0.0 raw-body: 3.0.0 zod: 3.25.76 - zod-to-json-schema: 3.24.5(zod@3.25.76) + zod-to-json-schema: 3.25.1(zod@3.25.76) transitivePeerDependencies: - supports-color @@ -14829,6 +14909,10 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 3.25.76 + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -15133,17 +15217,17 @@ snapshots: bluebird@3.7.2: {} - body-parser@2.2.0: + body-parser@2.3.0: dependencies: bytes: 3.1.2 - content-type: 1.0.5 + content-type: 2.0.0 debug: 4.4.3 - http-errors: 2.0.0 - iconv-lite: 0.6.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.0 - type-is: 2.0.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 transitivePeerDependencies: - supports-color @@ -15499,6 +15583,8 @@ snapshots: content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} convert-to-spaces@2.0.1: {} @@ -16442,7 +16528,7 @@ snapshots: eventsource@3.0.7: dependencies: - eventsource-parser: 3.0.6 + eventsource-parser: 3.1.0 exceljs@4.4.0: dependencies: @@ -16525,19 +16611,21 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - express-rate-limit@7.5.0(express@5.1.0): + express-rate-limit@8.5.2(express@5.2.1): dependencies: - express: 5.1.0 + express: 5.2.1 + ip-address: 10.2.0 - express@5.1.0: + express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.0 + body-parser: 2.3.0 content-disposition: 1.0.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 debug: 4.4.3 + depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -17155,6 +17243,8 @@ snapshots: highlight.js@11.11.1: {} + hono@4.12.27: {} + hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 @@ -17190,6 +17280,14 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -17241,6 +17339,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + identity-obj-proxy@3.0.0: dependencies: harmony-reflect: 1.6.2 @@ -17341,6 +17443,8 @@ snapshots: transitivePeerDependencies: - supports-color + ip-address@10.2.0: {} + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -17631,6 +17735,8 @@ snapshots: jiti@2.4.2: {} + jose@6.2.3: {} + joycon@3.1.1: {} js-base64@3.7.8: @@ -17700,6 +17806,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -19424,6 +19532,11 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -19441,6 +19554,13 @@ snapshots: iconv-lite: 0.6.3 unpipe: 1.0.0 + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -20121,6 +20241,11 @@ snapshots: es-errors: 1.3.0 object-inspect: 1.13.4 + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-map@1.0.1: dependencies: call-bound: 1.0.4 @@ -20144,6 +20269,14 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -20298,6 +20431,8 @@ snapshots: statuses@2.0.1: {} + statuses@2.0.2: {} + std-env@3.9.0: {} stdin-discarder@0.2.2: {} @@ -20763,6 +20898,12 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.1 + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.1 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 diff --git a/src/core/tools/UseMcpToolTool.ts b/src/core/tools/UseMcpToolTool.ts index 98a481c52..3005be169 100644 --- a/src/core/tools/UseMcpToolTool.ts +++ b/src/core/tools/UseMcpToolTool.ts @@ -1,4 +1,4 @@ -import type { ClineAskUseMcpServer, McpExecutionStatus } from "@roo-code/types" +import type { ClineAskUseMcpServer, McpExecutionStatus, McpResourceLink } from "@roo-code/types" import { Task } from "../task/Task" import { formatResponse } from "../prompts/responses" @@ -308,6 +308,10 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { } return "" } + if (item.type === "resource_link") { + const link = item as McpResourceLink + return `[${link.name}](${link.uri})${link.description ? ` — ${link.description}` : ""}` + } return "" }) .filter(Boolean) diff --git a/src/core/tools/__tests__/useMcpToolTool.spec.ts b/src/core/tools/__tests__/useMcpToolTool.spec.ts index 93f115e86..820f5ce80 100644 --- a/src/core/tools/__tests__/useMcpToolTool.spec.ts +++ b/src/core/tools/__tests__/useMcpToolTool.spec.ts @@ -806,6 +806,62 @@ describe("useMcpToolTool", () => { expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("with 1 image(s)")) }) + it("should render resource_link content as a markdown link (MCP spec 2025-06-18)", async () => { + const block: ToolUse = { + type: "tool_use", + name: "use_mcp_tool", + params: { + server_name: "figma-server", + tool_name: "get_node_info", + arguments: '{"nodeId": "123"}', + }, + nativeArgs: { + server_name: "figma-server", + tool_name: "get_node_info", + arguments: { nodeId: "123" }, + }, + partial: false, + } + + mockAskApproval.mockResolvedValue(true) + + const mockToolResult = { + content: [ + { + type: "resource_link", + uri: "https://example.com/spec.pdf", + name: "Design Spec", + description: "Full spec", + }, + ], + isError: false, + } + + mockProviderRef.deref.mockReturnValue({ + getMcpHub: () => ({ + callTool: vi.fn().mockResolvedValue(mockToolResult), + getAllServers: vi + .fn() + .mockReturnValue([ + { name: "figma-server", tools: [{ name: "get_node_info", description: "Get node info" }] }, + ]), + }), + postMessageToWebview: vi.fn(), + }) + + await useMcpToolTool.handle(mockTask as Task, block as any, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockTask.say).toHaveBeenCalledWith( + "mcp_server_response", + "[Design Spec](https://example.com/spec.pdf) — Full spec", + [], + ) + }) + it("should handle image with data URL already formatted", async () => { const block: ToolUse = { type: "tool_use", diff --git a/src/package.json b/src/package.json index 69e81cb64..5967268f1 100644 --- a/src/package.json +++ b/src/package.json @@ -490,7 +490,7 @@ "@google/genai": "^1.29.1", "@lmstudio/sdk": "^1.1.1", "@mistralai/mistralai": "^1.9.18", - "@modelcontextprotocol/sdk": "1.12.0", + "@modelcontextprotocol/sdk": "1.26.0", "@qdrant/js-client-rest": "^1.14.0", "@roo-code/cloud": "workspace:^", "@roo-code/core": "workspace:^", From 077f42a20b2e3024a6ee922002b114bba7d8782a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 18:10:49 +0200 Subject: [PATCH 02/16] chore(deps): pin @types/react and @types/react-dom to exact versions Port of Zoo-Code PR #423. Replaces caret ranges on the React type packages with exact pins across apps/cli, the root pnpm overrides, and webview-ui, and aligns apps/cli (which declared @types/react ^19.1.6) to the v18 the override already forces. Lockfile resolution is unchanged (@types/react@18.3.23, @types/react-dom@18.3.7); only the declared ranges tighten, removing caret drift. Co-authored-by: Elliott de Launay --- .../2026-06-27_zoo-423-pin-dependencies.md | 51 ++++ apps/cli/package.json | 2 +- package.json | 4 +- pnpm-lock.yaml | 218 +++++++++--------- webview-ui/package.json | 4 +- 5 files changed, 165 insertions(+), 114 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-423-pin-dependencies.md diff --git a/ai_plans/2026-06-27_zoo-423-pin-dependencies.md b/ai_plans/2026-06-27_zoo-423-pin-dependencies.md new file mode 100644 index 000000000..67bc99d17 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-423-pin-dependencies.md @@ -0,0 +1,51 @@ +# Port Zoo PR #423 — pin React type dependencies + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #423, commit `5ad7aab6b`, merged 2026-06-07. +- Original author: Elliott de Launay (renovate[bot] dropped as a bot). +- Commit trailer: + ``` + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why + +Renovate "pin dependencies" chore: replace caret ranges on `@types/react` / +`@types/react-dom` with exact versions across the three manifests that declare +them, and align `apps/cli` (which declared React **19** types while the root +override forces **18**) to the pinned v18. + +Pinning removes silent caret drift (reproducible installs) and fixes the +declared-vs-resolved React-types mismatch in the CLI. The lockfile already +resolves `@types/react@18.3.23` and `@types/react-dom@18.3.7`, so resolution is +unchanged — only the declared ranges tighten. + +Our fork is at the exact pre-PR state for these lines. + +## §2 Edits (exact) + +- `apps/cli/package.json`: `"@types/react": "^19.1.6"` → `"18.3.23"`. +- `package.json` (pnpm.overrides): `"@types/react": "^18.3.23"` → `"18.3.23"`; + `"@types/react-dom": "^18.3.5"` → `"18.3.7"`. +- `webview-ui/package.json`: `"@types/react": "^18.3.23"` → `"18.3.23"`; + `"@types/react-dom": "^18.3.5"` → `"18.3.7"`. +- Refresh `pnpm-lock.yaml` via `pnpm install --lockfile-only`. + +## §3 Scope cuts (YAGNI / divergence) + +- Do NOT touch `@types/node`, `glob`, or any other line the PR only showed as + diff context — our fork has its own values there (`glob: ">=11.1.0"`, + cli `@types/node: "^24.1.0"`). Only the React-type lines change. +- Do NOT re-add Roo branding / TTS / router / cloud. + +## §4 Verify (binary acceptance) + +- `grep '@types/react' apps/cli/package.json package.json webview-ui/package.json` + shows no caret on the react type lines; cli shows `18.3.23`. +- `pnpm install --lockfile-only` succeeds; lockfile still resolves + `@types/react@18.3.23` and `@types/react-dom@18.3.7`. +- `pnpm --filter @roo-code/vscode-webview check-types` → passes. +- `pnpm --filter @roo-code/cli check-types` → passes. + +## §5 Co-author — see §0. diff --git a/apps/cli/package.json b/apps/cli/package.json index 9276f1705..17b05a6d2 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -41,7 +41,7 @@ "@roo-code/config-eslint": "workspace:^", "@roo-code/config-typescript": "workspace:^", "@types/node": "^24.1.0", - "@types/react": "^19.1.6", + "@types/react": "18.3.23", "ink-testing-library": "^4.0.0", "rimraf": "^6.0.1", "tsup": "^8.4.0", diff --git a/package.json b/package.json index a61f90574..ae97fc0ba 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "form-data": ">=4.0.4", "bluebird": ">=3.7.2", "glob": ">=11.1.0", - "@types/react": "^18.3.23", - "@types/react-dom": "^18.3.5", + "@types/react": "18.3.23", + "@types/react-dom": "18.3.7", "zod": "3.25.76", "picomatch@>=4": "4.0.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0ca7871f..a69a54d45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ overrides: form-data: '>=4.0.4' bluebird: '>=3.7.2' glob: '>=11.1.0' - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 zod: 3.25.76 picomatch@>=4: 4.0.4 @@ -140,7 +140,7 @@ importers: specifier: ^24.1.0 version: 24.2.1 '@types/react': - specifier: ^18.3.23 + specifier: 18.3.23 version: 18.3.23 ink-testing-library: specifier: ^4.0.0 @@ -324,10 +324,10 @@ importers: specifier: ^1.1.6 version: 1.1.6 '@types/react': - specifier: ^18.3.23 + specifier: 18.3.23 version: 18.3.23 '@types/react-dom': - specifier: ^18.3.5 + specifier: 18.3.7 version: 18.3.7(@types/react@18.3.23) tailwindcss: specifier: ^4 @@ -1253,10 +1253,10 @@ importers: specifier: ^20.19.25 version: 20.19.41 '@types/react': - specifier: ^18.3.23 + specifier: 18.3.23 version: 18.3.23 '@types/react-dom': - specifier: ^18.3.5 + specifier: 18.3.7 version: 18.3.7(@types/react@18.3.23) '@types/shell-quote': specifier: ^1.7.5 @@ -2906,8 +2906,8 @@ packages: '@radix-ui/react-alert-dialog@1.1.13': resolution: {integrity: sha512-/uPs78OwxGxslYOG5TKeUsv9fZC0vo376cXSADdKirTmsLJU2au6L3n34c3p6W26rFDDDze/hwy4fYeNd0qdGA==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2919,8 +2919,8 @@ packages: '@radix-ui/react-arrow@1.1.6': resolution: {integrity: sha512-2JMfHJf/eVnwq+2dewT3C0acmCWD3XiVA1Da+jTDqo342UlU13WvXtqHhG+yJw5JeQmu4ue2eMy6gcEArLBlcw==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2932,8 +2932,8 @@ packages: '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2945,8 +2945,8 @@ packages: '@radix-ui/react-checkbox@1.3.1': resolution: {integrity: sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2958,8 +2958,8 @@ packages: '@radix-ui/react-collapsible@1.1.10': resolution: {integrity: sha512-O2mcG3gZNkJ/Ena34HurA3llPOEA/M4dJtIRMa6y/cknRDC8XY5UZBInKTsUwW5cUue9A4k0wi1XU5fKBzKe1w==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2971,8 +2971,8 @@ packages: '@radix-ui/react-collection@1.1.6': resolution: {integrity: sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2984,8 +2984,8 @@ packages: '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2997,7 +2997,7 @@ packages: '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3006,7 +3006,7 @@ packages: '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3015,8 +3015,8 @@ packages: '@radix-ui/react-dialog@1.1.13': resolution: {integrity: sha512-ARFmqUyhIVS3+riWzwGTe7JLjqwqgnODBUZdqpWar/z1WFs9z76fuOs/2BOWCR+YboRn4/WN9aoaGVwqNRr8VA==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3028,7 +3028,7 @@ packages: '@radix-ui/react-direction@1.1.1': resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3037,8 +3037,8 @@ packages: '@radix-ui/react-dismissable-layer@1.1.11': resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3050,8 +3050,8 @@ packages: '@radix-ui/react-dismissable-layer@1.1.9': resolution: {integrity: sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3063,8 +3063,8 @@ packages: '@radix-ui/react-dropdown-menu@2.1.14': resolution: {integrity: sha512-lzuyNjoWOoaMFE/VC5FnAAYM16JmQA8ZmucOXtlhm2kKR5TSU95YLAueQ4JYuRmUJmBvSqXaVFGIfuukybwZJQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3076,7 +3076,7 @@ packages: '@radix-ui/react-focus-guards@1.1.2': resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3085,8 +3085,8 @@ packages: '@radix-ui/react-focus-scope@1.1.6': resolution: {integrity: sha512-r9zpYNUQY+2jWHWZGyddQLL9YHkM/XvSFHVcWs7bdVuxMAnCwTAuy6Pf47Z4nw7dYcUou1vg/VgjjrrH03VeBw==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3103,7 +3103,7 @@ packages: '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3112,8 +3112,8 @@ packages: '@radix-ui/react-label@2.1.7': resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3125,8 +3125,8 @@ packages: '@radix-ui/react-menu@2.1.14': resolution: {integrity: sha512-0zSiBAIFq9GSKoSH5PdEaQeRB3RnEGxC+H2P0egtnKoKKLNBH8VBHyVO6/jskhjAezhOIplyRUj7U2lds9A+Yg==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3138,8 +3138,8 @@ packages: '@radix-ui/react-popover@1.1.13': resolution: {integrity: sha512-84uqQV3omKDR076izYgcha6gdpN8m3z6w/AeJ83MSBJYVG/AbOHdLjAgsPZkeC/kt+k64moXFCnio8BbqXszlw==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3151,8 +3151,8 @@ packages: '@radix-ui/react-popper@1.2.6': resolution: {integrity: sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3164,8 +3164,8 @@ packages: '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3177,8 +3177,8 @@ packages: '@radix-ui/react-portal@1.1.8': resolution: {integrity: sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3190,8 +3190,8 @@ packages: '@radix-ui/react-portal@1.1.9': resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3203,8 +3203,8 @@ packages: '@radix-ui/react-presence@1.1.4': resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3216,8 +3216,8 @@ packages: '@radix-ui/react-presence@1.1.5': resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3229,8 +3229,8 @@ packages: '@radix-ui/react-primitive@2.1.2': resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3242,8 +3242,8 @@ packages: '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3255,8 +3255,8 @@ packages: '@radix-ui/react-progress@1.1.6': resolution: {integrity: sha512-QzN9a36nKk2eZKMf9EBCia35x3TT+SOgZuzQBVIHyRrmYYi73VYBRK3zKwdJ6az/F5IZ6QlacGJBg7zfB85liA==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3268,8 +3268,8 @@ packages: '@radix-ui/react-radio-group@1.3.8': resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3281,8 +3281,8 @@ packages: '@radix-ui/react-roving-focus@1.1.10': resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3294,8 +3294,8 @@ packages: '@radix-ui/react-roving-focus@1.1.11': resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3307,8 +3307,8 @@ packages: '@radix-ui/react-roving-focus@1.1.9': resolution: {integrity: sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3320,8 +3320,8 @@ packages: '@radix-ui/react-scroll-area@1.2.9': resolution: {integrity: sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3333,8 +3333,8 @@ packages: '@radix-ui/react-select@2.2.4': resolution: {integrity: sha512-/OOm58Gil4Ev5zT8LyVzqfBcij4dTHYdeyuF5lMHZ2bIp0Lk9oETocYiJ5QC0dHekEQnK6L/FNJCceeb4AkZ6Q==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3346,8 +3346,8 @@ packages: '@radix-ui/react-separator@1.1.6': resolution: {integrity: sha512-Izof3lPpbCfTM7WDta+LRkz31jem890VjEvpVRoWQNKpDUMMVffuyq854XPGP1KYGWWmjmYvHvPFeocWhFCy1w==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3359,8 +3359,8 @@ packages: '@radix-ui/react-slider@1.3.4': resolution: {integrity: sha512-Cp6hEmQtRJFci285vkdIJ+HCDLTRDk+25VhFwa1fcubywjMUE3PynBgtN5RLudOgSCYMlT4jizCXdmV+8J7Y2w==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3372,7 +3372,7 @@ packages: '@radix-ui/react-slot@1.2.2': resolution: {integrity: sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3381,7 +3381,7 @@ packages: '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3390,8 +3390,8 @@ packages: '@radix-ui/react-tabs@1.1.12': resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3403,8 +3403,8 @@ packages: '@radix-ui/react-tooltip@1.2.6': resolution: {integrity: sha512-zYb+9dc9tkoN2JjBDIIPLQtk3gGyz8FMKoqYTb8EMVQ5a5hBcdHPECrsZVI4NpPAUOixhkoqg7Hj5ry5USowfA==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3416,8 +3416,8 @@ packages: '@radix-ui/react-tooltip@1.2.8': resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3429,7 +3429,7 @@ packages: '@radix-ui/react-use-callback-ref@1.1.1': resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3438,7 +3438,7 @@ packages: '@radix-ui/react-use-controllable-state@1.2.2': resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3447,7 +3447,7 @@ packages: '@radix-ui/react-use-effect-event@0.0.2': resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3456,7 +3456,7 @@ packages: '@radix-ui/react-use-escape-keydown@1.1.1': resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3465,7 +3465,7 @@ packages: '@radix-ui/react-use-layout-effect@1.1.1': resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3474,7 +3474,7 @@ packages: '@radix-ui/react-use-previous@1.1.1': resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3483,7 +3483,7 @@ packages: '@radix-ui/react-use-rect@1.1.1': resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3492,7 +3492,7 @@ packages: '@radix-ui/react-use-size@1.1.1': resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3501,8 +3501,8 @@ packages: '@radix-ui/react-visually-hidden@1.2.2': resolution: {integrity: sha512-ORCmRUbNiZIv6uV5mhFrhsIKw4UX/N3syZtyqvry61tbGm4JlgQuSn0hk5TwCARsCjkcnuRkSdCE3xfb+ADHew==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3514,8 +3514,8 @@ packages: '@radix-ui/react-visually-hidden@1.2.3': resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} peerDependencies: - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4209,8 +4209,8 @@ packages: engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 - '@types/react': ^18.3.23 - '@types/react-dom': ^18.3.5 + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7 react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: @@ -4511,7 +4511,7 @@ packages: '@types/react-dom@18.3.7': resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 '@types/react@18.3.23': resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} @@ -7019,7 +7019,7 @@ packages: resolution: {integrity: sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q==} engines: {node: '>=18'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 peerDependenciesMeta: '@types/react': optional: true @@ -7028,7 +7028,7 @@ packages: resolution: {integrity: sha512-QDt6FgJxgmSxAelcOvOHUvFxbIUjVpCH5bx+Slvc5m7IEcpGt3dYwbz/L+oRnqEGeRvwy1tineKK4ect3nW1vQ==} engines: {node: '>=20'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: '>=19.0.0' react-devtools-core: ^6.1.2 peerDependenciesMeta: @@ -8973,7 +8973,7 @@ packages: react-markdown@9.1.0: resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: '>=18' react-reconciler@0.33.0: @@ -8996,7 +8996,7 @@ packages: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': @@ -9006,7 +9006,7 @@ packages: resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -9016,7 +9016,7 @@ packages: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -10214,7 +10214,7 @@ packages: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -10251,7 +10251,7 @@ packages: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -10481,7 +10481,7 @@ packages: vscrui@0.2.2: resolution: {integrity: sha512-buw2OipqUl7GCBq1mxcAjUwoUsslGzVhdaxDPmEx27xzc3QAJJZHtT30QbakgZVJ1Jb3E6kcsguUIFEGxrgkyQ==} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 react: ^17 || ^18 || ^19 w3c-xmlserializer@5.0.0: @@ -10800,7 +10800,7 @@ packages: resolution: {integrity: sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==} engines: {node: '>=12.20.0'} peerDependencies: - '@types/react': ^18.3.23 + '@types/react': 18.3.23 immer: '>=9.0.6' react: '>=18.0.0' use-sync-external-store: '>=1.2.0' diff --git a/webview-ui/package.json b/webview-ui/package.json index 676b19082..d4b9f0698 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -93,8 +93,8 @@ "@types/jest": "^29.0.0", "@types/katex": "^0.16.7", "@types/node": "^20.19.25", - "@types/react": "^18.3.23", - "@types/react-dom": "^18.3.5", + "@types/react": "18.3.23", + "@types/react-dom": "18.3.7", "@types/shell-quote": "^1.7.5", "@types/stacktrace-js": "^2.0.3", "@types/vscode-webview": "^1.57.5", From 641b4db9166b25831c7b2fb04f90a5bb9c639788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 18:46:08 +0200 Subject: [PATCH 03/16] chore(deps): configure knip and remove dead dependencies Port of Zoo-Code PR #225. Upgrades the knip config (schema v5, per-workspace ignoreDependencies, rules) and removes dependencies verified unused in our fork: @openrouter/ai-sdk-provider, @vscode/test-electron, npm-run-all2, tsup, tsx, and zod-to-ts from src; knuth-shuffle-seeded and identity-obj-proxy from webview-ui (plus the orphaned types.d.ts). tsx moves to apps/cli, which actually uses it (previously hoisted from src). Each removal was checked against our diverged fork, not assumed from upstream. knip.json drops `vscode` from the webview ignore list (we don't declare it) and ignores our self-hosted-cloudapi sub-project. Co-authored-by: Elliott de Launay --- ai_plans/2026-06-27_zoo-225-knip-dead-code.md | 77 +++++++++ apps/cli/package.json | 1 + knip.json | 87 ++++++++-- package.json | 2 +- pnpm-lock.yaml | 154 +----------------- src/package.json | 8 +- webview-ui/package.json | 2 - webview-ui/src/types.d.ts | 5 - 8 files changed, 154 insertions(+), 182 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-225-knip-dead-code.md delete mode 100644 webview-ui/src/types.d.ts diff --git a/ai_plans/2026-06-27_zoo-225-knip-dead-code.md b/ai_plans/2026-06-27_zoo-225-knip-dead-code.md new file mode 100644 index 000000000..c131bcef5 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-225-knip-dead-code.md @@ -0,0 +1,77 @@ +# Port Zoo PR #225 — configure knip + remove dead dependencies + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #225, commit `8ea077942`, merged 2026-06-08. +- Original author: Elliott de Launay (roomote[bot] / renovate[bot] dropped as bots). +- Commit trailer: + ``` + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why + +Upgrade the knip config (schema v5, per-workspace `ignoreDependencies`, rules, +`ignoreExportsUsedInFile`) and remove dependencies knip flagged as dead. + +**Divergence guard (critical):** "dead in Zoo" ≠ "dead in our fork." Every removed +dependency was independently verified UNUSED in _our_ code before removal (grep +for imports + a scripts/config audit). The knip `ignoreDependencies` lists were +checked against our actual dep set (src 16/16, webview 10/11 — we lack `vscode`, +so it was dropped from our webview list). + +## §2 Edits (exact, verified) + +### Dead deps removed from `src/package.json` (all verified zero-usage here) + +- `@openrouter/ai-sdk-provider` — zero refs repo-wide; our `openrouter.ts` uses + `@anthropic-ai/sdk`, not this package. +- `@vscode/test-electron` — `apps/vscode-e2e` declares its own; src's is unused. +- `npm-run-all2` — no script uses `run-p`/`run-s`/`npm-run-all`. +- `tsup` — src builds via esbuild; only `apps/cli` uses tsup (has its own). +- `tsx` — only `apps/cli` scripts use it (was hoisted from src) → moved to cli. +- `zod-to-ts` — zero imports. + +### `apps/cli/package.json` + +- Add `"tsx": "^4.19.3"` (cli scripts `dev`/`test:integration` use it; previously + resolved via hoist from src). + +### `webview-ui/package.json` + delete `webview-ui/src/types.d.ts` + +- Remove `knuth-shuffle-seeded` (only referenced by the now-deleted `types.d.ts` + module declaration; no real import) and `identity-obj-proxy` (no config uses it). +- Delete `webview-ui/src/types.d.ts` (contained only the knuth-shuffle declaration). + +### `package.json` + +- `"knip": "knip --include files"` → `"knip": "knip"`. + +### `knip.json` (adapted to our fork) + +- Port Zoo's new structure verbatim where deps match; **dropped** `vscode` from the + webview `ignoreDependencies` (we don't declare it); **added** `self-hosted-cloudapi/**` + to top-level `ignore` (our fork has that extra Docker/Python sub-project Zoo lacks). + +## §3 Scope cuts (YAGNI / divergence) + +- Did NOT add `@vitest/coverage-v8` to src or cli (neither runs coverage here; Zoo + already had it — we don't, and don't need it). +- Did NOT re-order posthog-js/lucide-react in webview (cosmetic). +- Did NOT add `packages/evals` to knip workspaces (faithful to upstream's new config). +- Did NOT re-add Roo branding / TTS / router / cloud. + +## §4 Verify (binary acceptance) + +- `pnpm install` succeeds after removals. ✓ +- `pnpm --filter tumble-code check-types` / `@roo-code/vscode-webview` / + `@roo-code/cli` → all pass (tsc resolves every import; a removed-but-used dep + would error "cannot find module"). ✓ +- `tsx` bin resolves for `apps/cli`. ✓ +- Note: `pnpm knip` cannot complete on this machine — it crashes traversing the + root-owned (uid 70, mode 700) `self-hosted-cloudapi/.vol/postgres` Docker volume + with EACCES. Confirmed this happens with the OLD config too → pre-existing local + environment issue, independent of this change. The knip config itself parses + (knip reaches file traversal past config validation). + +## §5 Co-author — see §0. diff --git a/apps/cli/package.json b/apps/cli/package.json index 17b05a6d2..74be0a5bc 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -45,6 +45,7 @@ "ink-testing-library": "^4.0.0", "rimraf": "^6.0.1", "tsup": "^8.4.0", + "tsx": "^4.19.3", "vitest": "^3.2.3" } } diff --git a/knip.json b/knip.json index a3382bd2c..7ffb7ef5c 100644 --- a/knip.json +++ b/knip.json @@ -1,27 +1,82 @@ { - "$schema": "https://unpkg.com/knip@latest/schema.json", - "ignore": [ - "**/__tests__/**", - "apps/vscode-e2e/**", - "src/extension/api.ts", - "src/activate/**", - "src/workers/countTokens.ts", - "src/extension.ts", - "scripts/**", - "apps/cli/scripts/**", - "@vscode/ripgrep" - ], + "$schema": "https://unpkg.com/knip@5/schema.json", + "ignore": ["**/__tests__/**", "apps/vscode-e2e/**", "scripts/**", "apps/cli/scripts/**", "self-hosted-cloudapi/**"], + "ignoreDependencies": ["lint-staged", "only-allow", "ovsx"], + "ignoreExportsUsedInFile": true, "workspaces": { "src": { - "entry": ["extension.ts"], - "project": ["**/*.ts"] + "entry": ["extension.ts", "extension/api.ts", "workers/countTokens.ts"], + "project": ["**/*.ts", "!**/__tests__/**"], + "ignoreDependencies": [ + "@ai-sdk/amazon-bedrock", + "@ai-sdk/baseten", + "@ai-sdk/deepseek", + "@ai-sdk/fireworks", + "@ai-sdk/google", + "@ai-sdk/google-vertex", + "@ai-sdk/mistral", + "@ai-sdk/xai", + "@roo-code/config-typescript", + "@types/mocha", + "@types/node-cache", + "@types/vscode", + "@vscode/codicons", + "esbuild-wasm", + "sambanova-ai-provider", + "tree-sitter-wasms", + "vscode-material-icons", + "zhipu-ai-provider" + ] }, "webview-ui": { "entry": ["src/index.tsx"], - "project": ["src/**/*.{ts,tsx}", "../src/shared/*.ts"] + "project": ["src/**/*.{ts,tsx}", "../src/shared/*.ts"], + "ignoreDependencies": [ + "@roo-code/config-typescript", + "@types/katex", + "@types/stacktrace-js", + "babel-plugin-react-compiler", + "katex", + "react-compiler-runtime", + "rehype-highlight", + "source-map", + "tailwindcss", + "tailwindcss-animate" + ] }, - "packages/{build,cloud,evals,ipc,telemetry,types}": { + "apps/cli": { + "project": ["src/**/*.ts", "src/**/*.tsx"], + "ignoreDependencies": ["@vscode/ripgrep", "cross-spawn"] + }, + "packages/config-typescript": { + "ignoreUnresolved": ["vitest/globals"] + }, + "packages/core": { + "project": ["src/**/*.ts"], + "ignoreDependencies": ["esbuild"] + }, + "packages/vscode-shim": { + "project": ["src/**/*.ts"] + }, + "packages/cloud": { + "project": ["src/**/*.ts"], + "ignoreDependencies": ["@types/vscode", "ioredis", "p-wait-for"] + }, + "packages/telemetry": { + "project": ["src/**/*.ts"], + "ignoreDependencies": ["@types/vscode"] + }, + "packages/{build,ipc,types}": { "project": ["src/**/*.ts"] } + }, + "rules": { + "classMembers": "off", + "duplicates": "warn", + "enumMembers": "warn", + "exports": "warn", + "nsExports": "warn", + "types": "warn", + "nsTypes": "warn" } } diff --git a/package.json b/package.json index ae97fc0ba..6da4f250e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "install:vsix:nightly": "pnpm install --frozen-lockfile && pnpm clean && pnpm vsix:nightly && node scripts/install-vsix.js --nightly", "code-server:install": "node scripts/code-server.js", "changeset:version": "cp CHANGELOG.md src/CHANGELOG.md && changeset version && cp -vf src/CHANGELOG.md .", - "knip": "knip --include files", + "knip": "knip", "evals": "dotenvx run -f packages/evals/.env.development packages/evals/.env.local -- docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0", "npm:publish:types": "pnpm --filter @roo-code/types npm:publish" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a69a54d45..80d2afd12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,6 +151,9 @@ importers: tsup: specifier: ^8.4.0 version: 8.5.0(jiti@2.4.2)(postcss@8.5.15)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.9.0) + tsx: + specifier: ^4.19.3 + version: 4.19.4 vitest: specifier: ^3.2.3 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.19.4)(yaml@2.9.0) @@ -906,9 +909,6 @@ importers: '@ai-sdk/openai-compatible': specifier: ^2.0.28 version: 2.0.28(zod@3.25.76) - '@openrouter/ai-sdk-provider': - specifier: ^2.1.1 - version: 2.1.1(ai@6.0.77(zod@3.25.76))(zod@3.25.76) '@roo-code/build': specifier: workspace:^ version: link:../packages/build @@ -975,9 +975,6 @@ importers: '@types/vscode': specifier: ^1.84.0 version: 1.100.0 - '@vscode/test-electron': - specifier: ^2.5.2 - version: 2.5.2 '@vscode/vsce': specifier: 3.3.2 version: 3.3.2 @@ -999,27 +996,15 @@ importers: nock: specifier: ^14.0.4 version: 14.0.10 - npm-run-all2: - specifier: ^8.0.1 - version: 8.0.3 ovsx: specifier: 0.10.4 version: 0.10.4 rimraf: specifier: ^6.0.1 version: 6.0.1 - tsup: - specifier: ^8.4.0 - version: 8.5.0(jiti@2.4.2)(postcss@8.5.15)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.9.0) - tsx: - specifier: ^4.19.3 - version: 4.19.4 vitest: specifier: ^3.2.3 version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.19.4)(yaml@2.9.0) - zod-to-ts: - specifier: ^1.2.0 - version: 1.2.0(typescript@5.8.3)(zod@3.25.76) webview-ui: dependencies: @@ -1122,9 +1107,6 @@ importers: katex: specifier: ^0.16.11 version: 0.16.22 - knuth-shuffle-seeded: - specifier: ^1.0.6 - version: 1.0.6 lru-cache: specifier: ^11.1.0 version: 11.1.0 @@ -1276,9 +1258,6 @@ importers: babel-plugin-react-compiler: specifier: ^1.0.0 version: 1.0.0 - identity-obj-proxy: - specifier: ^3.0.0 - version: 3.0.0 jsdom: specifier: ^26.0.0 version: 26.1.0 @@ -2789,13 +2768,6 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@openrouter/ai-sdk-provider@2.1.1': - resolution: {integrity: sha512-UypPbVnSExxmG/4Zg0usRiit3auvQVrjUXSyEhm0sZ9GQnW/d8p/bKgCk2neh1W5YyRSo7PNQvCrAEBHZnqQkQ==} - engines: {node: '>=18'} - peerDependencies: - ai: ^6.0.0 - zod: 3.25.76 - '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -4863,10 +4835,6 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -6790,9 +6758,6 @@ packages: hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} - harmony-reflect@1.6.2: - resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -6968,10 +6933,6 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} - identity-obj-proxy@3.0.0: - resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} - engines: {node: '>=4'} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -7321,10 +7282,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} - isexe@3.1.5: resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} engines: {node: '>=18'} @@ -7435,10 +7392,6 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@4.0.0: - resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} - engines: {node: ^18.17.0 || >=20.5.0} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -7531,9 +7484,6 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4' - knuth-shuffle-seeded@1.0.6: - resolution: {integrity: sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==} - layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -8042,10 +7992,6 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - memorystream@0.3.1: - resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} - engines: {node: '>= 0.10.0'} - merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -8195,10 +8141,6 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.0.1: - resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} - engines: {node: 20 || >=22} - minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} @@ -8383,15 +8325,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-normalize-package-bin@4.0.0: - resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} - engines: {node: ^18.17.0 || >=20.5.0} - - npm-run-all2@8.0.3: - resolution: {integrity: sha512-0mAycidMUMThrLt8AT3LGtOMgfLaMg6/4oUKHTKMU0jDSIsdKBsKp98H8zBFcJylQC4CtOB140UUFbOlFyE9gA==} - engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'} - hasBin: true - npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -8690,11 +8623,6 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -9054,10 +8982,6 @@ packages: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} - read-package-json-fast@4.0.0: - resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} - engines: {node: ^18.17.0 || >=20.5.0} - read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -9316,9 +9240,6 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} - seed-random@2.2.0: - resolution: {integrity: sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==} - semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -10574,11 +10495,6 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true - which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -10781,12 +10697,6 @@ packages: peerDependencies: zod: 3.25.76 - zod-to-ts@1.2.0: - resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} - peerDependencies: - typescript: ^4.9.4 || ^5.0.2 - zod: 3.25.76 - zod-validation-error@3.4.1: resolution: {integrity: sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw==} engines: {node: '>=18.0.0'} @@ -12738,11 +12648,6 @@ snapshots: '@open-draft/until@2.1.0': {} - '@openrouter/ai-sdk-provider@2.1.1(ai@6.0.77(zod@3.25.76))(zod@3.25.76)': - dependencies: - ai: 6.0.77(zod@3.25.76) - zod: 3.25.76 - '@opentelemetry/api@1.9.0': {} '@oxc-project/types@0.133.0': {} @@ -14953,8 +14858,6 @@ snapshots: ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} - ansi-styles@6.2.3: {} ansi-to-html@0.7.2: @@ -17107,8 +17010,6 @@ snapshots: hachure-fill@0.5.2: {} - harmony-reflect@1.6.2: {} - has-bigints@1.1.0: {} has-flag@3.0.0: {} @@ -17343,10 +17244,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - identity-obj-proxy@3.0.0: - dependencies: - harmony-reflect: 1.6.2 - ieee754@1.2.1: {} ignore@5.3.2: {} @@ -17664,8 +17561,6 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.1: {} - isexe@3.1.5: {} isobject@3.0.1: {} @@ -17800,8 +17695,6 @@ snapshots: json-buffer@3.0.1: {} - json-parse-even-better-errors@4.0.0: {} - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -17919,10 +17812,6 @@ snapshots: zod: 3.25.76 zod-validation-error: 3.4.1(zod@3.25.76) - knuth-shuffle-seeded@1.0.6: - dependencies: - seed-random: 2.2.0 - layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -18491,8 +18380,6 @@ snapshots: media-typer@1.1.0: {} - memorystream@0.3.1: {} - merge-descriptors@2.0.0: {} merge-stream@2.0.0: {} @@ -18761,10 +18648,6 @@ snapshots: min-indent@1.0.1: {} - minimatch@10.0.1: - dependencies: - brace-expansion: 2.0.2 - minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -18957,19 +18840,6 @@ snapshots: normalize-path@3.0.0: {} - npm-normalize-package-bin@4.0.0: {} - - npm-run-all2@8.0.3: - dependencies: - ansi-styles: 6.2.1 - cross-spawn: 7.0.6 - memorystream: 0.3.1 - minimatch: 10.0.1 - pidtree: 0.6.0 - read-package-json-fast: 4.0.0 - shell-quote: 1.8.3 - which: 5.0.0 - npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -19305,8 +19175,6 @@ snapshots: picomatch@4.0.4: {} - pidtree@0.6.0: {} - pify@4.0.1: {} pirates@4.0.7: {} @@ -19709,11 +19577,6 @@ snapshots: react@19.2.3: {} - read-package-json-fast@4.0.0: - dependencies: - json-parse-even-better-errors: 4.0.0 - npm-normalize-package-bin: 4.0.0 - read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -20094,8 +19957,6 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 - seed-random@2.2.0: {} - semver-compare@1.0.0: {} semver@5.7.2: {} @@ -21710,10 +21571,6 @@ snapshots: dependencies: isexe: 3.1.5 - which@5.0.0: - dependencies: - isexe: 3.1.1 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -21883,11 +21740,6 @@ snapshots: dependencies: zod: 3.25.76 - zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.76): - dependencies: - typescript: 5.8.3 - zod: 3.25.76 - zod-validation-error@3.4.1(zod@3.25.76): dependencies: zod: 3.25.76 diff --git a/src/package.json b/src/package.json index 5967268f1..a55bc7545 100644 --- a/src/package.json +++ b/src/package.json @@ -567,7 +567,6 @@ }, "devDependencies": { "@ai-sdk/openai-compatible": "^2.0.28", - "@openrouter/ai-sdk-provider": "^2.1.1", "@roo-code/build": "workspace:^", "@roo-code/config-eslint": "workspace:^", "@roo-code/config-typescript": "workspace:^", @@ -590,7 +589,6 @@ "@types/tmp": "^0.2.6", "@types/turndown": "^5.0.5", "@types/vscode": "^1.84.0", - "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "3.3.2", "ai": "^6.0.75", "esbuild-wasm": "^0.25.0", @@ -598,12 +596,8 @@ "glob": "^11.1.0", "mkdirp": "^3.0.1", "nock": "^14.0.4", - "npm-run-all2": "^8.0.1", "ovsx": "0.10.4", "rimraf": "^6.0.1", - "tsup": "^8.4.0", - "tsx": "^4.19.3", - "vitest": "^3.2.3", - "zod-to-ts": "^1.2.0" + "vitest": "^3.2.3" } } diff --git a/webview-ui/package.json b/webview-ui/package.json index d4b9f0698..ecec8759c 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -48,7 +48,6 @@ "hast-util-to-jsx-runtime": "^2.3.6", "i18next": "^25.0.0", "katex": "^0.16.11", - "knuth-shuffle-seeded": "^1.0.6", "lru-cache": "^11.1.0", "lucide-react": "^0.518.0", "mermaid": "^11.15.0", @@ -101,7 +100,6 @@ "@vitejs/plugin-react": "^5.2.0", "@vitest/ui": "^3.2.3", "babel-plugin-react-compiler": "^1.0.0", - "identity-obj-proxy": "^3.0.0", "jsdom": "^26.0.0", "vite": "^8.0.13", "vitest": "^3.2.3" diff --git a/webview-ui/src/types.d.ts b/webview-ui/src/types.d.ts deleted file mode 100644 index ff5ef4784..000000000 --- a/webview-ui/src/types.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Type declarations for third-party modules - -declare module "knuth-shuffle-seeded" { - export default function knuthShuffle(array: T[], seed: any): T[] -} From daf5676d0b8a01a75383241f334a41b5b892be1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 19:11:47 +0200 Subject: [PATCH 04/16] chore(deps): raise vite floor to ^8.0.16 [security] Port of Zoo-Code PR #642 (vite 8.0.16 security update). Our lockfile already resolves vite@8.0.16 via the existing caret range, so this raises the declared webview-ui floor from ^8.0.13 to ^8.0.16 to prevent a future regression below the patched version. Keeps our caret convention; does not import Zoo's exact pin or its root pnpm.overrides.vite entry (our overrides intentionally differ). --- ...2026-06-27_zoo-642-vite-8-0-16-security.md | 37 +++++++++++++++++++ pnpm-lock.yaml | 18 ++++----- webview-ui/package.json | 2 +- 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-642-vite-8-0-16-security.md diff --git a/ai_plans/2026-06-27_zoo-642-vite-8-0-16-security.md b/ai_plans/2026-06-27_zoo-642-vite-8-0-16-security.md new file mode 100644 index 000000000..2dba01133 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-642-vite-8-0-16-security.md @@ -0,0 +1,37 @@ +# Port Zoo PR #642 — vite 8.0.16 security floor + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #642, commit `4bd064553`, merged 2026-06-18. +- Author: renovate[bot] only (bot — no human co-author to credit). + +## §1 What & why + +Security update of `vite` to `8.0.16`. Zoo pins exact versions (their fork ran +#423's "pin dependencies"), and also carries a `pnpm.overrides.vite` entry — **we +have neither**: our webview declares `vite: "^8.0.13"` (caret) and our root +`pnpm.overrides` has no vite/rollup entry. + +Our lockfile **already resolves `vite@8.0.16`** (the caret allows it), so the +vulnerable versions are already not installed. The meaningful, convention- +consistent port for our fork is to raise the declared floor so the patched +version can't silently regress on a future resolution. + +## §2 Edits + +- `webview-ui/package.json`: `"vite": "^8.0.13"` → `"^8.0.16"` (keep our caret + style; do NOT pin exact or add a root override — that's Zoo's divergence). +- Refresh `pnpm-lock.yaml` (expected near no-op; 8.0.16 already resolved). + +## §3 Scope cuts (divergence) + +- Do NOT add `pnpm.overrides.vite` / `rollup` / `esbuild: 0.28.1` from Zoo's root + package.json — our overrides intentionally differ. +- Do NOT pin exact `8.0.16` (our fork uses carets for vite). + +## §4 Verify (binary acceptance) + +- `grep '"vite"' webview-ui/package.json` shows `^8.0.16`. +- `pnpm install --lockfile-only` succeeds; lockfile resolves `vite@8.0.16`, no + `vite@8.0.1[0-5]` for the webview workspace. +- `pnpm --filter @roo-code/vscode-webview check-types` → passes. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80d2afd12..6a3f51be5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1262,7 +1262,7 @@ importers: specifier: ^26.0.0 version: 26.1.0 vite: - specifier: ^8.0.13 + specifier: ^8.0.16 version: 8.0.16(@types/node@20.19.41)(esbuild@0.25.9)(jiti@2.4.2)(tsx@4.19.4)(yaml@2.9.0) vitest: specifier: ^3.2.3 @@ -21213,9 +21213,9 @@ snapshots: esbuild: 0.25.9 fdir: 6.4.6(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.4 + postcss: 8.5.15 rollup: 4.40.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 20.17.50 fsevents: 2.3.3 @@ -21229,9 +21229,9 @@ snapshots: esbuild: 0.25.9 fdir: 6.4.6(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.4 + postcss: 8.5.15 rollup: 4.40.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 20.17.57 fsevents: 2.3.3 @@ -21245,9 +21245,9 @@ snapshots: esbuild: 0.25.9 fdir: 6.4.6(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.4 + postcss: 8.5.15 rollup: 4.40.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 20.19.41 fsevents: 2.3.3 @@ -21261,9 +21261,9 @@ snapshots: esbuild: 0.25.9 fdir: 6.4.6(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.4 + postcss: 8.5.15 rollup: 4.40.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.2.1 fsevents: 2.3.3 diff --git a/webview-ui/package.json b/webview-ui/package.json index ecec8759c..d8f600b54 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -101,7 +101,7 @@ "@vitest/ui": "^3.2.3", "babel-plugin-react-compiler": "^1.0.0", "jsdom": "^26.0.0", - "vite": "^8.0.13", + "vite": "^8.0.16", "vitest": "^3.2.3" } } From ec04b04734ada0e84752684c55f641bdeed43613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 19:42:41 +0200 Subject: [PATCH 05/16] fix: apply apiRequestTimeout consistently across providers Port of Zoo-Code PR #567. Wire the user-configured apiRequestTimeout into every provider that builds an OpenAI or Anthropic SDK client, via a single timeoutMs field on BaseProvider. Previously only openai/lm-studio and BaseOpenAiCompatible providers honored it. timeout-config now returns a number (never undefined), clamps to 1-3600s (out of range falls back to the 600s default), and rounds the ms so the Anthropic SDK's positive-integer validation never sees a float. The setting schema becomes an integer with minimum 1, and the localized descriptions drop the obsolete "0 = no timeout" wording and list the providers that ignore the value. Skips the Zoo-only zoo-gateway/opencode-go providers (absent in this fork). Co-authored-by: Oh Daewoong Co-authored-by: Naved Merchant --- ...-27_zoo-567-apirequesttimeout-providers.md | 60 +++++++++++++++++++ .../__tests__/anthropic-vertex.spec.ts | 5 ++ src/api/providers/__tests__/lite-llm.spec.ts | 4 ++ .../providers/__tests__/openrouter.spec.ts | 5 ++ src/api/providers/__tests__/requesty.spec.ts | 6 ++ .../__tests__/vercel-ai-gateway.spec.ts | 5 ++ .../__tests__/vertex-credentials.spec.ts | 4 ++ src/api/providers/__tests__/vertex.spec.ts | 4 ++ src/api/providers/__tests__/vscode-lm.spec.ts | 4 ++ src/api/providers/anthropic-vertex.ts | 4 +- src/api/providers/anthropic.ts | 1 + .../base-openai-compatible-provider.ts | 3 +- src/api/providers/base-provider.ts | 3 + src/api/providers/minimax.ts | 1 + src/api/providers/openai-codex.ts | 1 + src/api/providers/openai-native.ts | 1 + src/api/providers/openai.ts | 3 +- src/api/providers/openrouter.ts | 2 +- src/api/providers/qwen-code.ts | 1 + src/api/providers/requesty.ts | 1 + src/api/providers/router-provider.ts | 1 + src/api/providers/unbound.ts | 1 + .../utils/__tests__/timeout-config.spec.ts | 38 +++++++++--- src/api/providers/utils/timeout-config.ts | 34 +++++------ src/api/providers/xai.ts | 1 + src/package.json | 4 +- src/package.nls.ca.json | 2 +- src/package.nls.de.json | 2 +- src/package.nls.es.json | 2 +- src/package.nls.fr.json | 2 +- src/package.nls.hi.json | 2 +- src/package.nls.id.json | 2 +- src/package.nls.it.json | 2 +- src/package.nls.ja.json | 2 +- src/package.nls.json | 2 +- src/package.nls.ko.json | 2 +- src/package.nls.nl.json | 2 +- src/package.nls.pl.json | 2 +- src/package.nls.pt-BR.json | 2 +- src/package.nls.ru.json | 2 +- src/package.nls.tr.json | 2 +- src/package.nls.vi.json | 2 +- src/package.nls.zh-CN.json | 2 +- src/package.nls.zh-TW.json | 2 +- 44 files changed, 183 insertions(+), 50 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-567-apirequesttimeout-providers.md diff --git a/ai_plans/2026-06-27_zoo-567-apirequesttimeout-providers.md b/ai_plans/2026-06-27_zoo-567-apirequesttimeout-providers.md new file mode 100644 index 000000000..84fa6df9f --- /dev/null +++ b/ai_plans/2026-06-27_zoo-567-apirequesttimeout-providers.md @@ -0,0 +1,60 @@ +# Port Zoo PR #567 — apply apiRequestTimeout consistently across providers + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #567, commit `b747d56ba`, merged 2026-06-19. +- Authors: dw (Oh Daewoong), Naved Merchant. +- Commit trailers: + ``` + Co-authored-by: Oh Daewoong + Co-authored-by: Naved Merchant + ``` + +## §1 What & why + +Previously only `openai`/`lm-studio` and `BaseOpenAiCompatibleProvider` honored the +user's `apiRequestTimeout`. This wires it into **every** provider that builds an +OpenAI or Anthropic SDK client, via a single `timeoutMs` field on `BaseProvider`. + +## §2 Edits + +- `base-provider.ts`: add `protected readonly timeoutMs: number = getApiRequestTimeout()`. +- `timeout-config.ts`: refactor — `getApiRequestTimeout()` now returns `number` + (never `undefined`), clamps to the valid 1–3600s range (out-of-range/NaN/non-number + → 600s default), and `Math.round()`s the ms (avoids Anthropic SDK float-validation throw). +- `base-openai-compatible-provider.ts` + `openai.ts`: use `this.timeoutMs` instead of + calling `getApiRequestTimeout()` directly; drop the now-unused import. +- Add `timeout: this.timeoutMs` to client construction in: `anthropic`, `minimax`, + `anthropic-vertex` (3 sites), `openai-native`, `openai-codex`, `openrouter`, + `router-provider` (covers `lite-llm`/`vercel-ai-gateway` via inheritance), `requesty`, + `unbound`, `xai`, `qwen-code`. +- `src/package.json`: `tumble-code.apiRequestTimeout` → `type: integer`, `minimum: 1`. +- `package.nls*.json` (18 locales): updated description (drops the now-invalid + "0 = no timeout", lists unsupported providers). + +## §3 Divergence handled + +- **Skipped Zoo-only providers** `zoo-gateway` and `opencode-go` — they don't exist here. +- Kept our `roo-code`-branded request headers (`originator`, X-Unbound-Metadata) — out of + scope for this PR; only `timeout` was added. +- No changeset (our fork doesn't ship Zoo's changeset flow). + +## §4 Tests + +- Provider tests that mock `vscode` as `{}` (or via a global mock lacking + `getConfiguration`) threw once `BaseProvider`'s field initializer started calling + `getApiRequestTimeout()` at construction. Fixed by mocking `../utils/timeout-config` + to return a constant `600_000` in the 8 affected specs (anthropic-vertex, lite-llm, + openrouter, requesty, vercel-ai-gateway, vertex, vertex-credentials, vscode-lm). +- Added `timeout: 600_000` to the exact-args client-construction assertions in + anthropic-vertex, openrouter, requesty (×2), vercel-ai-gateway. +- `timeout-config.spec.ts`: replaced the obsolete "0/negative → undefined" tests with + out-of-range → default, plus min/max boundary and ms-rounding cases. + +## §5 Verify (binary acceptance) — all ✓ + +- `pnpm --filter tumble-code check-types` passes. +- `npx vitest run api/providers/__tests__ api/providers/utils/__tests__/timeout-config` + → 46 files, 904 pass, 1 skipped. + +## §6 Co-authors — see §0. diff --git a/src/api/providers/__tests__/anthropic-vertex.spec.ts b/src/api/providers/__tests__/anthropic-vertex.spec.ts index 2b64decdb..690b4df74 100644 --- a/src/api/providers/__tests__/anthropic-vertex.spec.ts +++ b/src/api/providers/__tests__/anthropic-vertex.spec.ts @@ -1,5 +1,9 @@ // npx vitest run src/api/providers/__tests__/anthropic-vertex.spec.ts +vitest.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vitest.fn().mockReturnValue(600_000), +})) + import { Anthropic } from "@anthropic-ai/sdk" import { AnthropicVertex } from "@anthropic-ai/vertex-sdk" @@ -64,6 +68,7 @@ describe("VertexHandler", () => { expect(AnthropicVertex).toHaveBeenCalledWith({ projectId: "test-project", region: "us-central1", + timeout: 600_000, }) }) }) diff --git a/src/api/providers/__tests__/lite-llm.spec.ts b/src/api/providers/__tests__/lite-llm.spec.ts index df0e8b152..b1a30ac39 100644 --- a/src/api/providers/__tests__/lite-llm.spec.ts +++ b/src/api/providers/__tests__/lite-llm.spec.ts @@ -1,3 +1,7 @@ +vi.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vi.fn().mockReturnValue(600_000), +})) + import OpenAI from "openai" import { Anthropic } from "@anthropic-ai/sdk" diff --git a/src/api/providers/__tests__/openrouter.spec.ts b/src/api/providers/__tests__/openrouter.spec.ts index e03abea63..a22f769a7 100644 --- a/src/api/providers/__tests__/openrouter.spec.ts +++ b/src/api/providers/__tests__/openrouter.spec.ts @@ -1,5 +1,9 @@ // pnpm --filter roo-cline test api/providers/__tests__/openrouter.spec.ts +vitest.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vitest.fn().mockReturnValue(600_000), +})) + vitest.mock("vscode", () => ({})) import { Anthropic } from "@anthropic-ai/sdk" @@ -104,6 +108,7 @@ describe("OpenRouterHandler", () => { "X-Title": "Roo Code", "User-Agent": `RooCode/${Package.version}`, }, + timeout: 600_000, }) }) diff --git a/src/api/providers/__tests__/requesty.spec.ts b/src/api/providers/__tests__/requesty.spec.ts index 88804553b..2c53c8908 100644 --- a/src/api/providers/__tests__/requesty.spec.ts +++ b/src/api/providers/__tests__/requesty.spec.ts @@ -1,5 +1,9 @@ // npx vitest run api/providers/__tests__/requesty.spec.ts +vitest.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vitest.fn().mockReturnValue(600_000), +})) + import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" @@ -76,6 +80,7 @@ describe("RequestyHandler", () => { "X-Title": "Roo Code", "User-Agent": `RooCode/${Package.version}`, }, + timeout: 600_000, }) }) @@ -91,6 +96,7 @@ describe("RequestyHandler", () => { "X-Title": "Roo Code", "User-Agent": `RooCode/${Package.version}`, }, + timeout: 600_000, }) }) diff --git a/src/api/providers/__tests__/vercel-ai-gateway.spec.ts b/src/api/providers/__tests__/vercel-ai-gateway.spec.ts index 2cb6bb8e2..fb4f1c987 100644 --- a/src/api/providers/__tests__/vercel-ai-gateway.spec.ts +++ b/src/api/providers/__tests__/vercel-ai-gateway.spec.ts @@ -1,6 +1,10 @@ // npx vitest run src/api/providers/__tests__/vercel-ai-gateway.spec.ts // Mock vscode first to avoid import errors +vitest.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vitest.fn().mockReturnValue(600_000), +})) + vitest.mock("vscode", () => ({})) import { Anthropic } from "@anthropic-ai/sdk" @@ -112,6 +116,7 @@ describe("VercelAiGatewayHandler", () => { "X-Title": "Roo Code", "User-Agent": expect.stringContaining("RooCode/"), }), + timeout: 600_000, }) }) diff --git a/src/api/providers/__tests__/vertex-credentials.spec.ts b/src/api/providers/__tests__/vertex-credentials.spec.ts index 98871c825..e6c960ba5 100644 --- a/src/api/providers/__tests__/vertex-credentials.spec.ts +++ b/src/api/providers/__tests__/vertex-credentials.spec.ts @@ -2,6 +2,10 @@ // Mock vscode first to avoid import errors when the provider stack pulls // transitive vscode-dependent modules during construction. +vitest.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vitest.fn().mockReturnValue(600_000), +})) + vitest.mock("vscode", () => ({})) vitest.mock("@roo-code/telemetry", () => ({ diff --git a/src/api/providers/__tests__/vertex.spec.ts b/src/api/providers/__tests__/vertex.spec.ts index ddf22bc34..62835bba2 100644 --- a/src/api/providers/__tests__/vertex.spec.ts +++ b/src/api/providers/__tests__/vertex.spec.ts @@ -1,6 +1,10 @@ // npx vitest run src/api/providers/__tests__/vertex.spec.ts // Mock vscode first to avoid import errors +vitest.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vitest.fn().mockReturnValue(600_000), +})) + vitest.mock("vscode", () => ({})) const mockCaptureException = vitest.fn() diff --git a/src/api/providers/__tests__/vscode-lm.spec.ts b/src/api/providers/__tests__/vscode-lm.spec.ts index 305305d22..120dc920e 100644 --- a/src/api/providers/__tests__/vscode-lm.spec.ts +++ b/src/api/providers/__tests__/vscode-lm.spec.ts @@ -1,3 +1,7 @@ +vi.mock("../utils/timeout-config", () => ({ + getApiRequestTimeout: vi.fn().mockReturnValue(600_000), +})) + import type { Mock } from "vitest" // Mocks must come first, before imports diff --git a/src/api/providers/anthropic-vertex.ts b/src/api/providers/anthropic-vertex.ts index 306b26191..3a1db8e29 100644 --- a/src/api/providers/anthropic-vertex.ts +++ b/src/api/providers/anthropic-vertex.ts @@ -51,6 +51,7 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple scopes: ["https://www.googleapis.com/auth/cloud-platform"], credentials: parsedVertexCredentials, }), + timeout: this.timeoutMs, }) } else if (this.options.vertexKeyFile) { this.client = new AnthropicVertex({ @@ -60,9 +61,10 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple scopes: ["https://www.googleapis.com/auth/cloud-platform"], keyFile: this.options.vertexKeyFile, }), + timeout: this.timeoutMs, }) } else { - this.client = new AnthropicVertex({ projectId, region }) + this.client = new AnthropicVertex({ projectId, region, timeout: this.timeoutMs }) } } diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index 03d14c168..01d624d2d 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -44,6 +44,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa this.client = new Anthropic({ baseURL: this.options.anthropicBaseUrl || undefined, [apiKeyFieldName]: this.options.apiKey, + timeout: this.timeoutMs, }) } diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index 9ae605f50..28c812660 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -13,7 +13,6 @@ import { DEFAULT_HEADERS } from "./constants" import { BaseProvider } from "./base-provider" import { handleOpenAIError } from "./utils/openai-error-handler" import { calculateApiCostOpenAI } from "../../shared/cost" -import { getApiRequestTimeout } from "./utils/timeout-config" import { extractReasoningFromDelta } from "./utils/extract-reasoning" type BaseOpenAiCompatibleProviderOptions = ApiHandlerOptions & { @@ -64,7 +63,7 @@ export abstract class BaseOpenAiCompatibleProvider baseURL, apiKey: this.options.apiKey, defaultHeaders: DEFAULT_HEADERS, - timeout: getApiRequestTimeout(), + timeout: this.timeoutMs, }) } diff --git a/src/api/providers/base-provider.ts b/src/api/providers/base-provider.ts index a6adeeadb..89366fb61 100644 --- a/src/api/providers/base-provider.ts +++ b/src/api/providers/base-provider.ts @@ -6,11 +6,14 @@ import type { ApiHandler, ApiHandlerCreateMessageMetadata } from "../index" import { ApiStream } from "../transform/stream" import { countTokens } from "../../utils/countTokens" import { isMcpTool } from "../../utils/mcp-name" +import { getApiRequestTimeout } from "./utils/timeout-config" /** * Base class for API providers that implements common functionality. */ export abstract class BaseProvider implements ApiHandler { + protected readonly timeoutMs: number = getApiRequestTimeout() + abstract createMessage( systemPrompt: string, messages: Anthropic.Messages.MessageParam[], diff --git a/src/api/providers/minimax.ts b/src/api/providers/minimax.ts index bfcf4e3be..03e01d320 100644 --- a/src/api/providers/minimax.ts +++ b/src/api/providers/minimax.ts @@ -73,6 +73,7 @@ export class MiniMaxHandler extends BaseProvider implements SingleCompletionHand this.client = new Anthropic({ baseURL, apiKey: options.minimaxApiKey, + timeout: this.timeoutMs, }) } diff --git a/src/api/providers/openai-codex.ts b/src/api/providers/openai-codex.ts index 9dfb37bc7..ed4a44519 100644 --- a/src/api/providers/openai-codex.ts +++ b/src/api/providers/openai-codex.ts @@ -371,6 +371,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion apiKey: accessToken, baseURL: CODEX_API_BASE_URL, defaultHeaders: codexHeaders, + timeout: this.timeoutMs, }) const stream = (await (client as any).responses.create(requestBody, { diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 6ce938276..42fbc1c6b 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -104,6 +104,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio session_id: this.sessionId, "User-Agent": userAgent, }, + timeout: this.timeoutMs, }) } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 8761cde59..b3d6ea822 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -22,7 +22,6 @@ import { getModelParams } from "../transform/model-params" import { DEFAULT_HEADERS } from "./constants" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" -import { getApiRequestTimeout } from "./utils/timeout-config" import { handleOpenAIError } from "./utils/openai-error-handler" import { extractReasoningFromDelta } from "./utils/extract-reasoning" @@ -86,7 +85,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(this.options.openAiHeaders || {}), } - const timeout = getApiRequestTimeout() + const timeout = this.timeoutMs if (isAzureAiInference) { // Azure AI Inference Service (e.g., for DeepSeek) uses a different path structure diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 7fcc24b15..b67f0e136 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -153,7 +153,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH const baseURL = this.options.openRouterBaseUrl || "https://openrouter.ai/api/v1" const apiKey = this.options.openRouterApiKey ?? "not-provided" - this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: DEFAULT_HEADERS }) + this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: DEFAULT_HEADERS, timeout: this.timeoutMs }) // Load models asynchronously to populate cache before getModel() is called this.loadDynamicModels().catch((error) => { diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index 0b7d7598a..cdf0f88e4 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -76,6 +76,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan "X-DashScope-UserAgent": `QwenCode/1.0.0 (${os.platform()}; ${os.arch()})`, "X-DashScope-AuthType": "qwen-oauth", }, + timeout: this.timeoutMs, }) } return this.client diff --git a/src/api/providers/requesty.ts b/src/api/providers/requesty.ts index a62e5444d..aacc5a817 100644 --- a/src/api/providers/requesty.ts +++ b/src/api/providers/requesty.ts @@ -69,6 +69,7 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan baseURL: this.baseURL, apiKey: apiKey, defaultHeaders: DEFAULT_HEADERS, + timeout: this.timeoutMs, }) } diff --git a/src/api/providers/router-provider.ts b/src/api/providers/router-provider.ts index 09b102d5b..20419bb45 100644 --- a/src/api/providers/router-provider.ts +++ b/src/api/providers/router-provider.ts @@ -52,6 +52,7 @@ export abstract class RouterProvider extends BaseProvider { ...DEFAULT_HEADERS, ...(options.openAiHeaders || {}), }, + timeout: this.timeoutMs, }) } diff --git a/src/api/providers/unbound.ts b/src/api/providers/unbound.ts index 921985dea..1e7aedd1b 100644 --- a/src/api/providers/unbound.ts +++ b/src/api/providers/unbound.ts @@ -63,6 +63,7 @@ export class UnboundHandler extends BaseProvider implements SingleCompletionHand ...DEFAULT_HEADERS, "X-Unbound-Metadata": JSON.stringify({ labels: [{ key: "app", value: "roo-code" }] }), }, + timeout: this.timeoutMs, }) } diff --git a/src/api/providers/utils/__tests__/timeout-config.spec.ts b/src/api/providers/utils/__tests__/timeout-config.spec.ts index f76c26480..bc26faf36 100644 --- a/src/api/providers/utils/__tests__/timeout-config.spec.ts +++ b/src/api/providers/utils/__tests__/timeout-config.spec.ts @@ -41,23 +41,47 @@ describe("getApiRequestTimeout", () => { expect(timeout).toBe(1200000) // 1200 seconds in milliseconds }) - it("should return undefined for zero timeout (disables timeout)", () => { + it("should fall back to default for zero (below the minimum of 1s)", () => { mockGetConfig.mockReturnValue(0) const timeout = getApiRequestTimeout() - // Zero means "no timeout" - return undefined so SDK uses its default - // (OpenAI SDK interprets 0 as "abort immediately", so we avoid that) - expect(timeout).toBeUndefined() + // 0 is out of the valid 1-3600s range, so we fall back to the default. + expect(timeout).toBe(600000) }) - it("should return undefined for negative values (disables timeout)", () => { + it("should fall back to default for negative values (below the minimum)", () => { mockGetConfig.mockReturnValue(-100) const timeout = getApiRequestTimeout() - // Negative values also mean "no timeout" - return undefined - expect(timeout).toBeUndefined() + expect(timeout).toBe(600000) + }) + + it("should fall back to default for values above the 3600s maximum", () => { + mockGetConfig.mockReturnValue(5000) + + const timeout = getApiRequestTimeout() + + expect(timeout).toBe(600000) + }) + + it("should accept the minimum boundary (1s)", () => { + mockGetConfig.mockReturnValue(1) + + expect(getApiRequestTimeout()).toBe(1000) + }) + + it("should accept the maximum boundary (3600s)", () => { + mockGetConfig.mockReturnValue(3600) + + expect(getApiRequestTimeout()).toBe(3600000) + }) + + it("should round fractional milliseconds to an integer", () => { + mockGetConfig.mockReturnValue(1.2345) // 1234.5ms + + expect(getApiRequestTimeout()).toBe(1235) }) it("should handle null by using default", () => { diff --git a/src/api/providers/utils/timeout-config.ts b/src/api/providers/utils/timeout-config.ts index 39adec620..e8d3ea318 100644 --- a/src/api/providers/utils/timeout-config.ts +++ b/src/api/providers/utils/timeout-config.ts @@ -1,26 +1,26 @@ import * as vscode from "vscode" import { Package } from "../../../shared/package" +const DEFAULT_TIMEOUT_SECONDS = 600 +const MIN_TIMEOUT_SECONDS = 1 +const MAX_TIMEOUT_SECONDS = 3600 + +function isValidTimeout(value: unknown): value is number { + return typeof value === "number" && !isNaN(value) && value >= MIN_TIMEOUT_SECONDS && value <= MAX_TIMEOUT_SECONDS +} + /** * Gets the API request timeout from VSCode configuration with validation. * - * @returns The timeout in milliseconds. Returns undefined to disable timeout - * (letting the SDK use its default), or a positive number for explicit timeout. + * @returns The timeout in milliseconds. Out-of-range, NaN, or non-number values + * fall back to the default. Rounded to an integer so SDK positive-integer + * validation (e.g. the Anthropic SDK) never sees a float. */ -export function getApiRequestTimeout(): number | undefined { - // Get timeout with validation to ensure it's a valid non-negative number - const configTimeout = vscode.workspace.getConfiguration(Package.name).get("apiRequestTimeout", 600) - - // Validate that it's actually a number and not NaN - if (typeof configTimeout !== "number" || isNaN(configTimeout)) { - return 600 * 1000 // Default to 600 seconds - } - - // 0 or negative means "no timeout" - return undefined to let SDK use its default - // (OpenAI SDK interprets 0 as "abort immediately", so we return undefined instead) - if (configTimeout <= 0) { - return undefined - } +export function getApiRequestTimeout(): number { + const configTimeout = vscode.workspace + .getConfiguration(Package.name) + .get("apiRequestTimeout", DEFAULT_TIMEOUT_SECONDS) - return configTimeout * 1000 // Convert to milliseconds + const seconds = isValidTimeout(configTimeout) ? configTimeout : DEFAULT_TIMEOUT_SECONDS + return Math.round(seconds * 1000) } diff --git a/src/api/providers/xai.ts b/src/api/providers/xai.ts index 0cd9cb027..e5c0ba0a8 100644 --- a/src/api/providers/xai.ts +++ b/src/api/providers/xai.ts @@ -34,6 +34,7 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler baseURL: "https://api.x.ai/v1", apiKey: apiKey, defaultHeaders: DEFAULT_HEADERS, + timeout: this.timeoutMs, }) } diff --git a/src/package.json b/src/package.json index a55bc7545..62dfbd588 100644 --- a/src/package.json +++ b/src/package.json @@ -395,9 +395,9 @@ "description": "%settings.useAgentRules.description%" }, "tumble-code.apiRequestTimeout": { - "type": "number", + "type": "integer", "default": 600, - "minimum": 0, + "minimum": 1, "maximum": 3600, "description": "%settings.apiRequestTimeout.description%" }, diff --git a/src/package.nls.ca.json b/src/package.nls.ca.json index e3fff590f..74804862e 100644 --- a/src/package.nls.ca.json +++ b/src/package.nls.ca.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Ruta a un fitxer de configuració de Tumble Code per importar automàticament en iniciar l'extensió. Admet rutes absolutes i rutes relatives al directori d'inici (per exemple, '~/Documents/tumble-code-settings.json'). Deixeu-ho en blanc per desactivar la importació automàtica.", "settings.maximumIndexedFilesForFileSearch.description": "Nombre màxim de fitxers per indexar per a la funció de cerca de fitxers @. Valors més alts proporcionen millors resultats de cerca en projectes grans però poden utilitzar més memòria. Per defecte: 10.000.", "settings.useAgentRules.description": "Activa la càrrega de fitxers AGENTS.md per a regles específiques de l'agent (vegeu https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Temps màxim en segons per esperar les respostes de l'API (0 = sense temps d'espera, 1-3600s, per defecte: 600s). Es recomanen valors més alts per a proveïdors locals com LM Studio i Ollama que poden necessitar més temps de processament.", + "settings.apiRequestTimeout.description": "Temps d'espera de resposta API (segons, predeterminat: 600, rang: 1–3600). Es recomanen valors més alts per a proveïdors locals. Proveïdors no admesos: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Requerir el paràmetre de tasques pendents quan es creïn noves tasques amb l'eina new_task", "settings.codeIndex.embeddingBatchSize.description": "La mida del lot per a operacions d'incrustació durant la indexació de codi. Ajusta això segons els límits del teu proveïdor d'API. Per defecte és 60.", "settings.debug.description": "Activa el mode de depuració per mostrar botons addicionals per veure l'historial de conversa de l'API i els missatges de la interfície d'usuari com a JSON embellert en fitxers temporals.", diff --git a/src/package.nls.de.json b/src/package.nls.de.json index d8e4cb30e..3835cc8c3 100644 --- a/src/package.nls.de.json +++ b/src/package.nls.de.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Pfad zu einer Tumble Code-Konfigurationsdatei, die beim Start der Erweiterung automatisch importiert wird. Unterstützt absolute Pfade und Pfade relativ zum Home-Verzeichnis (z.B. '~/Documents/tumble-code-settings.json'). Leer lassen, um den automatischen Import zu deaktivieren.", "settings.maximumIndexedFilesForFileSearch.description": "Maximale Anzahl der zu indizierenden Dateien für die @-Dateisuchfunktion. Höhere Werte bieten bessere Suchergebnisse in großen Projekten, können aber mehr Speicher verbrauchen. Standard: 10.000.", "settings.useAgentRules.description": "Aktiviert das Laden von AGENTS.md-Dateien für agentenspezifische Regeln (siehe https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Maximale Wartezeit in Sekunden auf API-Antworten (0 = kein Timeout, 1-3600s, Standard: 600s). Höhere Werte werden für lokale Anbieter wie LM Studio und Ollama empfohlen, die möglicherweise mehr Verarbeitungszeit benötigen.", + "settings.apiRequestTimeout.description": "API-Antwort-Timeout (Sekunden, Standard: 600, Bereich: 1–3600). Höhere Werte werden für lokale Anbieter empfohlen. Nicht unterstützte Anbieter: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Todos-Parameter beim Erstellen neuer Aufgaben mit dem new_task-Tool erfordern", "settings.codeIndex.embeddingBatchSize.description": "Die Batch-Größe für Embedding-Operationen während der Code-Indexierung. Passe dies an die Limits deines API-Anbieters an. Standard ist 60.", "settings.debug.description": "Aktiviere den Debug-Modus, um zusätzliche Schaltflächen zum Anzeigen des API-Konversationsverlaufs und der UI-Nachrichten als formatiertes JSON in temporären Dateien anzuzeigen.", diff --git a/src/package.nls.es.json b/src/package.nls.es.json index c42c55df5..7afe89ddb 100644 --- a/src/package.nls.es.json +++ b/src/package.nls.es.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Ruta a un archivo de configuración de Tumble Code para importar automáticamente al iniciar la extensión. Admite rutas absolutas y rutas relativas al directorio de inicio (por ejemplo, '~/Documents/tumble-code-settings.json'). Dejar vacío para desactivar la importación automática.", "settings.maximumIndexedFilesForFileSearch.description": "Número máximo de archivos a indexar para la función de búsqueda de archivos @. Valores más altos proporcionan mejores resultados de búsqueda en proyectos grandes pero pueden usar más memoria. Por defecto: 10.000.", "settings.useAgentRules.description": "Habilita la carga de archivos AGENTS.md para reglas específicas del agente (ver https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Tiempo máximo en segundos de espera para las respuestas de la API (0 = sin tiempo de espera, 1-3600s, por defecto: 600s). Se recomiendan valores más altos para proveedores locales como LM Studio y Ollama que puedan necesitar más tiempo de procesamiento.", + "settings.apiRequestTimeout.description": "Tiempo de espera de respuesta API (segundos, predeterminado: 600, rango: 1–3600). Se recomiendan valores más altos para proveedores locales. Proveedores no compatibles: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Requerir el parámetro todos al crear nuevas tareas con la herramienta new_task", "settings.codeIndex.embeddingBatchSize.description": "El tamaño del lote para operaciones de embedding durante la indexación de código. Ajusta esto según los límites de tu proveedor de API. Por defecto es 60.", "settings.debug.description": "Activa el modo de depuración para mostrar botones adicionales para ver el historial de conversación de API y los mensajes de la interfaz de usuario como JSON embellecido en archivos temporales.", diff --git a/src/package.nls.fr.json b/src/package.nls.fr.json index fe1d7490d..2b7a3cdfc 100644 --- a/src/package.nls.fr.json +++ b/src/package.nls.fr.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Chemin d'accès à un fichier de configuration Tumble Code à importer automatiquement au démarrage de l'extension. Prend en charge les chemins absolus et les chemins relatifs au répertoire de base (par exemple, '~/Documents/tumble-code-settings.json'). Laisser vide pour désactiver l'importation automatique.", "settings.maximumIndexedFilesForFileSearch.description": "Nombre maximum de fichiers à indexer pour la fonctionnalité de recherche de fichiers @. Des valeurs plus élevées offrent de meilleurs résultats de recherche dans les grands projets mais peuvent consommer plus de mémoire. Par défaut : 10 000.", "settings.useAgentRules.description": "Activer le chargement des fichiers AGENTS.md pour les règles spécifiques à l'agent (voir https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Temps maximum en secondes d'attente pour les réponses de l'API (0 = pas de timeout, 1-3600s, par défaut : 600s). Des valeurs plus élevées sont recommandées pour les fournisseurs locaux comme LM Studio et Ollama qui peuvent nécessiter plus de temps de traitement.", + "settings.apiRequestTimeout.description": "Délai d'attente des réponses API (secondes, défaut : 600, plage : 1–3600). Des valeurs plus élevées sont recommandées pour les fournisseurs locaux. Fournisseurs non pris en charge : Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Exiger le paramètre todos lors de la création de nouvelles tâches avec l'outil new_task", "settings.codeIndex.embeddingBatchSize.description": "La taille du lot pour les opérations d'embedding lors de l'indexation du code. Ajustez ceci selon les limites de votre fournisseur d'API. Par défaut, c'est 60.", "settings.debug.description": "Active le mode debug pour afficher des boutons supplémentaires permettant de visualiser l'historique de conversation de l'API et les messages de l'interface utilisateur sous forme de JSON formaté dans des fichiers temporaires.", diff --git a/src/package.nls.hi.json b/src/package.nls.hi.json index 639591572..5ac909101 100644 --- a/src/package.nls.hi.json +++ b/src/package.nls.hi.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Tumble Code कॉन्फ़िगरेशन फ़ाइल का पथ जिसे एक्सटेंशन स्टार्टअप पर स्वचालित रूप से आयात किया जाएगा। होम डायरेक्टरी के सापेक्ष पूर्ण पथ और पथों का समर्थन करता है (उदाहरण के लिए '~/Documents/tumble-code-settings.json')। ऑटो-इंपोर्ट को अक्षम करने के लिए खाली छोड़ दें।", "settings.maximumIndexedFilesForFileSearch.description": "@ फ़ाइल खोज सुविधा के लिए अनुक्रमित करने के लिए फ़ाइलों की अधिकतम संख्या। उच्च मान बड़ी परियोजनाओं में बेहतर खोज परिणाम प्रदान करते हैं लेकिन अधिक मेमोरी का उपयोग कर सकते हैं। डिफ़ॉल्ट: 10,000।", "settings.useAgentRules.description": "एजेंट-विशिष्ट नियमों के लिए AGENTS.md फ़ाइलों को लोड करना सक्षम करें (देखें https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "एपीआई प्रतिक्रियाओं की प्रतीक्षा करने के लिए सेकंड में अधिकतम समय (0 = कोई टाइमआउट नहीं, 1-3600s, डिफ़ॉल्ट: 600s)। एलएम स्टूडियो और ओलामा जैसे स्थानीय प्रदाताओं के लिए उच्च मानों की सिफारिश की जाती है जिन्हें अधिक प्रसंस्करण समय की आवश्यकता हो सकती है।", + "settings.apiRequestTimeout.description": "API प्रतिक्रिया टाइमआउट (सेकंड, डिफ़ॉल्ट: 600, रेंज: 1–3600)। स्थानीय प्रदाताओं के लिए उच्च मान अनुशंसित हैं। असमर्थित प्रदाता: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API।", "settings.newTaskRequireTodos.description": "new_task टूल के साथ नए कार्य बनाते समय टूडू पैरामीटर की आवश्यकता होती है", "settings.codeIndex.embeddingBatchSize.description": "कोड इंडेक्सिंग के दौरान एम्बेडिंग ऑपरेशन के लिए बैच साइज़। इसे अपने API प्रदाता की सीमाओं के अनुसार समायोजित करें। डिफ़ॉल्ट 60 है।", "settings.debug.description": "API conversation history और UI messages को temporary files में prettified JSON के रूप में देखने के लिए अतिरिक्त बटन दिखाने के लिए debug mode सक्षम करें।", diff --git a/src/package.nls.id.json b/src/package.nls.id.json index 0f00dbc79..2cf5ac5f0 100644 --- a/src/package.nls.id.json +++ b/src/package.nls.id.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Path ke file konfigurasi Tumble Code untuk diimpor secara otomatis saat ekstensi dimulai. Mendukung path absolut dan path relatif terhadap direktori home (misalnya '~/Documents/tumble-code-settings.json'). Biarkan kosong untuk menonaktifkan impor otomatis.", "settings.maximumIndexedFilesForFileSearch.description": "Jumlah maksimum file yang akan diindeks untuk fitur pencarian file @. Nilai yang lebih besar memberikan hasil pencarian yang lebih baik di proyek besar tetapi mungkin menggunakan lebih banyak memori. Default: 10.000.", "settings.useAgentRules.description": "Aktifkan pemuatan file AGENTS.md untuk aturan khusus agen (lihat https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Waktu maksimum dalam detik untuk menunggu respons API (0 = tidak ada batas waktu, 1-3600s, default: 600s). Nilai yang lebih tinggi disarankan untuk penyedia lokal seperti LM Studio dan Ollama yang mungkin memerlukan lebih banyak waktu pemrosesan.", + "settings.apiRequestTimeout.description": "Batas waktu respons API (detik, default: 600, rentang: 1–3600). Nilai lebih tinggi direkomendasikan untuk penyedia lokal. Penyedia tidak didukung: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Memerlukan parameter todos saat membuat tugas baru dengan alat new_task", "settings.codeIndex.embeddingBatchSize.description": "Ukuran batch untuk operasi embedding selama pengindeksan kode. Sesuaikan ini berdasarkan batas penyedia API kamu. Default adalah 60.", "settings.debug.description": "Aktifkan mode debug untuk menampilkan tombol tambahan untuk melihat riwayat percakapan API dan pesan UI sebagai JSON yang diformat dalam file sementara.", diff --git a/src/package.nls.it.json b/src/package.nls.it.json index f93a7cbe2..a49ba4859 100644 --- a/src/package.nls.it.json +++ b/src/package.nls.it.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Percorso di un file di configurazione di Tumble Code da importare automaticamente all'avvio dell'estensione. Supporta percorsi assoluti e percorsi relativi alla directory home (ad es. '~/Documents/tumble-code-settings.json'). Lasciare vuoto per disabilitare l'importazione automatica.", "settings.maximumIndexedFilesForFileSearch.description": "Numero massimo di file da indicizzare per la funzionalità di ricerca file @. Valori più alti forniscono migliori risultati di ricerca in progetti grandi ma possono consumare più memoria. Predefinito: 10.000.", "settings.useAgentRules.description": "Abilita il caricamento dei file AGENTS.md per regole specifiche dell'agente (vedi https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Tempo massimo in secondi di attesa per le risposte API (0 = nessun timeout, 1-3600s, predefinito: 600s). Valori più alti sono consigliati per provider locali come LM Studio e Ollama che potrebbero richiedere più tempo di elaborazione.", + "settings.apiRequestTimeout.description": "Timeout risposta API (secondi, predefinito: 600, intervallo: 1–3600). Valori più alti sono consigliati per provider locali. Provider non supportati: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Richiedere il parametro todos quando si creano nuove attività con lo strumento new_task", "settings.codeIndex.embeddingBatchSize.description": "La dimensione del batch per le operazioni di embedding durante l'indicizzazione del codice. Regola questo in base ai limiti del tuo provider API. Il valore predefinito è 60.", "settings.debug.description": "Abilita la modalità debug per mostrare pulsanti aggiuntivi per visualizzare la cronologia delle conversazioni API e i messaggi dell'interfaccia utente come JSON formattato in file temporanei.", diff --git a/src/package.nls.ja.json b/src/package.nls.ja.json index 2952a4a6c..eaf55c5b0 100644 --- a/src/package.nls.ja.json +++ b/src/package.nls.ja.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "拡張機能の起動時に自動的にインポートするTumble Code設定ファイルへのパス。絶対パスとホームディレクトリからの相対パスをサポートします(例:'~/Documents/tumble-code-settings.json')。自動インポートを無効にするには、空のままにします。", "settings.maximumIndexedFilesForFileSearch.description": "@ファイル検索機能のためにインデックス化するファイルの最大数。大きな値は大規模プロジェクトでより良い検索結果を提供しますが、より多くのメモリを使用する可能性があります。デフォルト: 10,000。", "settings.useAgentRules.description": "エージェント固有のルールのためにAGENTS.mdファイルの読み込みを有効にします(参照:https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "API応答を待機する最大時間(秒)(0 = タイムアウトなし、1-3600秒、デフォルト: 600秒)。LM StudioやOllamaのような、より多くの処理時間を必要とする可能性のあるローカルプロバイダーには、より高い値が推奨されます。", + "settings.apiRequestTimeout.description": "API応答タイムアウト(秒、デフォルト:600、範囲:1–3600)。ローカルプロバイダーには高い値を推奨します。非対応プロバイダー:Amazon Bedrock、Google Gemini(directly or through the Vertex AI platform)、Mistral、Moonshot、Ollama、Poe、VS Code LM API。", "settings.newTaskRequireTodos.description": "new_taskツールで新しいタスクを作成する際にtodosパラメータを必須にする", "settings.codeIndex.embeddingBatchSize.description": "コードインデックス作成中のエンベディング操作のバッチサイズ。APIプロバイダーの制限に基づいてこれを調整してください。デフォルトは60です。", "settings.debug.description": "デバッグモードを有効にして、API会話履歴とUIメッセージをフォーマットされたJSONとして一時ファイルで表示するための追加ボタンを表示します。", diff --git a/src/package.nls.json b/src/package.nls.json index e0d3f74d9..7edce7937 100644 --- a/src/package.nls.json +++ b/src/package.nls.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Path to a Tumble Code configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/tumble-code-settings.json'). Leave empty to disable auto-import.", "settings.maximumIndexedFilesForFileSearch.description": "Maximum number of files to index for the @ file search feature. Higher values provide better search results in large projects but may use more memory. Default: 10,000.", "settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time.", + "settings.apiRequestTimeout.description": "API response timeout (seconds, default: 600, range: 1–3600). Higher values are recommended for local providers. Unsupported providers: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Require todos parameter when creating new tasks with the new_task tool", "settings.codeIndex.embeddingBatchSize.description": "The batch size for embedding operations during code indexing. Adjust this based on your API provider's limits. Default is 60.", "settings.debug.description": "Enable debug mode to show additional buttons for viewing API conversation history and UI messages as prettified JSON in temporary files.", diff --git a/src/package.nls.ko.json b/src/package.nls.ko.json index ef7ac9ace..cc9e0356e 100644 --- a/src/package.nls.ko.json +++ b/src/package.nls.ko.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "확장 프로그램 시작 시 자동으로 가져올 Tumble Code 구성 파일의 경로입니다. 절대 경로 및 홈 디렉토리에 대한 상대 경로를 지원합니다(예: '~/Documents/tumble-code-settings.json'). 자동 가져오기를 비활성화하려면 비워 둡니다.", "settings.maximumIndexedFilesForFileSearch.description": "@ 파일 검색 기능을 위해 인덱싱할 최대 파일 수입니다. 더 큰 값은 대형 프로젝트에서 더 나은 검색 결과를 제공하지만 더 많은 메모리를 사용할 수 있습니다. 기본값: 10,000.", "settings.useAgentRules.description": "에이전트별 규칙에 대한 AGENTS.md 파일 로드를 활성화합니다 (참조: https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "API 응답을 기다리는 최대 시간(초) (0 = 시간 초과 없음, 1-3600초, 기본값: 600초). 더 많은 처리 시간이 필요할 수 있는 LM Studio 및 Ollama와 같은 로컬 공급자에게는 더 높은 값을 사용하는 것이 좋습니다.", + "settings.apiRequestTimeout.description": "API 응답 대기 시간(초, 기본값: 600, 범위: 1~3600). 로컬 공급자에는 더 높은 값을 권장합니다. 지원되지 않는 공급자: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "new_task 도구로 새 작업을 생성할 때 todos 매개변수 필요", "settings.codeIndex.embeddingBatchSize.description": "코드 인덱싱 중 임베딩 작업의 배치 크기입니다. API 공급자의 제한에 따라 이를 조정하세요. 기본값은 60입니다.", "settings.debug.description": "디버그 모드를 활성화하여 API 대화 기록과 UI 메시지를 임시 파일에 포맷된 JSON으로 보기 위한 추가 버튼을 표시합니다.", diff --git a/src/package.nls.nl.json b/src/package.nls.nl.json index 63e25c2b6..ba85668d8 100644 --- a/src/package.nls.nl.json +++ b/src/package.nls.nl.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Pad naar een Tumble Code-configuratiebestand om automatisch te importeren bij het opstarten van de extensie. Ondersteunt absolute paden en paden ten opzichte van de thuismap (bijv. '~/Documents/tumble-code-settings.json'). Laat leeg om automatisch importeren uit te schakelen.", "settings.maximumIndexedFilesForFileSearch.description": "Maximaal aantal bestanden om te indexeren voor de @ bestandszoekfunctie. Hogere waarden bieden betere zoekresultaten in grote projecten maar kunnen meer geheugen gebruiken. Standaard: 10.000.", "settings.useAgentRules.description": "Laden van AGENTS.md-bestanden voor agentspecifieke regels inschakelen (zie https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Maximale tijd in seconden om te wachten op API-reacties (0 = geen time-out, 1-3600s, standaard: 600s). Hogere waarden worden aanbevolen voor lokale providers zoals LM Studio en Ollama die mogelijk meer verwerkingstijd nodig hebben.", + "settings.apiRequestTimeout.description": "API-respons time-out (seconden, standaard: 600, bereik: 1–3600). Hogere waarden worden aanbevolen voor lokale providers. Niet-ondersteunde providers: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Todos-parameter vereisen bij het maken van nieuwe taken met de new_task tool", "settings.codeIndex.embeddingBatchSize.description": "De batchgrootte voor embedding-operaties tijdens code-indexering. Pas dit aan op basis van de limieten van je API-provider. Standaard is 60.", "settings.debug.description": "Schakel debug-modus in om extra knoppen te tonen voor het bekijken van API-conversatiegeschiedenis en UI-berichten als opgemaakte JSON in tijdelijke bestanden.", diff --git a/src/package.nls.pl.json b/src/package.nls.pl.json index f0ad96763..27015d374 100644 --- a/src/package.nls.pl.json +++ b/src/package.nls.pl.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Ścieżka do pliku konfiguracyjnego Tumble Code, który ma być automatycznie importowany podczas uruchamiania rozszerzenia. Obsługuje ścieżki bezwzględne i ścieżki względne do katalogu domowego (np. '~/Documents/tumble-code-settings.json'). Pozostaw puste, aby wyłączyć automatyczne importowanie.", "settings.maximumIndexedFilesForFileSearch.description": "Maksymalna liczba plików do indeksowania dla funkcji wyszukiwania plików @. Wyższe wartości zapewniają lepsze wyniki wyszukiwania w dużych projektach, ale mogą zużywać więcej pamięci. Domyślnie: 10 000.", "settings.useAgentRules.description": "Włącz wczytywanie plików AGENTS.md dla reguł specyficznych dla agenta (zobacz https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Maksymalny czas w sekundach oczekiwania na odpowiedzi API (0 = brak limitu czasu, 1-3600s, domyślnie: 600s). Wyższe wartości są zalecane dla lokalnych dostawców, takich jak LM Studio i Ollama, którzy mogą potrzebować więcej czasu na przetwarzanie.", + "settings.apiRequestTimeout.description": "Limit czasu odpowiedzi API (sekundy, domyślnie: 600, zakres: 1–3600). Wyższe wartości są zalecane dla lokalnych dostawców. Nieobsługiwani dostawcy: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Wymagaj parametru todos podczas tworzenia nowych zadań za pomocą narzędzia new_task", "settings.codeIndex.embeddingBatchSize.description": "Rozmiar partii dla operacji osadzania podczas indeksowania kodu. Dostosuj to w oparciu o limity twojego dostawcy API. Domyślnie to 60.", "settings.debug.description": "Włącz tryb debugowania, aby wyświetlić dodatkowe przyciski do przeglądania historii rozmów API i komunikatów interfejsu użytkownika jako sformatowany JSON w plikach tymczasowych.", diff --git a/src/package.nls.pt-BR.json b/src/package.nls.pt-BR.json index 4eb86cb10..d20956d7c 100644 --- a/src/package.nls.pt-BR.json +++ b/src/package.nls.pt-BR.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Caminho para um arquivo de configuração do Tumble Code para importar automaticamente na inicialização da extensão. Suporta caminhos absolutos e caminhos relativos ao diretório inicial (por exemplo, '~/Documents/tumble-code-settings.json'). Deixe em branco para desativar a importação automática.", "settings.maximumIndexedFilesForFileSearch.description": "Número máximo de arquivos a indexar para a funcionalidade de busca de arquivos @. Valores maiores fornecem melhores resultados de busca em projetos grandes, mas podem consumir mais memória. Padrão: 10.000.", "settings.useAgentRules.description": "Habilita o carregamento de arquivos AGENTS.md para regras específicas do agente (consulte https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Tempo máximo em segundos de espera pelas respostas da API (0 = sem tempo limite, 1-3600s, padrão: 600s). Valores mais altos são recomendados para provedores locais como LM Studio e Ollama que podem precisar de mais tempo de processamento.", + "settings.apiRequestTimeout.description": "Tempo limite de resposta da API (segundos, padrão: 600, intervalo: 1–3600). Valores mais altos são recomendados para provedores locais. Provedores não suportados: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Exigir parâmetro todos ao criar novas tarefas com a ferramenta new_task", "settings.codeIndex.embeddingBatchSize.description": "O tamanho do lote para operações de embedding durante a indexação de código. Ajuste isso com base nos limites do seu provedor de API. O padrão é 60.", "settings.debug.description": "Ativa o modo de depuração para mostrar botões adicionais para visualizar o histórico de conversas da API e mensagens da interface como JSON formatado em arquivos temporários.", diff --git a/src/package.nls.ru.json b/src/package.nls.ru.json index 5d0408ddf..db3b8d826 100644 --- a/src/package.nls.ru.json +++ b/src/package.nls.ru.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Путь к файлу конфигурации Tumble Code для автоматического импорта при запуске расширения. Поддерживает абсолютные пути и пути относительно домашнего каталога (например, '~/Documents/tumble-code-settings.json'). Оставьте пустым, чтобы отключить автоматический импорт.", "settings.maximumIndexedFilesForFileSearch.description": "Максимальное количество файлов для индексации при поиске файлов @. Большие значения обеспечивают лучшие результаты поиска в крупных проектах, но могут потреблять больше памяти. По умолчанию: 10 000.", "settings.useAgentRules.description": "Включить загрузку файлов AGENTS.md для специфичных для агента правил (см. https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Максимальное время в секундах для ожидания ответов API (0 = нет тайм-аута, 1-3600 с, по умолчанию: 600 с). Рекомендуются более высокие значения для локальных провайдеров, таких как LM Studio и Ollama, которым может потребоваться больше времени на обработку.", + "settings.apiRequestTimeout.description": "Тайм-аут ответа API (секунды, по умолчанию: 600, диапазон: 1–3600). Более высокие значения рекомендуются для локальных провайдеров. Неподдерживаемые провайдеры: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Требовать параметр todos при создании новых задач с помощью инструмента new_task", "settings.codeIndex.embeddingBatchSize.description": "Размер пакета для операций встраивания во время индексации кода. Настройте это в соответствии с ограничениями вашего API-провайдера. По умолчанию 60.", "settings.debug.description": "Включить режим отладки, чтобы отображать дополнительные кнопки для просмотра истории разговоров API и сообщений интерфейса в виде форматированного JSON во временных файлах.", diff --git a/src/package.nls.tr.json b/src/package.nls.tr.json index 8c60d13a8..0b047db2e 100644 --- a/src/package.nls.tr.json +++ b/src/package.nls.tr.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Uzantı başlangıcında otomatik olarak içe aktarılacak bir Tumble Code yapılandırma dosyasının yolu. Mutlak yolları ve ana dizine göreli yolları destekler (ör. '~/Documents/tumble-code-settings.json'). Otomatik içe aktarmayı devre dışı bırakmak için boş bırakın.", "settings.maximumIndexedFilesForFileSearch.description": "@ dosya arama özelliği için dizinlenecek maksimum dosya sayısı. Daha yüksek değerler büyük projelerde daha iyi arama sonuçları sağlar ancak daha fazla bellek kullanabilir. Varsayılan: 10.000.", "settings.useAgentRules.description": "Aracıya özgü kurallar için AGENTS.md dosyalarının yüklenmesini etkinleştirin (bkz. https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "API yanıtları için beklenecek maksimum süre (saniye cinsinden) (0 = zaman aşımı yok, 1-3600s, varsayılan: 600s). LM Studio ve Ollama gibi daha fazla işlem süresi gerektirebilecek yerel sağlayıcılar için daha yüksek değerler önerilir.", + "settings.apiRequestTimeout.description": "API yanıt zaman aşımı (saniye, varsayılan: 600, aralık: 1–3600). Yerel sağlayıcılar için daha yüksek değerler önerilir. Desteklenmeyen sağlayıcılar: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "new_task aracıyla yeni görevler oluştururken todos parametresini gerekli kıl", "settings.codeIndex.embeddingBatchSize.description": "Kod indeksleme sırasında gömme işlemleri için toplu iş boyutu. Bunu API sağlayıcınızın sınırlarına göre ayarlayın. Varsayılan 60'tır.", "settings.debug.description": "API konuşma geçmişini ve kullanıcı arayüzü mesajlarını geçici dosyalarda biçimlendirilmiş JSON olarak görüntülemek için ek düğmeler göstermek üzere hata ayıklama modunu etkinleştir.", diff --git a/src/package.nls.vi.json b/src/package.nls.vi.json index 706333ca5..8a95f1324 100644 --- a/src/package.nls.vi.json +++ b/src/package.nls.vi.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Đường dẫn đến tệp cấu hình Tumble Code để tự động nhập khi khởi động tiện ích mở rộng. Hỗ trợ đường dẫn tuyệt đối và đường dẫn tương đối đến thư mục chính (ví dụ: '~/Documents/tumble-code-settings.json'). Để trống để tắt tính năng tự động nhập.", "settings.maximumIndexedFilesForFileSearch.description": "Số lượng tệp tối đa để lập chỉ mục cho tính năng tìm kiếm tệp @. Giá trị cao hơn cung cấp kết quả tìm kiếm tốt hơn trong các dự án lớn nhưng có thể sử dụng nhiều bộ nhớ hơn. Mặc định: 10.000.", "settings.useAgentRules.description": "Bật tải tệp AGENTS.md cho các quy tắc dành riêng cho tác nhân (xem https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "Thời gian tối đa tính bằng giây để đợi phản hồi API (0 = không có thời gian chờ, 1-3600 giây, mặc định: 600 giây). Nên sử dụng các giá trị cao hơn cho các nhà cung cấp cục bộ như LM Studio và Ollama có thể cần thêm thời gian xử lý.", + "settings.apiRequestTimeout.description": "Thời gian chờ phản hồi API (giây, mặc định: 600, phạm vi: 1–3600). Nên dùng giá trị cao hơn cho các nhà cung cấp cục bộ. Nhà cung cấp không được hỗ trợ: Amazon Bedrock, Google Gemini(directly or through the Vertex AI platform), Mistral, Moonshot, Ollama, Poe, VS Code LM API.", "settings.newTaskRequireTodos.description": "Yêu cầu tham số todos khi tạo nhiệm vụ mới với công cụ new_task", "settings.codeIndex.embeddingBatchSize.description": "Kích thước lô cho các hoạt động nhúng trong quá trình lập chỉ mục mã. Điều chỉnh điều này dựa trên giới hạn của nhà cung cấp API của bạn. Mặc định là 60.", "settings.debug.description": "Bật chế độ gỡ lỗi để hiển thị các nút bổ sung để xem lịch sử hội thoại API và thông điệp giao diện người dùng dưới dạng JSON được định dạng trong các tệp tạm thời.", diff --git a/src/package.nls.zh-CN.json b/src/package.nls.zh-CN.json index 644a0e070..9847fc33f 100644 --- a/src/package.nls.zh-CN.json +++ b/src/package.nls.zh-CN.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Tumble Code 配置文件的路径,用于在扩展启动时自动导入。支持绝对路径和相对于主目录的路径(例如 '~/Documents/tumble-code-settings.json')。留空以禁用自动导入。", "settings.maximumIndexedFilesForFileSearch.description": "为 @ 文件搜索功能建立索引时要索引的最大文件数。较大的值在大型项目中提供更好的搜索结果,但可能占用更多内存。默认值:10,000。", "settings.useAgentRules.description": "为特定于代理的规则启用 AGENTS.md 文件的加载(请参阅 https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "等待 API 响应的最长时间(秒)(0 = 无超时,1-3600秒,默认值:600秒)。对于像 LM Studio 和 Ollama 这样可能需要更多处理时间的本地提供商,建议使用更高的值。", + "settings.apiRequestTimeout.description": "API 响应超时(秒,默认值:600,范围:1–3600)。建议本地提供商使用更高的值。不适用的提供商:Amazon Bedrock、Google Gemini(directly or through the Vertex AI platform)、Mistral、Moonshot、Ollama、Poe、VS Code LM API。", "settings.newTaskRequireTodos.description": "使用 new_task 工具创建新任务时需要 todos 参数", "settings.codeIndex.embeddingBatchSize.description": "代码索引期间嵌入操作的批处理大小。根据 API 提供商的限制调整此设置。默认值为 60。", "settings.debug.description": "启用调试模式以显示额外按钮,用于在临时文件中以格式化 JSON 查看 API 对话历史和 UI 消息。", diff --git a/src/package.nls.zh-TW.json b/src/package.nls.zh-TW.json index 9878ac318..5ea2a1ad4 100644 --- a/src/package.nls.zh-TW.json +++ b/src/package.nls.zh-TW.json @@ -40,7 +40,7 @@ "settings.autoImportSettingsPath.description": "Tumble Code 設定檔案的路徑,用於在擴充功能啟動時自動匯入。支援絕對路徑和相對於主目錄的路徑(例如 '~/Documents/tumble-code-settings.json')。留空以停用自動匯入。", "settings.maximumIndexedFilesForFileSearch.description": "為 @ 檔案搜尋功能建立索引時要索引的最大檔案數。較大的值在大型專案中提供更好的搜尋結果,但可能佔用更多記憶體。預設值:10,000。", "settings.useAgentRules.description": "為特定於代理的規則啟用 AGENTS.md 檔案的載入(請參閱 https://agent-rules.org/)", - "settings.apiRequestTimeout.description": "等待 API 回應的最長時間(秒)(0 = 無超時,1-3600秒,預設值:600秒)。對於像 LM Studio 和 Ollama 這樣可能需要更多處理時間的本地提供商,建議使用更高的值。", + "settings.apiRequestTimeout.description": "API 回應逾時(秒,預設值:600,範圍:1–3600)。建議本地提供商使用更高的值。不適用的提供商:Amazon Bedrock、Google Gemini(directly or through the Vertex AI platform)、Mistral、Moonshot、Ollama、Poe、VS Code LM API。", "settings.newTaskRequireTodos.description": "使用 new_task 工具建立新工作時需要 todos 參數", "settings.codeIndex.embeddingBatchSize.description": "程式碼索引期間嵌入操作的批次大小。根據 API 提供商的限制調整此設定。預設值為 60。", "settings.debug.description": "啟用偵錯模式以顯示額外按鈕,用於在暫存檔案中以格式化 JSON 檢視 API 對話歷史紀錄和使用者介面訊息。", From 5cbf437d62807906fb991a157bd0ee65ad5cf455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 19:49:41 +0200 Subject: [PATCH 06/16] feat(fireworks): add kimi-k2p7-code model Port of Zoo-Code PR #599. Adds Moonshot AI's Kimi K2.7 Code model to the Fireworks provider (type union + model config + test). Purely additive. Co-authored-by: Povilas Kanapickas --- ...-06-27_zoo-599-fireworks-kimi-k2p7-code.md | 27 +++++++++++++++++++ packages/types/src/providers/fireworks.ts | 15 +++++++++++ src/api/providers/__tests__/fireworks.spec.ts | 24 +++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 ai_plans/2026-06-27_zoo-599-fireworks-kimi-k2p7-code.md diff --git a/ai_plans/2026-06-27_zoo-599-fireworks-kimi-k2p7-code.md b/ai_plans/2026-06-27_zoo-599-fireworks-kimi-k2p7-code.md new file mode 100644 index 000000000..cf6b20da1 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-599-fireworks-kimi-k2p7-code.md @@ -0,0 +1,27 @@ +# Port Zoo PR #599 — add Fireworks kimi-k2p7-code model + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #599, commit `74583b55e`, merged 2026-06-20. +- Author: Povilas Kanapickas. + +## §1 What & why + +New model support: Fireworks `kimi-k2p7-code` (Moonshot AI coding model). Pure +additive change — no divergence concerns; our `fireworks.ts` matches the pre-PR +structure (type union, model map, neighboring deepseek-v4-pro / glm-5p1 anchors). + +## §2 Edits + +- `packages/types/src/providers/fireworks.ts`: + - add `"accounts/fireworks/models/kimi-k2p7-code"` to the `FireworksModelId` union + (after `kimi-k2p6`). + - add its entry to `fireworksModels` (maxTokens 16384, ctx 262144, images+cache+ + reasoning+temperature, in 0.95 / out 4.0 / cacheReads 0.19), inserted after + `deepseek-v4-pro`, before `glm-5p1` — matching upstream placement. +- `src/api/providers/__tests__/fireworks.spec.ts`: add the model-config test. + +## §3 Verify (binary acceptance) — all ✓ + +- `pnpm --filter @roo-code/types check-types` passes. +- `npx vitest run api/providers/__tests__/fireworks` → 32 pass. diff --git a/packages/types/src/providers/fireworks.ts b/packages/types/src/providers/fireworks.ts index 12e24c161..3b18ad50b 100644 --- a/packages/types/src/providers/fireworks.ts +++ b/packages/types/src/providers/fireworks.ts @@ -6,6 +6,7 @@ export type FireworksModelId = | "accounts/fireworks/models/kimi-k2-thinking" | "accounts/fireworks/models/kimi-k2p5" | "accounts/fireworks/models/kimi-k2p6" + | "accounts/fireworks/models/kimi-k2p7-code" | "accounts/fireworks/models/minimax-m2" | "accounts/fireworks/models/minimax-m2p1" | "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507" @@ -265,6 +266,20 @@ export const fireworksModels = { description: "DeepSeek V4 Pro is the latest iteration of the DeepSeek model family, with improved reasoning, code generation, and instruction following over the V3 series.", }, + "accounts/fireworks/models/kimi-k2p7-code": { + maxTokens: 16384, + contextWindow: 262144, + supportsImages: true, + supportsPromptCache: true, + preserveReasoning: true, + supportsTemperature: true, + defaultTemperature: 1.0, + inputPrice: 0.95, + outputPrice: 4.0, + cacheReadsPrice: 0.19, + description: + "Kimi K2.7 Code is Moonshot AI's latest coding-focused model, building on K2.6 with enhanced code generation, reasoning, and unified vision/text understanding for software development workflows.", + }, "accounts/fireworks/models/glm-5p1": { maxTokens: 25344, contextWindow: 202752, diff --git a/src/api/providers/__tests__/fireworks.spec.ts b/src/api/providers/__tests__/fireworks.spec.ts index 871c989ea..04af92244 100644 --- a/src/api/providers/__tests__/fireworks.spec.ts +++ b/src/api/providers/__tests__/fireworks.spec.ts @@ -181,6 +181,30 @@ describe("FireworksHandler", () => { ) }) + it("should return Kimi K2.7 code model with correct configuration", () => { + const testModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2p7-code" + const handlerWithModel = new FireworksHandler({ + apiModelId: testModelId, + fireworksApiKey: "test-fireworks-api-key", + }) + const model = handlerWithModel.getModel() + expect(model.id).toBe(testModelId) + expect(model.info).toEqual( + expect.objectContaining({ + maxTokens: 16384, + contextWindow: 262144, + supportsImages: true, + supportsPromptCache: true, + supportsTemperature: true, + preserveReasoning: true, + defaultTemperature: 1.0, + inputPrice: 0.95, + outputPrice: 4.0, + cacheReadsPrice: 0.19, + }), + ) + }) + it("should return MiniMax M2 model with correct configuration", () => { const testModelId: FireworksModelId = "accounts/fireworks/models/minimax-m2" const handlerWithModel = new FireworksHandler({ From e788bc255cc682b1ce779c3293487ab38dd69bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 19:57:11 +0200 Subject: [PATCH 07/16] chore(deps): bump undici to ^6.27.0 [security] Port of Zoo-Code PR #659. Our lockfile resolved undici@6.26.0 (below the patched 6.27.0), so this raises the floor in the root pnpm.overrides and src devDeps to ^6.27.0 and refreshes the lockfile to pull in the fix. Keeps our caret convention; skips the unrelated ClineProvider test refactor squashed into the upstream PR. Co-authored-by: Elliott de Launay --- ...26-06-27_zoo-659-undici-6-27-0-security.md | 35 +++++++++++++++++++ package.json | 2 +- pnpm-lock.yaml | 16 ++++----- src/package.json | 2 +- 4 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-659-undici-6-27-0-security.md diff --git a/ai_plans/2026-06-27_zoo-659-undici-6-27-0-security.md b/ai_plans/2026-06-27_zoo-659-undici-6-27-0-security.md new file mode 100644 index 000000000..baed50898 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-659-undici-6-27-0-security.md @@ -0,0 +1,35 @@ +# Port Zoo PR #659 — undici 6.27.0 security update + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #659, commit `f798462f4`, merged 2026-06-20. +- Author: Elliott de Launay (renovate[bot] dropped as a bot). +- Commit trailer: + ``` + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why + +Security update of `undici` to `6.27.0`. **Unlike the vite bump (#642), this one is +NOT already satisfied here**: our lockfile resolved `undici@6.26.0` (below the +patched 6.27.0) via the `^6.21.3` floor. Bumping the floor pulls in the fix. + +## §2 Edits + +- `package.json` (pnpm.overrides): `"undici": "^6.21.3"` → `"^6.27.0"`. +- `src/package.json` (devDeps): `"undici": "^6.21.3"` → `"^6.27.0"`. +- Refresh `pnpm-lock.yaml` → now resolves `undici@6.27.0`. + +## §3 Scope cuts (divergence / noise) + +- Our root override was already `^6.21.3` (Zoo's pre-PR was `>=5.29.0`); kept our + caret convention rather than Zoo's exact `6.27.0` pin. +- **Did NOT port the `ClineProvider.flicker-free-cancel.spec.ts` changes** bundled in + this PR — they are an unrelated test refactor squashed into the renovate bump, not + part of the security fix, and our ClineProvider tests have diverged. + +## §4 Verify (binary acceptance) — all ✓ + +- `pnpm install --lockfile-only` succeeds; lockfile resolves `undici@6.27.0`, no 6.26.0. +- `pnpm --filter tumble-code check-types` passes. diff --git a/package.json b/package.json index 6da4f250e..1aa72559e 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "overrides": { "tar-fs": ">=3.1.1", "esbuild": ">=0.25.0", - "undici": "^6.21.3", + "undici": "^6.27.0", "brace-expansion": "^2.0.2", "form-data": ">=4.0.4", "bluebird": ">=3.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a3f51be5..826dc99e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,7 +7,7 @@ settings: overrides: tar-fs: '>=3.1.1' esbuild: '>=0.25.0' - undici: ^6.21.3 + undici: ^6.27.0 brace-expansion: ^2.0.2 form-data: '>=4.0.4' bluebird: '>=3.7.2' @@ -882,8 +882,8 @@ importers: specifier: ^7.2.0 version: 7.2.0 undici: - specifier: ^6.21.3 - version: 6.26.0 + specifier: ^6.27.0 + version: 6.27.0 uuid: specifier: ^11.1.1 version: 11.1.1 @@ -10039,8 +10039,8 @@ packages: undici-types@7.10.0: resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - undici@6.26.0: - resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==} + undici@6.27.0: + resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} engines: {node: '>=18.17'} unicorn-magic@0.3.0: @@ -12730,7 +12730,7 @@ snapshots: '@qdrant/openapi-typescript-fetch': 1.2.6 '@sevinf/maybe': 0.5.0 typescript: 5.8.3 - undici: 6.26.0 + undici: 6.27.0 '@qdrant/openapi-typescript-fetch@1.2.6': {} @@ -15303,7 +15303,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 6.26.0 + undici: 6.27.0 whatwg-mimetype: 4.0.0 chokidar@3.6.0: @@ -20844,7 +20844,7 @@ snapshots: undici-types@7.10.0: {} - undici@6.26.0: {} + undici@6.27.0: {} unicorn-magic@0.3.0: {} diff --git a/src/package.json b/src/package.json index 62dfbd588..f48bdbb22 100644 --- a/src/package.json +++ b/src/package.json @@ -556,7 +556,7 @@ "tmp": "^0.2.3", "tree-sitter-wasms": "^0.1.12", "turndown": "^7.2.0", - "undici": "^6.21.3", + "undici": "^6.27.0", "uuid": "^11.1.1", "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.25.6", From 83825afe125c6e078ef04784709b0c972b8fb16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:04:31 +0200 Subject: [PATCH 08/16] chore(deps): remove unused @vscode/test-cli and .vscode-test.mjs Port of Zoo-Code PR #670. The @vscode/test-cli runner and its .vscode-test.mjs config are unused here: apps/vscode-e2e runs e2e via runTest.js (the @vscode/test-electron path), and no script invokes the vscode-test CLI. Removes both and prunes the lockfile. Co-authored-by: Elliott de Launay --- ...7_zoo-670-remove-unused-vscode-test-cli.md | 38 +++++ apps/vscode-e2e/.vscode-test.mjs | 16 -- apps/vscode-e2e/package.json | 1 - pnpm-lock.yaml | 161 ------------------ 4 files changed, 38 insertions(+), 178 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-670-remove-unused-vscode-test-cli.md delete mode 100644 apps/vscode-e2e/.vscode-test.mjs diff --git a/ai_plans/2026-06-27_zoo-670-remove-unused-vscode-test-cli.md b/ai_plans/2026-06-27_zoo-670-remove-unused-vscode-test-cli.md new file mode 100644 index 000000000..1824399b5 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-670-remove-unused-vscode-test-cli.md @@ -0,0 +1,38 @@ +# Port Zoo PR #670 — remove unused @vscode/test-cli + .vscode-test.mjs + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #670, commit `ad7dcfeab`, merged 2026-06-20. +- Author: Elliott de Launay (renovate[bot] dropped as a bot). +- Commit trailer: + ``` + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why + +The PR title says "update @vscode/test-cli to v0.0.12", but its second commit +("removing unused dep") instead **deletes** the `@vscode/test-cli` runner and its +`.vscode-test.mjs` config — they were unused. + +Verified the same holds in our fork: `apps/vscode-e2e` runs e2e via +`runTest.js` (the `@vscode/test-electron` path), not the `vscode-test` CLI. No +script invokes `vscode-test`, and nothing imports `.vscode-test.mjs` except the +file itself. So both are dead here too. + +## §2 Edits + +- Delete `apps/vscode-e2e/.vscode-test.mjs` (only consumer of `@vscode/test-cli`, + via `defineConfig`; carried our rebranded `RooVeterinaryInc.roo-cline` launchArg). +- Remove `"@vscode/test-cli": "^0.0.11"` from `apps/vscode-e2e/package.json`. +- Refresh `pnpm-lock.yaml` (prunes the test-cli dep tree, -161 lines). + +## §3 Scope cuts + +- Ignored `icon-map.json` "vscode-test" entries — unrelated material-icon mappings. + +## §4 Verify (binary acceptance) — all ✓ + +- No remaining `@vscode/test-cli` / `.vscode-test` references in tracked source. +- `pnpm install --lockfile-only` succeeds. +- `pnpm --filter @roo-code/vscode-e2e check-types` passes (e2e doesn't use the CLI). diff --git a/apps/vscode-e2e/.vscode-test.mjs b/apps/vscode-e2e/.vscode-test.mjs deleted file mode 100644 index c83f12c4b..000000000 --- a/apps/vscode-e2e/.vscode-test.mjs +++ /dev/null @@ -1,16 +0,0 @@ -/** - * See: https://code.visualstudio.com/api/working-with-extensions/testing-extension - */ - -import { defineConfig } from "@vscode/test-cli" - -export default defineConfig({ - label: "integrationTest", - files: "out/suite/**/*.test.js", - workspaceFolder: ".", - mocha: { - ui: "tdd", - timeout: 60000, - }, - launchArgs: ["--enable-proposed-api=RooVeterinaryInc.roo-cline", "--disable-extensions"], -}) diff --git a/apps/vscode-e2e/package.json b/apps/vscode-e2e/package.json index db647a873..e7962b9b4 100644 --- a/apps/vscode-e2e/package.json +++ b/apps/vscode-e2e/package.json @@ -16,7 +16,6 @@ "@types/mocha": "^10.0.10", "@types/node": "^20.19.25", "@types/vscode": "^1.95.0", - "@vscode/test-cli": "^0.0.11", "@vscode/test-electron": "^2.4.0", "glob": "^11.1.0", "mocha": "^11.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 826dc99e2..2351266a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,9 +178,6 @@ importers: '@types/vscode': specifier: ^1.95.0 version: 1.100.0 - '@vscode/test-cli': - specifier: ^0.0.11 - version: 0.0.11 '@vscode/test-electron': specifier: ^2.4.0 version: 2.5.2 @@ -1835,9 +1832,6 @@ packages: resolution: {integrity: sha512-H6bpd1JcDbuJsOS2dNft+CCGLzBqHJO/ST/4mMKhLAW641J6PpVJUw1szYsk/dTetdedbWxHpMkvFObOKeP8nw==} engines: {node: '>= 10'} - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} @@ -2414,10 +2408,6 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - '@jest/expect-utils@29.7.0': resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4663,11 +4653,6 @@ packages: '@vscode/ripgrep@1.17.0': resolution: {integrity: sha512-mBRKm+ASPkUcw4o9aAgfbusIu6H4Sdhw09bjeP1YOBFTJEZAnrnk6WZwzv8NEjgC82f7ILvhmb1WIElSugea6g==} - '@vscode/test-cli@0.0.11': - resolution: {integrity: sha512-qO332yvzFqGhBMJrp6TdwbIydiHgCtxXc2Nl6M58mbH/Z+0CyLR76Jzv4YWPEthhrARprzCRJUqzFvTHFhTj7Q==} - engines: {node: '>=18'} - hasBin: true - '@vscode/test-electron@2.5.2': resolution: {integrity: sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==} engines: {node: '>=16'} @@ -4847,10 +4832,6 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -5032,10 +5013,6 @@ packages: bignumber.js@9.3.0: resolution: {integrity: sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - binary@0.3.0: resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} @@ -5118,11 +5095,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - c8@9.1.0: - resolution: {integrity: sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==} - engines: {node: '>=14.14.0'} - hasBin: true - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -5218,10 +5190,6 @@ packages: resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} engines: {node: '>=18.17'} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -6851,9 +6819,6 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} @@ -7058,10 +7023,6 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-boolean-object@1.2.2: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} @@ -7290,18 +7251,6 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -7887,10 +7836,6 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - mammoth@1.12.0: resolution: {integrity: sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w==} engines: {node: '>=12.0.0'} @@ -9007,10 +8952,6 @@ packages: readdir-glob@1.1.3: resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -9720,10 +9661,6 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - supports-color@9.4.0: - resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} - engines: {node: '>=12'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -9771,10 +9708,6 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} @@ -10207,10 +10140,6 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -11696,8 +11625,6 @@ snapshots: '@basetenlabs/performance-client-win32-ia32-msvc': 0.0.10 '@basetenlabs/performance-client-win32-x64-msvc': 0.0.10 - '@bcoe/v8-coverage@0.2.3': {} - '@braintree/sanitize-url@7.1.1': {} '@changesets/apply-release-plan@7.0.13': @@ -12279,8 +12206,6 @@ snapshots: dependencies: minipass: 7.1.2 - '@istanbuljs/schema@0.1.3': {} - '@jest/expect-utils@29.7.0': dependencies: jest-get-type: 29.6.3 @@ -14657,18 +14582,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vscode/test-cli@0.0.11': - dependencies: - '@types/mocha': 10.0.10 - c8: 9.1.0 - chokidar: 3.6.0 - enhanced-resolve: 5.18.1 - glob: 11.1.0 - minimatch: 9.0.5 - mocha: 11.2.2 - supports-color: 9.4.0 - yargs: 17.7.2 - '@vscode/test-electron@2.5.2': dependencies: http-proxy-agent: 7.0.2 @@ -14866,11 +14779,6 @@ snapshots: any-promise@1.3.0: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - aproba@2.0.0: {} archiver-utils@2.1.0: @@ -15100,8 +15008,6 @@ snapshots: bignumber.js@9.3.0: {} - binary-extensions@2.3.0: {} - binary@0.3.0: dependencies: buffers: 0.1.1 @@ -15190,20 +15096,6 @@ snapshots: bytes@3.1.2: {} - c8@9.1.0: - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@istanbuljs/schema': 0.1.3 - find-up: 5.0.0 - foreground-child: 3.3.1 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.1.7 - test-exclude: 6.0.0 - v8-to-istanbul: 9.3.0 - yargs: 17.7.2 - yargs-parser: 21.1.1 - cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -15306,18 +15198,6 @@ snapshots: undici: 6.27.0 whatwg-mimetype: 4.0.0 - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -17156,8 +17036,6 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 - html-escaper@2.0.2: {} - html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 @@ -17381,10 +17259,6 @@ snapshots: dependencies: has-bigints: 1.1.0 - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-boolean-object@1.2.2: dependencies: call-bound: 1.0.4 @@ -17565,19 +17439,6 @@ snapshots: isobject@3.0.1: {} - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-reports@3.1.7: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -18142,10 +18003,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - make-dir@4.0.0: - dependencies: - semver: 7.7.3 - mammoth@1.12.0: dependencies: '@xmldom/xmldom': 0.8.10 @@ -19623,10 +19480,6 @@ snapshots: dependencies: minimatch: 5.1.6 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.2 - readdirp@4.1.2: {} reconnecting-eventsource@1.6.4: {} @@ -20518,8 +20371,6 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-color@9.4.0: {} - supports-preserve-symlinks-flag@1.0.0: {} symbol-tree@3.2.4: {} @@ -20573,12 +20424,6 @@ snapshots: term-size@2.2.1: {} - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 11.1.0 - minimatch: 3.1.2 - text-decoder@1.2.3: dependencies: b4a: 1.6.7 @@ -21016,12 +20861,6 @@ snapshots: uuid@9.0.1: {} - v8-to-istanbul@9.3.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 2.0.0 - vary@1.1.2: {} vaul@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): From 04f36ebcc8ee1ab3f464e3fe6d016e8414fe504b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:10:46 +0200 Subject: [PATCH 09/16] chore(deps): bump execa to ^9.6.1 Port of Zoo-Code PR #671. Raises the src execa floor from ^9.5.2 to ^9.6.1 (lockfile resolved 9.5.3) and refreshes the lockfile. Keeps our caret convention. Co-authored-by: Elliott de Launay --- ai_plans/2026-06-27_zoo-671-execa-9-6-1.md | 32 ++++++++++++++++++++++ pnpm-lock.yaml | 18 ++++++------ src/package.json | 2 +- 3 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-671-execa-9-6-1.md diff --git a/ai_plans/2026-06-27_zoo-671-execa-9-6-1.md b/ai_plans/2026-06-27_zoo-671-execa-9-6-1.md new file mode 100644 index 000000000..7b1d832a9 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-671-execa-9-6-1.md @@ -0,0 +1,32 @@ +# Port Zoo PR #671 — bump execa to 9.6.1 + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #671, merged 2026-06-20. +- Author: Elliott de Launay (renovate[bot] dropped as a bot). +- Commit trailer: + ``` + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why + +Routine (non-security) dependency bump of `execa` 9.5.3 → 9.6.1 in the extension. +Our src lockfile resolved 9.5.3 via the `^9.5.2` floor; raising the floor pulls +in 9.6.1. + +## §2 Edits + +- `src/package.json`: `"execa": "^9.5.2"` → `"^9.6.1"` (caret convention). +- Refresh `pnpm-lock.yaml` → src now resolves `execa@9.6.1`. + +## §3 Scope cuts + +- PR only touched `src/package.json`; left `apps/cli`, `packages/core`, + `packages/evals` execa ranges untouched (faithful to upstream scope; pnpm already + carries multiple execa versions across the monorepo). + +## §4 Verify (binary acceptance) — all ✓ + +- `pnpm install --lockfile-only` succeeds; src resolves `execa@9.6.1`. +- `pnpm --filter tumble-code check-types` passes (execa API unchanged for our usage). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2351266a6..f0a385d59 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -982,8 +982,8 @@ importers: specifier: ^0.25.0 version: 0.25.12 execa: - specifier: ^9.5.2 - version: 9.5.3 + specifier: ^9.6.1 + version: 9.6.1 glob: specifier: '>=11.1.0' version: 11.1.0 @@ -6287,14 +6287,14 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - execa@9.5.3: - resolution: {integrity: sha512-QFNnTvU3UjgWFy8Ef9iDHvIdcgZ344ebkwYx4/KLbR+CKQA4xBaHzv+iRpp86QfMHP8faFQLh8iOc57215y4Rg==} - engines: {node: ^18.19.0 || >=20.5.0} - execa@9.6.0: resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} engines: {node: ^18.19.0 || >=20.5.0} + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + exenv-es6@1.1.1: resolution: {integrity: sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==} @@ -16349,7 +16349,7 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.5.3: + execa@9.6.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 cross-spawn: 7.0.6 @@ -16364,7 +16364,7 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.1 - execa@9.6.0: + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 cross-spawn: 7.0.6 @@ -16377,7 +16377,7 @@ snapshots: pretty-ms: 9.2.0 signal-exit: 4.1.0 strip-final-newline: 4.0.0 - yoctocolors: 2.1.1 + yoctocolors: 2.1.2 exenv-es6@1.1.1: {} diff --git a/src/package.json b/src/package.json index f48bdbb22..85338dc69 100644 --- a/src/package.json +++ b/src/package.json @@ -592,7 +592,7 @@ "@vscode/vsce": "3.3.2", "ai": "^6.0.75", "esbuild-wasm": "^0.25.0", - "execa": "^9.5.2", + "execa": "^9.6.1", "glob": "^11.1.0", "mkdirp": "^3.0.1", "nock": "^14.0.4", From aa47bd208b3df7cda50b11308c3c82d9ff623016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:16:56 +0200 Subject: [PATCH 10/16] chore(deps): bump axios to ^1.18.0 Port of Zoo-Code PR #673. Raises the src axios floor from ^1.16.1 to ^1.18.0 (lockfile pinned 1.16.1) and refreshes the lockfile; our direct dep resolves to axios@1.18.1. Skips the upstream renovate.json change (our deps flow owns that). Co-authored-by: Elliott de Launay --- ai_plans/2026-06-27_zoo-673-axios-1-18-0.md | 39 +++++++++++++++++++++ pnpm-lock.yaml | 17 +++++++-- src/package.json | 2 +- 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-673-axios-1-18-0.md diff --git a/ai_plans/2026-06-27_zoo-673-axios-1-18-0.md b/ai_plans/2026-06-27_zoo-673-axios-1-18-0.md new file mode 100644 index 000000000..75aa03f75 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-673-axios-1-18-0.md @@ -0,0 +1,39 @@ +# Port Zoo PR #673 — bump axios to ^1.18.0 + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #673, commit `ccf07eba3`, merged 2026-06-20. +- Author: Elliott de Launay (renovate[bot] dropped as a bot). +- Commit trailer: + ``` + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why + +Renovate update of `axios` to v1.18.0. Zoo's PR left `package.json` at `^1.16.1` +and only refreshed the lockfile (a large -550-line dedup). Our src lockfile pinned +`axios@1.16.1`. + +For our fork the intent-documenting port is to raise the declared floor to +`^1.18.0` so the update is explicit; `pnpm install` then resolves our direct dep +to `axios@1.18.1` (latest 1.18.x, ≥ the target). + +## §2 Edits + +- `src/package.json`: `"axios": "^1.16.1"` → `"^1.18.0"`. +- Refresh `pnpm-lock.yaml` (targeted; direct axios → 1.18.1). + +## §3 Scope cuts (divergence / noise) + +- **Did NOT port `renovate.json`** — Zoo's renovate/bot config; our own deps flow + owns that (rubric: skip CI/bot config). +- Did not chase the remaining transitive `axios@1.16.1` (pulled by another + package's subtree, not our direct dependency). +- Did not reproduce Zoo's full 712-line lockfile dedup — our `pnpm install` + produces our own minimal resolution. + +## §4 Verify (binary acceptance) — all ✓ + +- `pnpm install --lockfile-only` succeeds; our direct axios resolves `1.18.1`. +- `pnpm --filter tumble-code check-types` passes. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0a385d59..821934b88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -711,8 +711,8 @@ importers: specifier: ^0.5.0 version: 0.5.0 axios: - specifier: ^1.16.1 - version: 1.16.1 + specifier: ^1.18.0 + version: 1.18.1 cheerio: specifier: ^1.0.0 version: 1.0.0 @@ -4939,6 +4939,9 @@ packages: axios@1.16.1: resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==} + axios@1.18.1: + resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + azure-devops-node-api@12.5.0: resolution: {integrity: sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==} @@ -14944,6 +14947,16 @@ snapshots: - debug - supports-color + axios@1.18.1: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.4 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + azure-devops-node-api@12.5.0: dependencies: tunnel: 0.0.6 diff --git a/src/package.json b/src/package.json index 85338dc69..43032c9e5 100644 --- a/src/package.json +++ b/src/package.json @@ -500,7 +500,7 @@ "@vscode/codicons": "^0.0.36", "ai-sdk-provider-poe": "2.0.18", "async-mutex": "^0.5.0", - "axios": "^1.16.1", + "axios": "^1.18.0", "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", From a784fc7c82b3eeefd3e28d97a7951be00a398d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:25:03 +0200 Subject: [PATCH 11/16] fix(ask_followup_question): report non-array follow_up as a type error Port of Zoo-Code PR #662 (fixes upstream #511). When a weak model emits follow_up as a present-but-non-array value (a keyed object/string/number from incremental JSON parsing), the tool reported a misleading "Missing value for required parameter 'follow_up'", causing retry loops with the same payload. Now null/undefined still reports a missing parameter, while a present-but-wrong-type value reports a precise "must be an array" type error so the model can correct it. Co-authored-by: Elliott de Launay --- ...7_zoo-662-followup-non-array-type-error.md | 48 +++++++++++++++++++ .../assistant-message/NativeToolCallParser.ts | 6 +++ src/core/tools/AskFollowupQuestionTool.ts | 25 +++++++++- .../__tests__/askFollowupQuestionTool.spec.ts | 32 ++++++++++++- 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-662-followup-non-array-type-error.md diff --git a/ai_plans/2026-06-27_zoo-662-followup-non-array-type-error.md b/ai_plans/2026-06-27_zoo-662-followup-non-array-type-error.md new file mode 100644 index 000000000..5363f62e7 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-662-followup-non-array-type-error.md @@ -0,0 +1,48 @@ +# Port Zoo PR #662 — ask_followup_question: report non-array follow_up as a type error + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #662, commit `37008b7f6`, merged 2026-06-22. Fixes #511. +- Author: edelauna (Elliott de Launay). +- Commit trailer: + ``` + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why (weak-model robustness) + +When a weak model emits `follow_up` as a non-array (a keyed object / string / number, +common from incremental JSON parsing), the tool reported the misleading +"Missing value for required parameter 'follow_up'" — so the model kept retrying the +same malformed payload. The fix distinguishes: + +- `follow_up` null/undefined → missing-parameter error (unchanged), vs +- `follow_up` present-but-not-an-array → a precise "must be an array" type error. + +Directly aligns with our weak-model design work ([[feedback_design_for_weak_models]]). +Our fork was at the exact pre-PR state (`if (!follow_up || !Array.isArray(follow_up))` +collapsed both cases into the missing-param branch). + +## §2 Edits + +- `src/core/tools/AskFollowupQuestionTool.ts`: + - add a `recordValidationError(message)` helper (increments mistake count, records + tool error, `say("error", …)`, pushes `formatResponse.toolError(message)`). + - split the validation: null/undefined → `recordMissingParamError`; non-array → + `recordValidationError("The 'follow_up' parameter must be an array … Retry with +'follow_up' as a JSON array.")`. + - interface comment documenting the runtime guard. +- `src/core/assistant-message/NativeToolCallParser.ts`: explanatory comment on the + `ask_followup_question` case (it already forwards the raw present-but-non-array + value so the tool can emit the precise error). +- `src/core/tools/__tests__/askFollowupQuestionTool.spec.ts`: strengthen the string + case to assert the type-error message (not "Missing value"), and add an object case + asserting `say("error", …)`, the pushed "must be an array" result, and that + `task.ask` is NOT called with an invalid payload. + +## §3 Verify (binary acceptance) — all ✓ + +- `npx vitest run core/tools/__tests__/askFollowupQuestionTool` → 36 pass. +- `pnpm --filter tumble-code check-types` passes. + +## §4 Co-author — see §0. diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index 6ccbb64ac..86d095f9a 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -829,6 +829,12 @@ export class NativeToolCallParser { break case "ask_followup_question": + // Require a question and a present follow_up. When follow_up is + // present-but-not-an-array (e.g. an object/string/number produced by + // incremental JSON parsing), we still construct nativeArgs and forward + // the raw value so the tool can emit a precise "must be an array" error + // instead of the generic parser failure, which would surface as a + // misleading "Missing value for required parameter 'follow_up'" error. if (args.question !== undefined && args.follow_up !== undefined) { nativeArgs = { question: args.question, diff --git a/src/core/tools/AskFollowupQuestionTool.ts b/src/core/tools/AskFollowupQuestionTool.ts index c45acc824..5298709ee 100644 --- a/src/core/tools/AskFollowupQuestionTool.ts +++ b/src/core/tools/AskFollowupQuestionTool.ts @@ -11,6 +11,9 @@ interface Suggestion { interface AskFollowupQuestionParams { question: string + // follow_up is typed as an array, but at runtime the value may arrive as a + // non-array (object/string/number) due to incremental JSON parsing, so the + // runtime validation in execute() guards against that explicitly. follow_up: Suggestion[] } @@ -28,17 +31,37 @@ export class AskFollowupQuestionTool extends BaseTool<"ask_followup_question"> { pushToolResult(await task.sayAndCreateMissingParamError("ask_followup_question", paramName)) } + const recordValidationError = async (message: string): Promise => { + task.consecutiveMistakeCount++ + task.recordToolError("ask_followup_question") + task.didToolFailInCurrentTurn = true + await task.say("error", message) + pushToolResult(formatResponse.toolError(message)) + } + try { if (!question) { await recordMissingParamError("question") return } - if (!follow_up || !Array.isArray(follow_up)) { + // Truly missing follow_up (null/undefined) -> report as a missing parameter. + if (follow_up === undefined || follow_up === null) { await recordMissingParamError("follow_up") return } + // Present-but-wrong-type follow_up (object/string/number) -> report a clear + // type/shape error rather than the misleading "Missing value" message, so the + // model can correct it instead of looping with the same payload. + if (!Array.isArray(follow_up)) { + await recordValidationError( + "The 'follow_up' parameter must be an array of suggestion objects, each shaped like { text: string, mode?: string }. " + + "Retry with 'follow_up' as a JSON array.", + ) + return + } + // Transform follow_up suggestions to the format expected by task.ask const follow_up_json = { question, diff --git a/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts b/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts index 08a8394ac..309eeea4b 100644 --- a/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts +++ b/src/core/tools/__tests__/askFollowupQuestionTool.spec.ts @@ -83,7 +83,7 @@ describe("AskFollowupQuestionTool", () => { expect(mockTask.sayAndCreateMissingParamError).toHaveBeenCalledWith("ask_followup_question", "follow_up") }) - it("should handle follow_up that is not an array", async () => { + it("should report a type error when follow_up is a string", async () => { const params = { question: "What?", follow_up: "not-an-array" as any } await tool.execute(params, mockTask, mockCallbacks) @@ -91,7 +91,35 @@ describe("AskFollowupQuestionTool", () => { expect(mockTask.consecutiveMistakeCount).toBe(1) expect(mockTask.recordToolError).toHaveBeenCalledWith("ask_followup_question") expect(mockTask.didToolFailInCurrentTurn).toBe(true) - expect(mockTask.sayAndCreateMissingParamError).toHaveBeenCalledWith("ask_followup_question", "follow_up") + // A present-but-non-array value is a type error. + expect(mockTask.sayAndCreateMissingParamError).not.toHaveBeenCalled() + const pushed = (mockCallbacks.pushToolResult as any).mock.calls[0][0] + expect(pushed).toContain("must be an array") + expect(pushed).not.toContain("Missing value") + }) + + it("should report a type error when follow_up is an object", async () => { + // Reproduces the issue: follow_up arrives as a keyed object instead of an array. + const params = { + question: "How should I proceed?", + follow_up: { + "0": { mode: null, text: "Keep the guard" }, + "1": { mode: null, text: "Remove the guard" }, + } as any, + } + + await tool.execute(params, mockTask, mockCallbacks) + + expect(mockTask.consecutiveMistakeCount).toBe(1) + expect(mockTask.recordToolError).toHaveBeenCalledWith("ask_followup_question") + expect(mockTask.didToolFailInCurrentTurn).toBe(true) + expect(mockTask.sayAndCreateMissingParamError).not.toHaveBeenCalled() + expect(mockTask.say).toHaveBeenCalledWith("error", expect.stringContaining("must be an array")) + const pushed = (mockCallbacks.pushToolResult as any).mock.calls[0][0] + expect(pushed).toContain("must be an array") + expect(pushed).not.toContain("Missing value") + // The tool must not proceed to ask the user with an invalid payload. + expect(mockTask.ask).not.toHaveBeenCalled() }) // ===== Happy path tests ===== From cdc62a71bfc8467c1944464ea0063e5fced3650f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:34:07 +0200 Subject: [PATCH 12/16] =?UTF-8?q?docs(zoo-port):=20defer=20#470=20(diffFuz?= =?UTF-8?q?zyThreshold)=20=E2=80=94=20assessment=20+=20groundwork?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...7_zoo-470-diff-fuzzy-threshold-DEFERRED.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 ai_plans/2026-06-27_zoo-470-diff-fuzzy-threshold-DEFERRED.md diff --git a/ai_plans/2026-06-27_zoo-470-diff-fuzzy-threshold-DEFERRED.md b/ai_plans/2026-06-27_zoo-470-diff-fuzzy-threshold-DEFERRED.md new file mode 100644 index 000000000..11a7f9eef --- /dev/null +++ b/ai_plans/2026-06-27_zoo-470-diff-fuzzy-threshold-DEFERRED.md @@ -0,0 +1,45 @@ +# Zoo PR #470 — configurable diffFuzzyThreshold — DEFERRED (needs dedicated session) + +Upstream: Zoo-Code-Org/Zoo-Code PR #470, commit `518bae479`, merged 2026-06-23. +Author: edelauna (+ coderabbitai bot, dropped). Fixes "Edit Unsuccessful" friction. + +## Why deferred (not unsafe, but too large + diverged for an autonomous loop tick) + +- **32 files / 588 insertions**: a new opt-in `diffFuzzyThreshold` setting threaded + through types → ClineProvider → Task → the multi-search-replace diff strategy, + plus a webview slider, SettingsView/ExtensionStateContext wiring, **18 locales**, + and **4 test files**. +- **Plumbing anchors diverge**: Zoo threads the setting next to `rateLimitClock` in + `Task` (TaskOptions + constructor) and at ClineProvider Task-creation sites — **our + fork has no `rateLimitClock`**, so each of ~10 sites needs individual re-anchoring. +- The core diff-strategy change itself is **low risk** (our `multi-search-replace.ts` + matches Zoo's pre-PR except trivial const→let; the `startLine !== undefined` → + `startLine` change is benign with 1-based line numbers). + +## Groundwork for the focused session (all diffs already analyzed) + +1. `packages/types/src/global-settings.ts`: add `export const DEFAULT_DIFF_FUZZY_THRESHOLD = 1.0` + and `diffFuzzyThreshold: z.number().min(0.5).max(1).optional()` to `globalSettingsSchema`. + **Skip** Zoo's unrelated import-reorder at the top of that file. +2. `packages/types/src/vscode-extension-host.ts`: add `diffFuzzyThreshold: number` to `ExtensionState`. +3. `src/core/diff/strategies/multi-search-replace.ts`: import `DEFAULT_DIFF_FUZZY_THRESHOLD`; + constructor clamps `Math.max(0.5, Math.min(1.0, fuzzyThreshold ?? DEFAULT_DIFF_FUZZY_THRESHOLD))`; + add Levenshtein-distance / search-length diagnostics to the no-match error; new + "Original Content" slice for the range branch. +4. `src/core/task/Task.ts`: add `diffFuzzyThreshold?: number` to `TaskOptions` and the + constructor destructuring (anchor on `initialStatus`, NOT `rateLimitClock`), then + `new MultiSearchReplaceDiffStrategy(diffFuzzyThreshold)`. +5. `src/core/webview/ClineProvider.ts`: destructure `diffFuzzyThreshold` from `getState()` + at the two `createTask`/rehydrate sites and pass to `new Task({...})`; add + `diffFuzzyThreshold: … ?? DEFAULT_DIFF_FUZZY_THRESHOLD` to getStateToPostToWebview and + getState return objects. Re-anchor away from `rateLimitClock` lines. +6. webview: `ContextManagementSettings.tsx` (Slider 0.5–1.0 step 0.01 under a new + `fileEdits.diffFuzzyThreshold` SearchableSetting), `SettingsView.tsx` (destructure + + pass prop + include in save payload), `ExtensionStateContext.tsx` (default). +7. i18n: add `contextManagement.fileEdits.diffFuzzyThreshold.{label,description}` to all + 18 `settings.json` locales (en text in the PR). +8. Tests: `multi-search-replace.spec.ts` (+218, diagnostics/clamp), `ClineProvider.spec.ts` + (+54, passthrough), `single-open-invariant.spec.ts` (+40), `ContextManagementSettings.spec.tsx` + (+21), `SettingsView.spec.tsx` (+38), `ExtensionStateContext.spec.tsx` (+3). + +Credit on port: `Co-authored-by: Elliott de Launay `. From 04057ad148e884d4aee777d6684ceff998ee9ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:37:17 +0200 Subject: [PATCH 13/16] docs(prompt): enhance apply_diff tool instructions for weaker models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port of Zoo-Code PR #619. Reword ':start_line:' from "required" to "strongly recommended" (matching our diff strategy, which tolerates a missing start line) and add a CRITICAL section spelling out exact ':start_line:[integer]' syntax, the whitespace-exact match rule, and separator placement — improving apply_diff success rates for weaker models like Gemini. Co-authored-by: Andrew Schmeder <149117631+awschmeder@users.noreply.github.com> --- ...-06-27_zoo-619-apply-diff-prompt-gemini.md | 34 +++++++++++++++++++ .../prompts/tools/native-tools/apply_diff.ts | 9 +++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-619-apply-diff-prompt-gemini.md diff --git a/ai_plans/2026-06-27_zoo-619-apply-diff-prompt-gemini.md b/ai_plans/2026-06-27_zoo-619-apply-diff-prompt-gemini.md new file mode 100644 index 000000000..275abd033 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-619-apply-diff-prompt-gemini.md @@ -0,0 +1,34 @@ +# Port Zoo PR #619 — enhance apply_diff prompt for weaker models (Gemini) + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #619, merged 2026-06-24. +- Author: Andrew Schmeder. +- Commit trailer: + ``` + Co-authored-by: Andrew Schmeder <149117631+awschmeder@users.noreply.github.com> + ``` + +## §1 What & why (weak-model robustness) + +Improves the `apply_diff` native-tool parameter description to raise weak-model +(Gemini, etc.) success rates: + +- `:start_line:` reworded from "is required" → "strongly recommended" (matches our + diff strategy, which already tolerates a missing start line). +- Adds a CRITICAL section spelling out exact `:start_line:[integer]` syntax (no + `:220` / `:start_line=220` shorthands), the 100% whitespace-exact match rule, and + the `-------` separator placement. + +Our `apply_diff.ts` matched Zoo's pre-PR exactly; clean port. Aligns with +[[feedback_design_for_weak_models]]. + +## §2 Edits + +- `src/core/prompts/tools/native-tools/apply_diff.ts`: update `DIFF_PARAMETER_DESCRIPTION`. +- Skipped Zoo's changeset file (our fork doesn't use that flow). + +## §3 Verify (binary acceptance) — all ✓ + +- No snapshot/spec asserts the old wording. +- `pnpm --filter tumble-code check-types` passes. diff --git a/src/core/prompts/tools/native-tools/apply_diff.ts b/src/core/prompts/tools/native-tools/apply_diff.ts index 3938e4886..cdbd99df3 100644 --- a/src/core/prompts/tools/native-tools/apply_diff.ts +++ b/src/core/prompts/tools/native-tools/apply_diff.ts @@ -2,14 +2,19 @@ import type OpenAI from "openai" const APPLY_DIFF_DESCRIPTION = `Apply precise, targeted modifications to an existing file using one or more search/replace blocks. This tool is for surgical edits only; the 'SEARCH' block must exactly match the existing content, including whitespace and indentation. To make multiple targeted changes, provide multiple SEARCH/REPLACE blocks in the 'diff' parameter. Use the 'read_file' tool first if you are not confident in the exact content to search for.` -const DIFF_PARAMETER_DESCRIPTION = `A string containing one or more search/replace blocks defining the changes. The ':start_line:' is required and indicates the starting line number of the original content. You must not add a start line for the replacement content. Each block must follow this format: +const DIFF_PARAMETER_DESCRIPTION = `A string containing one or more search/replace blocks defining the changes. The ':start_line:' is strongly recommended and indicates the starting line number of the original content. You must not add a start line for the replacement content. Each block must follow this format: <<<<<<< SEARCH :start_line:[line_number] ------- [exact content to find] ======= [new content to replace with] ->>>>>>> REPLACE` +>>>>>>> REPLACE + +CRITICAL: +- The ':start_line:[line_number]' header is strongly recommended for accurate matching. When provided, it must follow the exact syntax ':start_line:[integer]' (for example: ':start_line:220'). Do not write headers with shorthand forms like ':220' or variations like ':start_line=220'. +- Copy the exact lines from the source file for a 100% string match including all whitespace, indentation, and newlines. +- Ensure the separator '-------' is on its own line immediately following ':start_line:[line_number]' with a newline.` export const apply_diff = { type: "function", From 5d77025dbe12d3f696f9df7850ded5949ef3af7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:47:00 +0200 Subject: [PATCH 14/16] fix: parse Gemma 4 reasoning tags alongside Port of Zoo-Code PR #324. Gemma 4 emits reasoning inside rather than . Generalizes TagMatcher to accept multiple tag names so reasoning is extracted for both, while a stray won't close an active block. Wires ["think", "thought"] into the OpenAI-compatible, LM Studio, native Ollama, and OpenAI providers. Adds the upstream tag-matcher unit tests plus integration tests on the shared streaming path. Co-authored-by: Sagid M Co-authored-by: Sagid Magomedov Co-authored-by: Elliott de Launay --- .../2026-06-27_zoo-324-gemma4-thought-tags.md | 48 ++++++ .../base-openai-compatible-provider.spec.ts | 78 ++++++++- .../base-openai-compatible-provider.ts | 2 +- src/api/providers/lm-studio.ts | 2 +- src/api/providers/native-ollama.ts | 2 +- src/api/providers/openai.ts | 2 +- src/utils/__tests__/tag-matcher.spec.ts | 154 ++++++++++++++++++ src/utils/tag-matcher.ts | 72 ++++++-- 8 files changed, 331 insertions(+), 29 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-324-gemma4-thought-tags.md create mode 100644 src/utils/__tests__/tag-matcher.spec.ts diff --git a/ai_plans/2026-06-27_zoo-324-gemma4-thought-tags.md b/ai_plans/2026-06-27_zoo-324-gemma4-thought-tags.md new file mode 100644 index 000000000..92aa8558c --- /dev/null +++ b/ai_plans/2026-06-27_zoo-324-gemma4-thought-tags.md @@ -0,0 +1,48 @@ +# Port Zoo PR #324 — parse Gemma 4 `` reasoning tags alongside `` + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #324, commit `0084cc899`, merged 2026-06-26. +- Authors: Sagid M / Sagid Magomedov, Elliott de Launay (edelauna). +- Commit trailers: + ``` + Co-authored-by: Sagid M + Co-authored-by: Sagid Magomedov + Co-authored-by: Elliott de Launay + ``` + +## §1 What & why (weak-model robustness) + +Gemma 4 emits reasoning inside `` rather than ``. +This generalizes `TagMatcher` to accept multiple tag names so reasoning extraction +works for both, without a closing `` wrongly terminating a `` block. +Aligns with [[feedback_design_for_weak_models]]. + +Our `src/utils/tag-matcher.ts` matched Zoo's pre-PR exactly; clean port. + +## §2 Edits + +- `src/utils/tag-matcher.ts`: constructor accepts `string | [string, ...string[]]`; + tracks `tagNames` / `activeTagNames` / `candidates` so any of the names opens a + block and only the matching name closes it. (Replaced wholesale with the upstream + post-PR version — our file was byte-identical to Zoo's pre-PR.) +- `base-openai-compatible-provider.ts`, `lm-studio.ts`, `native-ollama.ts`, + `openai.ts`: `new TagMatcher("think", …)` → `new TagMatcher(["think", "thought"], …)`. +- Tests: add `src/utils/__tests__/tag-matcher.spec.ts` (new upstream file, 18 cases); + add two `` integration tests + tighten one flush assertion in + `base-openai-compatible-provider.spec.ts`. + +## §3 Scope cuts (divergence) + +- **Skipped the `openai.spec.ts` +169-line additions** — our `openai.spec.ts` has + diverged ~495 lines from Zoo's (timeout work in #567 + other), so the additions + don't anchor cleanly. The behavior is already covered by `tag-matcher.spec.ts` + (the matcher unit) and the `base-openai-compatible-provider.spec.ts` `` + integration tests (the shared streaming path). + +## §4 Verify (binary acceptance) — all ✓ + +- `pnpm --filter tumble-code check-types` passes. +- `npx vitest run utils/__tests__/tag-matcher api/providers/__tests__/base-openai-compatible-provider` + → 38 pass (incl. the 2 new `` tests). +- 4 affected provider suites still green (121 tests). diff --git a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts index c1753bc45..f7cc5da4d 100644 --- a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts +++ b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts @@ -95,6 +95,75 @@ describe("BaseOpenAiCompatibleProvider", () => { ]) }) + it("should handle reasoning tags () from stream", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vi + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: "Deep thought" } }] }, + }) + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: " here" } }] }, + }) + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: "Result: 42" } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + const stream = handler.createMessage("system prompt", []) + const chunks = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + expect(chunks).toEqual([ + { type: "reasoning", text: "Deep thought" }, + { type: "reasoning", text: " here" }, + { type: "text", text: "Result: 42" }, + ]) + }) + + it("should not close tag with tag", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vi + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: "Thinking" } }] }, + }) + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: " but closing with wrong tag" } }] }, + }) + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: " still thinking" } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + const stream = handler.createMessage("system prompt", []) + const chunks = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + // The tag should be treated as text since it doesn't match the active tag + expect(chunks).toEqual([ + { type: "reasoning", text: "Thinking" }, + { type: "reasoning", text: " but closing with wrong tag" }, + { type: "reasoning", text: " still thinking" }, + ]) + }) + it("should handle complete tag in a single chunk", async () => { mockCreate.mockImplementationOnce(() => { return { @@ -151,13 +220,8 @@ describe("BaseOpenAiCompatibleProvider", () => { chunks.push(chunk) } - // TagMatcher should handle incomplete tags and flush remaining content - expect(chunks.length).toBeGreaterThan(0) - expect( - chunks.some( - (c) => (c.type === "text" || c.type === "reasoning") && c.text.includes("Incomplete thought"), - ), - ).toBe(true) + // TagMatcher should flush incomplete reasoning content on stream end + expect(chunks).toContainEqual({ type: "reasoning", text: "Incomplete thought" }) }) it("should handle text without any tags", async () => { diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index 28c812660..b6094f9cc 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -118,7 +118,7 @@ export abstract class BaseOpenAiCompatibleProvider const stream = await this.createStream(systemPrompt, messages, metadata) const matcher = new TagMatcher( - "think", + ["think", "thought"], (chunk) => ({ type: chunk.matched ? "reasoning" : "text", diff --git a/src/api/providers/lm-studio.ts b/src/api/providers/lm-studio.ts index fd9c25ba3..238ae0aeb 100644 --- a/src/api/providers/lm-studio.ts +++ b/src/api/providers/lm-studio.ts @@ -105,7 +105,7 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan } const matcher = new TagMatcher( - "think", + ["think", "thought"], (chunk) => ({ type: chunk.matched ? "reasoning" : "text", diff --git a/src/api/providers/native-ollama.ts b/src/api/providers/native-ollama.ts index aa35d27a9..c392f6b5c 100644 --- a/src/api/providers/native-ollama.ts +++ b/src/api/providers/native-ollama.ts @@ -215,7 +215,7 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio ] const matcher = new TagMatcher( - "think", + ["think", "thought"], (chunk) => ({ type: chunk.matched ? "reasoning" : "text", diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index b3d6ea822..91e9a4eda 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -258,7 +258,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl } const matcher = new TagMatcher( - "think", + ["think", "thought"], (chunk) => ({ type: chunk.matched ? "reasoning" : "text", diff --git a/src/utils/__tests__/tag-matcher.spec.ts b/src/utils/__tests__/tag-matcher.spec.ts new file mode 100644 index 000000000..114eaccce --- /dev/null +++ b/src/utils/__tests__/tag-matcher.spec.ts @@ -0,0 +1,154 @@ +// npx vitest utils/__tests__/tag-matcher.spec.ts + +import { TagMatcher } from "../tag-matcher" + +describe("TagMatcher", () => { + describe("collect() chunk merging (line 52)", () => { + it("merges consecutive same-type chars into one chunk within a single call", () => { + // Two text chars in one update() → both hit collect() with matched=false + // second char finds last chunk same type → last.data += char (line 52) + const matcher = new TagMatcher("think") + const result = matcher.update("ab") + expect(result).toEqual([{ matched: false, data: "ab" }]) + }) + + it("merges consecutive reasoning chars within a single call", () => { + const matcher = new TagMatcher("think") + matcher.update("") + const result = matcher.update("ab") + expect(result).toEqual([{ matched: true, data: "ab" }]) + }) + }) + + describe("final() with a chunk argument (line 131)", () => { + it("processes a chunk passed directly to final()", () => { + // Call final() with a chunk instead of update() — exercises line 131 + const matcher = new TagMatcher("think") + const result = matcher.final("hello") + expect(result).toEqual([{ matched: false, data: "hello" }]) + }) + + it("processes a closing tag passed to final()", () => { + const matcher = new TagMatcher("think") + // Don't use update() — keeps reasoning in the buffer so final() flushes it + const result = matcher.final("reasoning") + expect(result.some((r) => r.matched && r.data === "reasoning")).toBe(true) + }) + }) + + describe("space handling in TAG_OPEN (lines 93-97)", () => { + it("tolerates a space before tag name has started (line 95: all candidates at index 0)", () => { + // "< think>" — space arrives when all candidates are at index 0 + // hits line 95 (continue), candidates survive, 't' then matches normally + const matcher = new TagMatcher("think") + const result = matcher.final("< think>content") + expect(result.some((r) => r.matched && r.data === "content")).toBe(true) + }) + + it("drops mid-match candidates on a space (line 97)", () => { + // "" — space arrives mid-match (index > 0, index < name.length) + // those candidates are dropped, tag is not opened + const matcher = new TagMatcher("think") + const result = matcher.final("content") + expect(result.every((r) => !r.matched)).toBe(true) + }) + }) + + describe("multi-tag constructor (string[])", () => { + it("opens and closes when constructed with array", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("deep reasoningdone") + expect(result.some((r) => r.matched && r.data === "deep reasoning")).toBe(true) + expect(result.some((r) => !r.matched && r.data === "done")).toBe(true) + }) + + it("opens and closes when constructed with array", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("thinkingdone") + expect(result.some((r) => r.matched && r.data === "thinking")).toBe(true) + expect(result.some((r) => !r.matched && r.data === "done")).toBe(true) + }) + + it(" open is not closed by (cross-tag isolation)", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("reasoningstill reasoningdone") + // must be treated as text since active tag is + expect(result.some((r) => r.matched && r.data.includes(""))).toBe(true) + expect(result.some((r) => !r.matched && r.data === "done")).toBe(true) + }) + + it(" open is not closed by (inverse cross-tag isolation)", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("reasoningstill reasoningdone") + // must be treated as text since active tag is + expect(result.some((r) => r.matched && r.data.includes(""))).toBe(true) + expect(result.some((r) => !r.matched && r.data === "done")).toBe(true) + }) + }) + + describe("chunk split at mid-tag-name boundary", () => { + it("correctly opens tag split across two update() calls", () => { + const matcher = new TagMatcher("think") + const first = matcher.update("content") + expect(second.some((r) => r.matched && r.data === "content")).toBe(true) + }) + }) + + describe("unmatched > in TAG_OPEN falls back to TEXT", () => { + it("treats as plain text when xyz is not a configured tag name", () => { + const matcher = new TagMatcher("think") + const result = matcher.final("content") + expect(result.every((r) => !r.matched)).toBe(true) + }) + + it("treats stray closing tag as plain text when no tag is open", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("finaltext") + expect(result).toEqual([{ matched: false, data: "finaltext" }]) + }) + + it("treats extra closing tag after a closed block as plain text", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("thinkingfinaltext") + expect(result.some((r) => r.matched && r.data === "thinking")).toBe(true) + expect(result.some((r) => !r.matched && r.data === "finaltext")).toBe(true) + }) + }) + + describe("nested tags", () => { + it("treats inner as text when outer is active", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("outerinner middlefinal") + expect(result.some((r) => r.matched && r.data.includes("inner"))).toBe(true) + expect(result.some((r) => !r.matched && r.data === "final")).toBe(true) + }) + + it("correctly unwinds nested same-name tags", () => { + const matcher = new TagMatcher(["think", "thought"]) + const result = matcher.final("outerinner middlefinal") + expect(result.some((r) => r.matched && r.data.includes("inner"))).toBe(true) + expect(result.some((r) => !r.matched && r.data === "final")).toBe(true) + }) + }) + + describe("space handling in TAG_CLOSE (line 119)", () => { + it("tolerates a trailing space before > in closing tag ()", () => { + // space at index === tagName.length hits line 119 (continue) + const matcher = new TagMatcher("think") + const result = matcher.final("reasoningafter") + expect(result.some((r) => r.matched && r.data === "reasoning")).toBe(true) + expect(result.some((r) => !r.matched && r.data === "after")).toBe(true) + }) + + it("tolerates a leading space after )", () => { + // space at index === 0 hits line 119 (continue) + const matcher = new TagMatcher("think") + const result = matcher.final("reasoningafter") + expect(result.some((r) => r.matched && r.data === "reasoning")).toBe(true) + expect(result.some((r) => !r.matched && r.data === "after")).toBe(true) + }) + }) +}) diff --git a/src/utils/tag-matcher.ts b/src/utils/tag-matcher.ts index 38d99a290..f10bf2f1e 100644 --- a/src/utils/tag-matcher.ts +++ b/src/utils/tag-matcher.ts @@ -17,11 +17,17 @@ export class TagMatcher { state: "TEXT" | "TAG_OPEN" | "TAG_CLOSE" = "TEXT" depth = 0 pointer = 0 + private readonly tagNames: string[] + private activeTagNames: string[] = [] + private candidates: { name: string; index: number }[] = [] + constructor( - readonly tagName: string, + tagName: string | [string, ...string[]], readonly transform?: (chunks: TagMatcherResult) => Result, readonly position = 0, - ) {} + ) { + this.tagNames = Array.isArray(tagName) ? tagName : [tagName] + } private collect() { if (!this.cached.length) { return @@ -56,39 +62,64 @@ export class TagMatcher { if (this.state === "TEXT") { if (char === "<" && (this.pointer <= this.position + 1 || this.matched)) { this.state = "TAG_OPEN" - this.index = 0 + if (this.depth === 0) { + this.candidates = this.tagNames.map((name) => ({ name, index: 0 })) + } else { + const active = this.activeTagNames.at(-1) + this.candidates = active ? [{ name: active, index: 0 }] : [] + } } else { this.collect() } } else if (this.state === "TAG_OPEN") { - if (char === ">" && this.index === this.tagName.length) { - this.state = "TEXT" - if (!this.matched) { - this.cached = [] + if (char === ">") { + const matched = this.candidates.find((c) => c.index === c.name.length) + if (matched) { + this.state = "TEXT" + this.activeTagNames.push(matched.name) + if (!this.matched) { + this.cached = [] + } + this.depth++ + this.matched = true + continue + } else { + this.state = "TEXT" + this.collect() } - this.depth++ - this.matched = true - } else if (this.index === 0 && char === "/") { + } else if (this.candidates.every((c) => c.index === 0) && char === "/") { this.state = "TAG_CLOSE" - } else if (char === " " && (this.index === 0 || this.index === this.tagName.length)) { + this.index = 0 continue - } else if (this.tagName[this.index] === char) { - this.index++ + } else if (char === " ") { + const remaining = this.candidates.filter((c) => c.index === 0 || c.index === c.name.length) + if (remaining.length === this.candidates.length) { + continue + } + this.candidates = remaining } else { - this.state = "TEXT" - this.collect() + this.candidates = this.candidates.filter((c) => c.name[c.index] === char) + for (const c of this.candidates) { + c.index++ + } + if (this.candidates.length === 0) { + this.state = "TEXT" + this.collect() + } } } else if (this.state === "TAG_CLOSE") { - if (char === ">" && this.index === this.tagName.length) { + const tagName = this.activeTagNames.at(-1) ?? this.tagNames[0] + if (char === ">" && this.index === tagName.length) { this.state = "TEXT" this.depth-- + this.activeTagNames.pop() this.matched = this.depth > 0 if (!this.matched) { this.cached = [] } - } else if (char === " " && (this.index === 0 || this.index === this.tagName.length)) { + } else if (char === " " && (this.index === 0 || this.index === tagName.length)) { continue - } else if (this.tagName[this.index] === char) { + } else if (tagName[this.index] === char) { this.index++ } else { this.state = "TEXT" @@ -102,10 +133,15 @@ export class TagMatcher { this._update(chunk) } this.collect() + this.candidates = [] + this.activeTagNames = [] return this.pop() } update(chunk: string) { this._update(chunk) + if (this.state === "TEXT") { + this.collect() + } return this.pop() } } From ea7c72e20b2b44628bc8c4092a3cdee2bd7e63cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 20:58:56 +0200 Subject: [PATCH 15/16] fix(delegation): serialize delegateParentAndOpenChild with atomicReadAndUpdate Port of Zoo-Code PR #691. Replaces the non-atomic read (getTaskWithId) + write (updateTaskHistory) of parent delegation metadata with a new TaskHistoryStore.atomicReadAndUpdate that performs the read-modify-write inside a single lock acquisition, so no concurrent writer can interleave. After the lock releases, delegation invalidates the recent-tasks cache and broadcasts taskHistoryItemUpdated when the view is launched. Adopts the upstream provider-delegation test rewrite minus its rollback case (our delegateParentAndOpenChild logs on persist failure rather than rolling back). Co-authored-by: Elliott de Launay Co-authored-by: Naved Merchant --- ...-06-27_zoo-691-delegation-atomic-update.md | 50 +++++ src/__tests__/delegation-concurrent.spec.ts | 147 +++++++++++++ src/__tests__/provider-delegation.spec.ts | 208 ++++++++++++------ src/core/task-persistence/TaskHistoryStore.ts | 70 ++++-- src/core/webview/ClineProvider.ts | 31 ++- 5 files changed, 410 insertions(+), 96 deletions(-) create mode 100644 ai_plans/2026-06-27_zoo-691-delegation-atomic-update.md create mode 100644 src/__tests__/delegation-concurrent.spec.ts diff --git a/ai_plans/2026-06-27_zoo-691-delegation-atomic-update.md b/ai_plans/2026-06-27_zoo-691-delegation-atomic-update.md new file mode 100644 index 000000000..d6831f259 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-691-delegation-atomic-update.md @@ -0,0 +1,50 @@ +# Port Zoo PR #691 — serialize delegateParentAndOpenChild with atomicReadAndUpdate + +## §0 Credit & provenance + +- Upstream: Zoo-Code-Org/Zoo-Code PR #691, commit `667096238`, merged 2026-06-25. +- Authors: edelauna (Elliott de Launay), Naved Merchant. +- Commit trailers: + ``` + Co-authored-by: Elliott de Launay + Co-authored-by: Naved Merchant + ``` + +## §1 What & why (concurrency fix) + +`delegateParentAndOpenChild` persisted parent delegation metadata with a separate +read (`getTaskWithId`) then write (`updateTaskHistory`) — a concurrent writer could +interleave and clobber the read-modify-write. This adds +`TaskHistoryStore.atomicReadAndUpdate(taskId, updater)`, which reads from cache and +writes back within a single lock acquisition, and switches delegation to use it. +Builds on our existing delegation work. + +Our `TaskHistoryStore.ts` matched Zoo's pre-PR exactly, and our ClineProvider's +step-5 block matched, so the core port applied cleanly. + +## §2 Edits + +- `src/core/task-persistence/TaskHistoryStore.ts`: + - extract `_upsertUnlocked()` from `upsert()` (so it can run inside a held lock). + - add `atomicReadAndUpdate()` — deep-copies the cached item, runs the pure updater, + guards the id, and persists via `_upsertUnlocked` inside `withLock`. +- `src/core/webview/ClineProvider.ts`: step 5 of `delegateParentAndOpenChild` now + calls `taskHistoryStore.atomicReadAndUpdate(...)`; after the lock releases it + invalidates `recentTasksCache` and (when `isViewLaunched`) posts + `taskHistoryItemUpdated` to the webview. + +## §3 Tests + +- Added `src/__tests__/delegation-concurrent.spec.ts` (new upstream file, 4 cases + exercising the atomic serialization / no-interleave invariant). +- Adopted the upstream `provider-delegation.spec.ts` rewrite (makeStoreStub + + `atomicReadAndUpdate` assertions, incl. the new `isViewLaunched` postMessage cases) + **minus its rollback test** — our `delegateParentAndOpenChild` logs on persist + failure and continues (no rollback), so that upstream test does not apply here. + (Our fork had already dropped the corresponding pre-PR rollback test.) + +## §4 Verify (binary acceptance) — all ✓ + +- `pnpm --filter tumble-code check-types` passes. +- `npx vitest run __tests__/provider-delegation __tests__/delegation-concurrent` → 8 pass. +- `npx vitest run core/task-persistence/__tests__/TaskHistoryStore` → 28 pass. diff --git a/src/__tests__/delegation-concurrent.spec.ts b/src/__tests__/delegation-concurrent.spec.ts new file mode 100644 index 000000000..40d9b49ee --- /dev/null +++ b/src/__tests__/delegation-concurrent.spec.ts @@ -0,0 +1,147 @@ +// npx vitest run __tests__/delegation-concurrent.spec.ts + +import { describe, it, expect, vi, beforeEach } from "vitest" +import type { HistoryItem } from "@roo-code/types" + +vi.mock("fs/promises", () => ({ + mkdir: vi.fn().mockResolvedValue(undefined), + readFile: vi.fn().mockRejectedValue(Object.assign(new Error("ENOENT"), { code: "ENOENT" })), + readdir: vi.fn().mockResolvedValue([]), + unlink: vi.fn().mockResolvedValue(undefined), +})) + +vi.mock("fs", () => ({ + default: { + watch: vi.fn().mockReturnValue({ on: vi.fn(), close: vi.fn() }), + existsSync: vi.fn().mockReturnValue(false), + }, + watch: vi.fn().mockReturnValue({ on: vi.fn(), close: vi.fn() }), + existsSync: vi.fn().mockReturnValue(false), +})) + +vi.mock("../utils/safeWriteJson", () => ({ + safeWriteJson: vi.fn().mockResolvedValue(undefined), +})) + +vi.mock("../utils/storage", () => ({ + getStorageBasePath: vi.fn().mockResolvedValue("/tmp/test-storage"), +})) + +import { TaskHistoryStore } from "../core/task-persistence/TaskHistoryStore" + +const makeItem = (id: string, overrides: Partial = {}): HistoryItem => + ({ + id, + ts: Date.now(), + task: "test task", + tokensIn: 0, + tokensOut: 0, + totalCost: 0, + status: "active", + mode: "code", + workspace: "/tmp", + ...overrides, + }) as HistoryItem + +describe("TaskHistoryStore.atomicReadAndUpdate", () => { + let store: TaskHistoryStore + + beforeEach(() => { + vi.clearAllMocks() + store = new TaskHistoryStore("/tmp/test-storage") + }) + + it("serializes concurrent operations — second caller reads the state written by the first", async () => { + // Seed the cache with an item that has no childIds yet. + const item = makeItem("parent-task", { childIds: [] }) + ;(store as any).cache.set(item.id, item) + + // Two concurrent delegations each append their child ID. + // Because they are serialized by the lock, the second caller must + // read the cache state that the first caller wrote — not the original. + const delegation1 = store.atomicReadAndUpdate("parent-task", (current) => ({ + ...current, + childIds: [...(current.childIds ?? []), "child-A"], + })) + + const delegation2 = store.atomicReadAndUpdate("parent-task", (current) => ({ + ...current, + childIds: [...(current.childIds ?? []), "child-B"], + })) + + await Promise.all([delegation1, delegation2]) + + // Both child IDs must be present: delegation2 saw delegation1's write. + const final = (store as any).cache.get("parent-task") as HistoryItem + expect(final.childIds).toContain("child-A") + expect(final.childIds).toContain("child-B") + }) + + it("two concurrent delegations produce consistent HistoryItem state (full field set)", async () => { + const item = makeItem("parent-task", { status: "active", childIds: [] }) + ;(store as any).cache.set(item.id, item) + + // Each delegation sets awaitingChildId and appends to childIds. + const delegation1 = store.atomicReadAndUpdate("parent-task", (historyItem) => { + const childIds = Array.from(new Set([...(historyItem.childIds ?? []), "child-A"])) + return { + ...historyItem, + status: "delegated", + delegatedToId: "child-A", + awaitingChildId: "child-A", + childIds, + } + }) + + const delegation2 = store.atomicReadAndUpdate("parent-task", (historyItem) => { + const childIds = Array.from(new Set([...(historyItem.childIds ?? []), "child-B"])) + return { + ...historyItem, + status: "delegated", + delegatedToId: "child-B", + awaitingChildId: "child-B", + childIds, + } + }) + + await Promise.all([delegation1, delegation2]) + + const final = (store as any).cache.get("parent-task") as HistoryItem + // Both child IDs present — neither write clobbered the other's childIds. + expect(final.childIds).toContain("child-A") + expect(final.childIds).toContain("child-B") + // The last writer wins on scalar fields; whichever child ran second is authoritative. + expect(final.status).toBe("delegated") + expect(["child-A", "child-B"]).toContain(final.awaitingChildId) + expect(final.delegatedToId).toBe(final.awaitingChildId) + expect(final.childIds).toContain(final.awaitingChildId) + }) + + it("completes without deadlock — updater is pure and does not re-acquire the lock", async () => { + const item = makeItem("task-1", { childIds: [] }) + ;(store as any).cache.set(item.id, item) + + // With the typed (taskId, updater) API, the updater is synchronous and + // cannot call upsert/withLock — no re-entrancy, no deadlock. + const result = await Promise.race([ + store + .atomicReadAndUpdate("task-1", (current) => ({ + ...current, + childIds: [...(current.childIds ?? []), "child-1"], + })) + .then(() => "completed"), + new Promise((resolve) => setTimeout(() => resolve("deadlocked"), 100)), + ]) + + expect(result).toBe("completed") + + const final = (store as any).cache.get("task-1") as HistoryItem + expect(final.childIds).toContain("child-1") + }) + + it("throws if the task ID is not in the cache", async () => { + await expect( + store.atomicReadAndUpdate("nonexistent-task", (current) => ({ ...current, status: "delegated" })), + ).rejects.toThrow("nonexistent-task") + }) +}) diff --git a/src/__tests__/provider-delegation.spec.ts b/src/__tests__/provider-delegation.spec.ts index 4b04fb5bb..eef495ea5 100644 --- a/src/__tests__/provider-delegation.spec.ts +++ b/src/__tests__/provider-delegation.spec.ts @@ -1,124 +1,193 @@ // npx vitest run __tests__/provider-delegation.spec.ts import { describe, it, expect, vi } from "vitest" +import type { HistoryItem } from "@roo-code/types" import { RooCodeEventName } from "@roo-code/types" import { ClineProvider } from "../core/webview/ClineProvider" +const parentHistoryItem: HistoryItem = { + id: "parent-1", + task: "Parent", + tokensIn: 0, + tokensOut: 0, + totalCost: 0, + childIds: [], +} as unknown as HistoryItem + +/** Minimal taskHistoryStore stub whose atomicReadAndUpdate calls the updater with the parent item. */ +function makeStoreStub( + overrides: Partial<{ atomicReadAndUpdate: ReturnType; get: ReturnType }> = {}, +) { + return { + atomicReadAndUpdate: vi.fn(async (_taskId: string, updater: (h: HistoryItem) => HistoryItem) => { + updater(parentHistoryItem) + return [] + }), + get: vi.fn().mockReturnValue(undefined), + ...overrides, + } +} + +/** + * Parent task double with the methods delegateParentAndOpenChild reads from + * `parent`. Without flushPendingToolResultsToHistory the method hits its + * non-fatal flush-error branch and never reaches the happy delegation path. + */ +const makeParentTask = () => + ({ + taskId: "parent-1", + emit: vi.fn(), + flushPendingToolResultsToHistory: vi.fn().mockResolvedValue(true), + retrySaveApiConversationHistory: vi.fn(), + }) as any + describe("ClineProvider.delegateParentAndOpenChild()", () => { - it("persists parent delegation metadata and emits TaskDelegated", async () => { + it("persists parent delegation metadata via atomicReadAndUpdate and emits TaskDelegated", async () => { const providerEmit = vi.fn() - const parentTask = { taskId: "parent-1", emit: vi.fn() } as any + const parentTask = makeParentTask() const childStart = vi.fn() - const updateTaskHistory = vi.fn() const removeClineFromStack = vi.fn().mockResolvedValue(undefined) const createTask = vi.fn().mockResolvedValue({ taskId: "child-1", start: childStart }) const handleModeSwitch = vi.fn().mockResolvedValue(undefined) - const getTaskWithId = vi.fn().mockImplementation(async (id: string) => { - if (id === "parent-1") { - return { - historyItem: { - id: "parent-1", - task: "Parent", - tokensIn: 0, - tokensOut: 0, - totalCost: 0, - childIds: [], - }, - } - } - // child-1 - return { - historyItem: { - id: "child-1", - task: "Do something", - tokensIn: 0, - tokensOut: 0, - totalCost: 0, - }, - } - }) + const taskHistoryStore = makeStoreStub() const provider = { emit: providerEmit, getCurrentTask: vi.fn(() => parentTask), removeClineFromStack, createTask, - getTaskWithId, - updateTaskHistory, handleModeSwitch, log: vi.fn(), + isViewLaunched: false, + recentTasksCache: undefined, + taskHistoryStore, } as unknown as ClineProvider - const params = { + const child = await (ClineProvider.prototype as any).delegateParentAndOpenChild.call(provider, { parentTaskId: "parent-1", message: "Do something", initialTodos: [], mode: "code", - } - - const child = await (ClineProvider.prototype as any).delegateParentAndOpenChild.call(provider, params) + }) expect(child.taskId).toBe("child-1") // Invariant: parent closed before child creation expect(removeClineFromStack).toHaveBeenCalledTimes(1) - // Child task is created with startTask: false and initialStatus: "active" + + // Child task created with startTask: false and initialStatus: "active" expect(createTask).toHaveBeenCalledWith("Do something", undefined, parentTask, { initialTodos: [], initialStatus: "active", startTask: false, }) - // Metadata persistence - parent gets "delegated" status (child status is set at creation via initialStatus) - expect(updateTaskHistory).toHaveBeenCalledTimes(1) - - // Parent set to "delegated" - const parentSaved = updateTaskHistory.mock.calls[0][0] - expect(parentSaved).toEqual( - expect.objectContaining({ - id: "parent-1", - status: "delegated", - delegatedToId: "child-1", - awaitingChildId: "child-1", - childIds: expect.arrayContaining(["child-1"]), - }), - ) + // Delegation metadata written via atomicReadAndUpdate with correct taskId + expect(taskHistoryStore.atomicReadAndUpdate).toHaveBeenCalledTimes(1) + const [calledTaskId, updater] = taskHistoryStore.atomicReadAndUpdate.mock.calls[0] + expect(calledTaskId).toBe("parent-1") + + // The updater must produce the correct delegation fields + const result = updater(parentHistoryItem) + expect(result).toMatchObject({ + id: "parent-1", + status: "delegated", + delegatedToId: "child-1", + awaitingChildId: "child-1", + childIds: expect.arrayContaining(["child-1"]), + }) - // child.start() must be called AFTER parent metadata is persisted + // child.start() called AFTER parent metadata is persisted expect(childStart).toHaveBeenCalledTimes(1) - // Event emission (provider-level) + // Provider-level event expect(providerEmit).toHaveBeenCalledWith(RooCodeEventName.TaskDelegated, "parent-1", "child-1") // Mode switch expect(handleModeSwitch).toHaveBeenCalledWith("code") }) - it("calls child.start() only after parent metadata is persisted (no race condition)", async () => { - const callOrder: string[] = [] + it("posts taskHistoryItemUpdated to the webview when isViewLaunched is true", async () => { + const updatedParent = { ...parentHistoryItem, status: "delegated" } as HistoryItem + const postMessageToWebview = vi.fn().mockResolvedValue(undefined) + const parentTask = makeParentTask() + const taskHistoryStore = makeStoreStub({ + get: vi.fn().mockReturnValue(updatedParent), + }) - const parentTask = { taskId: "parent-1", emit: vi.fn() } as any - const childStart = vi.fn(() => callOrder.push("child.start")) + const provider = { + emit: vi.fn(), + getCurrentTask: vi.fn(() => parentTask), + removeClineFromStack: vi.fn().mockResolvedValue(undefined), + createTask: vi.fn().mockResolvedValue({ taskId: "child-1", start: vi.fn() }), + handleModeSwitch: vi.fn().mockResolvedValue(undefined), + postMessageToWebview, + log: vi.fn(), + isViewLaunched: true, + recentTasksCache: undefined, + taskHistoryStore, + } as unknown as ClineProvider - const updateTaskHistory = vi.fn(async () => { - callOrder.push("updateTaskHistory") + await (ClineProvider.prototype as any).delegateParentAndOpenChild.call(provider, { + parentTaskId: "parent-1", + message: "Do something", + initialTodos: [], + mode: "code", }) + + expect(postMessageToWebview).toHaveBeenCalledWith({ + type: "taskHistoryItemUpdated", + taskHistoryItem: updatedParent, + }) + }) + + it("skips postMessageToWebview when isViewLaunched is true but store returns undefined", async () => { + const postMessageToWebview = vi.fn().mockResolvedValue(undefined) + const parentTask = makeParentTask() + const taskHistoryStore = makeStoreStub({ + get: vi.fn().mockReturnValue(undefined), + }) + + const provider = { + emit: vi.fn(), + getCurrentTask: vi.fn(() => parentTask), + removeClineFromStack: vi.fn().mockResolvedValue(undefined), + createTask: vi.fn().mockResolvedValue({ taskId: "child-1", start: vi.fn() }), + handleModeSwitch: vi.fn().mockResolvedValue(undefined), + postMessageToWebview, + log: vi.fn(), + isViewLaunched: true, + recentTasksCache: undefined, + taskHistoryStore, + } as unknown as ClineProvider + + await (ClineProvider.prototype as any).delegateParentAndOpenChild.call(provider, { + parentTaskId: "parent-1", + message: "Do something", + initialTodos: [], + mode: "code", + }) + + expect(postMessageToWebview).not.toHaveBeenCalled() + }) + + it("calls child.start() only after atomicReadAndUpdate completes (no race condition)", async () => { + const callOrder: string[] = [] + + const parentTask = makeParentTask() + const childStart = vi.fn(() => callOrder.push("child.start")) const removeClineFromStack = vi.fn().mockResolvedValue(undefined) const createTask = vi.fn(async () => { callOrder.push("createTask") return { taskId: "child-1", start: childStart } }) const handleModeSwitch = vi.fn().mockResolvedValue(undefined) - const getTaskWithId = vi.fn().mockResolvedValue({ - historyItem: { - id: "parent-1", - task: "Parent", - tokensIn: 0, - tokensOut: 0, - totalCost: 0, - childIds: [], - }, + const taskHistoryStore = makeStoreStub({ + atomicReadAndUpdate: vi.fn(async (_taskId: string, _updater: (h: HistoryItem) => HistoryItem) => { + callOrder.push("atomicReadAndUpdate") + return [] + }), }) const provider = { @@ -126,10 +195,11 @@ describe("ClineProvider.delegateParentAndOpenChild()", () => { getCurrentTask: vi.fn(() => parentTask), removeClineFromStack, createTask, - getTaskWithId, - updateTaskHistory, handleModeSwitch, log: vi.fn(), + isViewLaunched: false, + recentTasksCache: undefined, + taskHistoryStore, } as unknown as ClineProvider await (ClineProvider.prototype as any).delegateParentAndOpenChild.call(provider, { @@ -139,7 +209,7 @@ describe("ClineProvider.delegateParentAndOpenChild()", () => { mode: "code", }) - // Verify ordering: createTask → updateTaskHistory → child.start - expect(callOrder).toEqual(["createTask", "updateTaskHistory", "child.start"]) + // createTask → atomicReadAndUpdate → child.start: lock must release before start + expect(callOrder).toEqual(["createTask", "atomicReadAndUpdate", "child.start"]) }) }) diff --git a/src/core/task-persistence/TaskHistoryStore.ts b/src/core/task-persistence/TaskHistoryStore.ts index 4157d8b9f..a65d0e866 100644 --- a/src/core/task-persistence/TaskHistoryStore.ts +++ b/src/core/task-persistence/TaskHistoryStore.ts @@ -158,30 +158,36 @@ export class TaskHistoryStore { * updates the in-memory Map, and schedules a debounced index write. */ async upsert(item: HistoryItem): Promise { - return this.withLock(async () => { - const existing = this.cache.get(item.id) + return this.withLock(() => this._upsertUnlocked(item)) + } - // Merge: preserve existing metadata unless explicitly overwritten - const merged = existing ? { ...existing, ...item } : item + /** + * Upsert body executed without acquiring the lock. + * Must only be called from within a `withLock` callback. + */ + private async _upsertUnlocked(item: HistoryItem): Promise { + const existing = this.cache.get(item.id) - // Write per-task file (source of truth) - await this.writeTaskFile(merged) + // Merge: preserve existing metadata unless explicitly overwritten + const merged = existing ? { ...existing, ...item } : item - // Update in-memory cache - this.cache.set(merged.id, merged) + // Write per-task file (source of truth) + await this.writeTaskFile(merged) - // Schedule debounced index write - this.scheduleIndexWrite() + // Update in-memory cache + this.cache.set(merged.id, merged) - const all = this.getAll() + // Schedule debounced index write + this.scheduleIndexWrite() - // Call onWrite callback inside the lock for serialized write-through - if (this.onWrite) { - await this.onWrite(all) - } + const all = this.getAll() - return all - }) + // Call onWrite callback inside the lock for serialized write-through + if (this.onWrite) { + await this.onWrite(all) + } + + return all } /** @@ -529,6 +535,36 @@ export class TaskHistoryStore { }, TaskHistoryStore.RECONCILE_INTERVAL_MS) } + // ────────────────────────────── Atomic read-modify-write ────────────────────────────── + + /** + * Read a HistoryItem from the in-memory cache and write back an updated version, + * all within a single lock acquisition so no concurrent writer can interleave + * between the read and the write. + * + * The `updater` receives the current cached item and must return the new item + * synchronously. It must not perform I/O or acquire any other lock. + * + * @throws If the task ID is not present in the cache. + */ + public atomicReadAndUpdate(taskId: string, updater: (current: HistoryItem) => HistoryItem): Promise { + return this.withLock(async () => { + const current = this.cache.get(taskId) + if (!current) { + throw new Error(`[TaskHistoryStore] atomicReadAndUpdate: task ${taskId} not found in cache`) + } + // Deep-copy so a mutating updater cannot alter cached state before persistence. + const snapshot = structuredClone(current) + const updated = updater(snapshot) + if (updated.id !== taskId) { + throw new Error( + `[TaskHistoryStore] atomicReadAndUpdate: updater changed task id from ${taskId} to ${updated.id}`, + ) + } + return this._upsertUnlocked(updated) + }) + } + // ────────────────────────────── Private: Write lock ────────────────────────────── /** diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index af4b0c8b6..d9b6179e7 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -3473,17 +3473,28 @@ export class ClineProvider }) // 5) Persist parent delegation metadata BEFORE the child starts writing. + // atomicReadAndUpdate reads from the in-memory cache and writes back within a + // single lock acquisition — no concurrent writer can slip between the read and + // write, and the pure updater cannot re-enter the lock (no deadlock). + // Broadcast and cache invalidation happen outside the lock after it releases. try { - const { historyItem } = await this.getTaskWithId(parentTaskId) - const childIds = Array.from(new Set([...(historyItem.childIds ?? []), child.taskId])) - const updatedHistory: typeof historyItem = { - ...historyItem, - status: "delegated", - delegatedToId: child.taskId, - awaitingChildId: child.taskId, - childIds, - } - await this.updateTaskHistory(updatedHistory) + await this.taskHistoryStore.atomicReadAndUpdate(parentTaskId, (historyItem) => { + const childIds = Array.from(new Set([...(historyItem.childIds ?? []), child.taskId])) + return { + ...historyItem, + status: "delegated", + delegatedToId: child.taskId, + awaitingChildId: child.taskId, + childIds, + } + }) + this.recentTasksCache = undefined + if (this.isViewLaunched) { + const updatedItem = this.taskHistoryStore.get(parentTaskId) + if (updatedItem) { + await this.postMessageToWebview({ type: "taskHistoryItemUpdated", taskHistoryItem: updatedItem }) + } + } } catch (err) { this.log( `[delegateParentAndOpenChild] Failed to persist parent metadata for ${parentTaskId} -> ${child.taskId}: ${ From 6e4f813eb470abe38c286e996ef1b4e401a0f536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dre=C5=BCewski?= Date: Sat, 27 Jun 2026 21:05:12 +0200 Subject: [PATCH 16/16] =?UTF-8?q?docs(zoo-port):=20defer=20#657=20(rules?= =?UTF-8?q?=20management=20UI)=20=E2=80=94=20dedicated=20session=20groundw?= =?UTF-8?q?ork?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...27_zoo-657-rules-management-ui-DEFERRED.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 ai_plans/2026-06-27_zoo-657-rules-management-ui-DEFERRED.md diff --git a/ai_plans/2026-06-27_zoo-657-rules-management-ui-DEFERRED.md b/ai_plans/2026-06-27_zoo-657-rules-management-ui-DEFERRED.md new file mode 100644 index 000000000..7ad58f365 --- /dev/null +++ b/ai_plans/2026-06-27_zoo-657-rules-management-ui-DEFERRED.md @@ -0,0 +1,35 @@ +# Zoo PR #657 — rules management UI — DEFERRED (dedicated feature session) + +Upstream: Zoo-Code-Org/Zoo-Code PR #657, commit `f75b64e28`, merged 2026-06-26. +Author: Ivan Ramadhan Arifin. Credit on port: +`Co-authored-by: Ivan Ramadhan Arifin <…>` (resolve email from `zoo-prs show 657`). + +## Why deferred + +- **34 files / 3217 insertions** — the largest item in the batch. A complete new + rules-management subsystem, far beyond a safe autonomous loop tick. +- **New backend**: `src/services/rules/rules.ts` (+408), `src/core/webview/rulesMessageHandler.ts` + (+170), `packages/types/src/rules.ts` (+34). Our fork has **no** `src/services/rules` + dir today — rules loading lives in `src/services/roo-config/index.ts` and + `src/core/prompts/sections/custom-instructions.ts`. The new service must be + reconciled with that existing loader (and our `useAgentRules` / AGENTS.md support) + to avoid two competing rules pipelines. +- **New UI**: `RulesSettings.tsx` (+207), `CreateRuleDialog.tsx` (+218), wired through + `SettingsView.tsx`, `ExtensionStateContext.tsx`, `vscode-extension-host.ts`. +- **~1466 lines of new tests** across 6 files (rules.spec, rulesMessageHandler.spec, + webviewMessageHandler.spec, CreateRuleDialog.spec, RulesSettings.spec, + ExtensionStateContext.spec) + 18 locale `settings.json` updates. + +## Dedicated-session checklist + +1. Add `packages/types/src/rules.ts` + export from `index.ts`; extend `ExtensionState` + in `vscode-extension-host.ts`. +2. Port `src/services/rules/rules.ts` — **first audit `src/services/roo-config/index.ts`** + for overlap; decide whether the new service wraps or replaces our loader, and keep + AGENTS.md / `.roo/rules` behavior intact (do not regress `useAgentRules`). +3. Port `rulesMessageHandler.ts` and wire its cases into `webviewMessageHandler.ts`. +4. Port `RulesSettings.tsx` + `CreateRuleDialog.tsx`; wire into `SettingsView.tsx` + and `ExtensionStateContext.tsx` (re-anchor against our divergence). +5. Port the 6 test files; add the 18 locale strings. +6. Verify: types + src + webview typecheck; run the 6 new specs; manual smoke of the + Rules settings tab.