Commit Graph

684 Commits

Author SHA1 Message Date
Ben Brandt
a031eabe10 fix(v2): end an open compaction when the query ends on its own
The error idle of a query that ended went out without the turn-end cleanup
of closeTurn, so a compaction reported in_progress never ended. ended() now
resets it through reports.turnEnded() before the idle.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 23:05:46 +01:00
Ben Brandt
854a347bf0 fix(v2): keep a background command running when a resume replays its result
A replay of an active session re-rendered a background Bash call's result
with its terminal exit, and the shared V2Terminals replaced the running
entry. Replay now rewrites the results of the session's background work as
the live path does (SessionTasks.replayedBackground), and a replayed
terminal_info keeps the terminal's running entry.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 23:03:13 +01:00
Ben Brandt
c66464eace test: pin the acp-agent.js exports of the v2 agent branch
Keeps isLoginRequiredText re-exported from acp-agent.js after its move to
result-end.ts, and adds the names the v2 agent imports from acp-agent.js
(AUTONOMOUS_RESULT_ORIGINS, getAvailableSlashCommands, isAbortedResult)
to the pinned list.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 22:33:29 +01:00
Ben Brandt
6db2e6ae20 fix(v2): list a resumed Fable session's model as fable
Ports #1548 to the v2 agent: sessionModels applies aliasResumedModelRow
before the allowlist and model selection, as ClaudeAcpAgent.createSession
does, so a resumed Fable 5.2 session offers `fable` as a new one does.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 22:33:29 +01:00
Ben Brandt
1bae40d6cc feat(v2): remove the checklist and the approved plan on a conversation reset
Ports #1554 to the v2 agent: a conversation_reset (such as /clear) sends
plan_removed for the checklist (`plan`) and the approved ExitPlanMode
plan (`plan-mode`) through the shared resetPlanUpdates, as v1 does for a
client with plan operations. Before, it sent an empty plan_update. The
v2 agent publishes approved plans under the plain `plan-mode` id, also
to AIR, so AIR's plan is removed too. The fresh context of a plan
accepted with clearing the context still removes nothing: that plan
becomes the session's plan in it.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 22:33:28 +01:00
Ben Brandt
7af0804b8a fix(v2): port workflow cards, task tool calls and hook-only previews to the v2 agent
Ports #1550, #1552 and #1556, which changed the v2 output of the old
bridge, to the v2 agent:

- A workflow's task_progress shows on its tool call as its title and
  body, with the workflow_progress phases and agents
  (workflowCard in workflow-progress.ts). Progress before the launch
  result waits for it, and a session/resume snapshot carries it.
- A live background task that no tool call or terminal stands for gets
  a tool call of its own (task_<taskId>), sent before the first
  background list that names it, instead of a _claude/task item.
- An Edit or Write preview from the PreToolUse hook waits for the tool
  call's first publication instead of publishing a call that a
  workflow agent never ends.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 22:33:11 +01:00
