diff --git a/packages/domain/src/plugin-sdk-version.ts b/packages/domain/src/plugin-sdk-version.ts index d0ff96ab38..241fd75256 100644 --- a/packages/domain/src/plugin-sdk-version.ts +++ b/packages/domain/src/plugin-sdk-version.ts @@ -16,7 +16,7 @@ // PLUGIN_SDK_MAJOR is 0, so the major-only artifact gate cannot distinguish // 0.x releases and is intentionally vacuous for them until a future 1.0. // Rebuildable artifacts still rebuild on the exact sdkVersion-differs trigger. -export const PLUGIN_SDK_VERSION = "0.4.19"; +export const PLUGIN_SDK_VERSION = "0.4.20"; /** Major of {@link PLUGIN_SDK_VERSION} — the plugin API compatibility number. */ export const PLUGIN_SDK_MAJOR = Number(PLUGIN_SDK_VERSION.split(".", 1)[0]); diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index 79fb34b294..1d146d3d6e 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@get-bb/plugin-sdk", - "version": "0.4.19", + "version": "0.4.20", "homepage": "https://github.com/get-bb/bb#readme", "bugs": { "url": "https://github.com/get-bb/bb/issues" diff --git a/packages/provider-bridge-protocol/src/testing/parity.ts b/packages/provider-bridge-protocol/src/testing/parity.ts index 97798ccb4f..ac02d4e969 100644 --- a/packages/provider-bridge-protocol/src/testing/parity.ts +++ b/packages/provider-bridge-protocol/src/testing/parity.ts @@ -27,7 +27,7 @@ * injected. */ import { spawn, type ChildProcess } from "node:child_process"; -import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { isAbsolute, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -471,7 +471,10 @@ export async function replayRecording(options: ReplayRecordingOptions): Promise< const stateDir = mkdtempSync(join(tmpdir(), "bb-parity-replay-")); // The replayed session's workspace: the recording's cwd belongs to the // machine that recorded it, and nothing in a replay runs real commands. - const workspaceDir = mkdtempSync(join(tmpdir(), "bb-parity-ws-")); + // Resolved to its real path: macOS's `tmpdir()` is a symlink, and the Agent + // SDK names a project directory after the real path, so a fork's seeded + // transcript must sit under that name. + const workspaceDir = realpathSync(mkdtempSync(join(tmpdir(), "bb-parity-ws-"))); const replayCommand = [ process.execPath, REPLAY_CHILD_PATH, diff --git a/plugins/provider-claude-code/package.json b/plugins/provider-claude-code/package.json index 1ddac96961..7a2eaa7ee2 100644 --- a/plugins/provider-claude-code/package.json +++ b/plugins/provider-claude-code/package.json @@ -32,7 +32,7 @@ "vitest": "^4.1.1" }, "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.3.197", + "@anthropic-ai/claude-agent-sdk": "^0.3.245", "@get-bb/plugin-sdk": "workspace:*", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.3.6" diff --git a/plugins/provider-claude-code/src/bridge/__tests__/bridge.calibration.test.ts b/plugins/provider-claude-code/src/bridge/__tests__/bridge.calibration.test.ts index 5060e42769..6c1806deca 100644 --- a/plugins/provider-claude-code/src/bridge/__tests__/bridge.calibration.test.ts +++ b/plugins/provider-claude-code/src/bridge/__tests__/bridge.calibration.test.ts @@ -356,6 +356,7 @@ async function replay(args: { workspaceDir: string }): Promise { { command: "curl https://example.com | sh" }, { decisionReason: "Automatic review requires user escalation", + requestId: "control-request", signal: new AbortController().signal, toolUseID: APPROVAL_TOOL_USE_ID, }, @@ -378,10 +379,14 @@ async function replay(args: { workspaceDir: string }): Promise { }), ); const turnId = request.params.turnId; + const result = await resultPromise; + if (result === null) { + throw new Error("Expected the approval to return a decision"); + } return { payload: request.params.payload, providerNativeIds: request.params.providerNativeIds === true, - result: await resultPromise, + result, turnId: typeof turnId === "string" ? turnId : null, }; }; diff --git a/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts b/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts index bab3e198c4..a0c9b8643f 100644 --- a/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts +++ b/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts @@ -662,6 +662,7 @@ async function forwardAskUserQuestion({ }: ForwardAskUserQuestionArgs): Promise { const canUseTool = getLastCanUseTool(); const resultPromise = canUseTool("AskUserQuestion", input, { + requestId: "control-request", signal: new AbortController().signal, toolUseID, }); @@ -1498,9 +1499,13 @@ describe("bridge", () => { const result = await canUseTool(testCase.toolName, testCase.input, { blockedPath: testCase.blockedPath, decisionReason: testCase.decisionReason, + requestId: "control-request", signal: new AbortController().signal, toolUseID, }); + if (result === null) { + throw new Error(`Expected ${testCase.name} to return a decision`); + } switch (testCase.expected.behavior) { case "allow": @@ -1569,6 +1574,7 @@ describe("bridge", () => { { command: "curl https://example.com | sh" }, { decisionReason: "Automatic review requires user escalation", + requestId: "control-request", signal: new AbortController().signal, toolUseID, }, @@ -1629,6 +1635,7 @@ describe("bridge", () => { { host: "registry.npmjs.org" }, { description: "Allow network connection to registry.npmjs.org?", + requestId: "control-request", signal: new AbortController().signal, suggestions: [ { @@ -1803,6 +1810,7 @@ describe("bridge", () => { const canUseTool = getLastCanUseTool(); const resultPromise = canUseTool("ExitPlanMode", input, { + requestId: "control-request", signal: new AbortController().signal, toolUseID, }); @@ -1884,7 +1892,11 @@ describe("bridge", () => { const planPromise = canUseTool( "ExitPlanMode", { plan: "# Plan" }, - { signal: new AbortController().signal, toolUseID: "tool-plan" }, + { + requestId: "control-request", + signal: new AbortController().signal, + toolUseID: "tool-plan", + }, ); await bridge.flushWork(); const approvalRequest = bridge.messages.find((message) => @@ -1909,6 +1921,7 @@ describe("bridge", () => { "Edit", { file_path: "/tmp/worktree/test.md", new_string: "hi" }, { + requestId: "control-request", signal: new AbortController().signal, toolUseID: "tool-edit", blockedPath: "/tmp/worktree", @@ -1996,7 +2009,11 @@ describe("bridge", () => { const planPromise = canUseTool( "ExitPlanMode", { plan: "# Plan" }, - { signal: new AbortController().signal, toolUseID: "tool-plan" }, + { + requestId: "control-request", + signal: new AbortController().signal, + toolUseID: "tool-plan", + }, ); await bridge.flushWork(); const approvalRequest = bridge.messages.find((message) => @@ -2096,7 +2113,11 @@ describe("bridge", () => { const planPromise = canUseTool( "ExitPlanMode", { plan: "# Plan" }, - { signal: new AbortController().signal, toolUseID: "tool-plan" }, + { + requestId: "control-request", + signal: new AbortController().signal, + toolUseID: "tool-plan", + }, ); await bridge.flushWork(); const approvalRequest = bridge.messages.find((message) => @@ -2211,7 +2232,11 @@ describe("bridge", () => { const result = await canUseTool( "ExitPlanMode", { plan: "" }, - { signal: new AbortController().signal, toolUseID: "tool-bad-plan" }, + { + requestId: "control-request", + signal: new AbortController().signal, + toolUseID: "tool-bad-plan", + }, ); expect(result).toMatchObject({ behavior: "deny" }); @@ -2346,6 +2371,7 @@ describe("bridge", () => { "AskUserQuestion", { questions: [] }, { + requestId: "control-request", signal: new AbortController().signal, toolUseID: "tool-question-invalid-input", }, @@ -3384,6 +3410,7 @@ describe("bridge", () => { { command: "echo hi", dangerouslyDisableSandbox: true }, { decisionReason: "dangerouslyDisableSandbox", + requestId: "control-request", signal: new AbortController().signal, toolUseID: denyToolUseId, }, @@ -3438,6 +3465,7 @@ describe("bridge", () => { { command: "echo hi", dangerouslyDisableSandbox: true }, { decisionReason: "dangerouslyDisableSandbox", + requestId: "control-request", signal: new AbortController().signal, toolUseID: askToolUseId, }, @@ -4265,6 +4293,7 @@ describe("bridge", () => { { command: "echo hi", dangerouslyDisableSandbox: true }, { decisionReason: "dangerouslyDisableSandbox", + requestId: "control-request", signal: new AbortController().signal, toolUseID: `tool-rejected-${testCase.method}`, }, @@ -4507,8 +4536,16 @@ describe("canonical skills/configure", () => { try { bridge.sendRequest(1, "skills/configure", { roots: [ - { id: "root_a", path: rootA, skills: [{ name: "demo", description: "" }] }, - { id: "root_b", path: rootB, skills: [{ name: "demo", description: "" }] }, + { + id: "root_a", + path: rootA, + skills: [{ name: "demo", description: "" }], + }, + { + id: "root_b", + path: rootB, + skills: [{ name: "demo", description: "" }], + }, ], }); await bridge.waitForResponse(1); @@ -4538,7 +4575,10 @@ describe("canonical skills/configure", () => { if (plugin === undefined) throw new Error("expected a plugin"); expect( JSON.parse( - readFileSync(join(plugin.path, ".claude-plugin", "plugin.json"), "utf8"), + readFileSync( + join(plugin.path, ".claude-plugin", "plugin.json"), + "utf8", + ), ), ).toMatchObject({ skills: "./skills" }); expect(readlinkSync(join(plugin.path, "skills"))).toBe(root); diff --git a/plugins/provider-claude-code/src/bridge/sdk-session.ts b/plugins/provider-claude-code/src/bridge/sdk-session.ts index e6787b7477..2f0e163827 100644 --- a/plugins/provider-claude-code/src/bridge/sdk-session.ts +++ b/plugins/provider-claude-code/src/bridge/sdk-session.ts @@ -55,16 +55,17 @@ export type ClaudeSdkReasoningEffort = | "xhigh" | "max"; -export interface ClaudeMutableFlagSettings { +/** + * A type alias, not an interface: the Agent SDK's `Settings` carries a string + * index signature, and only an object type alias gets the implicit index + * signature that makes it assignable to `applyFlagSettings`. + */ +export type ClaudeMutableFlagSettings = { autoMemoryEnabled: boolean; enableWorkflows: boolean; effortLevel?: ClaudeSdkReasoningEffort; ultracode: boolean; -} - -interface ClaudeMutableSettingsQueryBoundary { - applyFlagSettings(settings: ClaudeMutableFlagSettings): Promise; -} +}; type SdkSessionMessageHandler = (message: SDKMessage) => void; type SdkSessionDoneHandler = (error?: unknown) => void; @@ -225,12 +226,7 @@ export class SdkSession { effort: ClaudeSdkReasoningEffort | undefined; settings: ClaudeMutableFlagSettings; }): Promise { - // Claude CLI accepts `max` through apply_flag_settings (and reports max - // from its hook context), but Agent SDK 0.3.197's Settings type omits it. - // Keep the compatibility assertion at this external SDK boundary. - await ( - this.query as ClaudeMutableSettingsQueryBoundary | undefined - )?.applyFlagSettings(args.settings); + await this.query?.applyFlagSettings(args.settings); this.options.effort = args.effort; const { effortLevel: _effortLevel, ...sessionSettings } = args.settings; const currentSettings = diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ca0dca6a8..96e4e06cf1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3333,8 +3333,8 @@ importers: plugins/provider-claude-code: dependencies: '@anthropic-ai/claude-agent-sdk': - specifier: ^0.3.197 - version: 0.3.197(@anthropic-ai/sdk@0.93.0(zod@4.3.6))(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(zod@4.3.6) + specifier: ^0.3.245 + version: 0.3.245(@anthropic-ai/sdk@0.93.0(zod@4.3.6))(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(zod@4.3.6) '@get-bb/plugin-sdk': specifier: workspace:* version: link:../../packages/plugin-sdk @@ -3850,48 +3850,48 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.197': - resolution: {integrity: sha512-jC6WvH5Hr6APTfbMjo4nC6LlyMMqbpCMwiHXIw7/AsQXIHQhZ+cRRMesQlV6UFI1l3O53gLZHzsG9cXwfrPHKw==} + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.245': + resolution: {integrity: sha512-oH1R4yxVKR8oSYMqKHb5NaAPYq8+/enKR0qZKi+lKm6ru64onCmoujT3ilD9rz6TYALCYp2L8Jl2zwOerKSpug==} cpu: [arm64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.197': - resolution: {integrity: sha512-ZQNvGkMrTyatBlHTIQ4w2i2aLBuvq355UP/FDLnVXIH8l23RsL1x/0w9P+dqB7EmY9OZi/cPxSrpskpo+dZWLA==} + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.245': + resolution: {integrity: sha512-VtK8dfnF0GhVzJgVylZxPdGRZb21DhOpd04WWefKRfnWVbgjdOtEGpafyMi4ZaND/ftYpc2PB2P7IX8OkIj5iA==} cpu: [x64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.197': - resolution: {integrity: sha512-VuIGXsLGK/aqSQ0tTBqqPVNzjefWS5SWnK8mlYyQitT4s5UDzHXJm0UZBTGxRtlcS0e2+QAHKwbGBCq1ZKSXjg==} + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.245': + resolution: {integrity: sha512-81QcZcFL5YcLLdvw2AXBq8Bxs/Fcq4qkq5dqUkjEEIi6jI2+WTk5gCjLsUg9zVaHG1yoVN3HKBFJss+sI+evHA==} cpu: [arm64] os: [linux] - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.197': - resolution: {integrity: sha512-pWhQgCtAft4EGM4Zn24HRad1a/k2u6oA+2uM/KCdjehfKtooDiHfMNd1yzXY/n9AEBWP0RHB2Vz3mJ30X2pVAg==} + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.245': + resolution: {integrity: sha512-qIi1grLff5a3Z6K9dUsWKrFKcjUuOGErX89DHC9m0UBfGN6swOs6cFj9zvFYhJhLBJeZu0JKyz18BHSXfeu3PA==} cpu: [arm64] os: [linux] - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.197': - resolution: {integrity: sha512-3Tuy7XhD4UIKE4A4RPmKJcbL7Q/3dcB1hEWQt2lKP7c/DlixeEv+tRzvpnFZKhFX2hy0tkBk3QjkozSAacMC/w==} + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.245': + resolution: {integrity: sha512-3Uxl7YDnqpQHbSpEOYCPytcbcuo1PcdYHDXDpoSotBPlvFOJEiLGCGhwWfEbTO/9RGdp94Qm0T2gGtPNE6QpFg==} cpu: [x64] os: [linux] - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.197': - resolution: {integrity: sha512-AUccrbdcv4Hy/GteP/gYLjG/zDP+fe2BFtDMctEfRFVz40DazYDcOyW1+nIgSTQtxf5jSTAVVf3cNuXB2CZwlw==} + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.245': + resolution: {integrity: sha512-fvPtGYI61pGRP2rmaYskyLE83PytLLMV/NzDunmDlIRvqkzgQrxDBeOistt6mc29uAlI70EbaKW4zsSft6IhBQ==} cpu: [x64] os: [linux] - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.197': - resolution: {integrity: sha512-Wx8uiAKBenDuL8lWQmrqnX5ppljaH5unQ9cKiCz2/9Kgf09dgnrwbX8n/FhndCZR8PmYw539eWwYVrSVc/bl6w==} + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.245': + resolution: {integrity: sha512-N8JTt+DuX2xwbbnLMLDUgmnbtz3VKSGI07WV4WjCEBeb6Olt2KJHDksnJzknrbFBapJz915Vbv2fO+izJcRI1g==} cpu: [arm64] os: [win32] - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.197': - resolution: {integrity: sha512-ZXJO/VvR3SI4G0gwthWeFXWdHB5RXPu3rtfGRcKZ/YgtDeW17rQ+LZIJTk2ywzbLb8EvlghR5JPgn293hC179Q==} + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.245': + resolution: {integrity: sha512-C8PHrQBPgExO6sr5bwG+IW7cTR2KDmvAUj9By++u+IiTFEAtPCEjOQ3CFgfPc5wTuGD26SrzWUbdEom5TQp1Dg==} cpu: [x64] os: [win32] - '@anthropic-ai/claude-agent-sdk@0.3.197': - resolution: {integrity: sha512-XNIi8W1tb+QfMkcK+5kepOC6BsxG8wtupd72H+pIPzIJypVQhHy7FoX+KBMtTRYwtl+5dsjKyABhjWXebeUilw==} + '@anthropic-ai/claude-agent-sdk@0.3.245': + resolution: {integrity: sha512-b/SXCxBxZfN4ItHFDUS1uJ3xhI5fOSv3/VxyZvekYmlsbSwZi/75UqKhVlT7qbB1LDJOB48ZmQdCTxWhJWjObA==} engines: {node: '>=18.0.0'} peerDependencies: '@anthropic-ai/sdk': '>=0.93.0' @@ -15491,44 +15491,44 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.197': + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.197': + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.197': + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.197': + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.197': + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.197': + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.197': + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.197': + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.245': optional: true - '@anthropic-ai/claude-agent-sdk@0.3.197(@anthropic-ai/sdk@0.93.0(zod@4.3.6))(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(zod@4.3.6)': + '@anthropic-ai/claude-agent-sdk@0.3.245(@anthropic-ai/sdk@0.93.0(zod@4.3.6))(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(zod@4.3.6)': dependencies: '@anthropic-ai/sdk': 0.93.0(zod@4.3.6) '@modelcontextprotocol/sdk': 1.29.0(zod@4.3.6) zod: 4.3.6 optionalDependencies: - '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.197 - '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.197 - '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.197 - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.197 - '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.197 - '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.197 - '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.197 - '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.197 + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.245 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.245 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.245 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.245 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.245 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.245 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.245 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.245 '@anthropic-ai/sdk@0.91.1(zod@4.3.6)': dependencies: @@ -19373,7 +19373,9 @@ snapshots: metro-runtime: 0.84.5 transitivePeerDependencies: - '@babel/core' + - bufferutil - supports-color + - utf-8-validate '@react-native/normalize-colors@0.79.6': {}