From 9d9da1b279aa1f156595c04d31479be69a37d9fa Mon Sep 17 00:00:00 2001 From: jsgerrchg Date: Wed, 29 Jul 2026 17:03:07 -0400 Subject: [PATCH] Update Claude ACP to 0.63.0 --- vendor/Claude-agent-acp-upstream/.gitignore | 2 +- .../.release-please-manifest.json | 2 +- vendor/Claude-agent-acp-upstream/CHANGELOG.md | 14 + vendor/Claude-agent-acp-upstream/README.md | 12 + .../dist/acp-agent.d.ts | 42 +- .../dist/acp-agent.d.ts.map | 2 +- .../dist/acp-agent.js | 209 +++++- .../dist/tests/acp-agent.test.js | 501 +++++++++++++- .../dist/tests/create-session-options.test.js | 26 + .../dist/tests/fast-mode-config.test.js | 74 ++- .../dist/tests/tools.test.js | 74 +++ .../dist/tools.d.ts.map | 2 +- .../Claude-agent-acp-upstream/dist/tools.js | 21 +- .../examples/steering.ts | 1 - .../package-lock.json | 134 ++-- vendor/Claude-agent-acp-upstream/package.json | 10 +- .../src/acp-agent.ts | 262 +++++++- .../src/tests/acp-agent.test.ts | 621 +++++++++++++++++- .../src/tests/create-session-options.test.ts | 32 + .../src/tests/fast-mode-config.test.ts | 104 ++- .../src/tests/tools.test.ts | 88 +++ vendor/Claude-agent-acp-upstream/src/tools.ts | 23 +- vendor/README.md | 14 +- 23 files changed, 2136 insertions(+), 134 deletions(-) diff --git a/vendor/Claude-agent-acp-upstream/.gitignore b/vendor/Claude-agent-acp-upstream/.gitignore index 9ddc48f5..ff121fe6 100644 --- a/vendor/Claude-agent-acp-upstream/.gitignore +++ b/vendor/Claude-agent-acp-upstream/.gitignore @@ -3,4 +3,4 @@ dist .DS_Store .claude/*.local.* CLAUDE.local.md -.idea/ +.idea/ \ No newline at end of file diff --git a/vendor/Claude-agent-acp-upstream/.release-please-manifest.json b/vendor/Claude-agent-acp-upstream/.release-please-manifest.json index 4f40d13b..12bb1a5c 100644 --- a/vendor/Claude-agent-acp-upstream/.release-please-manifest.json +++ b/vendor/Claude-agent-acp-upstream/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.62.0" + ".": "0.63.0" } diff --git a/vendor/Claude-agent-acp-upstream/CHANGELOG.md b/vendor/Claude-agent-acp-upstream/CHANGELOG.md index 34d26e3e..722b2c9e 100644 --- a/vendor/Claude-agent-acp-upstream/CHANGELOG.md +++ b/vendor/Claude-agent-acp-upstream/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.63.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.62.0...v0.63.0) (2026-07-27) + + +### Features + +* Update to claude agent sdk v0.3.220 ([#921](https://github.com/agentclientprotocol/claude-agent-acp/issues/921)) ([4c7b897](https://github.com/agentclientprotocol/claude-agent-acp/commit/4c7b89718306229254879e5045a183233b5ed073)) + + +### Bug Fixes + +* Only resolve a denied tool call the client was told about ([#923](https://github.com/agentclientprotocol/claude-agent-acp/issues/923)) ([8f67b6a](https://github.com/agentclientprotocol/claude-agent-acp/commit/8f67b6a92bec24ae43b3dfbd087fe35df0531857)), closes [#918](https://github.com/agentclientprotocol/claude-agent-acp/issues/918) +* Report tool_progress heartbeats against the tool call they describe ([#916](https://github.com/agentclientprotocol/claude-agent-acp/issues/916)) ([5559ba8](https://github.com/agentclientprotocol/claude-agent-acp/commit/5559ba890ca614cdaa189500aba65d81cc4cd51a)) +* **tools:** key Bash terminal metas off the announced tool_use id ([#917](https://github.com/agentclientprotocol/claude-agent-acp/issues/917)) ([d060414](https://github.com/agentclientprotocol/claude-agent-acp/commit/d0604140f907adbf9747f26a070690926e1de82d)) + ## [0.62.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.61.0...v0.62.0) (2026-07-24) diff --git a/vendor/Claude-agent-acp-upstream/README.md b/vendor/Claude-agent-acp-upstream/README.md index f4b0e8dd..0c6c899c 100644 --- a/vendor/Claude-agent-acp-upstream/README.md +++ b/vendor/Claude-agent-acp-upstream/README.md @@ -12,12 +12,24 @@ This tool implements an ACP agent by using the official [Claude Agent SDK](https - Following - Edit review - TODO lists +- Nested subagent transcripts - Interactive (and background) terminals - Custom [Slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands) - Client MCP servers Learn more about the [Agent Client Protocol](https://agentclientprotocol.com/). +### Nested subagent transcripts + +ACP 1.2 has no standard subagent tool kind or nested-message relationship. Clients that can render +nested transcripts can opt in with `clientCapabilities._meta["subagent-transcript"] = true`. +The agent then forwards subagent text, thinking, and tool calls, relating nested updates to the +launching Agent/Task call through `_meta.claudeCode.parentToolUseId`. Agent/Task calls are marked +with `_meta.claudeCode.subagent = true`. + +Clients that do not advertise the capability retain the legacy flattened behavior. In both modes, +the normal Agent/Task tool result is preserved as the protocol-compatible fallback. + ## Contribution Policy This project does not require a Contributor License Agreement (CLA). Instead, contributions are accepted under the following terms: diff --git a/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts b/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts index 6c1fa3c7..af016493 100644 --- a/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts +++ b/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts @@ -1,5 +1,5 @@ import { AuthenticateRequest, CancelNotification, ClientCapabilities, CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse, DisableProviderRequest, DisableProviderResponse, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, ListProvidersRequest, ListProvidersResponse, LlmProtocol, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, LogoutRequest, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, SetProviderRequest, SetProviderResponse, RequestPermissionRequest, RequestPermissionResponse, ResumeSessionRequest, ResumeSessionResponse, SessionConfigOption, SessionModeState, SessionNotification, SetSessionConfigOptionRequest, SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk"; -import { AgentInfo, CanUseTool, FastModeState, ModelInfo, Options, PermissionMode, PermissionUpdate, Query, SDKMessageOrigin, SDKPartialAssistantMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk"; +import { AgentInfo, CanUseTool, FastModeDisabledReason, FastModeState, ModelInfo, Options, PermissionMode, PermissionUpdate, Query, SDKMessageOrigin, SDKPartialAssistantMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk"; import { ContentBlockParam } from "@anthropic-ai/sdk/resources"; import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs"; import { SettingsManager } from "./settings.js"; @@ -229,6 +229,13 @@ type Session = { * user's intent so it persists across model switches; the Fast mode config * option is only surfaced while the selected model supports it. */ fastModeEnabled: boolean; + /** Why the SDK currently can't serve Fast mode, when the reason is one worth + * telling the user about (see {@link FAST_MODE_UNAVAILABLE_EXPLANATIONS} — + * routine states like the SDK's own opt-in requirement normalize to + * `undefined`). Refreshed from every `fast_mode_disabled_reason` the SDK + * reports on `system`/init and user-turn `result`s; surfaced in the Fast mode + * option's description so a toggle that snaps back off explains itself. */ + fastModeDisabledReason?: FastModeDisabledReason; abortController: AbortController; /** Signal the consumer races `query.next()` against. Aborted by cancel() * (after a grace period) to force the active turn to settle "cancelled" when @@ -242,6 +249,9 @@ type Session = { * cancel. */ forceCancelTimer?: ReturnType; emitRawSDKMessages: boolean | SDKMessageFilter[]; + /** Whether nested subagent text/thinking is forwarded to the ACP client. + * Enabled by either the ACP capability or the pre-existing SDK option. */ + forwardSubagentText: boolean; /** Context window size of the session's current model, carried across * prompts so mid-stream usage_update notifications report a correct `size` * before the turn's first result message arrives. Seeded synchronously at @@ -496,10 +506,12 @@ type ProviderConfig = { export type ToolUpdateMeta = { claudeCode?: { toolName: string; + title?: string; toolResponse?: unknown; parentToolUseId?: string; nonExecutionKind?: string; userFeedback?: string; + subagent?: true; }; terminal_info?: { terminal_id: string; @@ -819,7 +831,14 @@ export declare class ClaudeAcpAgent { * here). * - `cooldown`: a transient suspension of an already-enabled fast mode. * Leave the toggle as-is rather than flapping it — and never let a stray - * cooldown spuriously enable a toggle the user has off. */ + * cooldown spuriously enable a toggle the user has off. + * + * `reason` is the SDK's `fast_mode_disabled_reason`, reported alongside the + * state. Only explainable reasons are retained (see + * {@link normalizeFastModeDisabledReason}), so the comparison below tracks + * exactly what the user can see: a routine `sdk_opt_in_required` report on + * every turn's result can't churn the option, while a real blocker updates + * the description even when the toggle's own value is unchanged. */ private syncFastModeState; private getOrCreateSession; /** @@ -856,6 +875,12 @@ export declare const FAST_MODE_OFF = "off"; * docs) keeps the toggle on so it reflects the user's intent — only an * explicit `off` clears it. */ export declare function fastModeStateEnabled(state: FastModeState): boolean; +/** Normalize an SDK-reported `fast_mode_disabled_reason` to the one we retain: + * a reason we have an explanation for, else `undefined`. Keeping only + * explainable reasons means state comparisons (see `syncFastModeState`) track + * exactly what the user can see, so routine reports like + * `sdk_opt_in_required` never churn the config option. */ +export declare function normalizeFastModeDisabledReason(reason: FastModeDisabledReason | undefined): FastModeDisabledReason | undefined; /** Whether the Client advertised support for boolean session config options * (`session.configOptions.boolean`). Agents MUST only send `type: "boolean"` * config options to Clients that opt in; otherwise we fall back to a `select`. @@ -864,8 +889,14 @@ export declare function clientSupportsBooleanConfigOptions(clientCapabilities?: /** Build the Fast mode config option. When the Client supports boolean config * options we expose a native `type: "boolean"` toggle; otherwise we degrade to * a two-value `select` ("on"/"off") so older Clients still get a usable - * control. */ -export declare function createFastModeConfigOption(enabled: boolean, useBooleanOption: boolean): SessionConfigOption; + * control. + * + * `disabledReason` (the SDK's `fast_mode_disabled_reason`) is folded into the + * description while the toggle reads off, so a user whose account or provider + * can't serve Fast mode sees why instead of a switch that silently refuses to + * stay on. Ignored while enabled: a reason reported alongside an `on`/`cooldown` + * state isn't blocking anything right now. */ +export declare function createFastModeConfigOption(enabled: boolean, useBooleanOption: boolean, disabledReason?: FastModeDisabledReason): SessionConfigOption; /** Resolve the requested Fast mode value from a `session/set_config_option` * request. Accepts a native boolean (boolean-capable Clients) or the * "on"/"off" select-fallback strings. */ @@ -877,6 +908,9 @@ export type FastModeOptionState = { enabled: boolean; /** Whether the Client opted into boolean config options. */ useBooleanOption: boolean; + /** Latest explainable `fast_mode_disabled_reason`, folded into the option's + * description while the toggle reads off. */ + disabledReason?: FastModeDisabledReason; }; export declare function buildConfigOptions(modes: SessionModeState, models: SessionModelState, modelInfos: ModelInfo[], currentEffortLevel?: string, agents?: AgentInfo[], currentAgent?: string, fastMode?: FastModeOptionState): SessionConfigOption[]; export declare function resolveModelPreference(models: ModelInfo[], preference: string): ModelInfo | null; diff --git a/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts.map b/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts.map index 682a50cd..b6bdd6aa 100644 --- a/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts.map +++ b/vendor/Claude-agent-acp-upstream/dist/acp-agent.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/vendor/Claude-agent-acp-upstream/dist/acp-agent.js b/vendor/Claude-agent-acp-upstream/dist/acp-agent.js index 451fbd5f..da73dac1 100644 --- a/vendor/Claude-agent-acp-upstream/dist/acp-agent.js +++ b/vendor/Claude-agent-acp-upstream/dist/acp-agent.js @@ -140,6 +140,23 @@ const PROVIDER_ID = "main"; * env-var mappings understood by {@link createEnvForProvider}. */ const SUPPORTED_PROTOCOLS = ["anthropic", "bedrock", "vertex"]; +const SUBAGENT_TRANSCRIPT_CAPABILITY = "subagent-transcript"; +function supportsSubagentTranscript(capabilities) { + return capabilities?._meta?.[SUBAGENT_TRANSCRIPT_CAPABILITY] === true; +} +function parentToolUseIdOf(message) { + if (!("parent_tool_use_id" in message)) + return null; + return typeof message.parent_tool_use_id === "string" ? message.parent_tool_use_id : null; +} +function stripSubagentTextAndThinking(content) { + if (!Array.isArray(content)) + return content; + return content.filter((item) => !item || + typeof item !== "object" || + !("type" in item) || + (item.type !== "text" && item.type !== "thinking")); +} /** * Advance the lexer state across the fragment appended since the last delta: * just enough JSON awareness (string/escape, nesting depth) to spot commas @@ -1583,7 +1600,7 @@ export class ClaudeAcpAgent { // A fresh `system`/init (e.g. after reinitialize) can carry an // updated Fast mode state; reconcile it with what we seeded at // session creation. - await this.syncFastModeState(message.session_id, session, message.fast_mode_state); + await this.syncFastModeState(message.session_id, session, message.fast_mode_state, message.fast_mode_disabled_reason); break; case "status": { // These banners count as delivered text (via sendUpdate), so @@ -1828,6 +1845,28 @@ export class ClaudeAcpAgent { // already emitted as a `tool_call`, so mark it failed with the // rejection reason — otherwise the client shows a tool call // that silently never resolves. + // + // The id is the executing call's own, and the frame lands + // between its `tool_use` and its `tool_result` (the SDK enqueues + // it from inside canUseTool), so the call is normally in flight + // here. Not always: the assistant message carrying the tool_use + // is dropped by the cancelled-turn guard below, and a denial for + // it can still arrive afterwards — the case the `tool_result` + // fallback in `toAcpNotifications` gates on `wasEmitted` for. + // Drop the update rather than reference a tool call the client + // was never given (see `ensureToolCallEmitted`, issue #851). + if (!session.emittedToolCalls.has(message.tool_use_id)) { + break; + } + // A denial inside a subagent identifies the subagent by + // `agent_id` (as canUseTool does with `agentID`), never by the + // Agent/Task call that spawned it. Resolve it the same way so + // the update lands in the subagent's transcript alongside the + // `tool_call` it resolves, which carries the parent stamped from + // `parent_tool_use_id` (see `liveBackgroundTasks`). + const parentToolUseId = message.agent_id + ? session.liveBackgroundTasks.get(message.agent_id)?.parentToolUseId + : undefined; const reason = message.decision_reason ?? message.message; await sendUpdate({ sessionId: message.session_id, @@ -1844,6 +1883,7 @@ export class ClaudeAcpAgent { _meta: { claudeCode: { toolName: message.tool_name, + ...(parentToolUseId ? { parentToolUseId } : {}), toolResponse: { decisionReasonType: message.decision_reason_type, decisionReason: message.decision_reason, @@ -2077,7 +2117,7 @@ export class ClaudeAcpAgent { // an autonomous cycle's state lands on the next user turn's // result. Runs even when the turn errors or was cancelled. if (!isAutonomousResult) { - await this.syncFastModeState(params.sessionId, session, message.fast_mode_state); + await this.syncFastModeState(params.sessionId, session, message.fast_mode_state, message.fast_mode_disabled_reason); } // A user-turn result needs an active turn so its stop reason is // attributed and the turn settles at idle. Local-only commands carry @@ -2691,11 +2731,13 @@ export class ClaudeAcpAgent { // the record stays bounded to the in-flight message. streamedBlocks.length = 0; } - else if (message.type === "assistant") { - // Subagent assistant message (`parent_tool_use_id !== null`). It is - // never streamed live and its text/thinking is internal to the tool - // call — keep dropping it so subagent prose doesn't leak into the - // top-level feed. + else if (message.type === "assistant" && + !(session.forwardSubagentText || supportsSubagentTranscript(this.clientCapabilities))) { + // Legacy clients don't understand nested transcripts. Keep the + // historical behavior for them: subagent text/thinking remains + // internal to the tool call instead of leaking into the top-level + // feed. Capable clients opt into the branch above unchanged, with + // `parentToolUseId` stamped by toAcpNotifications. content = message.message.content.filter((item) => item.type !== "text" && item.type !== "thinking"); } else { @@ -2724,11 +2766,30 @@ export class ClaudeAcpAgent { break; } case "tool_progress": { + // Not every beat reports under the id of a tool call the client has + // seen: heartbeats derive `-heartbeat-`, and the + // `agent_api_retry` beats behind `subagentRetry` report under + // `agent_`. Forwarding those verbatim leaves the + // client resolving an id it has never been told about (the same trap + // `ensureToolCallEmitted` documents for #851). The SDK stamps + // `parent_tool_use_id` with the executing tool's real id whenever the + // beat doesn't carry one of its own, so fall back to it rather than + // pattern-matching each synthetic id shape. Beats that do report a real + // id (a subagent's `bash_progress`, whose parent is the spawning Agent + // call) keep resolving to that id. + const toolCallId = session.emittedToolCalls.has(message.tool_use_id) + ? message.tool_use_id + : message.parent_tool_use_id; + // Ids leave `emittedToolCalls` at `tool_result`, so this also stops a + // beat that races past completion from reopening a finished call. + if (toolCallId === null || !session.emittedToolCalls.has(toolCallId)) { + break; + } await sendUpdate({ sessionId: message.session_id, update: { sessionUpdate: "tool_call_update", - toolCallId: message.tool_use_id, + toolCallId, status: "in_progress", _meta: { claudeCode: { @@ -3239,6 +3300,8 @@ export class ClaudeAcpAgent { async replaySessionHistory(sessionId) { const toolUseCache = {}; const messages = await getSessionMessages(sessionId); + const forwardSubagentText = this.sessions[sessionId]?.forwardSubagentText ?? + supportsSubagentTranscript(this.clientCapabilities); for (const message of messages) { // Backfill the ACP messageId -> SDK uuid mapping for messages we didn't // observe live (resumed/loaded sessions), so rewind/resume can translate @@ -3257,6 +3320,10 @@ export class ClaudeAcpAgent { } // @ts-expect-error - untyped in SDK but we handle all of these let content = message.message.content; + const parentToolUseId = parentToolUseIdOf(message); + if (message.type === "assistant" && parentToolUseId && !forwardSubagentText) { + content = stripSubagentTextAndThinking(content); + } // @ts-expect-error - untyped in SDK but we handle all of these if (message.message.role === "user") { content = stripLocalCommandMetadata(content); @@ -3273,6 +3340,7 @@ export class ClaudeAcpAgent { cwd: this.sessions[sessionId]?.cwd, taskState: this.sessions[sessionId]?.taskState, messageId: replayMessageId, + parentToolUseId, })) { await this.client.sessionUpdate(notification); } @@ -3719,6 +3787,13 @@ export class ClaudeAcpAgent { else { session.modes = { ...session.modes, availableModes: newAvailableModes }; } + // `model_not_allowed` described the model we just left, so it must not + // follow us onto the new one; the remaining reasons are account- or + // environment-scoped and stay true across a switch. Either way the next + // init/result report refreshes this. + if (session.fastModeDisabledReason === "model_not_allowed") { + session.fastModeDisabledReason = undefined; + } // Rebuild config options since effort levels depend on the selected model const effortOpt = session.configOptions.find((o) => o.id === EFFORT_CONFIG_ID); const currentEffort = typeof effortOpt?.currentValue === "string" ? effortOpt.currentValue : undefined; @@ -3729,6 +3804,7 @@ export class ClaudeAcpAgent { supported: newModelInfo?.supportsFastMode ?? false, enabled: session.fastModeEnabled, useBooleanOption: clientSupportsBooleanConfigOptions(this.clientCapabilities), + disabledReason: session.fastModeDisabledReason, }); // Sync effort with the SDK if it changed after the model switch const newEffortOpt = session.configOptions.find((o) => o.id === EFFORT_CONFIG_ID); @@ -3810,7 +3886,7 @@ export class ClaudeAcpAgent { * option isn't present, so callers must confirm the current model surfaces * it first. */ refreshFastModeOption(session, enabled) { - const refreshed = createFastModeConfigOption(enabled, clientSupportsBooleanConfigOptions(this.clientCapabilities)); + const refreshed = createFastModeConfigOption(enabled, clientSupportsBooleanConfigOptions(this.clientCapabilities), session.fastModeDisabledReason); session.configOptions = session.configOptions.map((o) => o.id === FAST_MODE_CONFIG_ID ? refreshed : o); } /** Toggle Fast mode for a session: push the SDK flag, record the user's @@ -3839,8 +3915,15 @@ export class ClaudeAcpAgent { * here). * - `cooldown`: a transient suspension of an already-enabled fast mode. * Leave the toggle as-is rather than flapping it — and never let a stray - * cooldown spuriously enable a toggle the user has off. */ - async syncFastModeState(sessionId, session, state) { + * cooldown spuriously enable a toggle the user has off. + * + * `reason` is the SDK's `fast_mode_disabled_reason`, reported alongside the + * state. Only explainable reasons are retained (see + * {@link normalizeFastModeDisabledReason}), so the comparison below tracks + * exactly what the user can see: a routine `sdk_opt_in_required` report on + * every turn's result can't churn the option, while a real blocker updates + * the description even when the toggle's own value is unchanged. */ + async syncFastModeState(sessionId, session, state, reason) { if (state === undefined) { return; } @@ -3851,11 +3934,31 @@ export class ClaudeAcpAgent { return; } const enabled = state === "on"; - if (enabled === session.fastModeEnabled) { + // A reason only describes an off state; drop any that rides an `on` report + // so it can't decorate the option the next time fast mode goes off. + const nextReason = enabled ? undefined : normalizeFastModeDisabledReason(reason); + if (enabled === session.fastModeEnabled && nextReason === session.fastModeDisabledReason) { return; } + // The user asked for Fast mode and the SDK is telling us it can't serve it. + // The description carries the same explanation, but a toggle silently + // snapping back is the case worth saying out loud once, at the flip. + const explain = session.fastModeEnabled && !enabled && nextReason !== undefined; session.fastModeEnabled = enabled; + session.fastModeDisabledReason = nextReason; this.refreshFastModeOption(session, enabled); + if (explain) { + await this.client.sessionUpdate({ + sessionId, + update: { + sessionUpdate: "agent_message_chunk", + content: { + type: "text", + text: `**Fast mode turned off:** ${FAST_MODE_UNAVAILABLE_EXPLANATIONS[nextReason]}.`, + }, + }, + }); + } await this.client.sessionUpdate({ sessionId, update: { @@ -3987,6 +4090,8 @@ export class ClaudeAcpAgent { // Extract options from _meta if provided const sessionMeta = params._meta; const userProvidedOptions = sessionMeta?.claudeCode?.options; + const forwardSubagentText = supportsSubagentTranscript(this.clientCapabilities) || + userProvidedOptions?.forwardSubagentText === true; // Configure thinking behavior from environment variable const thinking = resolveThinkingConfig(process.env.MAX_THINKING_TOKENS, this.logger); // Parse model configuration from environment (e.g. Bedrock model overrides) @@ -4055,6 +4160,7 @@ export class ClaudeAcpAgent { // Override certain fields that must be controlled by ACP cwd: params.cwd, includePartialMessages: true, + forwardSubagentText, mcpServers: { ...(userProvidedOptions?.mcpServers || {}), ...mcpServers }, // If we want bypassPermissions to be an option, we have to allow it here. // But it doesn't work in root mode, so we only activate it if it will work. @@ -4283,10 +4389,18 @@ export class ClaudeAcpAgent { // the resolved model advertises `supportsFastMode`. const fastModeEnabled = initializationResult.fast_mode_state !== undefined && fastModeStateEnabled(initializationResult.fast_mode_state); + // `fast_mode_disabled_reason` reflects the post-switch model since SDK + // 0.3.219 (the initialize response used to answer from the spawn-time + // model). A fresh SDK session reports `sdk_opt_in_required` — the toggle IS + // the opt-in — which normalizes away, so only real blockers are retained. + const fastModeDisabledReason = fastModeEnabled + ? undefined + : normalizeFastModeDisabledReason(initializationResult.fast_mode_disabled_reason); const fastMode = { supported: currentModelInfo?.supportsFastMode ?? false, enabled: fastModeEnabled, useBooleanOption: clientSupportsBooleanConfigOptions(this.clientCapabilities), + disabledReason: fastModeDisabledReason, }; const configOptions = buildConfigOptions(modes, models, modelInfos, settingsManager.getSettings().effortLevel, agents, currentAgent, fastMode); // Apply the initial effort level to the SDK so it matches the UI default @@ -4348,8 +4462,10 @@ export class ClaudeAcpAgent { agents, currentAgent, fastModeEnabled, + fastModeDisabledReason, abortController, emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false, + forwardSubagentText, contextWindowSize: seededWindow.size, contextWindowAuthoritative: seededWindow.authoritative, providerCacheKey, @@ -4607,6 +4723,32 @@ const FAST_MODE_DESCRIPTION = "Faster responses on supported models"; export function fastModeStateEnabled(state) { return state !== "off"; } +/** User-facing explanations for the SDK's `fast_mode_disabled_reason` values + * that a user can act on (or at least wants to know about). Deliberately + * partial — the omitted reasons are not worth surfacing: + * - `sdk_opt_in_required`: every SDK session starts here (the toggle IS the + * opt-in), so it describes the default, not a problem. + * - `preference`: the user turned Fast mode off themselves. + * - `pending`: eligibility is still resolving; the next report supersedes it. + * - `unknown`: nothing meaningful to say. + * Unknown future reasons fall through the same way (open set — the SDK's docs + * say to ignore values you don't handle). */ +const FAST_MODE_UNAVAILABLE_EXPLANATIONS = { + free: "not available on the free plan", + extra_usage_disabled: "requires extra usage to be enabled for this account", + model_not_allowed: "not available for the selected model", + not_first_party: "not available on this API provider", + disabled_by_env: "disabled by environment configuration", + network_error: "eligibility could not be verified (network error)", +}; +/** Normalize an SDK-reported `fast_mode_disabled_reason` to the one we retain: + * a reason we have an explanation for, else `undefined`. Keeping only + * explainable reasons means state comparisons (see `syncFastModeState`) track + * exactly what the user can see, so routine reports like + * `sdk_opt_in_required` never churn the config option. */ +export function normalizeFastModeDisabledReason(reason) { + return reason && FAST_MODE_UNAVAILABLE_EXPLANATIONS[reason] ? reason : undefined; +} /** Whether the Client advertised support for boolean session config options * (`session.configOptions.boolean`). Agents MUST only send `type: "boolean"` * config options to Clients that opt in; otherwise we fall back to a `select`. @@ -4617,12 +4759,21 @@ export function clientSupportsBooleanConfigOptions(clientCapabilities) { /** Build the Fast mode config option. When the Client supports boolean config * options we expose a native `type: "boolean"` toggle; otherwise we degrade to * a two-value `select` ("on"/"off") so older Clients still get a usable - * control. */ -export function createFastModeConfigOption(enabled, useBooleanOption) { + * control. + * + * `disabledReason` (the SDK's `fast_mode_disabled_reason`) is folded into the + * description while the toggle reads off, so a user whose account or provider + * can't serve Fast mode sees why instead of a switch that silently refuses to + * stay on. Ignored while enabled: a reason reported alongside an `on`/`cooldown` + * state isn't blocking anything right now. */ +export function createFastModeConfigOption(enabled, useBooleanOption, disabledReason) { + const explanation = enabled + ? undefined + : disabledReason && FAST_MODE_UNAVAILABLE_EXPLANATIONS[disabledReason]; const base = { id: FAST_MODE_CONFIG_ID, name: "Fast mode", - description: FAST_MODE_DESCRIPTION, + description: explanation ? `${FAST_MODE_DESCRIPTION} — ${explanation}` : FAST_MODE_DESCRIPTION, category: "model_config", }; if (useBooleanOption) { @@ -4715,7 +4866,7 @@ export function buildConfigOptions(modes, models, modelInfos, currentEffortLevel // option renders as a native boolean toggle for Clients that opted in, and a // two-value select otherwise. if (fastMode?.supported) { - options.push(createFastModeConfigOption(fastMode.enabled, fastMode.useBooleanOption)); + options.push(createFastModeConfigOption(fastMode.enabled, fastMode.useBooleanOption, fastMode.disabledReason)); } // Only surface the Agent picker when there's a real choice — i.e. the user // has configured at least one custom agent (built-ins are filtered out in @@ -5298,6 +5449,24 @@ function isTaskTool(toolName) { function shouldEmitToolCall(toolName) { return toolName !== "TodoWrite" && !isTaskTool(toolName); } +/** Build the Claude Code-specific metadata for a tool call. Bash descriptions + * are kept out of ACP's standard `title`, which clients may use as the shell + * command preview, while still giving clients access to Claude's concise + * human-readable title. */ +function claudeCodeMetaFromToolUse(toolUse) { + const description = toolUse.name === "Bash" && + toolUse.input !== null && + typeof toolUse.input === "object" && + "description" in toolUse.input && + typeof toolUse.input.description === "string" + ? toolUse.input.description + : undefined; + return { + toolName: toolUse.name, + ...(description ? { title: description } : {}), + ...((toolUse.name === "Agent" || toolUse.name === "Task") && { subagent: true }), + }; +} /** Build the `tool_call` (or, with `refine`, the `tool_call_update`) * notification for a tool_use. Shared by every site that surfaces a tool call: * the streamed tool_use path (first encounter → tool_call, later encounter → @@ -5308,7 +5477,7 @@ function shouldEmitToolCall(toolName) { function toolCallNotification(toolUse, rawInput, supportsTerminalOutput, cwd, refine = false) { if (refine) { return { - _meta: { claudeCode: { toolName: toolUse.name } }, + _meta: { claudeCode: claudeCodeMetaFromToolUse(toolUse) }, toolCallId: toolUse.id, sessionUpdate: "tool_call_update", rawInput, @@ -5317,7 +5486,7 @@ function toolCallNotification(toolUse, rawInput, supportsTerminalOutput, cwd, re } return { _meta: { - claudeCode: { toolName: toolUse.name }, + claudeCode: claudeCodeMetaFromToolUse(toolUse), ...(toolUse.name === "Bash" && supportsTerminalOutput ? { terminal_info: { terminal_id: toolUse.id } } : {}), @@ -5342,7 +5511,9 @@ function streamedInputRefinement(toolUse, input, supportsTerminalOutput, cwd) { } const { title, kind, locations } = toolInfoFromToolUse({ ...toolUse, input }, supportsTerminalOutput, cwd); return { - _meta: { claudeCode: { toolName: toolUse.name } }, + _meta: { + claudeCode: claudeCodeMetaFromToolUse({ ...toolUse, input }), + }, toolCallId: toolUse.id, sessionUpdate: "tool_call_update", rawInput: input, diff --git a/vendor/Claude-agent-acp-upstream/dist/tests/acp-agent.test.js b/vendor/Claude-agent-acp-upstream/dist/tests/acp-agent.test.js index 33cce83d..8e9523b5 100644 --- a/vendor/Claude-agent-acp-upstream/dist/tests/acp-agent.test.js +++ b/vendor/Claude-agent-acp-upstream/dist/tests/acp-agent.test.js @@ -95,6 +95,7 @@ function mockSessionState(overrides = {}) { currentAgent: "default", abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -401,6 +402,20 @@ describe("tool conversions", () => { ], }); }); + it("should use the Bash command as the title when no description is provided", () => { + const tool_use = { + type: "tool_use", + id: "toolu_01VtsS2mxUFwpBJZYd7BmbC9", + name: "Bash", + input: { + command: "git diff", + }, + }; + expect(toolInfoFromToolUse(tool_use)).toMatchObject({ + kind: "execute", + title: "git diff", + }); + }); it("should handle Glob nicely", () => { const tool_use = { type: "tool_use", @@ -442,6 +457,40 @@ describe("tool conversions", () => { ], }); }); + it("marks Agent and legacy Task tool calls as subagent launches", () => { + for (const name of ["Agent", "Task"]) { + const notifications = toAcpNotifications([ + { + type: "tool_use", + id: `toolu_${name}`, + name, + input: { description: "Explore", prompt: "Inspect the project" }, + }, + ], "assistant", "test-session", {}, {}, console); + expect(notifications[0]?.update).toMatchObject({ + sessionUpdate: "tool_call", + _meta: { claudeCode: { toolName: name, subagent: true } }, + }); + } + const nestedAgent = toAcpNotifications([ + { + type: "tool_use", + id: "nested-agent", + name: "Agent", + input: { description: "Review tests", prompt: "Inspect tests" }, + }, + ], "assistant", "test-session", {}, {}, console, { parentToolUseId: "outer-agent" }); + expect(nestedAgent[0]?.update).toMatchObject({ + sessionUpdate: "tool_call", + _meta: { + claudeCode: { + toolName: "Agent", + subagent: true, + parentToolUseId: "outer-agent", + }, + }, + }); + }); it("should handle Grep tool calls", () => { const tool_use = { type: "tool_use", @@ -744,6 +793,7 @@ describe("tool conversions", () => { inference_geo: null, iterations: null, output_tokens_details: null, + fallback_credit: null, speed: null, }, context_management: null, @@ -1300,6 +1350,77 @@ describe("synthetic login message (issue #863)", () => { expect(JSON.stringify(updates)).not.toContain("/login"); }); }); +describe("subagent transcript replay", () => { + const replayHistory = [ + { + type: "assistant", + uuid: "subagent-message", + session_id: "s1", + parent_tool_use_id: "parent-agent-call", + parent_agent_id: "agent-1", + message: { + id: "api-subagent-message", + model: "claude-sonnet-4-5", + role: "assistant", + type: "message", + stop_reason: "tool_use", + content: [ + { type: "text", text: "nested report" }, + { type: "tool_use", id: "child-tool", name: "Bash", input: { command: "pwd" } }, + ], + }, + }, + ]; + async function replay(capable) { + const updates = []; + const client = { + sessionUpdate: async (update) => updates.push(update), + }; + const agent = new ClaudeAcpAgent(client, { log: () => { }, error: () => { } }); + agent.clientCapabilities = capable ? { _meta: { "subagent-transcript": true } } : {}; + vi.mocked(getSessionMessages).mockResolvedValueOnce(replayHistory); + await agent.replaySessionHistory("s1"); + return updates; + } + it("preserves nested text and child tool attribution for capable clients", async () => { + const updates = await replay(true); + expect(updates).toEqual(expect.arrayContaining([ + expect.objectContaining({ + update: expect.objectContaining({ + sessionUpdate: "agent_message_chunk", + content: { type: "text", text: "nested report" }, + _meta: expect.objectContaining({ + claudeCode: expect.objectContaining({ parentToolUseId: "parent-agent-call" }), + }), + }), + }), + expect.objectContaining({ + update: expect.objectContaining({ + sessionUpdate: "tool_call", + toolCallId: "child-tool", + _meta: expect.objectContaining({ + claudeCode: expect.objectContaining({ parentToolUseId: "parent-agent-call" }), + }), + }), + }), + ])); + }); + it("keeps legacy text filtering without losing child tool attribution", async () => { + const updates = await replay(false); + expect(updates.some(({ update }) => update.sessionUpdate === "agent_message_chunk")).toBe(false); + expect(updates).toEqual(expect.arrayContaining([ + expect.objectContaining({ + update: expect.objectContaining({ + sessionUpdate: "tool_call", + toolCallId: "child-tool", + _meta: expect.objectContaining({ + claudeCode: expect.objectContaining({ parentToolUseId: "parent-agent-call" }), + }), + }), + }), + ])); + }); +}); describe("escape markdown", () => { it("should escape markdown characters", () => { let text = "Hello *world*!"; @@ -1641,6 +1762,7 @@ describe("permission request cancellation", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -1727,7 +1849,7 @@ describe("tool_call emitted before permission request", () => { } it("emits the tool_call (then asks permission) when the stream hasn't yet", async () => { const { agent, events, updates, session } = setup(); - const result = await agent.canUseTool("session-1")("Bash", { command: "ls" }, { + const result = await agent.canUseTool("session-1")("Bash", { command: "git diff", description: "Show current diff" }, { signal: new AbortController().signal, suggestions: [], toolUseID: "tool-1", @@ -1738,6 +1860,10 @@ describe("tool_call emitted before permission request", () => { sessionUpdate: "tool_call", toolCallId: "tool-1", status: "pending", + title: "git diff", + _meta: { + claudeCode: { toolName: "Bash", title: "Show current diff" }, + }, }); expect(session.emittedToolCalls.has("tool-1")).toBe(true); expect(result).toMatchObject({ behavior: "allow" }); @@ -3180,6 +3306,7 @@ describe("session/close", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -3255,6 +3382,7 @@ describe("session/delete", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -3344,6 +3472,7 @@ describe("getOrCreateSession param change detection", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -5042,6 +5171,29 @@ describe("assembled assistant text fallback", () => { expect(messageChunkTexts(updates)).toEqual([]); expect(thoughtChunkTexts(updates)).toEqual([]); }); + it("forwards subagent text and thinking as nested chunks for capable clients", async () => { + const { agent, updates } = createMockAgentWithCapture(); + agent.clientCapabilities = { _meta: { "subagent-transcript": true } }; + injectSession(agent, [ + assistantMessage("msg-subagent", [ + { type: "thinking", thinking: "checking" }, + { type: "text", text: "nested report" }, + ], "tool_use_1"), + result(), + idle, + ]); + await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] }); + const nestedUpdates = updates.filter(({ update }) => update.sessionUpdate === "agent_message_chunk" || + update.sessionUpdate === "agent_thought_chunk"); + expect(nestedUpdates).toHaveLength(2); + for (const { update } of nestedUpdates) { + expect(update._meta).toMatchObject({ + claudeCode: { parentToolUseId: "tool_use_1" }, + }); + } + expect(messageChunkTexts(updates)).toContain("nested report"); + expect(thoughtChunkTexts(updates)).toContain("checking"); + }); it("forwards distinct blocks that a gateway splits across same-id messages", async () => { const { agent, updates } = createMockAgentWithCapture(); // Observed with OpenAI-compatible gateways: one response id split into an @@ -5798,6 +5950,7 @@ describe("post-error recovery", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -8428,6 +8581,7 @@ describe("session/cancel wedge recovery (issue #680)", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -8901,6 +9055,14 @@ describe("streamEventToAcpNotifications", () => { title: "Research dependencies", rawInput: { description: "Research dependencies" }, }, + { + case: "Bash description", + name: "Bash", + partialJson: '{"command":"git diff","description":"Show current diff","timeout":', + title: "git diff", + metaTitle: "Show current diff", + rawInput: { command: "git diff", description: "Show current diff" }, + }, { case: "Bash command with comma and escaped quotes", name: "Bash", @@ -9013,6 +9175,11 @@ describe("streamEventToAcpNotifications", () => { title: testCase.title, rawInput: testCase.rawInput, }); + if ("metaTitle" in testCase) { + expect(refined[0].update._meta).toMatchObject({ + claudeCode: { title: testCase.metaTitle }, + }); + } // Refinements never carry `content`: content built from partial input is // misleading (an Edit missing new_string renders as a deletion) or // invalid (a Write diff without content lacks the required newText). @@ -9433,6 +9600,7 @@ describe("agent selection config option", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -9492,3 +9660,334 @@ describe("agent selection config option", () => { }); }); }); +describe("tool_progress heartbeats", () => { + /** Run a turn carrying `messages`, with `inFlight` tool calls already emitted, + * and return the tool_call_updates it produced. */ + async function run(messages, inFlight) { + const updates = []; + const mockClient = { + sessionUpdate: async (n) => { + updates.push(n); + }, + }; + const agent = new ClaudeAcpAgent(mockClient, { log: () => { }, error: () => { } }); + const input = new Pushable(); + async function* messageGenerator() { + const { value, done } = await input[Symbol.asyncIterator]().next(); + if (!done && value) { + yield { + type: "user", + message: value.message, + parent_tool_use_id: null, + uuid: value.uuid, + session_id: "test-session", + isReplay: true, + }; + } + yield* messages; + yield { + type: "result", + subtype: "success", + stop_reason: null, + is_error: false, + result: "", + usage: {}, + uuid: randomUUID(), + session_id: "test-session", + }; + yield { type: "system", subtype: "session_state_changed", state: "idle" }; + } + agent.sessions["test-session"] = mockSessionState({ + query: wrapQuery(messageGenerator()), + input, + emittedToolCalls: new Set(inFlight), + }); + await agent.prompt({ + sessionId: "test-session", + prompt: [{ type: "text", text: "test" }], + }); + return updates + .map((n) => n.update) + .filter((u) => u.sessionUpdate === "tool_call_update"); + } + /** A heartbeat as the SDK emits it: a derived `tool_use_id`, with + * `parent_tool_use_id` stamped with the real id of the executing tool. */ + function beat(toolUseId, parentToolUseId, extra = {}) { + return { + type: "tool_progress", + tool_use_id: toolUseId, + tool_name: "Bash", + parent_tool_use_id: parentToolUseId, + elapsed_time_seconds: 30, + heartbeat: true, + uuid: randomUUID(), + session_id: "test-session", + ...extra, + }; + } + it("reports every beat against the tool call it describes", async () => { + const sent = await run([beat("toolu_abc-heartbeat-0", "toolu_abc"), beat("toolu_abc-heartbeat-1", "toolu_abc")], ["toolu_abc"]); + expect(sent.map((u) => u.toolCallId)).toEqual(["toolu_abc", "toolu_abc"]); + expect(sent[0].status).toBe("in_progress"); + }); + // Covers a call that was never emitted and one whose tool_result already + // removed it, so a straggling beat can't reopen a call seen to finish. + it("drops a beat for a tool call that is not in flight", async () => { + expect(await run([beat("toolu_ghost-heartbeat-0", "toolu_ghost")], [])).toHaveLength(0); + }); + it("preserves the elapsed time", async () => { + const sent = await run([beat("toolu_abc-heartbeat-2", "toolu_abc", { elapsed_time_seconds: 90 })], ["toolu_abc"]); + expect(sent[0].toolCallId).toBe("toolu_abc"); + expect(sent[0]._meta.claudeCode).toMatchObject({ + toolName: "Bash", + toolResponse: { elapsedTimeSeconds: 90 }, + }); + }); + // `agent_api_retry` beats — the only source of `subagentType`/`subagentRetry` + // — are not heartbeats and don't use the `-heartbeat-` id shape: they + // report under `agent_`, with the retrying Agent call's + // real id in `parent_tool_use_id`. Resolving via the suffix alone dropped + // them, leaving a stalled spawn with no explanation. + it("reports a subagent retry beat against the Agent call that is retrying", async () => { + const sent = await run([ + { + type: "tool_progress", + tool_use_id: "agent_msg_01xyz", + tool_name: "Task", + parent_tool_use_id: "toolu_task", + elapsed_time_seconds: 0, + subagent_type: "code-reviewer", + subagent_retry: { + agent_id: "agent-1", + attempt: 2, + max_retries: 5, + retry_delay_ms: 4000, + error_status: 529, + error_category: "overloaded", + }, + uuid: randomUUID(), + session_id: "test-session", + }, + ], ["toolu_task"]); + expect(sent[0].toolCallId).toBe("toolu_task"); + expect(sent[0]._meta.claudeCode).toMatchObject({ + toolName: "Task", + toolResponse: { + subagentType: "code-reviewer", + subagentRetry: { attempt: 2, max_retries: 5, error_status: 529 }, + }, + }); + }); + // A subagent's own `bash_progress` reports the inner tool's real id, with the + // spawning Agent call as its parent. The real id wins so the beat lands on the + // nested call rather than being hoisted onto the Agent card. + it("leaves a beat that reports a real tool call alone", async () => { + const sent = await run([beat("toolu_inner", "toolu_task", { heartbeat: undefined })], ["toolu_task", "toolu_inner"]); + expect(sent.map((u) => u.toolCallId)).toEqual(["toolu_inner"]); + }); +}); +describe("permission_denied", () => { + /** Run a turn carrying `messages` and return the updates it produced. */ + async function run(messages, overrides = {}) { + const updates = []; + const mockClient = { + sessionUpdate: async (n) => { + updates.push(n.update); + }, + }; + const agent = new ClaudeAcpAgent(mockClient, { log: () => { }, error: () => { } }); + const input = new Pushable(); + async function* messageGenerator() { + const { value, done } = await input[Symbol.asyncIterator]().next(); + if (!done && value) { + yield { + type: "user", + message: value.message, + parent_tool_use_id: null, + uuid: value.uuid, + session_id: "test-session", + isReplay: true, + }; + } + yield* messages; + yield { + type: "result", + subtype: "success", + stop_reason: null, + is_error: false, + result: "", + usage: {}, + uuid: randomUUID(), + session_id: "test-session", + }; + yield { type: "system", subtype: "session_state_changed", state: "idle" }; + } + agent.sessions["test-session"] = mockSessionState({ + query: wrapQuery(messageGenerator()), + input, + ...overrides, + }); + await agent.prompt({ + sessionId: "test-session", + prompt: [{ type: "text", text: "test" }], + }); + return updates; + } + /** The assistant message announcing `toolUseId`, as the streamed tool_use + * arrives when partial messages are off. */ + function toolUse(toolUseId, parentToolUseId = null) { + return { + type: "assistant", + message: { + role: "assistant", + content: [ + { + type: "tool_use", + id: toolUseId, + name: "Write", + input: { file_path: "/tmp/denied.txt", content: "hi" }, + }, + ], + usage: {}, + }, + parent_tool_use_id: parentToolUseId, + uuid: randomUUID(), + session_id: "test-session", + }; + } + /** The rejection the model sees, which follows the denial frame. */ + function toolResult(toolUseId, parentToolUseId = null) { + return { + type: "user", + message: { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: toolUseId, + content: "Permission to use Write has been denied.", + is_error: true, + }, + ], + }, + parent_tool_use_id: parentToolUseId, + uuid: randomUUID(), + session_id: "test-session", + }; + } + function denial(toolUseId, extra = {}) { + return { + type: "system", + subtype: "permission_denied", + tool_name: "Write", + tool_use_id: toolUseId, + decision_reason_type: "mode", + decision_reason: "dontAsk mode denies tools that require approval", + message: "Permission to use Write has been denied.", + uuid: randomUUID(), + session_id: "test-session", + ...extra, + }; + } + const denials = (updates) => updates.filter((u) => u.sessionUpdate === "tool_call_update" && + JSON.stringify(u.content ?? "").includes("Permission denied")); + it("marks the announced tool call failed with the denial reason", async () => { + const updates = await run([ + toolUse("toolu_denied"), + denial("toolu_denied"), + toolResult("toolu_denied"), + ]); + // The denial resolves the call the preceding tool_call announced, before the + // tool_result's own failed update lands. + expect(updates[0]).toMatchObject({ sessionUpdate: "tool_call", toolCallId: "toolu_denied" }); + const sent = denials(updates); + expect(sent).toHaveLength(1); + expect(sent[0]).toMatchObject({ + toolCallId: "toolu_denied", + status: "failed", + content: [ + { + type: "content", + content: { + type: "text", + text: "Permission denied: dontAsk mode denies tools that require approval", + }, + }, + ], + }); + expect(sent[0]._meta.claudeCode).toMatchObject({ + toolName: "Write", + toolResponse: { decisionReasonType: "mode" }, + }); + // Top-level denial: nothing to attribute it to. + expect(sent[0]._meta.claudeCode).not.toHaveProperty("parentToolUseId"); + }); + it("falls back to the SDK's rejection message when there is no decision reason", async () => { + const updates = await run([ + toolUse("toolu_denied"), + denial("toolu_denied", { decision_reason: undefined }), + ]); + expect(denials(updates)[0].content).toEqual([ + { + type: "content", + content: { + type: "text", + text: "Permission denied: Permission to use Write has been denied.", + }, + }, + ]); + }); + // A cancel drops the assistant message carrying the tool_use, so no tool_call + // is emitted; a denial for it still arrives. Forwarding that left the client + // resolving an id it was never given — the case the tool_result fallback in + // `toAcpNotifications` already gates on `wasEmitted` for. + it("drops a denial for a tool call that was never announced", async () => { + expect(denials(await run([denial("toolu_ghost")]))).toHaveLength(0); + }); + // Ids leave `emittedToolCalls` at `tool_result`, so a denial that somehow + // trails its own result can't flip a call the client has seen finish back to + // failed. + it("drops a denial for a tool call that already resolved", async () => { + const updates = await run([ + toolUse("toolu_denied"), + toolResult("toolu_denied"), + denial("toolu_denied"), + ]); + expect(denials(updates)).toHaveLength(0); + }); + // A subagent's denial names the subagent (`agent_id`), never the Agent/Task + // call that spawned it, so without the `liveBackgroundTasks` lookup the update + // lands at the top level while the tool_call it resolves sits in the + // subagent's transcript. + it("attributes a subagent denial to the Agent call that spawned it", async () => { + const updates = await run([ + { + type: "system", + subtype: "task_started", + task_id: "agent-1", + tool_use_id: "toolu_agent", + subagent_type: "general-purpose", + uuid: randomUUID(), + session_id: "test-session", + }, + toolUse("toolu_inner", "toolu_agent"), + denial("toolu_inner", { agent_id: "agent-1" }), + ]); + expect(denials(updates)[0]._meta.claudeCode).toMatchObject({ + toolName: "Write", + parentToolUseId: "toolu_agent", + }); + }); + // The attribution rests on `task_started` having been seen for the agent id. + // If it wasn't, the denial still resolves the call — unattributed beats + // dropped. + it("still resolves a subagent denial when the parent is unknown", async () => { + const updates = await run([ + toolUse("toolu_inner", "toolu_agent"), + denial("toolu_inner", { agent_id: "agent-unknown" }), + ]); + const sent = denials(updates); + expect(sent).toHaveLength(1); + expect(sent[0]._meta.claudeCode).not.toHaveProperty("parentToolUseId"); + }); +}); diff --git a/vendor/Claude-agent-acp-upstream/dist/tests/create-session-options.test.js b/vendor/Claude-agent-acp-upstream/dist/tests/create-session-options.test.js index acab444c..9790b292 100644 --- a/vendor/Claude-agent-acp-upstream/dist/tests/create-session-options.test.js +++ b/vendor/Claude-agent-acp-upstream/dist/tests/create-session-options.test.js @@ -225,6 +225,32 @@ describe("createSession options merging", () => { }); expect(capturedOptions.tools).toEqual([]); }); + describe("subagent transcript forwarding", () => { + it("keeps the legacy default when neither the client nor caller opts in", async () => { + await agent.newSession({ cwd: process.cwd(), mcpServers: [] }); + expect(capturedOptions.forwardSubagentText).toBe(false); + }); + it("preserves the pre-existing caller-provided SDK option", async () => { + await agent.newSession({ + cwd: process.cwd(), + mcpServers: [], + _meta: { claudeCode: { options: { forwardSubagentText: true } } }, + }); + expect(capturedOptions.forwardSubagentText).toBe(true); + }); + it("enables SDK forwarding when the ACP client advertises support", async () => { + await agent.initialize({ + protocolVersion: 1, + clientCapabilities: { _meta: { "subagent-transcript": true } }, + }); + await agent.newSession({ + cwd: process.cwd(), + mcpServers: [], + _meta: { claudeCode: { options: { forwardSubagentText: false } } }, + }); + expect(capturedOptions.forwardSubagentText).toBe(true); + }); + }); describe("systemPrompt via _meta", () => { it("defaults to the claude_code preset when not provided", async () => { await agent.newSession({ cwd: process.cwd(), mcpServers: [] }); diff --git a/vendor/Claude-agent-acp-upstream/dist/tests/fast-mode-config.test.js b/vendor/Claude-agent-acp-upstream/dist/tests/fast-mode-config.test.js index 78286754..a845d675 100644 --- a/vendor/Claude-agent-acp-upstream/dist/tests/fast-mode-config.test.js +++ b/vendor/Claude-agent-acp-upstream/dist/tests/fast-mode-config.test.js @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { buildConfigOptions, clientSupportsBooleanConfigOptions, createFastModeConfigOption, fastModeStateEnabled, resolveFastModeEnabled, FAST_MODE_CONFIG_ID, FAST_MODE_ON, FAST_MODE_OFF, ClaudeAcpAgent, } from "../acp-agent.js"; +import { buildConfigOptions, clientSupportsBooleanConfigOptions, createFastModeConfigOption, fastModeStateEnabled, normalizeFastModeDisabledReason, resolveFastModeEnabled, FAST_MODE_CONFIG_ID, FAST_MODE_ON, FAST_MODE_OFF, ClaudeAcpAgent, } from "../acp-agent.js"; const MODES = { currentModeId: "default", availableModes: [{ id: "default", name: "Default", description: "Standard behavior" }], @@ -24,6 +24,18 @@ describe("createFastModeConfigOption", () => { currentValue: true, }); }); + it("explains a blocking disabled reason while the toggle reads off", () => { + const option = createFastModeConfigOption(false, true, "free"); + expect(option.description).toBe("Faster responses on supported models — not available on the free plan"); + }); + it("ignores routine and on-state reasons", () => { + // Every SDK session starts at `sdk_opt_in_required` — the toggle IS the + // opt-in, so it must not read as a blocker. + expect(createFastModeConfigOption(false, true, "sdk_opt_in_required").description).toBe("Faster responses on supported models"); + expect(createFastModeConfigOption(false, true, "preference").description).toBe("Faster responses on supported models"); + // A reason riding an `on` state isn't blocking anything right now. + expect(createFastModeConfigOption(true, true, "free").description).toBe("Faster responses on supported models"); + }); it("falls back to an on/off select when the client did not opt in", () => { const option = createFastModeConfigOption(false, false); expect(option).toMatchObject({ @@ -72,6 +84,24 @@ describe("fastModeStateEnabled", () => { expect(fastModeStateEnabled("off")).toBe(false); }); }); +describe("normalizeFastModeDisabledReason", () => { + it("retains only reasons we have an explanation for", () => { + expect(normalizeFastModeDisabledReason("free")).toBe("free"); + expect(normalizeFastModeDisabledReason("extra_usage_disabled")).toBe("extra_usage_disabled"); + expect(normalizeFastModeDisabledReason("model_not_allowed")).toBe("model_not_allowed"); + expect(normalizeFastModeDisabledReason("not_first_party")).toBe("not_first_party"); + expect(normalizeFastModeDisabledReason("disabled_by_env")).toBe("disabled_by_env"); + expect(normalizeFastModeDisabledReason("network_error")).toBe("network_error"); + }); + it("drops routine states and unknown values", () => { + expect(normalizeFastModeDisabledReason("sdk_opt_in_required")).toBeUndefined(); + expect(normalizeFastModeDisabledReason("preference")).toBeUndefined(); + expect(normalizeFastModeDisabledReason("pending")).toBeUndefined(); + expect(normalizeFastModeDisabledReason("unknown")).toBeUndefined(); + expect(normalizeFastModeDisabledReason(undefined)).toBeUndefined(); + expect(normalizeFastModeDisabledReason("some_future_reason")).toBeUndefined(); + }); +}); describe("buildConfigOptions Fast mode", () => { it("omits the Fast mode option when the model does not support it", () => { const options = buildConfigOptions(MODES, MODELS, MODEL_INFOS, undefined, [], "default", { @@ -191,6 +221,7 @@ describe("syncFastModeState (SDK-driven state changes)", () => { const session = { query: {}, fastModeEnabled: opts.fastModeEnabled, + fastModeDisabledReason: undefined, configOptions: opts.withOption ? [createFastModeConfigOption(opts.fastModeEnabled, true)] : [], @@ -235,8 +266,49 @@ describe("syncFastModeState (SDK-driven state changes)", () => { const { sync, session, sessionUpdates } = setup({ fastModeEnabled: false, withOption: true }); await sync(SESSION_ID, session, undefined); await sync(SESSION_ID, session, "off"); + // A routine reason normalizes away, so it's still "unchanged" — an + // `sdk_opt_in_required` on every turn's result must not churn the option. + await sync(SESSION_ID, session, "off", "sdk_opt_in_required"); expect(sessionUpdates).toHaveLength(0); }); + it("explains a blocking reason once when the SDK refuses a toggle the user turned on", async () => { + const { sync, session, sessionUpdates } = setup({ fastModeEnabled: true, withOption: true }); + await sync(SESSION_ID, session, "off", "extra_usage_disabled"); + expect(session.fastModeEnabled).toBe(false); + expect(session.fastModeDisabledReason).toBe("extra_usage_disabled"); + expect(sessionUpdates).toHaveLength(2); + expect(sessionUpdates[0].update).toEqual({ + sessionUpdate: "agent_message_chunk", + content: { + type: "text", + text: "**Fast mode turned off:** requires extra usage to be enabled for this account.", + }, + }); + expect(sessionUpdates[1].update).toMatchObject({ sessionUpdate: "config_option_update" }); + expect(session.configOptions).toContainEqual(createFastModeConfigOption(false, true, "extra_usage_disabled")); + // The same report again changes nothing the user can see: no repeat notice. + await sync(SESSION_ID, session, "off", "extra_usage_disabled"); + expect(sessionUpdates).toHaveLength(2); + }); + it("updates the description when only the reason changes", async () => { + const { sync, session, sessionUpdates } = setup({ fastModeEnabled: false, withOption: true }); + await sync(SESSION_ID, session, "off", "not_first_party"); + expect(session.fastModeEnabled).toBe(false); + // The toggle was already off, so the flip-time notice doesn't fire — only + // the description carries the explanation. + expect(sessionUpdates).toHaveLength(1); + expect(sessionUpdates[0].update).toMatchObject({ sessionUpdate: "config_option_update" }); + expect(session.configOptions).toContainEqual(createFastModeConfigOption(false, true, "not_first_party")); + }); + it("drops a retained reason once fast mode comes back on", async () => { + const { sync, session } = setup({ fastModeEnabled: false, withOption: true }); + await sync(SESSION_ID, session, "off", "network_error"); + expect(session.fastModeDisabledReason).toBe("network_error"); + await sync(SESSION_ID, session, "on"); + expect(session.fastModeEnabled).toBe(true); + expect(session.fastModeDisabledReason).toBeUndefined(); + expect(session.configOptions).toContainEqual(createFastModeConfigOption(true, true)); + }); it("preserves the retained setting (no clobber) when the model has no Fast mode option", async () => { // Model without fast support: the SDK reports a capability-driven state, not // the user's intent. We must leave session.fastModeEnabled untouched so it's diff --git a/vendor/Claude-agent-acp-upstream/dist/tests/tools.test.js b/vendor/Claude-agent-acp-upstream/dist/tests/tools.test.js index b5cb7f5f..bdbf76e7 100644 --- a/vendor/Claude-agent-acp-upstream/dist/tests/tools.test.js +++ b/vendor/Claude-agent-acp-upstream/dist/tests/tools.test.js @@ -341,6 +341,80 @@ describe("Bash terminal output", () => { }, }); }); + it("keys the terminal metas off the tool_use id, which is what was announced", () => { + // `toolInfoFromToolUse` announces the terminal as `toolUse.id`, so the + // result's metas have to use the same value for the client to match them + // up. A result block that disagrees (or omits `tool_use_id`) must not be + // allowed to retarget them. + const toolResult = { + ...makeBashResult("out", "", 0), + tool_use_id: "toolu_something_else", + }; + const update = toolUpdateFromToolResult(toolResult, bashToolUse, true); + expect(update.content).toEqual([{ type: "terminal", terminalId: "toolu_bash" }]); + expect(update._meta).toEqual({ + terminal_info: { terminal_id: "toolu_bash" }, + terminal_output: { terminal_id: "toolu_bash", data: "out" }, + terminal_exit: { terminal_id: "toolu_bash", exit_code: 0, signal: null }, + }); + }); + it("falls back to the result's tool_use_id when the tool_use is unavailable", () => { + const toolResult = makeBashResult("out", "", 0); + const update = toolUpdateFromToolResult(toolResult, { name: "Bash" }, true); + expect(update.content).toEqual([{ type: "terminal", terminalId: "toolu_bash" }]); + expect(update._meta).toEqual({ + terminal_info: { terminal_id: "toolu_bash" }, + terminal_output: { terminal_id: "toolu_bash", data: "out" }, + terminal_exit: { terminal_id: "toolu_bash", exit_code: 0, signal: null }, + }); + }); + it("renders a code block instead of a dangling terminal when no id is available", () => { + // Previously this emitted `terminal_id: ""` for all three metas. Nothing + // on the client has a terminal under that id, so the output was stranded + // (Zed buffers output/exit for unknown terminals indefinitely) and the + // user saw an empty terminal. Degrade to the non-terminal rendering. + // `tool_use_id` is required on every result-block type, so a block without + // it can only arrive at runtime (an older or non-conforming emitter). The + // source guards for it with `"tool_use_id" in toolResult`, so exercise that + // path with a cast rather than pretending the type allows it. + const { content, type } = makeBashResult("out", "", 0); + const update = toolUpdateFromToolResult({ content, type }, { name: "Bash" }, true); + expect(update._meta).toBeUndefined(); + expect(update.content).toEqual([ + { + type: "content", + content: { type: "text", text: "```console\nout\n```" }, + }, + ]); + }); + it("treats an empty tool_use id as no id and falls back to the result block", () => { + const toolResult = makeBashResult("out", "", 0); + const update = toolUpdateFromToolResult(toolResult, { id: "", name: "Bash" }, true); + expect(update.content).toEqual([{ type: "terminal", terminalId: "toolu_bash" }]); + expect(update._meta).toEqual({ + terminal_info: { terminal_id: "toolu_bash" }, + terminal_output: { terminal_id: "toolu_bash", data: "out" }, + terminal_exit: { terminal_id: "toolu_bash", exit_code: 0, signal: null }, + }); + }); + it("renders a code block when neither id is a usable string", () => { + // A present-but-undefined `tool_use_id` still satisfies an `in` check, and + // stringifying it would key all three metas on the literal "undefined" — an + // id no client ever created a terminal for, so the output strands exactly as + // it did under the old empty-string id. + const toolResult = { + ...makeBashResult("out", "", 0), + tool_use_id: undefined, + }; + const update = toolUpdateFromToolResult(toolResult, { id: "", name: "Bash" }, true); + expect(update._meta).toBeUndefined(); + expect(update.content).toEqual([ + { + type: "content", + content: { type: "text", text: "```console\nout\n```" }, + }, + ]); + }); it("should include exit_code from return_code in terminal_exit", () => { const toolResult = makeBashResult("", "command not found", 127); const update = toolUpdateFromToolResult(toolResult, bashToolUse, true); diff --git a/vendor/Claude-agent-acp-upstream/dist/tools.d.ts.map b/vendor/Claude-agent-acp-upstream/dist/tools.d.ts.map index 4d29110b..8ecd8ee4 100644 --- a/vendor/Claude-agent-acp-upstream/dist/tools.d.ts.map +++ b/vendor/Claude-agent-acp-upstream/dist/tools.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAaL,eAAe,EACf,gBAAgB,EAChB,eAAe,EAKhB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAGL,oBAAoB,EAEpB,6BAA6B,EAE9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,yCAAyC,EAGzC,qCAAqC,EAGrC,kCAAkC,EAGlC,+CAA+C,EAI/C,wBAAwB,EACxB,kCAAkC,EAIlC,gCAAgC,EAEhC,iCAAiC,EAClC,MAAM,sCAAsC,CAAC;AA2B9C,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE;QACN,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC;QACF,eAAe,CAAC,EAAE;YAChB,WAAW,EAAE,MAAM,CAAC;YACpB,IAAI,EAAE,MAAM,CAAC;SACd,CAAC;QACF,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;SACvB,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,GAAG,EACZ,sBAAsB,GAAE,OAAe,EACvC,GAAG,CAAC,EAAE,MAAM,GACX,QAAQ,CAqWV;AAoFD,wBAAgB,wBAAwB,CACtC,UAAU,EACN,oBAAoB,GACpB,wBAAwB,GACxB,iCAAiC,GACjC,gCAAgC,GAChC,6BAA6B,GAC7B,qCAAqC,GACrC,yCAAyC,GACzC,+CAA+C,GAC/C,kCAAkC,GAClC,kCAAkC,EACtC,OAAO,EAAE,GAAG,GAAG,SAAS,EACxB,sBAAsB,GAAE,OAAe,EACvC,aAAa,CAAC,EAAE,OAAO,GACtB,UAAU,CAmUZ;AAiHD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAChD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAMxF;AAED;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AACF,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAiCpF;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,eAAe,GAAG,SAAS,EAClC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GACnC,IAAI,CASN;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAiB1F;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CAMpE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQnD;AAeD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,OAAO,GAAG;IACrE,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC,CAoCA;AAcD,eAAO,MAAM,oBAAoB,GAC/B,WAAW,MAAM,EACjB,wBAEG;IACD,iBAAiB,CAAC,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,YAAY,EAAE,OAAO,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,SAKF,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAC/B,UAAU;IAAE,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,KAAG,YAiBtD,CAAC;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GACxB,SAAS;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,KAAG,YAsBpE,CAAC"} \ No newline at end of file diff --git a/vendor/Claude-agent-acp-upstream/dist/tools.js b/vendor/Claude-agent-acp-upstream/dist/tools.js index f31edfc2..fd0c6611 100644 --- a/vendor/Claude-agent-acp-upstream/dist/tools.js +++ b/vendor/Claude-agent-acp-upstream/dist/tools.js @@ -501,7 +501,17 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu } case "Bash": { const result = toolResult.content; - const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : ""; + // The terminal was announced under the tool_use's own id (see + // `toolInfoFromToolUse`), so key the output/exit metas off that: it is the + // id the client actually created a terminal for. `toolResult.tool_use_id` + // is the same value whenever present — the caller looks the tool_use up by + // it — so preferring `toolUse.id` only adds a source for the case where the + // result block carries no id at all. Anything that isn't a non-empty + // string is no id at all: `""` matches no terminal, and stringifying a + // present-but-undefined field would invent the literal `"undefined"`. + const terminalIdOf = (id) => typeof id === "string" && id.length > 0 ? id : undefined; + const terminalId = terminalIdOf(toolUse?.id) ?? + terminalIdOf("tool_use_id" in toolResult ? toolResult.tool_use_id : undefined); const isError = "is_error" in toolResult && toolResult.is_error; // Extract output and exit code from either format: // 1. The structured BashOutput (message-level tool_use_result): its @@ -576,7 +586,14 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu return toAcpContentUpdate(result, isError); } } - if (supportsTerminalOutput) { + // Without a terminal id there is nothing the client can reconcile these + // metas against, and emitting them anyway strands the output: a client that + // buffers output/exit for terminals it has not been told about (Zed keeps + // them in `pending_terminal_output`/`pending_terminal_exit`, drained only on + // a matching create) would hold them forever behind an id that never + // arrives, showing an empty terminal. Fall through to the code-block + // rendering below instead. + if (supportsTerminalOutput && terminalId !== undefined) { return { content: [{ type: "terminal", terminalId }], _meta: { diff --git a/vendor/Claude-agent-acp-upstream/examples/steering.ts b/vendor/Claude-agent-acp-upstream/examples/steering.ts index ba91a97a..3971b3e1 100644 --- a/vendor/Claude-agent-acp-upstream/examples/steering.ts +++ b/vendor/Claude-agent-acp-upstream/examples/steering.ts @@ -189,4 +189,3 @@ main().catch((err) => { log(`fatal: ${err?.stack ?? err}`); process.exit(1); }); - diff --git a/vendor/Claude-agent-acp-upstream/package-lock.json b/vendor/Claude-agent-acp-upstream/package-lock.json index 03001b3d..0e08b800 100644 --- a/vendor/Claude-agent-acp-upstream/package-lock.json +++ b/vendor/Claude-agent-acp-upstream/package-lock.json @@ -1,31 +1,31 @@ { "name": "@agentclientprotocol/claude-agent-acp", - "version": "0.62.0", + "version": "0.63.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agentclientprotocol/claude-agent-acp", - "version": "0.62.0", + "version": "0.63.0", "license": "Apache-2.0", "dependencies": { "@agentclientprotocol/sdk": "1.3.0", - "@anthropic-ai/claude-agent-sdk": "0.3.219", + "@anthropic-ai/claude-agent-sdk": "0.3.220", "zod": "^3.25.0 || ^4.0.0" }, "bin": { "claude-agent-acp": "dist/index.js" }, "devDependencies": { - "@anthropic-ai/sdk": "0.114.0", + "@anthropic-ai/sdk": "0.115.0", "@eslint/js": "10.0.1", "@tsconfig/node22": "22.0.5", "@types/node": "26.1.1", "@typescript-eslint/eslint-plugin": "8.65.0", "@typescript-eslint/parser": "8.65.0", - "eslint": "10.7.0", + "eslint": "10.8.0", "eslint-config-prettier": "10.1.8", - "globals": "17.7.0", + "globals": "17.8.0", "prettier": "3.9.6", "ts-node": "10.9.2", "typescript": "6.0.3", @@ -45,22 +45,22 @@ } }, "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.219.tgz", - "integrity": "sha512-dJjzKxoyCQSEEEo9a0g3wBQprRMCpsqjmsNOZaGOWw+W+JHZsolDBhoYfoZ9BA8EdwgaJmbQUvhkAZjROUsBnQ==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.220.tgz", + "integrity": "sha512-glc7SdwPkOkLw8oxwLo9PKTdLJGqW/PIR4urWXFoRtX9YllwozsEVc5Tc1+EvLSkfrsxPJqQWqOgpjUOQXf1oA==", "license": "SEE LICENSE IN README.md", "engines": { "node": ">=18.0.0" }, "optionalDependencies": { - "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.219", - "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.219", - "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.219", - "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.219", - "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.219", - "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.219", - "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.219", - "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.219" + "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.220", + "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.220", + "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.220" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", @@ -69,9 +69,9 @@ } }, "node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.219.tgz", - "integrity": "sha512-TQhGAlbMsOGXi03dwf4nD274Lc5BOJg4QFJTuXcQyhLhx0hUqzxCmtvpXt6S70K6yl1Zgc+2n5W/r1Dgt8G8iw==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.220.tgz", + "integrity": "sha512-7VxlbEosK7DODiOnsjoVd0DSJzbnaPrM2jelMHI0y8zx1UnLS3WC6EFUXbvy74F2sXqEznh2tzn7EKWInaRN6Q==", "cpu": [ "arm64" ], @@ -82,9 +82,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.219.tgz", - "integrity": "sha512-zA/wHN+os4yqXASXJxvGGNSD1p7LZ6BMN71CCphoy5u+d6IpNOCtATnZwINEc2fGFHpv/QDci6jhywJ2O9Flbw==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.220.tgz", + "integrity": "sha512-X9RwDsSmbF6ultKZroaip+DL8WRgC64gHbrAwrRlAFSPNZV7zmJyP2ur8rW7KrxqmtuehdMMkw8+SAC/6hD2PA==", "cpu": [ "x64" ], @@ -95,9 +95,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.219.tgz", - "integrity": "sha512-lizasky9Xj0ouYrz/Akst573Sm8NO3k/0iGCeCDBoQKLnONbVFwc7sHq9x426Pfcllgb4C2Xyc79/1iOALh/9g==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.220.tgz", + "integrity": "sha512-WkROPwWskqhKR9XgnmseHQ6rLi9zM9qt57IWoToIjL/eXOqDWipp7JXZ1L5ud+LrA42dunHPZfBwD/vXZ+A7LA==", "cpu": [ "arm64" ], @@ -111,9 +111,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.219.tgz", - "integrity": "sha512-yiZo+UBCp42FAYqSakgVj/g6LfTH0AklWqIZNEHfVDPaofledXdSu0QJM5yScWNg0hVMaNRXPt0DmuS1oIWW6g==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.220.tgz", + "integrity": "sha512-OHoZOZ8Cf2TBr6oXIXPwyvUxj9jrq2w8E4poA8dMpacXszcPSPiCQCMuuOh4aWJzfeJE1+TtWxhKMVb2csXyZQ==", "cpu": [ "arm64" ], @@ -127,9 +127,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.219.tgz", - "integrity": "sha512-yzXhEsT5XcKa8Dc3sM4D1CQ92/6YGmsBbAhRAdw0PYNkG8x6wbHqfzYZS491Bdjry4tSWemNFtGPNkHiWVo3Hg==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.220.tgz", + "integrity": "sha512-tkTJFnpR9VifvWX2fmkCAPkT6+8Wk/gVu8B5jsVekKZPiZoWRHmMXO30BnZn+f0TZhgYP+82PSX3S8crH1kn+w==", "cpu": [ "x64" ], @@ -143,9 +143,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.219.tgz", - "integrity": "sha512-IhfG57/XorWOQTB6BVdjEFPGH0LwOWO6HnAi+FNuCeiC+XRFl9QI9AKY43opS0gbvJWRobZO865tHX5j7Ou+ow==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.220.tgz", + "integrity": "sha512-K+FWj+LcGhC1Z7wqeWoLxm1iemcba5xKpLLFVwYm4V6HyMx3ruYd/2r2TiQtjT+JWeNFWIys0ScHiItR6vWAiA==", "cpu": [ "x64" ], @@ -159,9 +159,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.219.tgz", - "integrity": "sha512-L/cAT27t3FGP4GlJ16WpXMtaeqDu9olpOlIaqqmjElQp5Lknd8alJ3NIY/SlRAtHDvm4WXug74wnfBOSlwppkg==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.220.tgz", + "integrity": "sha512-rIwgq0UwQExWl6KrHUyC4w5KwpL9l6nd95aUTx6RitexaAuEw//xtfTVLnuE4hDDQZFkzEwpdKc3nxDWoGcUbA==", "cpu": [ "arm64" ], @@ -172,9 +172,9 @@ ] }, "node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": { - "version": "0.3.219", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.219.tgz", - "integrity": "sha512-Fl8Rb9K64jbpC/omIF9y828LIvwRmm1zhZgO8zjalTpjZR/0C31Kl+5FwAS9KkXXUXwmdHSY+82e9dyvBrbIRw==", + "version": "0.3.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.220.tgz", + "integrity": "sha512-MuOuXhbr66HlGaWXD2f3w0k2PsvmnbkwcUZ0dAe2poFLdl72GC2dapwwOBefxm9QmoNqk9+jmv/dSKGOVWyvLw==", "cpu": [ "x64" ], @@ -185,9 +185,9 @@ ] }, "node_modules/@anthropic-ai/sdk": { - "version": "0.114.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.114.0.tgz", - "integrity": "sha512-zRFGTVMFEm77gt70Q0B+CDRKa9AcguydCcp6bD/dXWv8UkfsVFqCbaqU8+4B/pob3+Vy434LgtrBmwSvxfKr3g==", + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.115.0.tgz", + "integrity": "sha512-BJrFIVyjNuU8lfDyIJTvlRYzgQg+zEl78BxE7fq8esULsGz9IRQvGtW5spq3tydmtjQb/GFdooKGdGsetpx+lQ==", "license": "MIT", "dependencies": { "json-schema-to-ts": "^3.1.1", @@ -306,9 +306,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -377,9 +377,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.15", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.15.tgz", - "integrity": "sha512-Za2ai6TLdKjUvnur+eenO6nuYYipVAEhyCAdaV8IRvmU9kK8crOZUSYvIXn72E4f8fJqyAbpcJuTsYYmZp9Deg==", + "version": "1.19.17", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", + "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", "license": "MIT", "peer": true, "engines": { @@ -1741,9 +1741,9 @@ } }, "node_modules/eslint": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", - "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -1753,7 +1753,7 @@ "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", + "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", @@ -1777,7 +1777,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2069,9 +2069,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.0.tgz", - "integrity": "sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==", + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", + "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", "license": "MIT", "peer": true, "dependencies": { @@ -2320,9 +2320,9 @@ } }, "node_modules/globals": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", - "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", "dev": true, "license": "MIT", "engines": { @@ -2447,9 +2447,9 @@ "peer": true }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", + "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==", "license": "MIT", "peer": true, "engines": { @@ -3193,9 +3193,9 @@ } }, "node_modules/postcss": { - "version": "8.5.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz", - "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==", + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "dev": true, "funding": [ { diff --git a/vendor/Claude-agent-acp-upstream/package.json b/vendor/Claude-agent-acp-upstream/package.json index 762fc930..c731e308 100644 --- a/vendor/Claude-agent-acp-upstream/package.json +++ b/vendor/Claude-agent-acp-upstream/package.json @@ -3,7 +3,7 @@ "publishConfig": { "access": "public" }, - "version": "0.62.0", + "version": "0.63.0", "description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)", "main": "dist/lib.js", "types": "dist/lib.d.ts", @@ -64,19 +64,19 @@ }, "dependencies": { "@agentclientprotocol/sdk": "1.3.0", - "@anthropic-ai/claude-agent-sdk": "0.3.219", + "@anthropic-ai/claude-agent-sdk": "0.3.220", "zod": "^3.25.0 || ^4.0.0" }, "devDependencies": { - "@anthropic-ai/sdk": "0.114.0", + "@anthropic-ai/sdk": "0.115.0", "@eslint/js": "10.0.1", "@tsconfig/node22": "22.0.5", "@types/node": "26.1.1", "@typescript-eslint/eslint-plugin": "8.65.0", "@typescript-eslint/parser": "8.65.0", - "eslint": "10.7.0", + "eslint": "10.8.0", "eslint-config-prettier": "10.1.8", - "globals": "17.7.0", + "globals": "17.8.0", "prettier": "3.9.6", "ts-node": "10.9.2", "typescript": "6.0.3", diff --git a/vendor/Claude-agent-acp-upstream/src/acp-agent.ts b/vendor/Claude-agent-acp-upstream/src/acp-agent.ts index c7eba075..7cd77f64 100644 --- a/vendor/Claude-agent-acp-upstream/src/acp-agent.ts +++ b/vendor/Claude-agent-acp-upstream/src/acp-agent.ts @@ -60,6 +60,7 @@ import { CanUseTool, deleteSession, EffortLevel, + FastModeDisabledReason, FastModeState, getSessionInfo, getSessionMessages, @@ -438,6 +439,13 @@ type Session = { * user's intent so it persists across model switches; the Fast mode config * option is only surfaced while the selected model supports it. */ fastModeEnabled: boolean; + /** Why the SDK currently can't serve Fast mode, when the reason is one worth + * telling the user about (see {@link FAST_MODE_UNAVAILABLE_EXPLANATIONS} — + * routine states like the SDK's own opt-in requirement normalize to + * `undefined`). Refreshed from every `fast_mode_disabled_reason` the SDK + * reports on `system`/init and user-turn `result`s; surfaced in the Fast mode + * option's description so a toggle that snaps back off explains itself. */ + fastModeDisabledReason?: FastModeDisabledReason; abortController: AbortController; /** Signal the consumer races `query.next()` against. Aborted by cancel() * (after a grace period) to force the active turn to settle "cancelled" when @@ -451,6 +459,9 @@ type Session = { * cancel. */ forceCancelTimer?: ReturnType; emitRawSDKMessages: boolean | SDKMessageFilter[]; + /** Whether nested subagent text/thinking is forwarded to the ACP client. + * Enabled by either the ACP capability or the pre-existing SDK option. */ + forwardSubagentText: boolean; /** Context window size of the session's current model, carried across * prompts so mid-stream usage_update notifications report a correct `size` * before the turn's first result message arrives. Seeded synchronously at @@ -798,6 +809,8 @@ export type ToolUpdateMeta = { claudeCode?: { /* The name of the tool that was used in Claude Code. */ toolName: string; + /* A human-readable title supplied by Claude Code for the tool call. */ + title?: string; /* The structured output provided by Claude Code. */ toolResponse?: unknown; /* For a tool call made inside a subagent: the tool_use id of the @@ -813,6 +826,11 @@ export type ToolUpdateMeta = { /* Free-text the user supplied when rejecting the tool call, when the harness collected any. Only ever present alongside nonExecutionKind. */ userFeedback?: string; + /* Marks Agent/Task tool calls as subagent launches. ACP 1.2 has no + standard subagent ToolKind yet, so clients that support nested + transcripts need a namespaced marker instead of inferring from + `toolName` or the generic `think` kind. */ + subagent?: true; }; /* Terminal metadata for Bash tool execution, matching codex-acp's _meta protocol. */ terminal_info?: { @@ -829,6 +847,28 @@ export type ToolUpdateMeta = { }; }; +const SUBAGENT_TRANSCRIPT_CAPABILITY = "subagent-transcript"; + +function supportsSubagentTranscript(capabilities?: ClientCapabilities | null): boolean { + return capabilities?._meta?.[SUBAGENT_TRANSCRIPT_CAPABILITY] === true; +} + +function parentToolUseIdOf(message: { parent_tool_use_id?: unknown }): string | null { + if (!("parent_tool_use_id" in message)) return null; + return typeof message.parent_tool_use_id === "string" ? message.parent_tool_use_id : null; +} + +function stripSubagentTextAndThinking(content: unknown): unknown { + if (!Array.isArray(content)) return content; + return content.filter( + (item) => + !item || + typeof item !== "object" || + !("type" in item) || + (item.type !== "text" && item.type !== "thinking"), + ); +} + export type ToolUseCache = { [key: string]: { type: "tool_use" | "server_tool_use" | "mcp_tool_use"; @@ -2469,7 +2509,12 @@ export class ClaudeAcpAgent { // A fresh `system`/init (e.g. after reinitialize) can carry an // updated Fast mode state; reconcile it with what we seeded at // session creation. - await this.syncFastModeState(message.session_id, session, message.fast_mode_state); + await this.syncFastModeState( + message.session_id, + session, + message.fast_mode_state, + message.fast_mode_disabled_reason, + ); break; case "status": { // These banners count as delivered text (via sendUpdate), so @@ -2723,6 +2768,28 @@ export class ClaudeAcpAgent { // already emitted as a `tool_call`, so mark it failed with the // rejection reason — otherwise the client shows a tool call // that silently never resolves. + // + // The id is the executing call's own, and the frame lands + // between its `tool_use` and its `tool_result` (the SDK enqueues + // it from inside canUseTool), so the call is normally in flight + // here. Not always: the assistant message carrying the tool_use + // is dropped by the cancelled-turn guard below, and a denial for + // it can still arrive afterwards — the case the `tool_result` + // fallback in `toAcpNotifications` gates on `wasEmitted` for. + // Drop the update rather than reference a tool call the client + // was never given (see `ensureToolCallEmitted`, issue #851). + if (!session.emittedToolCalls.has(message.tool_use_id)) { + break; + } + // A denial inside a subagent identifies the subagent by + // `agent_id` (as canUseTool does with `agentID`), never by the + // Agent/Task call that spawned it. Resolve it the same way so + // the update lands in the subagent's transcript alongside the + // `tool_call` it resolves, which carries the parent stamped from + // `parent_tool_use_id` (see `liveBackgroundTasks`). + const parentToolUseId = message.agent_id + ? session.liveBackgroundTasks.get(message.agent_id)?.parentToolUseId + : undefined; const reason = message.decision_reason ?? message.message; await sendUpdate({ sessionId: message.session_id, @@ -2739,6 +2806,7 @@ export class ClaudeAcpAgent { _meta: { claudeCode: { toolName: message.tool_name, + ...(parentToolUseId ? { parentToolUseId } : {}), toolResponse: { decisionReasonType: message.decision_reason_type, decisionReason: message.decision_reason, @@ -2979,7 +3047,12 @@ export class ClaudeAcpAgent { // an autonomous cycle's state lands on the next user turn's // result. Runs even when the turn errors or was cancelled. if (!isAutonomousResult) { - await this.syncFastModeState(params.sessionId, session, message.fast_mode_state); + await this.syncFastModeState( + params.sessionId, + session, + message.fast_mode_state, + message.fast_mode_disabled_reason, + ); } // A user-turn result needs an active turn so its stop reason is @@ -3664,11 +3737,15 @@ export class ClaudeAcpAgent { // Consumed: reset so the next message's blocks accumulate fresh and // the record stays bounded to the in-flight message. streamedBlocks.length = 0; - } else if (message.type === "assistant") { - // Subagent assistant message (`parent_tool_use_id !== null`). It is - // never streamed live and its text/thinking is internal to the tool - // call — keep dropping it so subagent prose doesn't leak into the - // top-level feed. + } else if ( + message.type === "assistant" && + !(session.forwardSubagentText || supportsSubagentTranscript(this.clientCapabilities)) + ) { + // Legacy clients don't understand nested transcripts. Keep the + // historical behavior for them: subagent text/thinking remains + // internal to the tool call instead of leaking into the top-level + // feed. Capable clients opt into the branch above unchanged, with + // `parentToolUseId` stamped by toAcpNotifications. content = message.message.content.filter( (item) => item.type !== "text" && item.type !== "thinking", ); @@ -3708,11 +3785,30 @@ export class ClaudeAcpAgent { break; } case "tool_progress": { + // Not every beat reports under the id of a tool call the client has + // seen: heartbeats derive `-heartbeat-`, and the + // `agent_api_retry` beats behind `subagentRetry` report under + // `agent_`. Forwarding those verbatim leaves the + // client resolving an id it has never been told about (the same trap + // `ensureToolCallEmitted` documents for #851). The SDK stamps + // `parent_tool_use_id` with the executing tool's real id whenever the + // beat doesn't carry one of its own, so fall back to it rather than + // pattern-matching each synthetic id shape. Beats that do report a real + // id (a subagent's `bash_progress`, whose parent is the spawning Agent + // call) keep resolving to that id. + const toolCallId = session.emittedToolCalls.has(message.tool_use_id) + ? message.tool_use_id + : message.parent_tool_use_id; + // Ids leave `emittedToolCalls` at `tool_result`, so this also stops a + // beat that races past completion from reopening a finished call. + if (toolCallId === null || !session.emittedToolCalls.has(toolCallId)) { + break; + } await sendUpdate({ sessionId: message.session_id, update: { sessionUpdate: "tool_call_update", - toolCallId: message.tool_use_id, + toolCallId, status: "in_progress", _meta: { claudeCode: { @@ -4270,6 +4366,9 @@ export class ClaudeAcpAgent { private async replaySessionHistory(sessionId: string): Promise { const toolUseCache: ToolUseCache = {}; const messages = await getSessionMessages(sessionId); + const forwardSubagentText = + this.sessions[sessionId]?.forwardSubagentText ?? + supportsSubagentTranscript(this.clientCapabilities); for (const message of messages) { // Backfill the ACP messageId -> SDK uuid mapping for messages we didn't @@ -4291,6 +4390,10 @@ export class ClaudeAcpAgent { // @ts-expect-error - untyped in SDK but we handle all of these let content: unknown = message.message.content; + const parentToolUseId = parentToolUseIdOf(message); + if (message.type === "assistant" && parentToolUseId && !forwardSubagentText) { + content = stripSubagentTextAndThinking(content); + } // @ts-expect-error - untyped in SDK but we handle all of these if (message.message.role === "user") { content = stripLocalCommandMetadata(content); @@ -4312,6 +4415,7 @@ export class ClaudeAcpAgent { cwd: this.sessions[sessionId]?.cwd, taskState: this.sessions[sessionId]?.taskState, messageId: replayMessageId, + parentToolUseId, }, )) { await this.client.sessionUpdate(notification); @@ -4858,6 +4962,14 @@ export class ClaudeAcpAgent { session.modes = { ...session.modes, availableModes: newAvailableModes }; } + // `model_not_allowed` described the model we just left, so it must not + // follow us onto the new one; the remaining reasons are account- or + // environment-scoped and stay true across a switch. Either way the next + // init/result report refreshes this. + if (session.fastModeDisabledReason === "model_not_allowed") { + session.fastModeDisabledReason = undefined; + } + // Rebuild config options since effort levels depend on the selected model const effortOpt = session.configOptions.find((o) => o.id === EFFORT_CONFIG_ID); const currentEffort = @@ -4876,6 +4988,7 @@ export class ClaudeAcpAgent { supported: newModelInfo?.supportsFastMode ?? false, enabled: session.fastModeEnabled, useBooleanOption: clientSupportsBooleanConfigOptions(this.clientCapabilities), + disabledReason: session.fastModeDisabledReason, }, ); @@ -4974,6 +5087,7 @@ export class ClaudeAcpAgent { const refreshed = createFastModeConfigOption( enabled, clientSupportsBooleanConfigOptions(this.clientCapabilities), + session.fastModeDisabledReason, ); session.configOptions = session.configOptions.map((o) => o.id === FAST_MODE_CONFIG_ID ? refreshed : o, @@ -5007,11 +5121,19 @@ export class ClaudeAcpAgent { * here). * - `cooldown`: a transient suspension of an already-enabled fast mode. * Leave the toggle as-is rather than flapping it — and never let a stray - * cooldown spuriously enable a toggle the user has off. */ + * cooldown spuriously enable a toggle the user has off. + * + * `reason` is the SDK's `fast_mode_disabled_reason`, reported alongside the + * state. Only explainable reasons are retained (see + * {@link normalizeFastModeDisabledReason}), so the comparison below tracks + * exactly what the user can see: a routine `sdk_opt_in_required` report on + * every turn's result can't churn the option, while a real blocker updates + * the description even when the toggle's own value is unchanged. */ private async syncFastModeState( sessionId: string, session: Session, state: FastModeState | undefined, + reason?: FastModeDisabledReason, ): Promise { if (state === undefined) { return; @@ -5023,11 +5145,31 @@ export class ClaudeAcpAgent { return; } const enabled = state === "on"; - if (enabled === session.fastModeEnabled) { + // A reason only describes an off state; drop any that rides an `on` report + // so it can't decorate the option the next time fast mode goes off. + const nextReason = enabled ? undefined : normalizeFastModeDisabledReason(reason); + if (enabled === session.fastModeEnabled && nextReason === session.fastModeDisabledReason) { return; } + // The user asked for Fast mode and the SDK is telling us it can't serve it. + // The description carries the same explanation, but a toggle silently + // snapping back is the case worth saying out loud once, at the flip. + const explain = session.fastModeEnabled && !enabled && nextReason !== undefined; session.fastModeEnabled = enabled; + session.fastModeDisabledReason = nextReason; this.refreshFastModeOption(session, enabled); + if (explain) { + await this.client.sessionUpdate({ + sessionId, + update: { + sessionUpdate: "agent_message_chunk", + content: { + type: "text", + text: `**Fast mode turned off:** ${FAST_MODE_UNAVAILABLE_EXPLANATIONS[nextReason]}.`, + }, + }, + }); + } await this.client.sessionUpdate({ sessionId, update: { @@ -5192,6 +5334,9 @@ export class ClaudeAcpAgent { // Extract options from _meta if provided const sessionMeta = params._meta as NewSessionMeta | undefined; const userProvidedOptions = sessionMeta?.claudeCode?.options; + const forwardSubagentText = + supportsSubagentTranscript(this.clientCapabilities) || + userProvidedOptions?.forwardSubagentText === true; // Configure thinking behavior from environment variable const thinking = resolveThinkingConfig(process.env.MAX_THINKING_TOKENS, this.logger); @@ -5270,6 +5415,7 @@ export class ClaudeAcpAgent { // Override certain fields that must be controlled by ACP cwd: params.cwd, includePartialMessages: true, + forwardSubagentText, mcpServers: { ...(userProvidedOptions?.mcpServers || {}), ...mcpServers }, // If we want bypassPermissions to be an option, we have to allow it here. // But it doesn't work in root mode, so we only activate it if it will work. @@ -5533,10 +5679,18 @@ export class ClaudeAcpAgent { const fastModeEnabled = initializationResult.fast_mode_state !== undefined && fastModeStateEnabled(initializationResult.fast_mode_state); + // `fast_mode_disabled_reason` reflects the post-switch model since SDK + // 0.3.219 (the initialize response used to answer from the spawn-time + // model). A fresh SDK session reports `sdk_opt_in_required` — the toggle IS + // the opt-in — which normalizes away, so only real blockers are retained. + const fastModeDisabledReason = fastModeEnabled + ? undefined + : normalizeFastModeDisabledReason(initializationResult.fast_mode_disabled_reason); const fastMode: FastModeOptionState = { supported: currentModelInfo?.supportsFastMode ?? false, enabled: fastModeEnabled, useBooleanOption: clientSupportsBooleanConfigOptions(this.clientCapabilities), + disabledReason: fastModeDisabledReason, }; const configOptions = buildConfigOptions( @@ -5612,8 +5766,10 @@ export class ClaudeAcpAgent { agents, currentAgent, fastModeEnabled, + fastModeDisabledReason, abortController, emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false, + forwardSubagentText, contextWindowSize: seededWindow.size, contextWindowAuthoritative: seededWindow.authoritative, providerCacheKey, @@ -5918,6 +6074,36 @@ export function fastModeStateEnabled(state: FastModeState): boolean { return state !== "off"; } +/** User-facing explanations for the SDK's `fast_mode_disabled_reason` values + * that a user can act on (or at least wants to know about). Deliberately + * partial — the omitted reasons are not worth surfacing: + * - `sdk_opt_in_required`: every SDK session starts here (the toggle IS the + * opt-in), so it describes the default, not a problem. + * - `preference`: the user turned Fast mode off themselves. + * - `pending`: eligibility is still resolving; the next report supersedes it. + * - `unknown`: nothing meaningful to say. + * Unknown future reasons fall through the same way (open set — the SDK's docs + * say to ignore values you don't handle). */ +const FAST_MODE_UNAVAILABLE_EXPLANATIONS: Partial> = { + free: "not available on the free plan", + extra_usage_disabled: "requires extra usage to be enabled for this account", + model_not_allowed: "not available for the selected model", + not_first_party: "not available on this API provider", + disabled_by_env: "disabled by environment configuration", + network_error: "eligibility could not be verified (network error)", +}; + +/** Normalize an SDK-reported `fast_mode_disabled_reason` to the one we retain: + * a reason we have an explanation for, else `undefined`. Keeping only + * explainable reasons means state comparisons (see `syncFastModeState`) track + * exactly what the user can see, so routine reports like + * `sdk_opt_in_required` never churn the config option. */ +export function normalizeFastModeDisabledReason( + reason: FastModeDisabledReason | undefined, +): FastModeDisabledReason | undefined { + return reason && FAST_MODE_UNAVAILABLE_EXPLANATIONS[reason] ? reason : undefined; +} + /** Whether the Client advertised support for boolean session config options * (`session.configOptions.boolean`). Agents MUST only send `type: "boolean"` * config options to Clients that opt in; otherwise we fall back to a `select`. @@ -5931,15 +6117,25 @@ export function clientSupportsBooleanConfigOptions( /** Build the Fast mode config option. When the Client supports boolean config * options we expose a native `type: "boolean"` toggle; otherwise we degrade to * a two-value `select` ("on"/"off") so older Clients still get a usable - * control. */ + * control. + * + * `disabledReason` (the SDK's `fast_mode_disabled_reason`) is folded into the + * description while the toggle reads off, so a user whose account or provider + * can't serve Fast mode sees why instead of a switch that silently refuses to + * stay on. Ignored while enabled: a reason reported alongside an `on`/`cooldown` + * state isn't blocking anything right now. */ export function createFastModeConfigOption( enabled: boolean, useBooleanOption: boolean, + disabledReason?: FastModeDisabledReason, ): SessionConfigOption { + const explanation = enabled + ? undefined + : disabledReason && FAST_MODE_UNAVAILABLE_EXPLANATIONS[disabledReason]; const base = { id: FAST_MODE_CONFIG_ID, name: "Fast mode", - description: FAST_MODE_DESCRIPTION, + description: explanation ? `${FAST_MODE_DESCRIPTION} — ${explanation}` : FAST_MODE_DESCRIPTION, category: "model_config", } as const; @@ -5982,6 +6178,9 @@ export type FastModeOptionState = { enabled: boolean; /** Whether the Client opted into boolean config options. */ useBooleanOption: boolean; + /** Latest explainable `fast_mode_disabled_reason`, folded into the option's + * description while the toggle reads off. */ + disabledReason?: FastModeDisabledReason; }; export function buildConfigOptions( @@ -6059,7 +6258,13 @@ export function buildConfigOptions( // option renders as a native boolean toggle for Clients that opted in, and a // two-value select otherwise. if (fastMode?.supported) { - options.push(createFastModeConfigOption(fastMode.enabled, fastMode.useBooleanOption)); + options.push( + createFastModeConfigOption( + fastMode.enabled, + fastMode.useBooleanOption, + fastMode.disabledReason, + ), + ); } // Only surface the Agent picker when there's a real choice — i.e. the user @@ -6714,6 +6919,29 @@ function shouldEmitToolCall(toolName: string): boolean { return toolName !== "TodoWrite" && !isTaskTool(toolName); } +/** Build the Claude Code-specific metadata for a tool call. Bash descriptions + * are kept out of ACP's standard `title`, which clients may use as the shell + * command preview, while still giving clients access to Claude's concise + * human-readable title. */ +function claudeCodeMetaFromToolUse(toolUse: { + name: string; + input?: unknown; +}): NonNullable { + const description = + toolUse.name === "Bash" && + toolUse.input !== null && + typeof toolUse.input === "object" && + "description" in toolUse.input && + typeof toolUse.input.description === "string" + ? toolUse.input.description + : undefined; + return { + toolName: toolUse.name, + ...(description ? { title: description } : {}), + ...((toolUse.name === "Agent" || toolUse.name === "Task") && { subagent: true as const }), + }; +} + /** Build the `tool_call` (or, with `refine`, the `tool_call_update`) * notification for a tool_use. Shared by every site that surfaces a tool call: * the streamed tool_use path (first encounter → tool_call, later encounter → @@ -6730,7 +6958,7 @@ function toolCallNotification( ): SessionNotification["update"] { if (refine) { return { - _meta: { claudeCode: { toolName: toolUse.name } } satisfies ToolUpdateMeta, + _meta: { claudeCode: claudeCodeMetaFromToolUse(toolUse) } satisfies ToolUpdateMeta, toolCallId: toolUse.id, sessionUpdate: "tool_call_update", rawInput, @@ -6739,7 +6967,7 @@ function toolCallNotification( } return { _meta: { - claudeCode: { toolName: toolUse.name }, + claudeCode: claudeCodeMetaFromToolUse(toolUse), ...(toolUse.name === "Bash" && supportsTerminalOutput ? { terminal_info: { terminal_id: toolUse.id } } : {}), @@ -6774,7 +7002,9 @@ function streamedInputRefinement( cwd, ); return { - _meta: { claudeCode: { toolName: toolUse.name } } satisfies ToolUpdateMeta, + _meta: { + claudeCode: claudeCodeMetaFromToolUse({ ...toolUse, input }), + } satisfies ToolUpdateMeta, toolCallId: toolUse.id, sessionUpdate: "tool_call_update", rawInput: input, diff --git a/vendor/Claude-agent-acp-upstream/src/tests/acp-agent.test.ts b/vendor/Claude-agent-acp-upstream/src/tests/acp-agent.test.ts index abae4061..e8cd20d9 100644 --- a/vendor/Claude-agent-acp-upstream/src/tests/acp-agent.test.ts +++ b/vendor/Claude-agent-acp-upstream/src/tests/acp-agent.test.ts @@ -156,6 +156,7 @@ function mockSessionState(overrides: Record = {}) { currentAgent: "default", abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -530,6 +531,22 @@ describe("tool conversions", () => { }); }); + it("should use the Bash command as the title when no description is provided", () => { + const tool_use = { + type: "tool_use", + id: "toolu_01VtsS2mxUFwpBJZYd7BmbC9", + name: "Bash", + input: { + command: "git diff", + }, + }; + + expect(toolInfoFromToolUse(tool_use)).toMatchObject({ + kind: "execute", + title: "git diff", + }); + }); + it("should handle Glob nicely", () => { const tool_use = { type: "tool_use", @@ -576,6 +593,58 @@ describe("tool conversions", () => { }); }); + it("marks Agent and legacy Task tool calls as subagent launches", () => { + for (const name of ["Agent", "Task"]) { + const notifications = toAcpNotifications( + [ + { + type: "tool_use", + id: `toolu_${name}`, + name, + input: { description: "Explore", prompt: "Inspect the project" }, + }, + ] as any, + "assistant", + "test-session", + {}, + {} as AcpClient, + console, + ); + + expect(notifications[0]?.update).toMatchObject({ + sessionUpdate: "tool_call", + _meta: { claudeCode: { toolName: name, subagent: true } }, + }); + } + + const nestedAgent = toAcpNotifications( + [ + { + type: "tool_use", + id: "nested-agent", + name: "Agent", + input: { description: "Review tests", prompt: "Inspect tests" }, + }, + ] as any, + "assistant", + "test-session", + {}, + {} as AcpClient, + console, + { parentToolUseId: "outer-agent" }, + ); + expect(nestedAgent[0]?.update).toMatchObject({ + sessionUpdate: "tool_call", + _meta: { + claudeCode: { + toolName: "Agent", + subagent: true, + parentToolUseId: "outer-agent", + }, + }, + }); + }); + it("should handle Grep tool calls", () => { const tool_use = { type: "tool_use", @@ -906,6 +975,7 @@ describe("tool conversions", () => { inference_geo: null, iterations: null, output_tokens_details: null, + fallback_credit: null, speed: null, }, context_management: null, @@ -1568,6 +1638,90 @@ describe("synthetic login message (issue #863)", () => { }); }); +describe("subagent transcript replay", () => { + const replayHistory = [ + { + type: "assistant", + uuid: "subagent-message", + session_id: "s1", + parent_tool_use_id: "parent-agent-call", + parent_agent_id: "agent-1", + message: { + id: "api-subagent-message", + model: "claude-sonnet-4-5", + role: "assistant", + type: "message", + stop_reason: "tool_use", + content: [ + { type: "text", text: "nested report" }, + { type: "tool_use", id: "child-tool", name: "Bash", input: { command: "pwd" } }, + ], + }, + }, + ] as Awaited>; + + async function replay(capable: boolean): Promise { + const updates: SessionNotification[] = []; + const client = { + sessionUpdate: async (update: SessionNotification) => updates.push(update), + } as unknown as AcpClient; + const agent = new ClaudeAcpAgent(client, { log: () => {}, error: () => {} }); + (agent as any).clientCapabilities = capable ? { _meta: { "subagent-transcript": true } } : {}; + vi.mocked(getSessionMessages).mockResolvedValueOnce(replayHistory); + + await ( + agent as unknown as { replaySessionHistory(sessionId: string): Promise } + ).replaySessionHistory("s1"); + return updates; + } + + it("preserves nested text and child tool attribution for capable clients", async () => { + const updates = await replay(true); + expect(updates).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + update: expect.objectContaining({ + sessionUpdate: "agent_message_chunk", + content: { type: "text", text: "nested report" }, + _meta: expect.objectContaining({ + claudeCode: expect.objectContaining({ parentToolUseId: "parent-agent-call" }), + }), + }), + }), + expect.objectContaining({ + update: expect.objectContaining({ + sessionUpdate: "tool_call", + toolCallId: "child-tool", + _meta: expect.objectContaining({ + claudeCode: expect.objectContaining({ parentToolUseId: "parent-agent-call" }), + }), + }), + }), + ]), + ); + }); + + it("keeps legacy text filtering without losing child tool attribution", async () => { + const updates = await replay(false); + expect(updates.some(({ update }) => update.sessionUpdate === "agent_message_chunk")).toBe( + false, + ); + expect(updates).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + update: expect.objectContaining({ + sessionUpdate: "tool_call", + toolCallId: "child-tool", + _meta: expect.objectContaining({ + claudeCode: expect.objectContaining({ parentToolUseId: "parent-agent-call" }), + }), + }), + }), + ]), + ); + }); +}); + describe("escape markdown", () => { it("should escape markdown characters", () => { let text = "Hello *world*!"; @@ -1948,6 +2102,7 @@ describe("permission request cancellation", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -2046,11 +2201,15 @@ describe("tool_call emitted before permission request", () => { it("emits the tool_call (then asks permission) when the stream hasn't yet", async () => { const { agent, events, updates, session } = setup(); - const result = await agent.canUseTool("session-1")("Bash", { command: "ls" }, { - signal: new AbortController().signal, - suggestions: [], - toolUseID: "tool-1", - } as any); + const result = await agent.canUseTool("session-1")( + "Bash", + { command: "git diff", description: "Show current diff" }, + { + signal: new AbortController().signal, + suggestions: [], + toolUseID: "tool-1", + } as any, + ); // tool_call is sent before the permission request is raised. expect(events).toEqual(["update:tool_call", "permission"]); @@ -2058,6 +2217,10 @@ describe("tool_call emitted before permission request", () => { sessionUpdate: "tool_call", toolCallId: "tool-1", status: "pending", + title: "git diff", + _meta: { + claudeCode: { toolName: "Bash", title: "Show current diff" }, + }, }); expect(session.emittedToolCalls.has("tool-1")).toBe(true); expect(result).toMatchObject({ behavior: "allow" }); @@ -3860,6 +4023,7 @@ describe("session/close", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -3952,6 +4116,7 @@ describe("session/delete", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -4061,6 +4226,7 @@ describe("getOrCreateSession param change detection", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -5978,6 +6144,39 @@ describe("assembled assistant text fallback", () => { expect(thoughtChunkTexts(updates)).toEqual([]); }); + it("forwards subagent text and thinking as nested chunks for capable clients", async () => { + const { agent, updates } = createMockAgentWithCapture(); + (agent as any).clientCapabilities = { _meta: { "subagent-transcript": true } }; + injectSession(agent, [ + assistantMessage( + "msg-subagent", + [ + { type: "thinking", thinking: "checking" }, + { type: "text", text: "nested report" }, + ], + "tool_use_1", + ), + result(), + idle, + ]); + + await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] }); + + const nestedUpdates = updates.filter( + ({ update }) => + update.sessionUpdate === "agent_message_chunk" || + update.sessionUpdate === "agent_thought_chunk", + ); + expect(nestedUpdates).toHaveLength(2); + for (const { update } of nestedUpdates) { + expect(update._meta).toMatchObject({ + claudeCode: { parentToolUseId: "tool_use_1" }, + }); + } + expect(messageChunkTexts(updates)).toContain("nested report"); + expect(thoughtChunkTexts(updates)).toContain("checking"); + }); + it("forwards distinct blocks that a gateway splits across same-id messages", async () => { const { agent, updates } = createMockAgentWithCapture(); // Observed with OpenAI-compatible gateways: one response id split into an @@ -6885,6 +7084,7 @@ describe("post-error recovery", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -9796,6 +9996,7 @@ describe("session/cancel wedge recovery (issue #680)", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -10377,6 +10578,14 @@ describe("streamEventToAcpNotifications", () => { title: "Research dependencies", rawInput: { description: "Research dependencies" }, }, + { + case: "Bash description", + name: "Bash", + partialJson: '{"command":"git diff","description":"Show current diff","timeout":', + title: "git diff", + metaTitle: "Show current diff", + rawInput: { command: "git diff", description: "Show current diff" }, + }, { case: "Bash command with comma and escaped quotes", name: "Bash", @@ -10494,6 +10703,11 @@ describe("streamEventToAcpNotifications", () => { title: testCase.title, rawInput: testCase.rawInput, }); + if ("metaTitle" in testCase) { + expect(refined[0].update._meta).toMatchObject({ + claudeCode: { title: testCase.metaTitle }, + }); + } // Refinements never carry `content`: content built from partial input is // misleading (an Edit missing new_string renders as a deletion) or // invalid (a Write diff without content lacks the required newText). @@ -11067,6 +11281,7 @@ describe("agent selection config option", () => { fastModeEnabled: false, abortController: new AbortController(), emitRawSDKMessages: false, + forwardSubagentText: false, contextWindowSize: 200000, contextWindowAuthoritative: false, providerCacheKey: "default", @@ -11137,3 +11352,399 @@ describe("agent selection config option", () => { }); }); }); + +describe("tool_progress heartbeats", () => { + // Heartbeat beats identify themselves with a derived + // `-heartbeat-` that never had a `tool_call` of its own. + // Forwarding that id verbatim made clients synthesize a phantom tool call per + // beat, one every ~30s for the life of the tool. + type ToolCallUpdate = Extract< + SessionNotification["update"], + { sessionUpdate: "tool_call_update" } + >; + + /** Run a turn carrying `messages`, with `inFlight` tool calls already emitted, + * and return the tool_call_updates it produced. */ + async function run(messages: any[], inFlight: string[]) { + const updates: SessionNotification[] = []; + const mockClient = { + sessionUpdate: async (n: SessionNotification) => { + updates.push(n); + }, + } as unknown as AcpClient; + const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); + const input = new Pushable(); + async function* messageGenerator() { + const { value, done } = await input[Symbol.asyncIterator]().next(); + if (!done && value) { + yield { + type: "user", + message: value.message, + parent_tool_use_id: null, + uuid: value.uuid, + session_id: "test-session", + isReplay: true, + }; + } + yield* messages; + yield { + type: "result", + subtype: "success", + stop_reason: null, + is_error: false, + result: "", + usage: {}, + uuid: randomUUID(), + session_id: "test-session", + }; + yield { type: "system", subtype: "session_state_changed", state: "idle" }; + } + agent.sessions["test-session"] = mockSessionState({ + query: wrapQuery(messageGenerator()), + input, + emittedToolCalls: new Set(inFlight), + }); + await agent.prompt({ + sessionId: "test-session", + prompt: [{ type: "text", text: "test" }], + }); + return updates + .map((n) => n.update) + .filter((u): u is ToolCallUpdate => u.sessionUpdate === "tool_call_update"); + } + + /** A heartbeat as the SDK emits it: a derived `tool_use_id`, with + * `parent_tool_use_id` stamped with the real id of the executing tool. */ + function beat( + toolUseId: string, + parentToolUseId: string | null, + extra: Record = {}, + ) { + return { + type: "tool_progress", + tool_use_id: toolUseId, + tool_name: "Bash", + parent_tool_use_id: parentToolUseId, + elapsed_time_seconds: 30, + heartbeat: true, + uuid: randomUUID(), + session_id: "test-session", + ...extra, + }; + } + + it("reports every beat against the tool call it describes", async () => { + const sent = await run( + [beat("toolu_abc-heartbeat-0", "toolu_abc"), beat("toolu_abc-heartbeat-1", "toolu_abc")], + ["toolu_abc"], + ); + + expect(sent.map((u) => u.toolCallId)).toEqual(["toolu_abc", "toolu_abc"]); + expect(sent[0].status).toBe("in_progress"); + }); + + // Covers a call that was never emitted and one whose tool_result already + // removed it, so a straggling beat can't reopen a call seen to finish. + it("drops a beat for a tool call that is not in flight", async () => { + expect(await run([beat("toolu_ghost-heartbeat-0", "toolu_ghost")], [])).toHaveLength(0); + }); + + it("preserves the elapsed time", async () => { + const sent = await run( + [beat("toolu_abc-heartbeat-2", "toolu_abc", { elapsed_time_seconds: 90 })], + ["toolu_abc"], + ); + + expect(sent[0].toolCallId).toBe("toolu_abc"); + expect((sent[0]._meta as any).claudeCode).toMatchObject({ + toolName: "Bash", + toolResponse: { elapsedTimeSeconds: 90 }, + }); + }); + + // `agent_api_retry` beats — the only source of `subagentType`/`subagentRetry` + // — are not heartbeats and don't use the `-heartbeat-` id shape: they + // report under `agent_`, with the retrying Agent call's + // real id in `parent_tool_use_id`. Resolving via the suffix alone dropped + // them, leaving a stalled spawn with no explanation. + it("reports a subagent retry beat against the Agent call that is retrying", async () => { + const sent = await run( + [ + { + type: "tool_progress", + tool_use_id: "agent_msg_01xyz", + tool_name: "Task", + parent_tool_use_id: "toolu_task", + elapsed_time_seconds: 0, + subagent_type: "code-reviewer", + subagent_retry: { + agent_id: "agent-1", + attempt: 2, + max_retries: 5, + retry_delay_ms: 4000, + error_status: 529, + error_category: "overloaded", + }, + uuid: randomUUID(), + session_id: "test-session", + }, + ], + ["toolu_task"], + ); + + expect(sent[0].toolCallId).toBe("toolu_task"); + expect((sent[0]._meta as any).claudeCode).toMatchObject({ + toolName: "Task", + toolResponse: { + subagentType: "code-reviewer", + subagentRetry: { attempt: 2, max_retries: 5, error_status: 529 }, + }, + }); + }); + + // A subagent's own `bash_progress` reports the inner tool's real id, with the + // spawning Agent call as its parent. The real id wins so the beat lands on the + // nested call rather than being hoisted onto the Agent card. + it("leaves a beat that reports a real tool call alone", async () => { + const sent = await run( + [beat("toolu_inner", "toolu_task", { heartbeat: undefined })], + ["toolu_task", "toolu_inner"], + ); + + expect(sent.map((u) => u.toolCallId)).toEqual(["toolu_inner"]); + }); +}); + +describe("permission_denied", () => { + // The SDK enqueues this frame from inside canUseTool, so it lands between the + // denied call's `tool_use` and the `tool_result` carrying the rejection: the + // call is announced and still in flight. The handler forwarded it without + // checking, which mattered for the denials that arrive outside that window. + type ToolCallUpdate = Extract< + SessionNotification["update"], + { sessionUpdate: "tool_call_update" } + >; + + /** Run a turn carrying `messages` and return the updates it produced. */ + async function run(messages: any[], overrides: Record = {}) { + const updates: SessionNotification["update"][] = []; + const mockClient = { + sessionUpdate: async (n: SessionNotification) => { + updates.push(n.update); + }, + } as unknown as AcpClient; + const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }); + const input = new Pushable(); + async function* messageGenerator() { + const { value, done } = await input[Symbol.asyncIterator]().next(); + if (!done && value) { + yield { + type: "user", + message: value.message, + parent_tool_use_id: null, + uuid: value.uuid, + session_id: "test-session", + isReplay: true, + }; + } + yield* messages; + yield { + type: "result", + subtype: "success", + stop_reason: null, + is_error: false, + result: "", + usage: {}, + uuid: randomUUID(), + session_id: "test-session", + }; + yield { type: "system", subtype: "session_state_changed", state: "idle" }; + } + agent.sessions["test-session"] = mockSessionState({ + query: wrapQuery(messageGenerator()), + input, + ...overrides, + }); + await agent.prompt({ + sessionId: "test-session", + prompt: [{ type: "text", text: "test" }], + }); + return updates; + } + + /** The assistant message announcing `toolUseId`, as the streamed tool_use + * arrives when partial messages are off. */ + function toolUse(toolUseId: string, parentToolUseId: string | null = null) { + return { + type: "assistant", + message: { + role: "assistant", + content: [ + { + type: "tool_use", + id: toolUseId, + name: "Write", + input: { file_path: "/tmp/denied.txt", content: "hi" }, + }, + ], + usage: {}, + }, + parent_tool_use_id: parentToolUseId, + uuid: randomUUID(), + session_id: "test-session", + }; + } + + /** The rejection the model sees, which follows the denial frame. */ + function toolResult(toolUseId: string, parentToolUseId: string | null = null) { + return { + type: "user", + message: { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: toolUseId, + content: "Permission to use Write has been denied.", + is_error: true, + }, + ], + }, + parent_tool_use_id: parentToolUseId, + uuid: randomUUID(), + session_id: "test-session", + }; + } + + function denial(toolUseId: string, extra: Record = {}) { + return { + type: "system", + subtype: "permission_denied", + tool_name: "Write", + tool_use_id: toolUseId, + decision_reason_type: "mode", + decision_reason: "dontAsk mode denies tools that require approval", + message: "Permission to use Write has been denied.", + uuid: randomUUID(), + session_id: "test-session", + ...extra, + }; + } + + const denials = (updates: SessionNotification["update"][]) => + updates.filter( + (u): u is ToolCallUpdate => + u.sessionUpdate === "tool_call_update" && + JSON.stringify(u.content ?? "").includes("Permission denied"), + ); + + it("marks the announced tool call failed with the denial reason", async () => { + const updates = await run([ + toolUse("toolu_denied"), + denial("toolu_denied"), + toolResult("toolu_denied"), + ]); + + // The denial resolves the call the preceding tool_call announced, before the + // tool_result's own failed update lands. + expect(updates[0]).toMatchObject({ sessionUpdate: "tool_call", toolCallId: "toolu_denied" }); + const sent = denials(updates); + expect(sent).toHaveLength(1); + expect(sent[0]).toMatchObject({ + toolCallId: "toolu_denied", + status: "failed", + content: [ + { + type: "content", + content: { + type: "text", + text: "Permission denied: dontAsk mode denies tools that require approval", + }, + }, + ], + }); + expect((sent[0]._meta as any).claudeCode).toMatchObject({ + toolName: "Write", + toolResponse: { decisionReasonType: "mode" }, + }); + // Top-level denial: nothing to attribute it to. + expect((sent[0]._meta as any).claudeCode).not.toHaveProperty("parentToolUseId"); + }); + + it("falls back to the SDK's rejection message when there is no decision reason", async () => { + const updates = await run([ + toolUse("toolu_denied"), + denial("toolu_denied", { decision_reason: undefined }), + ]); + + expect(denials(updates)[0].content).toEqual([ + { + type: "content", + content: { + type: "text", + text: "Permission denied: Permission to use Write has been denied.", + }, + }, + ]); + }); + + // A cancel drops the assistant message carrying the tool_use, so no tool_call + // is emitted; a denial for it still arrives. Forwarding that left the client + // resolving an id it was never given — the case the tool_result fallback in + // `toAcpNotifications` already gates on `wasEmitted` for. + it("drops a denial for a tool call that was never announced", async () => { + expect(denials(await run([denial("toolu_ghost")]))).toHaveLength(0); + }); + + // Ids leave `emittedToolCalls` at `tool_result`, so a denial that somehow + // trails its own result can't flip a call the client has seen finish back to + // failed. + it("drops a denial for a tool call that already resolved", async () => { + const updates = await run([ + toolUse("toolu_denied"), + toolResult("toolu_denied"), + denial("toolu_denied"), + ]); + + expect(denials(updates)).toHaveLength(0); + }); + + // A subagent's denial names the subagent (`agent_id`), never the Agent/Task + // call that spawned it, so without the `liveBackgroundTasks` lookup the update + // lands at the top level while the tool_call it resolves sits in the + // subagent's transcript. + it("attributes a subagent denial to the Agent call that spawned it", async () => { + const updates = await run([ + { + type: "system", + subtype: "task_started", + task_id: "agent-1", + tool_use_id: "toolu_agent", + subagent_type: "general-purpose", + uuid: randomUUID(), + session_id: "test-session", + }, + toolUse("toolu_inner", "toolu_agent"), + denial("toolu_inner", { agent_id: "agent-1" }), + ]); + + expect((denials(updates)[0]._meta as any).claudeCode).toMatchObject({ + toolName: "Write", + parentToolUseId: "toolu_agent", + }); + }); + + // The attribution rests on `task_started` having been seen for the agent id. + // If it wasn't, the denial still resolves the call — unattributed beats + // dropped. + it("still resolves a subagent denial when the parent is unknown", async () => { + const updates = await run([ + toolUse("toolu_inner", "toolu_agent"), + denial("toolu_inner", { agent_id: "agent-unknown" }), + ]); + + const sent = denials(updates); + expect(sent).toHaveLength(1); + expect((sent[0]._meta as any).claudeCode).not.toHaveProperty("parentToolUseId"); + }); +}); diff --git a/vendor/Claude-agent-acp-upstream/src/tests/create-session-options.test.ts b/vendor/Claude-agent-acp-upstream/src/tests/create-session-options.test.ts index 12a7d23e..bad683c6 100644 --- a/vendor/Claude-agent-acp-upstream/src/tests/create-session-options.test.ts +++ b/vendor/Claude-agent-acp-upstream/src/tests/create-session-options.test.ts @@ -263,6 +263,38 @@ describe("createSession options merging", () => { expect(capturedOptions!.tools).toEqual([]); }); + describe("subagent transcript forwarding", () => { + it("keeps the legacy default when neither the client nor caller opts in", async () => { + await agent.newSession({ cwd: process.cwd(), mcpServers: [] }); + + expect(capturedOptions!.forwardSubagentText).toBe(false); + }); + + it("preserves the pre-existing caller-provided SDK option", async () => { + await agent.newSession({ + cwd: process.cwd(), + mcpServers: [], + _meta: { claudeCode: { options: { forwardSubagentText: true } } }, + }); + + expect(capturedOptions!.forwardSubagentText).toBe(true); + }); + + it("enables SDK forwarding when the ACP client advertises support", async () => { + await agent.initialize({ + protocolVersion: 1, + clientCapabilities: { _meta: { "subagent-transcript": true } }, + }); + await agent.newSession({ + cwd: process.cwd(), + mcpServers: [], + _meta: { claudeCode: { options: { forwardSubagentText: false } } }, + }); + + expect(capturedOptions!.forwardSubagentText).toBe(true); + }); + }); + describe("systemPrompt via _meta", () => { it("defaults to the claude_code preset when not provided", async () => { await agent.newSession({ cwd: process.cwd(), mcpServers: [] }); diff --git a/vendor/Claude-agent-acp-upstream/src/tests/fast-mode-config.test.ts b/vendor/Claude-agent-acp-upstream/src/tests/fast-mode-config.test.ts index 2843076f..9d190347 100644 --- a/vendor/Claude-agent-acp-upstream/src/tests/fast-mode-config.test.ts +++ b/vendor/Claude-agent-acp-upstream/src/tests/fast-mode-config.test.ts @@ -1,11 +1,12 @@ import { describe, it, expect, vi } from "vitest"; import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk"; -import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk"; +import type { FastModeDisabledReason, ModelInfo } from "@anthropic-ai/claude-agent-sdk"; import { buildConfigOptions, clientSupportsBooleanConfigOptions, createFastModeConfigOption, fastModeStateEnabled, + normalizeFastModeDisabledReason, resolveFastModeEnabled, FAST_MODE_CONFIG_ID, FAST_MODE_ON, @@ -42,6 +43,28 @@ describe("createFastModeConfigOption", () => { }); }); + it("explains a blocking disabled reason while the toggle reads off", () => { + const option = createFastModeConfigOption(false, true, "free"); + expect(option.description).toBe( + "Faster responses on supported models — not available on the free plan", + ); + }); + + it("ignores routine and on-state reasons", () => { + // Every SDK session starts at `sdk_opt_in_required` — the toggle IS the + // opt-in, so it must not read as a blocker. + expect(createFastModeConfigOption(false, true, "sdk_opt_in_required").description).toBe( + "Faster responses on supported models", + ); + expect(createFastModeConfigOption(false, true, "preference").description).toBe( + "Faster responses on supported models", + ); + // A reason riding an `on` state isn't blocking anything right now. + expect(createFastModeConfigOption(true, true, "free").description).toBe( + "Faster responses on supported models", + ); + }); + it("falls back to an on/off select when the client did not opt in", () => { const option = createFastModeConfigOption(false, false); expect(option).toMatchObject({ @@ -104,6 +127,28 @@ describe("fastModeStateEnabled", () => { }); }); +describe("normalizeFastModeDisabledReason", () => { + it("retains only reasons we have an explanation for", () => { + expect(normalizeFastModeDisabledReason("free")).toBe("free"); + expect(normalizeFastModeDisabledReason("extra_usage_disabled")).toBe("extra_usage_disabled"); + expect(normalizeFastModeDisabledReason("model_not_allowed")).toBe("model_not_allowed"); + expect(normalizeFastModeDisabledReason("not_first_party")).toBe("not_first_party"); + expect(normalizeFastModeDisabledReason("disabled_by_env")).toBe("disabled_by_env"); + expect(normalizeFastModeDisabledReason("network_error")).toBe("network_error"); + }); + + it("drops routine states and unknown values", () => { + expect(normalizeFastModeDisabledReason("sdk_opt_in_required")).toBeUndefined(); + expect(normalizeFastModeDisabledReason("preference")).toBeUndefined(); + expect(normalizeFastModeDisabledReason("pending")).toBeUndefined(); + expect(normalizeFastModeDisabledReason("unknown")).toBeUndefined(); + expect(normalizeFastModeDisabledReason(undefined)).toBeUndefined(); + expect( + normalizeFastModeDisabledReason("some_future_reason" as FastModeDisabledReason), + ).toBeUndefined(); + }); +}); + describe("buildConfigOptions Fast mode", () => { it("omits the Fast mode option when the model does not support it", () => { const options = buildConfigOptions(MODES, MODELS, MODEL_INFOS, undefined, [], "default", { @@ -249,6 +294,7 @@ describe("syncFastModeState (SDK-driven state changes)", () => { const session = { query: {}, fastModeEnabled: opts.fastModeEnabled, + fastModeDisabledReason: undefined as FastModeDisabledReason | undefined, configOptions: opts.withOption ? [createFastModeConfigOption(opts.fastModeEnabled, true)] : [], @@ -261,6 +307,7 @@ describe("syncFastModeState (SDK-driven state changes)", () => { sessionId: string, session: unknown, state: string | undefined, + reason?: FastModeDisabledReason, ) => Promise; } ).syncFastModeState.bind(agent); @@ -319,10 +366,65 @@ describe("syncFastModeState (SDK-driven state changes)", () => { await sync(SESSION_ID, session, undefined); await sync(SESSION_ID, session, "off"); + // A routine reason normalizes away, so it's still "unchanged" — an + // `sdk_opt_in_required` on every turn's result must not churn the option. + await sync(SESSION_ID, session, "off", "sdk_opt_in_required"); expect(sessionUpdates).toHaveLength(0); }); + it("explains a blocking reason once when the SDK refuses a toggle the user turned on", async () => { + const { sync, session, sessionUpdates } = setup({ fastModeEnabled: true, withOption: true }); + + await sync(SESSION_ID, session, "off", "extra_usage_disabled"); + + expect(session.fastModeEnabled).toBe(false); + expect(session.fastModeDisabledReason).toBe("extra_usage_disabled"); + expect(sessionUpdates).toHaveLength(2); + expect(sessionUpdates[0].update).toEqual({ + sessionUpdate: "agent_message_chunk", + content: { + type: "text", + text: "**Fast mode turned off:** requires extra usage to be enabled for this account.", + }, + }); + expect(sessionUpdates[1].update).toMatchObject({ sessionUpdate: "config_option_update" }); + expect(session.configOptions).toContainEqual( + createFastModeConfigOption(false, true, "extra_usage_disabled"), + ); + + // The same report again changes nothing the user can see: no repeat notice. + await sync(SESSION_ID, session, "off", "extra_usage_disabled"); + expect(sessionUpdates).toHaveLength(2); + }); + + it("updates the description when only the reason changes", async () => { + const { sync, session, sessionUpdates } = setup({ fastModeEnabled: false, withOption: true }); + + await sync(SESSION_ID, session, "off", "not_first_party"); + + expect(session.fastModeEnabled).toBe(false); + // The toggle was already off, so the flip-time notice doesn't fire — only + // the description carries the explanation. + expect(sessionUpdates).toHaveLength(1); + expect(sessionUpdates[0].update).toMatchObject({ sessionUpdate: "config_option_update" }); + expect(session.configOptions).toContainEqual( + createFastModeConfigOption(false, true, "not_first_party"), + ); + }); + + it("drops a retained reason once fast mode comes back on", async () => { + const { sync, session } = setup({ fastModeEnabled: false, withOption: true }); + + await sync(SESSION_ID, session, "off", "network_error"); + expect(session.fastModeDisabledReason).toBe("network_error"); + + await sync(SESSION_ID, session, "on"); + expect(session.fastModeEnabled).toBe(true); + expect(session.fastModeDisabledReason).toBeUndefined(); + expect(session.configOptions).toContainEqual(createFastModeConfigOption(true, true)); + }); + it("preserves the retained setting (no clobber) when the model has no Fast mode option", async () => { // Model without fast support: the SDK reports a capability-driven state, not // the user's intent. We must leave session.fastModeEnabled untouched so it's diff --git a/vendor/Claude-agent-acp-upstream/src/tests/tools.test.ts b/vendor/Claude-agent-acp-upstream/src/tests/tools.test.ts index e08b6731..da62cc6c 100644 --- a/vendor/Claude-agent-acp-upstream/src/tests/tools.test.ts +++ b/vendor/Claude-agent-acp-upstream/src/tests/tools.test.ts @@ -491,6 +491,94 @@ describe("Bash terminal output", () => { }); }); + it("keys the terminal metas off the tool_use id, which is what was announced", () => { + // `toolInfoFromToolUse` announces the terminal as `toolUse.id`, so the + // result's metas have to use the same value for the client to match them + // up. A result block that disagrees (or omits `tool_use_id`) must not be + // allowed to retarget them. + const toolResult = { + ...makeBashResult("out", "", 0), + tool_use_id: "toolu_something_else", + }; + const update = toolUpdateFromToolResult(toolResult, bashToolUse, true); + + expect(update.content).toEqual([{ type: "terminal", terminalId: "toolu_bash" }]); + expect(update._meta).toEqual({ + terminal_info: { terminal_id: "toolu_bash" }, + terminal_output: { terminal_id: "toolu_bash", data: "out" }, + terminal_exit: { terminal_id: "toolu_bash", exit_code: 0, signal: null }, + }); + }); + + it("falls back to the result's tool_use_id when the tool_use is unavailable", () => { + const toolResult = makeBashResult("out", "", 0); + const update = toolUpdateFromToolResult(toolResult, { name: "Bash" }, true); + + expect(update.content).toEqual([{ type: "terminal", terminalId: "toolu_bash" }]); + expect(update._meta).toEqual({ + terminal_info: { terminal_id: "toolu_bash" }, + terminal_output: { terminal_id: "toolu_bash", data: "out" }, + terminal_exit: { terminal_id: "toolu_bash", exit_code: 0, signal: null }, + }); + }); + + it("renders a code block instead of a dangling terminal when no id is available", () => { + // Previously this emitted `terminal_id: ""` for all three metas. Nothing + // on the client has a terminal under that id, so the output was stranded + // (Zed buffers output/exit for unknown terminals indefinitely) and the + // user saw an empty terminal. Degrade to the non-terminal rendering. + // `tool_use_id` is required on every result-block type, so a block without + // it can only arrive at runtime (an older or non-conforming emitter). The + // source guards for it with `"tool_use_id" in toolResult`, so exercise that + // path with a cast rather than pretending the type allows it. + const { content, type } = makeBashResult("out", "", 0); + const update = toolUpdateFromToolResult( + { content, type } as unknown as Parameters[0], + { name: "Bash" }, + true, + ); + + expect(update._meta).toBeUndefined(); + expect(update.content).toEqual([ + { + type: "content", + content: { type: "text", text: "```console\nout\n```" }, + }, + ]); + }); + + it("treats an empty tool_use id as no id and falls back to the result block", () => { + const toolResult = makeBashResult("out", "", 0); + const update = toolUpdateFromToolResult(toolResult, { id: "", name: "Bash" }, true); + + expect(update.content).toEqual([{ type: "terminal", terminalId: "toolu_bash" }]); + expect(update._meta).toEqual({ + terminal_info: { terminal_id: "toolu_bash" }, + terminal_output: { terminal_id: "toolu_bash", data: "out" }, + terminal_exit: { terminal_id: "toolu_bash", exit_code: 0, signal: null }, + }); + }); + + it("renders a code block when neither id is a usable string", () => { + // A present-but-undefined `tool_use_id` still satisfies an `in` check, and + // stringifying it would key all three metas on the literal "undefined" — an + // id no client ever created a terminal for, so the output strands exactly as + // it did under the old empty-string id. + const toolResult = { + ...makeBashResult("out", "", 0), + tool_use_id: undefined, + } as unknown as Parameters[0]; + const update = toolUpdateFromToolResult(toolResult, { id: "", name: "Bash" }, true); + + expect(update._meta).toBeUndefined(); + expect(update.content).toEqual([ + { + type: "content", + content: { type: "text", text: "```console\nout\n```" }, + }, + ]); + }); + it("should include exit_code from return_code in terminal_exit", () => { const toolResult = makeBashResult("", "command not found", 127); const update = toolUpdateFromToolResult(toolResult, bashToolUse, true); diff --git a/vendor/Claude-agent-acp-upstream/src/tools.ts b/vendor/Claude-agent-acp-upstream/src/tools.ts index 9980187b..b7179902 100644 --- a/vendor/Claude-agent-acp-upstream/src/tools.ts +++ b/vendor/Claude-agent-acp-upstream/src/tools.ts @@ -686,7 +686,19 @@ export function toolUpdateFromToolResult( case "Bash": { const result = toolResult.content; - const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : ""; + // The terminal was announced under the tool_use's own id (see + // `toolInfoFromToolUse`), so key the output/exit metas off that: it is the + // id the client actually created a terminal for. `toolResult.tool_use_id` + // is the same value whenever present — the caller looks the tool_use up by + // it — so preferring `toolUse.id` only adds a source for the case where the + // result block carries no id at all. Anything that isn't a non-empty + // string is no id at all: `""` matches no terminal, and stringifying a + // present-but-undefined field would invent the literal `"undefined"`. + const terminalIdOf = (id: unknown): string | undefined => + typeof id === "string" && id.length > 0 ? id : undefined; + const terminalId: string | undefined = + terminalIdOf(toolUse?.id) ?? + terminalIdOf("tool_use_id" in toolResult ? toolResult.tool_use_id : undefined); const isError = "is_error" in toolResult && toolResult.is_error; // Extract output and exit code from either format: @@ -767,7 +779,14 @@ export function toolUpdateFromToolResult( } } - if (supportsTerminalOutput) { + // Without a terminal id there is nothing the client can reconcile these + // metas against, and emitting them anyway strands the output: a client that + // buffers output/exit for terminals it has not been told about (Zed keeps + // them in `pending_terminal_output`/`pending_terminal_exit`, drained only on + // a matching create) would hold them forever behind an id that never + // arrives, showing an empty terminal. Fall through to the code-block + // rendering below instead. + if (supportsTerminalOutput && terminalId !== undefined) { return { content: [{ type: "terminal" as const, terminalId }], _meta: { diff --git a/vendor/README.md b/vendor/README.md index 0d16d630..3121a050 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -64,10 +64,10 @@ That means the directory is intentionally reproducible, but not yet minimal. - `vendor/codex-acp/src/thread.rs` - `vendor/codex-acp/vendor/codex-utils-pty/` - `Claude-agent-acp-upstream/` - - vendored snapshot is currently based on `@agentclientprotocol/claude-agent-acp` `0.62.0` - - upstream tag: `v0.62.0` - - upstream commit: `53a0c36ce3b0b76929d11d8b9565e319da745608` - - dependencies match the upstream `0.62.0` release (`@agentclientprotocol/sdk` `1.3.0`, `@anthropic-ai/claude-agent-sdk` `0.3.219`, `@anthropic-ai/sdk` `0.114.0`) + - vendored snapshot is currently based on `@agentclientprotocol/claude-agent-acp` `0.63.0` + - upstream tag: `v0.63.0` + - upstream commit: `15979bba7907484ee22111cdc33b79b0bdcd452d` + - dependencies match the upstream `0.63.0` release (`@agentclientprotocol/sdk` `1.3.0`, `@anthropic-ai/claude-agent-sdk` `0.3.220`, `@anthropic-ai/sdk` `0.115.0`) - `dist/` is generated from the upstream source snapshot because the desktop packaging flow depends on it even though upstream does not track it in git - `acp12/` - local package names: `agent-client-protocol-legacy` and @@ -217,9 +217,11 @@ compatibility paths that NeverWrite depends on. ## Current Claude Delta -The Claude vendor is based on upstream `@agentclientprotocol/claude-agent-acp` `0.62.0` at commit `53a0c36ce3b0b76929d11d8b9565e319da745608`, with no NeverWrite-specific runtime source delta. +The Claude vendor is based on upstream `@agentclientprotocol/claude-agent-acp` `0.63.0` at commit `15979bba7907484ee22111cdc33b79b0bdcd452d`, with no NeverWrite-specific runtime source delta. -The previous NeverWrite trailer-parsing hardening is fully absorbed by upstream. Version `0.62.0` retains the linear-time ReDoS protection and its whole-line matching, so no local runtime patch needs to be reapplied. +The previous NeverWrite trailer-parsing hardening is fully absorbed by upstream. Version `0.63.0` retains the linear-time ReDoS protection and its whole-line matching, so no local runtime patch needs to be reapplied. + +Upstream `0.63.0` includes an opt-in `subagent-transcript` client capability. NeverWrite does not advertise that capability, so Claude keeps the legacy behavior that filters nested subagent text and thinking from the top-level feed. Rich nested transcript integration remains intentionally out of scope. The `dist/` directory is rebuilt from the vendored source snapshot because the desktop packaging flow stages the compiled runtime files, while upstream does