Ben Brandt
97b77d7cf7 Merge origin/main into v2-core-exploration
Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 22:32:52 +01:00
Ben Brandt
dbe8a3fce8 refactor: read CLAUDE_MODEL_CONFIG in the shared configuredSettings
Both cores call session-env's configuredSettings, which now applies the
models of CLAUDE_MODEL_CONFIG itself. The v2 wrapper of the same name is
gone.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:58:20 +01:00
Ben Brandt
3dc4582b95 refactor: drop the unread settled flag of v2's file-change reports
Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:55:05 +01:00
Ben Brandt
d31f4a9306 refactor: forget a session's held prompts once the last one settles
Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:53:53 +01:00
Ben Brandt
f0e99ea5a0 refactor: drop the dead query check before the subscription guard
Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:53:14 +01:00
Ben Brandt
c9c3f092d2 refactor: read results by one set of rules in both cores
resultEnd now makes exactly the decisions of v1's subtype switch, and the
v1 prompt loop calls it, keeping its own session failure categories and
result forwarding. v1 is unchanged. v2 now follows v1 where the copies
differed: a limit result (error_max_turns, error_max_budget_usd,
error_max_structured_output_retries) ignores stop_reason max_tokens, and a
success error with the /login text fails with authRequired even at
max_tokens. isLoginRequiredText moves to result-end.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:52:20 +01:00
Ben Brandt
400e531404 fix: don't hold prompts or the route change on a busy session's restart
A route change (providers/set, providers/disable, an auth/logout that
drops a provider or gateway login, or a resume with new additional
directories) now restarts a session at a turn boundary right away, and
the request waits only for those restarts. A busy session (an open turn,
a pending prompt, a live subagent, or a restart that runs) restarts at
its next turn boundary without the request waiting, and its prompts keep
going to the current query until then. Prompts wait only for a restart
that runs.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:45:41 +01:00
Ben Brandt
855447c7cd fix: update to @anthropic-ai/claude-agent-sdk 0.3.321 (#1557)
## Summary

Updates `@anthropic-ai/claude-agent-sdk` from 0.3.319 to 0.3.321. It
bundles Claude Code 2.1.321, which adds Fable 5.2 (`claude-fable-5-2`)
as the new default `fable` model. Also bumps these dev dependencies:
`@anthropic-ai/sdk` 1.6.0 → 1.7.0 and `@typescript-eslint/*` 8.72.1 →
8.72.2. `typescript` 7 is left for its own PR.

## Fable 5.2

Checked against the real CLI:

- A new session lists `fable` → `claude-fable-5-2`, and
`claude-fable-5-1` as its own row.
- A resumed Fable 5.2 session lists `claude-fable-5-2` and no `fable`
row, the same pattern 5.1 had.

`aliasResumedModelRow` already handles any version, so the code is
unchanged. Its doc comment and test fixture now describe the 2.1.321
lists. The fixture also covers a resumed Fable 5.1 session keeping
`claude-fable-5-1`, because a newer Fable is listed.

## Changelog review

- No SDK type changes that affect us. The only changes are doc text and
a new optional `endedLoopWakeup` field in `sdk-tools.d.ts`.
- Queued subagent reports in auto mode now share one model call, and all
but the last end in a `num_turns: 0` placeholder result. These results
carry `task-notification` or `peer` origins, which we already treat as
placeholders. That handling dates from CLI 2.1.274, which did the same
for background-task notifications.
- Fixes we pick up: one `undefined` entry in `hooks` no longer drops
every hook; a PreToolUse `ask` is no longer lost when another hook's
`defer` finishes first; project skills and commands no longer go missing
after entering a worktree; MCP OAuth no longer sends refresh tokens to
another host after a redirect.

## Testing

- `npm run build`, `npm run check`, `npm run test:run`
- Real CLI: listed the models for a new session and for a resumed Fable
5.2 session

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-09 19:43:47 +00:00
Ben Brandt
d098fb08d5 fix: report v2 background tasks without a tool call as their own tool call (#1556)
## Summary

Background tasks that have no tool call or terminal on v2 now get a tool
call of their own. Before, they showed up as a `_claude/task` vendor
item. This follows the spec rule in
agentclientprotocol/agent-client-protocol#2786.

Examples: a workflow, or an MCP task that starts after its tool call
already returned, or a task a hook started.

- `BackgroundWork.adoptTasks` starts a tool call `task_<taskId>` for
each such task. It uses the task description as title, `kind: "other"`,
and `in_progress` or `paused`. A subagent's task goes on the subagent's
stream. The message loop sends these calls before `sync`, so a call is
always announced before the first `background` list names it. The same
path covers the `state_update` snapshot on `session/resume`.
- These calls are listed as `tool_call` items, so `tool_call/cancel`
stops the task. When the task ends they get a final status like any
other listed call (`completed`, `failed`, or `cancelled`).
- The `_claude/task` item type is gone. `BackgroundItem` is now
`terminal | tool_call | subagent`.
- Ambient tasks such as the monitor, and tasks whose own tool call is
still open, are unchanged.

`adoptTasks` is synchronous and returns the notifications, so the loop
only awaits when there is something to send. An extra await on every
pass broke the steering-cancel timing in `acp-agent.test.ts`.

## Compatibility

- ACP v1 clients, including JetBrains AIR's `_session/async_task/*`
extension, are unaffected. Background lists, and therefore this path,
only run when the v2 background sink exists and the session has
`perTaskStopAffordance`. On v1, `adoptTasks` returns `[]`.
- v2 clients that saw `_claude/task` items (v0.100.0 to v0.101.1) now
see `task_<id>` tool calls instead.

## Tests

`acp-v2.test.ts` replaces the `_claude/task` tests. New tests cover:

- starting and listing a call for a task that starts after its tool call
returned
- cancelling such a task with `tool_call/cancel`
- the call being sent before the `session/resume` snapshot
- a subagent's task appearing on the subagent's stream

`npm run check`, `build` and `test:run` pass.
2026-11-09 20:40:39 +01:00
Ben Brandt
1cf208b68b refactor: rename the v2-native tests to v2
Renames v2-native.test.ts to v2.test.ts and v2-native-integration.test.ts
to v2-integration.test.ts, and drops "native" from their describe blocks,
helpers and client names.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:10:19 +01:00
Ben Brandt
3f0beb033b refactor: move the v2 agent from src/v2-native/ into src/v2/
There is one ACP v2 implementation now, so it lives in src/v2/ beside
the v2 wire conversions it uses. src/v2/permission.ts merges into
permissions.ts, which owns the v2 permission requests.

Renames nativeV2AgentApp to v2AgentApp, NativeV2Agent to V2Agent,
NativeSession to V2Session and NativeAuth to V2Auth, and rewords
"native v2 core" comments to "v2 core". No behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:10:07 +01:00
Ben Brandt
4a79f0487a refactor: rename src/v2/session.ts to session-requests.ts
The file holds the v1 form of the v2 session setup and config requests
and the v2 form of config options and commands. The rename frees
src/v2/session.ts for the v2 session that moves in next.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 20:09:27 +01:00
Ben Brandt
aa13302252 docs: describe the native core as the only ACP v2 implementation
Updates `docs/v2-native-core.md` (status, a history note in place of
"Replacing the bridge", the differences from the old bridge in the past
tense, the test setup), the README, the AIR extensions doc and
AGENTS.md, and rewrites code comments that described the bridge.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 19:25:53 +01:00
Ben Brandt
71792d29a9 refactor: serve only ACP v1 with ClaudeAcpAgent
Removes the v2 paths that only the deleted bridge reached: the `v2`,
`foreground` and `background` constructor options, `ForegroundEvents` and
the awaiting-user tracking, the background list reporting
(`BackgroundWorkReporter`, `cancelBackgroundWork`, the reported paths of
`SessionBackgroundWork`), the Edit/Write preview hook, `this.v2` branches
in initialize, permissions and setup, and the public methods only the
bridge called (`foregroundState`, `publishLiveSubagents`,
`publishBackgroundToolCalls`, `cancelBackgroundTerminal`,
`cancelBackgroundToolCall`, `authenticateMcpServers`,
`publishCurrentSessionInfo`).

`foregroundWork` stays as the record of who owns the foreground work
(a turn or an autonomous cycle): v1 still reads it in `claudeIsWorking`,
`workEndReported` and `requestOwnerTurn`. The `background` list types
move to `src/v2/background.ts`, next to `V2Background`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 19:25:53 +01:00
Ben Brandt
c99d3a1e59 refactor: serve every ACP v2 client with the native core and delete the bridge
The protocol router now always serves v2 with `src/v2-native/`. Removes
`CLAUDE_AGENT_ACP_V2_CORE`, `V2Core`, `v2CoreFromEnv`, the bridge's
`src/v2/agent.ts`, the conversions only it used (v1 initialize, restore and
setup responses, the prompt turn events, child state copies) and
`scripts/compare-v2-cores.mjs`. The rest of `src/v2/` stays as the v2 wire
conversions that the native core imports.

The v2 tests and scenarios run once, on the native core, against the
existing golden files.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 19:01:10 +01:00
Ben Brandt
92e695d1cc Merge branch 'native-v2/air-features' into v2-core-exploration 2026-11-09 18:48:12 +01:00
Ben Brandt
e3c5a8af96 feat: serve JetBrains AIR v2 clients with the native core
A v2 client that declares _meta.jetbrains.air now gets the native core,
which serves it the AIR extensions that ACP v2 does not cover, with the
v1 core's wire output:

- recommendedValue on the model and effort options, and the shown effort
  set on the query at the start and at each model switch
- the AIR permission presentation and AIR's permission tool call meta
- AskUserQuestion's custom answer marker
- the predicted follow-up prompt, cleared at the next prompt and at a
  conversation reset
- agentFileChangeReport, sent at the end of the turn that serves the
  prompt, before its idle, since a v2 prompt is answered at insertion

initialize lists agentFileChangeReport and recommendedValue to an AIR
client only. The extensions that v2 covers (subagent sessions,
background lists, file plans, patches, compaction, systemPrompt, failed
idles) are not served. CLAUDE_AGENT_ACP_V2_CORE=bridge still serves AIR
with the bridge.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 18:46:06 +01:00
Ben Brandt
afe4434be7 refactor: share the prompt suggestion state and the AIR permission meta between cores
PromptSuggestions holds the prediction a session's client holds and the
window a result opens, which the v1 core kept in two session fields.
airPermissionMeta builds _meta.jetbrains.air.permission apart from the
renderer, so the native v2 core can add it to a v2-rendered request.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 18:45:55 +01:00
Ben Brandt
05a827c69f fix: stop a foreground subagent reported after the root's cancel
A foreground subagent whose task_started reached the native v2 core after
the root's session/cancel, but before the cancelled work's result, kept
its permission and elicitation requests going to the client and its
consolidated messages going out. SessionTasks now keeps the root's cancel
in effect until the stopped work's result, Claude Code's idle, the next
prompt's start, or a failed interrupt, and stops such a subagent the same
way as one present at the cancel. A request of a subagent whose run a
cancel stops is withdrawn before it goes out.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-09 18:25:28 +01:00
Ben Brandt
acf21d2468 fix: resolve the best alias and gateway model spellings, keep v2 AIR clients on the bridge, and test the native v2 core against the real CLI
- `resolveModelPreference` resolves `best` (and `best[1m]`, whose hint the
  CLI ignores) as `opus`, the CLI's flagship fallback, unless an entry's
  value is `best`. Its preference words drop leading gateway segments before
  `claude-` (`vertex_ai/`, `openrouter/anthropic/`, `litellm/`,
  `publishers/anthropic/models/`) and a trailing `-latest`, which resolved
  before 51ab745 and gave null since.
- With `v2Core: "native"`, a v2 client that declares `_meta.jetbrains.air`
  gets the bridge (`airClientsOnBridge` in serve.ts): the native core serves
  no AIR extension, so a v2 AIR client would silently lose sessionFailure
  (API retry warnings), prompt suggestions, file change reports and AIR's
  subagent form. The docs' "Not served yet" section is corrected.
- `v2-native-integration.test.ts` (RUN_INTEGRATION_TESTS) runs the default
  v2 core in the adapter's process against the bundled CLI: insertion,
  running -> idle, requires_action at a permission request, and a cancel
  that fails a queued prompt with -32800.
2026-11-09 16:15:41 +01:00
Ben Brandt
766fd142f6 feat: send the checklist as a plan operation and remove plans on reset (#1554)
## Summary

Follow-up to the [plan operations
RFD](https://agentclientprotocol.com/rfds/plan-operations). The adapter
already sent approved ExitPlanMode plans as `markdown` plans to clients
with the `plan` capability, but two parts of the RFD were unused:

- **The checklist is now a plan operation on v1 too.** Clients with
`plan` (every v2 client, and v1 clients that opt in) get the
TodoWrite/Task checklist as `plan_update` with type `items` and the ID
`plan`. Before, v1 clients with the capability still got an ID-less
legacy `plan` next to the `plan-mode` plan. Clients without the
capability still get the legacy `plan`.
- **`plan_removed` is now sent.**
- An empty checklist sends `plan_removed` for `plan`. v2 clients used to
get a `plan_update` with no entries.
- A conversation reset (`/clear`) removes the checklist and the old
conversation's approved `plan-mode` plan. AIR keeps its approved plans,
which are joined to their approval cards by ID (#1469).

The new `src/plans.ts` builds the checklist updates. All three places
that send the checklist use it, and so does the v2 conversion of a
legacy `plan`, so v1 and v2 follow one rule.

The reset sends `plan_removed` for `plan-mode` even when the session
never published an approved plan. Tracking whether one was published
would be state that a restart loses, so the reset would behave
differently before and after one.

`file` plans are left out. A `file://` URI only works for a client on
the same machine, and v2 has no client filesystem.

## Tests

- `tools.test.ts`: TodoWrite with and without todos, and a TaskCreate
snapshot, for a v1 client with `plan`.
- `acp-agent.test.ts`: the reset test now runs for a client without plan
operations, a client with them, and AIR.
- `acp-v2.test.ts`: v2 end to end, TodoWrite then `/clear`, and an empty
legacy plan converting to `plan_removed`.
2026-11-09 14:58:36 +00:00
Ben Brandt
0dc4b370e0 feat: serve ACP v2 with the native core by default
CLAUDE_AGENT_ACP_V2_CORE now defaults to native; CLAUDE_AGENT_ACP_V2_CORE=bridge
selects the bridge as a fallback. acpProtocolRouter's v2Core option defaults to
native too. The unit vitest project pins the v2 tests and scenarios to the
bridge, and the v2-native-core project runs them on the native core.
2026-11-09 15:52:55 +01:00
Ben Brandt
176ae0634c feat: list workflow phases and agents on the workflow card (#1552)
## Summary

Show the phases and agents of a running workflow on its ACP v2 tool
card, not only the current activity and totals.

Claude Code attaches a `workflow_progress` snapshot to some of a
workflow's `task_progress` events: every phase, and every agent with its
label, phase, state, tokens, tool calls, duration and last tool. #1550
deliberately left it out. This change renders it under the existing card
body:

```
**Count**
- two: done · 14,629 tokens · 0 tool calls · 1s
- three: running · Bash: echo 3

**Sum**
- not started
```

### Why an untyped field

The SDK (0.3.319) does not type `workflow_progress`. Workflow agents
also don't stream their messages through the root query, so this
snapshot is the only per-agent data the adapter gets.
`src/workflow-progress.ts` reads the shape the CLI sends and skips
entries that don't match it. If the CLI changes the format, the card
loses the list but still renders. The right long-term fix is for the SDK
to type this field, or to stream workflow agents with a parent id so
each one can become a child session.

### Behavior

- The snapshot is a full list, and the CLI leaves it out of some
reports. A report without one keeps the last list.
- A running agent shows its last tool (e.g. `Bash: echo 3`). A finished
one shows tokens, tool calls and duration.
- Agents with no known phase are listed first. A phase with no agent yet
shows `not started`. A retry shows its attempt. A state other than
`start`/`done` is shown as the CLI sends it.
- The existing duplicate suppression and the `session/resume` snapshot
cover the list too.
- AIR `async_task_progress` is unchanged. Like the rest of the card
progress from #1550, this only reaches v2 clients that get workflow
progress on the card.

Result previews, models and prompt previews are left out to keep the
card short.

## Validation

- Live probe on Claude Code 2.1.319 (SDK `query()`, two-phase
three-agent workflow, permissions limited to `Workflow` and one `echo
3`). It captured the snapshot shape used in the test doubles. Every
captured snapshot rendered through the built module as shown above. This
checked the protocol output, not the Delta UI.
- New v2 scenario built from the captured payload, plus unit cases in
`src/tests/workflow-progress.test.ts`.
- `npm run check`, `npm run build`, `npm run test:run` (3,691 passed, 30
skipped).
2026-11-09 14:49:48 +00:00
Ben Brandt
a14c72b080 Merge branch 'native-v2/r3-gaps' into v2-core-exploration 2026-11-09 15:45:59 +01:00
Ben Brandt
01fe4b00d7 fix: end subagents at a conversation reset and drop a cancelled child's consolidated messages on the native v2 core
As the bridge does: after the root's session/cancel, a foreground child's
consolidated messages are dropped until its run ends, apart from the results
of the tool calls the client holds; a conversation_reset ends each working
child idle with no stop reason and forgets the children. Documents that both
cores reject a replayFrom cursor other than start with -32602, and the
remaining intended differences.
2026-11-09 15:43:38 +01:00
Ben Brandt
f90e379f6c fix: serve the bridge's SendMessage permission, resumedAgentId restore and replayed child configuration on the native v2 core 2026-11-09 15:28:18 +01:00
Ben Brandt
51ab74538c fix: reject a set_config_option model that names no entry with -32602
resolveModelPreference matched preference tokens as substrings, so
"not-a-model" resolved to Default ("a" is inside "default") and
"son" or "foo-sonnet" to Sonnet. configOptionValue, which v1, the v2
bridge and the native v2 core share, then accepted the value. Words now
match whole, and the fuzzy tiers need every model word of the
preference; provider ids, dates, -fast and context hints are dropped
with normalizeModelId first, so aliases and provider spellings still
resolve.
2026-11-09 15:19:43 +01:00
Ben Brandt
30600ba348 fix: render workflow tool cards and progress correctly in ACP v2 (#1550)
## Summary

Fix workflow tool cards that remain pending indefinitely, and surface
workflow progress through standard ACP v2 updates.

### Prevent orphan preview cards

Claude Code invokes Edit/Write preview hooks for workflow agents without
streaming those tools' uses and results through the root query.
Publishing a card from the hook alone leaves a pending call with no
completion path.

Stage early preview patches until the stream or a permission request
publishes the actual tool call. Keep patches pinned against later input
refinements, bound unclaimed previews, and clear them on teardown. A
call's own content takes precedence over a staged preview.

### Show workflow progress in place

Forward the SDK's typed task-progress fields to the existing workflow
card: current activity, summary, last activity, token count, tool-call
count, and elapsed time. Preserve progress arriving before the launch
result, suppress duplicate display updates, and include the latest
progress on session resume. Progress does not change paused/running
state or reopen completed calls.

This reads no workflow transcripts, journals, or metadata files and
introduces no ACP extension. The CLI also emits an untyped
`workflow_progress` snapshot; consuming that richer phase/agent data is
deliberately left out of this change. Existing v1 AIR progress uses the
same field normalization as before.

## Validation

- Regression reproduced before the orphan-preview fix.
- Nine relevant Vitest suites: **1,972 passed, 10 skipped**, including
v1/v2 scenarios, preview ordering, progress-before-launch, idle and
paused updates, duplicate suppression, resume snapshots, and late
progress after completion.
- `npm run check`
- `npm run build`
- `git diff --check`
- Live Claude Code **2.1.319** probes: captured SDK workflow events and
ran a real workflow through the updated ACP v2 adapter. One card
received two progress updates and then reached `completed`. This
verified protocol updates, not the Delta UI.

Rebased onto current `main` and reran the automated checks before
opening the PR.
2026-11-09 14:59:02 +01:00
Ben Brandt
39244f7952 test: check the v2 scenario golden files in full on the native core
acp-scenarios-v2.test.ts runs again in the v2-native-core vitest project
with V2_CORE_UNDER_TEST=native, comparing every message of each run with
the bridge's golden files and never writing them. It supersedes
acp-scenarios-v2-native.test.ts, which compared only session updates.
2026-11-09 14:49:54 +01:00
Ben Brandt
911ba98f14 fix: answer a native v2 prompt before its turn's running, as the bridge does
The native core's async prompt handler added promise hops, so the turn's
running state_update went out before the session/prompt response. v2
allows either order; the bridge's golden files hold the response first.
2026-11-09 14:49:54 +01:00
Ben Brandt
9a15f4889a fix: close the native v2 core's restart, guard, config and setup gaps
- Restarts restore effort, Fast mode, ultracode, the picked agent and
  prePlanMode as the bridge does (SessionConfig.restoreFrom), and start
  without the setup's _meta effort (withoutEffort, moved to session-effort).
- A route restart waits for a running account restart; the old query ends
  at its next turn boundary after the new one started; a close during a
  restart forgets the session.
- Config changes of a session run one at a time.
- The subscription guard runs in the session's prompt chain: cancel and
  close answer a prompt it judges with -32800, and /remote-control keeps
  its place.
- systemPrompt is validated on every setup path before it acts (active
  resume, fork before the copy).
- A session whose query ended stays active until close: its requests fail
  with -32603 and what happened instead of -32002.
- A resume of the active session with other additional directories
  restarts it with them at its next turn boundary.
- A replayed tool call without a result ends failed.
- The clear-context prompts get no user_message; the clear-context mode is
  resolved at the answer, with no fallback notice.
- No "needs authentication" notice when the session runs MCP OAuth.
2026-11-09 14:43:52 +01:00
Mark Tkachenko
dd9eed4e46 test: pin runtime exports of lib.js and acp-agent.js (#1546)
Adds a test that pins the runtime export names of `lib.js` and
`acp-agent.js`.

`acp-agent.ts` is about to be split into smaller modules in a series of
pure-move PRs. Each move keeps the old exports as re-exports, because
`"exports": { "./*": "./*" }` lets consumers deep-import
`dist/acp-agent.js`. tsc only catches a missing export that something in
the repo imports; this test catches the rest. The list should change
only on purpose.
2026-11-09 14:07:08 +01:00
Ben Brandt
ce892a59ae Merge branch 'native-v2/r2-subagents' into v2-core-exploration
# Conflicts:
#	docs/v2-native-core.md
#	src/acp-agent.ts
#	src/v2-native/agent.ts
#	src/v2-native/stream.ts
2026-11-09 13:59:33 +01:00
Ben Brandt
19f42bfb96 fix: restore the model and mode through the native config's request form after a restart 2026-11-09 13:56:08 +01:00
Ben Brandt
b658390078 Merge branch 'native-v2/r2-lifecycle' into v2-core-exploration
# Conflicts:
#	docs/v2-native-core.md
#	src/v2-native/agent.ts
#	src/v2-native/auth.ts
2026-11-09 13:56:08 +01:00
Ben Brandt
d649d2c423 Merge branch 'native-v2/r2-config' into v2-core-exploration 2026-11-09 13:50:15 +01:00
Ben Brandt
4cceb06b31 fix: list a resumed Fable 5.1 session's model as fable (#1548)
## Problem

The Claude Code CLI gives Fable 5.1 a different option value depending
on how the session starts:

- **New session:** listed as `fable` (`resolvedModel:
"claude-fable-5-1"`).
- **Resumed session whose transcript last ran Fable 5.1:** the same row
is listed as `claude-fable-5-1`, and there is no `fable` row.

Reproduced with CLI 2.1.310. Other families don't do this: a session
resumed on `claude-opus-5-5` still lists `opus`.

Clients store the option value as the session's choice. A choice saved
in a new session (`fable`) is not among the options of a resumed one,
and the reverse. A client that checks its saved value against the
options then refuses to apply it, and the model choice keeps flipping
between the two ids.

## Fix

`aliasResumedModelRow` in `session-model.ts` gives the resumed model's
row its family alias back. It changes a row only when:

- the row's value is the model read from the resumed transcript, equals
its own `resolvedModel` and has no context hint,
- no row already has the alias, and
- no newer version of the family is listed, since the alias runs the
family's newest model.

`createSession` applies it before the allowlist and model selection, so
new, resumed and loaded sessions over ACP v1 and v2 all get the same
list.

This is a workaround for the CLI listing the row under a different
value. The CLI also lists rows this way when `ANTHROPIC_MODEL` or
`settings.model` pins `claude-fable-5-1`. Those rows keep the pinned id
on purpose: relabelling them would turn the user's pinned version into
the alias, which moves to a newer Fable when one ships.

## Tests

- `acp-agent-settings.test.ts`: a session resumed on `claude-fable-5-1`
offers `fable` as the current value and option, with no `setModel` call.
Fails without the fix.
- `model-resolution.test.ts`: unit cases for the relabel and for each
case that keeps the list as it is.
- Checked end to end with the real CLI: a Fable turn, then
`session/load`, reports `fable`.
2026-11-09 13:47:11 +01:00
Ben Brandt
ab2ff616a8 feat: serve the config options, model fallback, context window, conversation reset and clear-context plans on the native v2 core
Port the bridge's effort, Fast mode, ultracode and agent options and their set
paths, the refusal fallback notice and model swap, the context window seed
(getContextUsage and the cross-session cache), conversation_reset handling, and
ExitPlanMode's clear-context option to the native v2 core.

The protocol-neutral code moves out of acp-agent.ts into shared modules that
both cores use: session-config-options.ts, session-fast-mode.ts,
context-window.ts, the effort switch helpers in session-effort.ts,
withCurrentModelInfo, and modelRefusalFallback.

The native core continues an accepted clear-context plan with Claude Code's own
/clear on the same query instead of the bridge's fresh query; the client sees
the same updates.
2026-11-09 13:46:34 +01:00
Ben Brandt
e83770c609 feat: re-route live sessions, the subscription guard and /remote-control on the native v2 core
- providers/set, providers/disable and an auth/logout that drops a route
  restart each live session's query at its next turn boundary, resuming the
  same Claude session, as the bridge does (src/v2-native/restarts.ts).
  Prompts sent meanwhile wait and go to the new query.
- The --hide-claude-auth subscription guard holds on the native core, as the
  bridge applies it to v2 sessions (src/v2-native/subscription-guard.ts).
- /remote-control is listed and run as a local command of its own turn, and
  messages typed in another client show as user messages
  (src/v2-native/remote-control.ts). Its helpers move out of acp-agent.ts
  into src/remote-control.ts.
- Docs: the gateway login routes only new sessions on the bridge too;
  steering is not served to v2 by the bridge either; file change reports are
  an AIR extension; the active-resume MCP server and /remote-control
  differences are listed as intended.
2026-11-09 13:41:32 +01:00
Ben Brandt
9bf4fedee6 feat: serve the remaining subagent behavior on the native v2 core
A child's configuration from its hooks (mode, effort, agent type,
PostModelSwitch) merged with its response models, SendMessage and
SubagentHandback calls as session messages live and in a replay, a
child's tool progress, the replay's notice for missing subagent history,
and the restore of an agent from before a restart from the session's
subagent history, as the bridge does. The hook options and their order
move to subagent-configuration.ts, shared with the v1 core.
2026-11-09 13:35:56 +01:00
Ben Brandt
6db28b2cba feat: match the bridge's turn output after a cancel on the native v2 core
- share recordStreamedDelta and unstreamedRemainder (streamed-text.ts) with
  the v1 core: a consolidated message adds the tail of a cut-short text block
- after session/cancel, drop the stopped work's consolidated messages except
  the results of tool calls the client holds, as the bridge does
- report zero usage and quota on the idle of a prompt's turn without a result
- run acp-v2.test.ts against the native core too, as a vitest project
2026-11-09 13:22:03 +01:00
Ben Brandt
35dee0a20f Merge remote-tracking branch 'origin/main' into v2-core-exploration 2026-11-09 13:03:01 +01:00
Ben Brandt
c700127ad2 fix: address review findings in the native v2 core
- End a child's unfinished tool calls before its idle (cancelled, failed,
  or dropped before it ran), as v2 asks of updates after a cancel.
- Skip the late result of a dead turn (cancelled after started), and keep
  dropping an abandoned prompt's run when another one's cancelled frame comes.
- Send no request of a session after session/close answered.
- Resume of the active session: announce every working child, send each
  listed item of a child before its state, and keep live children's states
  after a replay.
- Report running again when a cancel's stop or interrupt fails.
- Keep requires_action for an accepted MCP URL elicitation until Claude Code
  reports it complete, and only when every foreground subagent is blocked.
- Read the idle's stop reason with the v1 core's rules (shared resultEnd),
  map assistant error kinds and the sign-in message like the bridge, and use
  model_refusal_no_fallback's explanation.
- Serve SandboxNetworkAccess tool calls, permission_denied, the subject's
  _meta (shared permissionToolCallMeta), prePlanMode, the Auto mode fallback,
  PostModelSwitch, MCP OAuth after fork, and the stopped-work check for
  AskUserQuestion.
- Read the settings file before the settings watchers open (shared
  configuredSettings), and forget canUseTool waiters that timed out.
2026-11-09 12:58:25 +01:00
Ben Brandt
d81307c108 test: drop the native-only interrupt in the idle background-subagent cancel test
The merged native core interrupts Claude Code on session/cancel also while
idle, as the bridge does, so the test runs the same on both cores.
2026-11-09 12:03:13 +01:00