Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions src-tauri/src/acp/background_watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,19 @@
//!
//! * **Session title** — Claude Code's generated name arrives as a dedicated
//! `ai-title` transcript record whenever the background summarizer finishes,
//! routinely AFTER the turn that triggered it ended. The ACP adapter only
//! pulls the name at turn-end (`maybeUpdateSessionTitle`, claude-agent-acp
//! 0.69.0), so on a short session there is nothing to read yet and no wire
//! event ever follows. These bytes are already being tailed, so the records
//! are folded here and handed to [`publish_native_title`] — the same path a
//! live ACP title takes. Not activity: it rides alongside the activity event
//! rather than inside it (see `run_watch`).
//! routinely AFTER the turn that triggered it ended. Through claude-agent-acp
//! 0.70.0 the adapter only pulled the name at turn-end
//! (`maybeUpdateSessionTitle`), so on a short session there was nothing to
//! read yet and no wire event ever followed. 0.71.0 (#984) asks the CLI to
//! generate one at turn-end instead (`SessionTitles`, a background
//! `generate_session_title` control request), so a wire title now usually
//! does follow — but generation needs ten characters of collected context
//! and can return null, so this fold stays the backstop. These bytes are
//! already being tailed, so the records are folded here and handed to
//! [`publish_native_title`] — the same skip-cached path a live ACP title
//! takes, which also absorbs the overlap when both fire. Not activity: it
//! rides alongside the activity event rather than inside it (see
//! `run_watch`).
//!
//! The watcher is connection-scoped on purpose: background work cannot outlive
//! the agent CLI process, whose lifetime IS the connection's. Poll ticks are
Expand Down
33 changes: 21 additions & 12 deletions src-tauri/src/acp/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3737,18 +3737,22 @@ fn build_client_capabilities(
//
// codex-acp 1.7.0 added a third, "nativeSubagentSessions" (the draft ACP
// subagent RFD; the canonical gate is a `clientCapabilities.subagents: {}`
// field, with this AIR key as the fallback for SDKs that strip it). It must
// field, with this AIR key as the fallback for SDKs that strip it);
// claude-agent-acp 0.71.0 speaks it too, plus a fourth, "asyncTasks"
// (`async_task_spawned` / `async_task_progress` /
// `async_task_state_update`), gated the same way. They must
// stay out for a harder reason than cost: `agent-client-protocol-schema`
// 0.11.7 cannot RECEIVE the result. Its `SessionUpdate` is an
// 0.11.7 cannot RECEIVE the results. Its `SessionUpdate` is an
// internally-tagged enum with no catch-all arm, so the `subagent_spawned` /
// `subagent_state_update` notifications would fail to deserialize — and
// since the adapter switches child messages, thoughts, tools and
// since the adapters switch child messages, thoughts, tools and
// permissions onto a child session id announced only in that first
// notification, opting in would make subagent work vanish from the timeline
// rather than render better. Without the advertisement the lifecycle stays
// the legacy `subAgentActivity` tool call codeg already renders, whose
// shape is unchanged from 1.4.0. Revisit when the schema crate ships both
// the capability field and the update variants.
// what codeg already renders — codex's legacy `subAgentActivity` tool call
// (unchanged from 1.4.0) and claude's `subagent-transcript` streaming
// (verified intact through 0.73.0). Revisit when the schema crate ships
// both the capability field and the update variants.
if matches!(agent_type, AgentType::ClaudeCode | AgentType::Codex) {
meta.insert(
"jetbrains".to_string(),
Expand Down Expand Up @@ -10224,8 +10228,10 @@ fn is_codex_plan_review(
/// `_meta.permission = {version: 1, title, description?}`. The title is now one
/// of four fixed strings and the reason lives only in `description`, so a card
/// built from the tool call alone would read "Edit files" where it used to
/// explain WHY the edit needs approval. claude-agent-acp does not send this
/// block; nothing changes for it.
/// explain WHY the edit needs approval. claude-agent-acp joined with its
/// 0.71.0 permission rebuild — same `{version: 1, title, description?}`, the
/// SDK's decisionReason riding in `description` as "Reason: …" — so the hoist
/// serves both built-ins.
///
/// Hoisting rather than adding an event field is deliberate: the tool call is
/// already the card's payload end-to-end (`PendingPermissionState.tool_call`,
Expand Down Expand Up @@ -13323,14 +13329,17 @@ mod tests {
.iter()
.any(|v| v.as_str() == Some("sessionFailure")));
// And nothing else. Adding a capability here is not free — it is
// what turns the corresponding behavior on, and neither of the two
// what turns the corresponding behavior on, and none of the three
// that exist is wanted: "agentFileChangeReport"
// (claude-agent-acp 0.69.0 / codex-acp 1.4.0) buys an extra model
// round-trip per turn for a clamped, self-reported subset of what
// the `workspace_state` watcher already sees, and
// "nativeSubagentSessions" (codex-acp 1.7.0) would move subagent
// the `workspace_state` watcher already sees;
// "nativeSubagentSessions" (codex-acp 1.7.0 / claude-agent-acp
// 0.71.0) would move subagent
// output onto child session ids carried by `SessionUpdate` variants
// `agent-client-protocol-schema` 0.11.7 cannot deserialize at all.
// `agent-client-protocol-schema` 0.11.7 cannot deserialize at all;
// and "asyncTasks" (claude-agent-acp 0.71.0) rides the same
// undeliverable `async_task_*` variants.
// See the reasoning at the advertisement site before relaxing this.
assert_eq!(
capabilities,
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/acp/preflight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub struct CheckItem {
/// `None` on [`PreflightResult`] for every non-adapter agent.
#[derive(Debug, Clone, Serialize)]
pub struct AdapterInfo {
/// npm spec codeg installs, e.g. "@agentclientprotocol/claude-agent-acp@0.69.0".
/// npm spec codeg installs, e.g. "@agentclientprotocol/claude-agent-acp@0.73.0".
pub adapter_package: String,
/// Command the launch gate resolves, e.g. "claude-agent-acp".
pub adapter_cmd: String,
Expand Down Expand Up @@ -724,7 +724,7 @@ mod adapter_tests {
);
assert_eq!(
info.adapter_package,
"@agentclientprotocol/claude-agent-acp@0.69.0"
"@agentclientprotocol/claude-agent-acp@0.73.0"
);
assert_eq!(info.adapter_cmd, "claude-agent-acp");
assert!(!info.adapter_installed);
Expand Down
118 changes: 112 additions & 6 deletions src-tauri/src/acp/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,9 +577,112 @@ pub fn get_agent_meta(agent_type: AgentType) -> AcpAgentMeta {
// can advertise `["sessionFailure", "agentFileChangeReport"]` — an
// ADDITIVE element in an array codeg only ever membership-tests,
// so the session-failure gate is unaffected.
// 0.70.0 (#1002) is provider plumbing only: the
// `providers/list|set|disable` methods and the `providers: {}`
// advertisement (both already in 0.69.0) now also cover LOADED
// sessions — a switch respawns the SDK behind the live session
// with a rebuilt routing env (base-URL/Bedrock/Vertex vars,
// local credential checks bypassed via "acp-proxy" sentinels).
// All of it sits inside client-called requests codeg never
// sends; dependencies, the session_update surface and the Node
// floor are unchanged (tarball-diffed — the ambient additions
// are a "Claude ACP started" boot line and provider-resolution
// logging).
// 0.71.0 (nine PRs; claude-agent-sdk 0.3.232→0.3.238, which
// vendors runtime 2.1.238; zod floor raised to ^4, #1057; dist
// split into modules) adds five session_update KINDS, every one
// gated on a client advertisement codeg does not make:
// `subagent_spawned`/`subagent_state_update` (#1017) ride only
// for a client advertising `clientCapabilities.subagents`
// (inexpressible in `agent-client-protocol-schema` 0.11.7) or
// AIR `nativeSubagentSessions`, and `async_task_spawned`/
// `_progress`/`_state_update` only behind AIR `asyncTasks` —
// codeg's AIR array is pinned to exactly ["sessionFailure"]
// (tested), which the no-catch-all `SessionUpdate` argument in
// codex-acp 1.7.0's NOT-adopted paragraph makes load-bearing:
// the same "would fail to deserialize" logic applies verbatim.
// Without the advertisements the legacy shape is intact
// (tarball-verified): `subagent-transcript` streaming,
// `_meta.claudeCode.parentToolUseId`, permissions on the root
// session. The advertisement array grows to [sessionFailure,
// agentFileChangeReport, nativeSubagentSessions, asyncTasks] and
// `sessionCapabilities` gains `subagents: {}` — additive shapes
// codeg membership-tests or ignores. What codeg's wire DOES
// gain: (a) real session titles (#984) — the CLI's own
// auto-titler never arms under the Agent SDK, so the old
// turn-end pull mostly republished the raw first prompt; the
// adapter now asks for a generated title (a background
// `generate_session_title` control request, once per session,
// needs ≥10 chars of context, a user /rename always wins) over
// the SAME `session_info_update {title, updatedAt}` wire the
// SessionInfoUpdate arm already feeds to `publish_native_title`;
// the transcript-tail fallback in background_watch.rs stays as
// the backstop. (b) The permission layer is rebuilt
// (`src/permissions/*`): the option-level `_meta.permission =
// {version, changes[]}` contract from 0.64.1/#930 is GONE, and
// claude joins codex-acp 1.7.0's REQUEST-level
// `_meta.permission = {version: 1, title, description?}`
// (description = "Reason: <decisionReason>") — exactly what
// `hoist_request_permission_meta` forwards, so the card's reason
// line lights up for claude with no code change while
// `parsePermissionOptionChanges` simply no longer fires for it.
// ExitPlanMode approval gains clear-context lanes (#1004): new
// opaque option ids under standard kinds, and accepting one
// swaps the underlying SDK session while the SAME ACP turn stays
// in flight (`current_mode_update` + `config_option_update`
// mid-turn, then the plan continuation streams on) — transparent
// to codeg's turn-scoped runtime by construction. (c) Modes
// (#1025): the advertised list is now default/acceptEdits/plan/
// auto (+ bypassPermissions behind the adapter's ALLOW_BYPASS),
// `dontAsk` is still parsed but no longer advertised, and each
// mode/option carries `_meta.kind` (standard/plan/auto_review/
// full_access) — additive, unread. (d) The prompt response gains
// `_meta.quota = {token_count, model_usage[]}` (#1037),
// deliberately codex-shaped — unread here, input for a future
// usage surface. (e) `session/fork` (registered since 0.69.0)
// honors an AIR fork point (`_meta.jetbrains.air.fork.messageId`,
// #1046) — client-called; codeg doesn't. (f) #1045: a
// `_session/steering` arriving while a permission/elicitation
// card is open is delivered at non-interrupting priority instead
// of aborting the running cycle into the open prompt; the
// `promptRequired` contract is unchanged (tarball-verified), so
// the 0.65.0 floor keeps holding, just safer. The Windows
// `PowerShell` shell tool is now recognized alongside Bash (same
// description-title treatment).
// 0.72.0 moves both SDKs (@agentclientprotocol/sdk 1.3.0→1.4.0,
// claude-agent-sdk 0.3.238→0.3.252 = runtime 2.1.252) and rides
// the new runtime in #1065: a PostModelSwitch hook (fires on CLI
// 2.1.251+ only) mirrors switches the adapter didn't drive — a
// `/model x` typed as a prompt — back into the picker through
// the EXISTING `config_option_update`/`current_mode_update`
// surfaces; PreModelSwitch hooks can veto (`setModel` then fails
// with "Model switch blocked by a PreModelSwitch hook"); effort
// now seeds from the CLI's persisted per-model settings unless
// the user pinned it via the picker this session; and results
// echo the triggering send's `user_message_uuid` (SDK 0.3.246+),
// upgrading the adapter's internal orphan-result heuristics to
// an exact join. The ACP SDK bump is API-surface only:
// `unstable_createElicitation`/`unstable_completeElicitation`
// lose their prefixes while the wire methods stay
// `elicitation/create`/`elicitation/complete` (schema method
// tables diffed byte-identical) — moot here anyway, codeg
// advertises elicitation for codex and deepseek only.
// 0.73.0 (#1066) is the runtime and nothing else: dist/ is
// byte-identical to 0.72.0's and the only manifest change is
// claude-agent-sdk 0.3.252→0.3.257 (runtime 2.1.257). That is
// the point of this pin: model availability ships in the
// runtime — the API refuses new model ids on old ones ("Claude
// Code 2.1.247 does not support this model; version 2.1.251 or
// newer is required" for `claude-fable-5-1`, and the old pin's
// SDK 0.3.232 vendors 2.1.232) — so from 0.72.0 on the dynamic
// model config option lists new Anthropic models with no codeg
// change (verified live over stdio: 0.73.0 initializes and
// session/new's model option offers Default/Opus 1M/Fable/
// Sonnet/Haiku). `engines.node` stays ">=22" across 0.70–0.73,
// so `node_required` holds.
distribution: AgentDistribution::Npx {
version: "0.69.0",
package: "@agentclientprotocol/claude-agent-acp@0.69.0",
version: "0.73.0",
package: "@agentclientprotocol/claude-agent-acp@0.73.0",
cmd: "claude-agent-acp",
args: &[],
env: &[],
Expand Down Expand Up @@ -734,8 +837,11 @@ pub fn get_agent_meta(agent_type: AgentType) -> AcpAgentMeta {
// standard ACP fields `parse_permission_tool_call` already reads
// (`rawInput.command/cwd/url/additionalPermissions`, `locations`,
// `content`). `handle_permission_request` hoists the request meta
// onto the card so the reason survives; claude-agent-acp 0.69.0
// still emits `changes[]`, so that parser stays. Codex's option
// onto the card so the reason survives; claude-agent-acp joined
// this request-level contract in 0.71.0 (its `changes[]` ended at
// 0.70.0), so the option-level parser now serves codex's
// description-only MCP-approval options and any custom agent
// still speaking `changes[]`. Codex's option
// IDs were also renamed (`allow_for_session`,
// `accept_execpolicy_amendment`, `apply_network_policy_amendment:N`
// …) — inert here, codeg only echoes back the selected id — and
Expand Down Expand Up @@ -1616,8 +1722,8 @@ mod tests {
fn registry_pins_current_acp_agent_versions() {
assert_npx_version(
AgentType::ClaudeCode,
"0.69.0",
"@agentclientprotocol/claude-agent-acp@0.69.0",
"0.73.0",
"@agentclientprotocol/claude-agent-acp@0.73.0",
Some("22.0.0"),
);
assert_npx_version(
Expand Down
Loading