Compare commits

...

5 Commits

Author SHA1 Message Date
Slapoguzov Alexander
e369479f93 fix: preserve foreground fork reports until launch completes (#1431)
A foreground fork can stream a copied `tool_result` for the Agent call
that launched it. That placeholder completed the call and removed its
tracked state, so the real report arriving later was dropped. Ignore
only results whose tool ID equals the message's parent tool ID, keeping
the launching call open for its actual report.

Keep other results in the same message and discard the message-level
`tool_use_result` sidecar when its launch result was removed, so it
cannot be attributed to a remaining tool result. Regression coverage
exercises plain and Air clients, root and nested launch results, and a
mixed message containing a genuine nested Agent result.

Fixes #1389.

Validation:

- [Linux
CI](https://github.com/agentclientprotocol/claude-agent-acp/actions/runs/40217593917)
passes the full suite: 3,304 passed, 32 skipped (57 test files passed, 4
skipped). All PR checks are green.
- `npm run check` and `npm run build` pass.
- `vitest run src/tests/acp-agent.test.ts --maxWorkers=1`: 712 passed,
10 skipped. The four new regression cases fail on the original code and
pass with the fix.
- Eight actual-SDK runs against localhost fake gateways complete
successfully without model calls: plain and Air foreground forks, native
Air child sessions, nested forks with the background option omitted or
false, the SDK's fork-inside-fork rejection, a background launch
receipt, and history replay. Foreground reports arrive exactly once and
no launch-result tracking warning remains.

The local full-suite run encountered 19 failures in
`session-index.test.ts` and then stopped progressing, so it was
interrupted. A standalone recursive-file-watcher probe also emits
`EMFILE` on this machine. The successful Linux CI run above provides the
complete-suite verification.

Review dependencies (updated 26 October):

| Related proposal | Relationship to this PR |
| --- | --- |
|
[#1408](https://github.com/agentclientprotocol/claude-agent-acp/pull/1408)
— targeted session lookup | Independent. Both orders merge cleanly onto
the checked main and produce the same tree. |
|
[#1422](https://github.com/agentclientprotocol/claude-agent-acp/pull/1422)
— resumed subagents | Complementary, with no dependency on this PR. It
now conflicts with upstream main in `src/acp-agent.ts`; its author needs
to resolve that conflict, integrate the [separately shared AIR routing
patch](https://github.com/agentclientprotocol/claude-agent-acp/pull/1422#issuecomment-6401008862)
and complete its validation. |

<details>
<summary>Additional local integration validation — 25 October</summary>

A local combination of the recorded main, #1408, #1431, #1422 and the
already-reviewed routing patch passed 199 focused
foreground/resume/native-subagent checks (647 other cases filtered or
skipped), TypeScript build, and two replays of previously captured SDK
frames for AIR and standard subagents. The two captured-frame replays
were independently repeated. No new model calls were made.

The combination is a private test tree, not a published change. It does
not replace full CI for #1422 or live Air acceptance. The routing patch
needed three-way application against that recorded main because its
surrounding source had moved; it applied cleanly that way. The
current-main conflict in #1422 means this historical result does not
establish today’s combined compatibility.

Checked main `739dde3`, #1408 `e3b3aa4`, #1431 `8f96f46`, #1422
`dd3bf2c`. The two orders of #1408 and #1431 produce tree `1716d7e`; the
full local test tree is `61c0447`. Refresh this evidence if these inputs
change.

</details>

Co-authored-by: Codex <codex@openai.com>
2026-10-26 11:20:15 +01:00
dependabot[bot]
c26e640915 fix: bump body-parser from 2.3.0 to 2.4.0 (#1436)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 2.3.0
to 2.4.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/body-parser/releases">body-parser's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.0</h2>
<h2>Important: Security</h2>
<ul>
<li>Security fix for <a
href="https://www.cve.org/CVERecord?id=CVE-2026-111181">CVE-2026-111181</a>
(<a
href="https://github.com/expressjs/body-parser/security/advisories/GHSA-27r3-jxmh-p828">GHSA-27r3-jxmh-p828</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump actions/checkout from 6.0.3 to 7.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/737">expressjs/body-parser#737</a></li>
<li>chore: group github actions updates by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/743">expressjs/body-parser#743</a></li>
<li>build(deps): bump the github-actions group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/745">expressjs/body-parser#745</a></li>
<li>test: fix 'presist' typo in test descriptions by <a
href="https://github.com/maxtaran2010"><code>@​maxtaran2010</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/749">expressjs/body-parser#749</a></li>
<li>docs: include security fix in 2.3.0 changes by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/748">expressjs/body-parser#748</a></li>
<li>fix: avoid mutating read options when reading body by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/742">expressjs/body-parser#742</a></li>
<li>refactor: move getCharset helper to only usage by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/707">expressjs/body-parser#707</a></li>
<li>build(deps): bump the github-actions group with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/753">expressjs/body-parser#753</a></li>
<li>refactor: switch to neostandard by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/751">expressjs/body-parser#751</a></li>
<li>perf(json): avoid first-char regex for direct JSON bodies by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/744">expressjs/body-parser#744</a></li>
<li>docs: fix entity verify error description by <a
href="https://github.com/Rawal27"><code>@​Rawal27</code></a> in <a
href="https://redirect.github.com/expressjs/body-parser/pull/755">expressjs/body-parser#755</a></li>
<li>feat: zstandard support by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/586">expressjs/body-parser#586</a></li>
<li>test: remove unused experimentalZstd option by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/757">expressjs/body-parser#757</a></li>
<li>ci: run tests on windows and macos matrix by <a
href="https://github.com/kilisamemarisaaa"><code>@​kilisamemarisaaa</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/759">expressjs/body-parser#759</a></li>
<li>deps: qs@^6.16.0 by <a
href="https://github.com/krzysdz"><code>@​krzysdz</code></a> in <a
href="https://redirect.github.com/expressjs/body-parser/pull/762">expressjs/body-parser#762</a></li>
<li>build(deps): bump the github-actions group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/763">expressjs/body-parser#763</a></li>
<li>ci: trigger npm publish on release published and set dist-tag by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/768">expressjs/body-parser#768</a></li>
<li>build(deps): bump the github-actions group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/771">expressjs/body-parser#771</a></li>
<li>fix(test): pin supertest to 7.3.0 to avoid 7.3.1 regression by <a
href="https://github.com/saadkhantareen"><code>@​saadkhantareen</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/773">expressjs/body-parser#773</a></li>
<li>perf: inline content stream setup by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/750">expressjs/body-parser#750</a></li>
<li>docs: note that req.body may be undefined in v2 by <a
href="https://github.com/Hashim1999164"><code>@​Hashim1999164</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/752">expressjs/body-parser#752</a></li>
<li>docs: fix strict option description by <a
href="https://github.com/Rawal27"><code>@​Rawal27</code></a> in <a
href="https://redirect.github.com/expressjs/body-parser/pull/756">expressjs/body-parser#756</a></li>
<li>docs: fix depth error description by <a
href="https://github.com/johnnyhuirilef"><code>@​johnnyhuirilef</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/776">expressjs/body-parser#776</a></li>
<li>chore: update dependencies by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/778">expressjs/body-parser#778</a></li>
<li>Release: 2.4.0 by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/777">expressjs/body-parser#777</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/maxtaran2010"><code>@​maxtaran2010</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/749">expressjs/body-parser#749</a></li>
<li><a href="https://github.com/Rawal27"><code>@​Rawal27</code></a> made
their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/755">expressjs/body-parser#755</a></li>
<li><a
href="https://github.com/kilisamemarisaaa"><code>@​kilisamemarisaaa</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/759">expressjs/body-parser#759</a></li>
<li><a
href="https://github.com/saadkhantareen"><code>@​saadkhantareen</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/773">expressjs/body-parser#773</a></li>
<li><a
href="https://github.com/Hashim1999164"><code>@​Hashim1999164</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/752">expressjs/body-parser#752</a></li>
<li><a
href="https://github.com/johnnyhuirilef"><code>@​johnnyhuirilef</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/776">expressjs/body-parser#776</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/expressjs/body-parser/compare/v2.3.0...v2.4.0">https://github.com/expressjs/body-parser/compare/v2.3.0...v2.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/body-parser/blob/master/HISTORY.md">body-parser's
changelog</a>.</em></p>
<blockquote>
<h1>2.4.0</h1>
<ul>
<li>Security fix for <a
href="https://github.com/expressjs/body-parser/security/advisories/GHSA-27r3-jxmh-p828">GHSA-27r3-jxmh-p828</a>
(CVE-2026-111181)</li>
<li>feat: add <code>zstd</code> content-encoding support (<a
href="https://redirect.github.com/expressjs/body-parser/issues/586">#586</a>)
<ul>
<li>Requires Node.js <code>^22.15.0</code> or <code>&gt;=23.8.0</code>;
older versions keep returning <code>415</code> for <code>zstd</code>
encoded bodies</li>
</ul>
</li>
<li>fix: avoid mutating the parser options and the request object while
reading the body (<a
href="https://redirect.github.com/expressjs/body-parser/issues/742">#742</a>,
<a
href="https://redirect.github.com/expressjs/body-parser/issues/750">#750</a>)</li>
<li>perf(json): avoid first-char regex for direct JSON bodies (<a
href="https://redirect.github.com/expressjs/body-parser/issues/744">#744</a>)</li>
<li>deps:
<ul>
<li>content-type@^2.1.0</li>
<li>iconv-lite^0.7.3</li>
<li>qs@^6.16.1</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b4744d739e"><code>b4744d7</code></a>
2.4.0 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/777">#777</a>)</li>
<li><a
href="f7075eae3a"><code>f7075ea</code></a>
fix: prevent process crash from trailing bytes after a compressed
body</li>
<li><a
href="185ff1d555"><code>185ff1d</code></a>
chore: update dependencies (<a
href="https://redirect.github.com/expressjs/body-parser/issues/778">#778</a>)</li>
<li><a
href="73713f76c4"><code>73713f7</code></a>
docs: fix depth error description (<a
href="https://redirect.github.com/expressjs/body-parser/issues/776">#776</a>)</li>
<li><a
href="2dcc826c6d"><code>2dcc826</code></a>
docs: improve readability (<a
href="https://redirect.github.com/expressjs/body-parser/issues/756">#756</a>)</li>
<li><a
href="402dbdc6f1"><code>402dbdc</code></a>
docs: note that req.body may be undefined in v2 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/752">#752</a>)</li>
<li><a
href="9883a1eeb4"><code>9883a1e</code></a>
perf: inline content stream setup (<a
href="https://redirect.github.com/expressjs/body-parser/issues/750">#750</a>)</li>
<li><a
href="02101da415"><code>02101da</code></a>
fix(test): pin supertest to 7.3.0 to avoid 7.3.1 regression (<a
href="https://redirect.github.com/expressjs/body-parser/issues/773">#773</a>)</li>
<li><a
href="822a0fd462"><code>822a0fd</code></a>
build(deps): bump the github-actions group with 3 updates (<a
href="https://redirect.github.com/expressjs/body-parser/issues/771">#771</a>)</li>
<li><a
href="aa9889bc35"><code>aa9889b</code></a>
ci: trigger npm publish on release published and set dist-tag (<a
href="https://redirect.github.com/expressjs/body-parser/issues/768">#768</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/expressjs/body-parser/compare/v2.3.0...v2.4.0">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for body-parser since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=body-parser&package-manager=npm_and_yarn&previous-version=2.3.0&new-version=2.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/agentclientprotocol/claude-agent-acp/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-26 11:19:09 +01:00
acp-release-bot[bot]
f6958db1ee chore(main): release 0.96.0 (#1425)
🤖 I have created a release *beep* *boop*
---


##
[0.96.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.95.0...v0.96.0)
(2026-10-26)


### Features

* report SendMessage delegations as linked session messages
([#1424](https://github.com/agentclientprotocol/claude-agent-acp/issues/1424))
([739dde3](739dde3ae5))
* report the model of each native subagent
([#1432](https://github.com/agentclientprotocol/claude-agent-acp/issues/1432))
([c319e5e](c319e5e542))


### Bug Fixes

* do not hold an AIR load replay until the CLI starts
([#1433](https://github.com/agentclientprotocol/claude-agent-acp/issues/1433))
([eb9d63e](eb9d63e5fd))
* refine RFD child models after a load, and retry a failed model update
([#1434](https://github.com/agentclientprotocol/claude-agent-acp/issues/1434))
([9a8bf71](9a8bf71d45))
* send subagents' current state after a load response
([#1423](https://github.com/agentclientprotocol/claude-agent-acp/issues/1423))
([17a967b](17a967b4da))
* update to Claude Code 2.1.310
([#1435](https://github.com/agentclientprotocol/claude-agent-acp/issues/1435))
([2f3c6bb](2f3c6bb7b3))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
2026-10-26 09:50:05 +00:00
Ben Brandt
2f3c6bb7b3 fix: update to Claude Code 2.1.310 (#1435)
Updates `@anthropic-ai/claude-agent-sdk` from 0.3.309 to **0.3.310**
(Claude Code **2.1.310**), `@agentclientprotocol/sdk` 2.0.1 → 2.0.2 and
`@anthropic-ai/sdk` 1.1.0 → 1.3.0.

TypeScript stays on 6.0.3, because typescript-eslint 8.72 still requires
`typescript <6.1.0`.

## Behaviour changes ACP users may notice

- **2.1.310:** the Monitor tool and plugin monitors now run on Bedrock,
Vertex and Foundry, and with telemetry or nonessential traffic disabled.
On those setups, long foreground sleeps are now blocked. We already hide
Monitor tasks from the client (`local_monitor` / `HIDDEN_TASK_TOOLS`),
so the only visible change is that a blocked sleep shows up as a failed
Bash call.
- **2.1.310:** when an MCP tool call times out because its elicitation
went unanswered, the error now tells Claude not to repeat the call.

## Upstream fixes this picks up

- Requests behind gateways that reject the `effort-2025-11-24` beta or
mid-conversation system messages are retried without them. This affects
our `providers/set` gateway sessions.
- After a "Prompt is too long" error, compaction now trims dense content
(base64, SVG) properly, so the next request no longer fails again.
Compaction in SDK sessions with very large transcripts also uses less
memory.
- File read permission prompts now show where a symbolic link leads.
- Windows: PreToolUse and PermissionRequest hooks no longer let a tool
call through when Git Bash is missing and the hook's command is only
quoted strings.

## Checked, no change needed

- **Agent SDK 0.3.310:** the `sdk.d.ts` diff is doc text only
(`connectorId` in the `sdkMcpServerConfigs` comment, and the provider
list for `'highlights'` thinking display). Every `CLAUDE_*` env var the
adapter reads is still in the 2.1.310 binary.
- **ACP SDK 2.0.2:** only fixes the HTTP transport (CR line endings in
SSE, request-scoped POST failures), which we don't use.
- **`@anthropic-ai/sdk` 1.2/1.3:** adds API endpoints (message threads,
usage/cost reports, Claude Tag); we only use its types.

Verified: `npm run build`, `npm run check`, `npm run test:run` (3323
passed, 30 skipped), and a typecheck of `examples/`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-26 10:46:44 +01:00
nikita-ashihmin
9a8bf71d45 fix: refine RFD child models after a load, and retry a failed model update (#1434)
## Summary

Follow-up to #1433, which was merged before these review fixes landed.

- **RFD child models after a load.** On a real `session/load` (session
not live in this process), #1433 also gave `RfdSubagentReplay` no model
list, so after the load response `currentStates` could not map a child's
model to the root session's `model` entry. RFD children kept the raw
model ID. The replay now reads the session's models when needed; only
the in-replay mapping is off while the load replays.
- **Retry after a failed publish.** `reportModel` skipped a model that
matched the stored options, which are written before the publish. A
failed publish then blocked every later report of the same model. It now
compares with the last model the client actually received.
- **Failed load replay.** The replayed child models are also dropped
when the replay itself fails.
- The buffer of an unannounced child collapses only model reports from
`reportModel`; other config updates stay as they are.

## Tests

- `replayRfd` gained a `load` option that replays like a real load;
"sends the model of each child's responses, and after the load response
only a changed entry" uses it (fails without the fix).
- A retry after a failed publish, for the RFD and AIR forms.
- "refines an ended AIR child's model" now goes through the load path.
- `npm run build`, `npm run check`, `npm run test:run`: 3323 passed, 30
skipped.

## Related

- Follow-up to #1433 and #1432
- RFD change:
https://github.com/agentclientprotocol/agent-client-protocol/pull/2482
- RFD section: [Child
configuration](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx#child-configuration)
- JetBrains issue:
[IJAI-4754](https://youtrack.jetbrains.com/issue/IJAI-4754)
2026-10-25 23:24:19 -07:00
9 changed files with 324 additions and 82 deletions

View File

@@ -1,3 +1,3 @@
{
".": "0.95.0"
".": "0.96.0"
}

View File

@@ -1,5 +1,21 @@
# Changelog
## [0.96.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.95.0...v0.96.0) (2026-10-26)
### Features
* report SendMessage delegations as linked session messages ([#1424](https://github.com/agentclientprotocol/claude-agent-acp/issues/1424)) ([739dde3](https://github.com/agentclientprotocol/claude-agent-acp/commit/739dde3ae5bb848bdb90e2c9150e2cc4687d480c))
* report the model of each native subagent ([#1432](https://github.com/agentclientprotocol/claude-agent-acp/issues/1432)) ([c319e5e](https://github.com/agentclientprotocol/claude-agent-acp/commit/c319e5e542aabe353bb2c5e177e4c5853392b8c4))
### Bug Fixes
* do not hold an AIR load replay until the CLI starts ([#1433](https://github.com/agentclientprotocol/claude-agent-acp/issues/1433)) ([eb9d63e](https://github.com/agentclientprotocol/claude-agent-acp/commit/eb9d63e5fde9b8fd8fe692dfb3280faba6626a42))
* refine RFD child models after a load, and retry a failed model update ([#1434](https://github.com/agentclientprotocol/claude-agent-acp/issues/1434)) ([9a8bf71](https://github.com/agentclientprotocol/claude-agent-acp/commit/9a8bf71d454fa85d03f55d4101a77e3ea7a149d0))
* send subagents' current state after a load response ([#1423](https://github.com/agentclientprotocol/claude-agent-acp/issues/1423)) ([17a967b](https://github.com/agentclientprotocol/claude-agent-acp/commit/17a967b4dab78bbb9cf9fedae60e6821578dc5cb))
* update to Claude Code 2.1.310 ([#1435](https://github.com/agentclientprotocol/claude-agent-acp/issues/1435)) ([2f3c6bb](https://github.com/agentclientprotocol/claude-agent-acp/commit/2f3c6bb7b3451a00cd77d7bb31c09e690ca220b3))
## [0.95.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.94.0...v0.95.0) (2026-10-23)

104
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.95.0",
"version": "0.96.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.95.0",
"version": "0.96.0",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "2.0.1",
"@anthropic-ai/claude-agent-sdk": "0.3.309",
"@agentclientprotocol/sdk": "2.0.2",
"@anthropic-ai/claude-agent-sdk": "0.3.310",
"diff": "9.0.0",
"zod": "4.6.7"
},
@@ -18,7 +18,7 @@
"claude-agent-acp": "dist/index.js"
},
"devDependencies": {
"@anthropic-ai/sdk": "1.1.0",
"@anthropic-ai/sdk": "1.3.0",
"@eslint/js": "10.0.1",
"@tsconfig/node22": "22.0.6",
"@types/node": "26.6.5",
@@ -38,31 +38,31 @@
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.0.1.tgz",
"integrity": "sha512-v4E/NcBTGFEKmorGso8ysAjJTkNh5UMJPP4aDznyMNmnP9t10g/aCCo/Djy7RYaYZy5ZROFK3G5cEj9tDgGOTQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.0.2.tgz",
"integrity": "sha512-jLywRB42hZMRMc4aUgt0uRjkY9FQnMdybv4XgLl+ddi75QuJn8Vbl0+CHcVHJDC0+WDfi1KQXRWtHaR5vp6gHw==",
"license": "Apache-2.0",
"peerDependencies": {
"zod": "^3.25.76 || ^4.0.0"
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.309.tgz",
"integrity": "sha512-Tou0fXBkxKJDcgT09v3Iv/Jd7bNgapgfAiHxGPctAADPFQhidfhZbcv7at1cVL0Lon5sbs+OzP/pEOigw2TI0A==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.310.tgz",
"integrity": "sha512-fo+sr+9Ks73P001f6XKRQlyzfIBAJMgXArLUkH5Tu98NikRDZeGtFQVj23d5/hj2ijpFgzffrr4fHywY9qUkoQ==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.309",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.309",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.309",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.309",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.309",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.309",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.309",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.309"
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.310",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.310",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.310",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.310",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.310",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.310",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.310",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.310"
},
"peerDependencies": {
"@anthropic-ai/sdk": ">=0.93.0",
@@ -71,9 +71,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.309.tgz",
"integrity": "sha512-qC93iDGEioaXqWsYazcZzNv3hd8d4VHkCFgb+AzvbkCWOzZHnRu1EqF4Iz5kCTLDs4VdMvCa1NkzlUwtTedBVw==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.310.tgz",
"integrity": "sha512-gpzMjDZdcdA2Webuqm9j76exG8yNK/755ZYWbS3MM4vVBlAsm+enEO+hhNt0Mx1VMil28RWetvTO4ZJ3YODVMA==",
"cpu": [
"arm64"
],
@@ -84,9 +84,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.309.tgz",
"integrity": "sha512-RvP0UOcYxn42c1lgByoMufHaMOH86U2ThGGFupOxBKATY4Q6WJd6M1CukQ/qygfgJ8lcxvK8GOOd3SIny4kQ8Q==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.310.tgz",
"integrity": "sha512-ZRQ4ZrzE2OOfR+Fx8h+CM4NKh0UiYqlh5TowP7HUhn5KUoYMjoSR/xIhix8ow42X2IDiBy0FjEVaA/Awhu2KAw==",
"cpu": [
"x64"
],
@@ -97,9 +97,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.309.tgz",
"integrity": "sha512-+8rQwzycuXTW2e/C+yotvP/SDiEDHxItdPvIriGAf2ixh7nPRjVuzHOj1tamgVO0J8nmpPvIAO4edzJ7VZCcAg==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.310.tgz",
"integrity": "sha512-l6SPgUmJknjx+6/Pb7OR353fm3xBNAWqodFoC/Cflg0yMcLfz9iwNIc3Drgos2DqfHnid+ODqDzSO93AgQ7RmA==",
"cpu": [
"arm64"
],
@@ -113,9 +113,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.309.tgz",
"integrity": "sha512-14ZD9hdUeobw+jr166YvCZyZwSYG3hDWi5S4yQxpSCrRuY/1z4Kl1AfymUlugiN3UT4Q1rif32K6GnjRJBvQdA==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.310.tgz",
"integrity": "sha512-viXuvvtli3pfHzTEI+oeJzk3nldkfHvj757Xh/uAeximUT9ANZIq4SMZw2cSqbhASjni0qcO6B1x4BGx2LuQpw==",
"cpu": [
"arm64"
],
@@ -129,9 +129,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.309.tgz",
"integrity": "sha512-96G8/uTArlvTTg0qGtiP36W76Ug/YT0gMBh8KX7J2RigdOTrk6QkHtUeCzrw6yff5hEIzecN7EWNw6uh9USo4A==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.310.tgz",
"integrity": "sha512-jx7awHPoofXuE6cRRQSzLUFyW6aUXbYD9AtnNIDUHmmPPrgJ9nSnqjPaqcNhOdaZH7onUUujEAq26N2tx29fYg==",
"cpu": [
"x64"
],
@@ -145,9 +145,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.309.tgz",
"integrity": "sha512-9ViOt4MGMiE2h8V+JUPR5l9F0ZTFuhKC3jawIL/MrMfYY1m2Gt3gcMuP7h7YK914a4tmpXrmITWrpTOu1WOZQQ==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.310.tgz",
"integrity": "sha512-/qFgN5LcofB8PayxBXoa+xDxkYPPQeo/eCyN0DsyyeI67ifH/l49pz9J+sH0+ctDe9und0vMCJPzng70bx2K+Q==",
"cpu": [
"x64"
],
@@ -161,9 +161,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.309.tgz",
"integrity": "sha512-lDsmw5hguDF7KNMG2OvvKlG0Zgs9bKlrX9Gs2Rv/NjLVl0PIy1TUsh7byOsUR4pXwoMU1toyU1CKqBc/Au8hHw==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.310.tgz",
"integrity": "sha512-tRmDMhZpoWAIrZxFBTh6vyZB8W5gldU5/Rfo+cyq/OvBKTxxbim+/0tIa9QlJQncoVLXVN9eySYeeB9KMjio7w==",
"cpu": [
"arm64"
],
@@ -174,9 +174,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
"version": "0.3.309",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.309.tgz",
"integrity": "sha512-otGHQimz6F++6SajGqSAKBclRPGTn60cx+8u3dnhMa5yovKZ5SgChFgstFsiDIWr/ZBqpJysvFemSgHX61mlmQ==",
"version": "0.3.310",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.310.tgz",
"integrity": "sha512-zvrIApxPccTJc6gCOtw5hj27QjWhhTi3z5x5iMmf9f0LZAKR+9mrgENVeFL7PP7zRGdYVhhX2SahbzxN83L/eA==",
"cpu": [
"x64"
],
@@ -187,9 +187,9 @@
]
},
"node_modules/@anthropic-ai/sdk": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-1.1.0.tgz",
"integrity": "sha512-Op/fKMsfVwFCfObc87leLOFlORE0RjOvLzRzPCWWLI7Ww6bobpKR6YmYC8ZYgulRh+m2a/3/61VUXoWHFtCXIw==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-1.3.0.tgz",
"integrity": "sha512-k7z9BlLLPxfZH+ov7dS6EnaN/wiZeV8YNT1LZfrpAHIy7oY7pHr4UzirF9f2OlHtFEnRBOX+AZQ91Y5fK+TPTw==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/api": "^1.4.1",
@@ -1250,19 +1250,19 @@
}
},
"node_modules/body-parser": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz",
"integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^2.0.0",
"content-type": "^2.1.0",
"debug": "^4.4.3",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"iconv-lite": "^0.7.3",
"on-finished": "^2.4.1",
"qs": "^6.15.2",
"qs": "^6.16.1",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.95.0",
"version": "0.96.0",
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@@ -65,13 +65,13 @@
"node": ">=22"
},
"dependencies": {
"@agentclientprotocol/sdk": "2.0.1",
"@anthropic-ai/claude-agent-sdk": "0.3.309",
"@agentclientprotocol/sdk": "2.0.2",
"@anthropic-ai/claude-agent-sdk": "0.3.310",
"diff": "9.0.0",
"zod": "4.6.7"
},
"devDependencies": {
"@anthropic-ai/sdk": "1.1.0",
"@anthropic-ai/sdk": "1.3.0",
"@eslint/js": "10.0.1",
"@tsconfig/node22": "22.0.6",
"@types/node": "26.6.5",

View File

@@ -3116,7 +3116,10 @@ export class ClaudeAcpAgent {
}
timing.phase("session-ready");
const replayed = await replay;
if (!replayed.ok) throw replayed.error;
if (!replayed.ok) {
this.replayedChildModels.delete(params.sessionId);
throw replayed.error;
}
return {
sessionId: response.sessionId,
modes: response.modes,
@@ -8197,6 +8200,7 @@ export class ClaudeAcpAgent {
}
let content: typeof message.message.content;
let toolUseResult = message.type === "user" ? message.tool_use_result : undefined;
if (
message.type === "assistant" &&
(message.parent_tool_use_id === null || forwardsSubagentText())
@@ -8217,6 +8221,20 @@ export class ClaudeAcpAgent {
(item) => item.type !== "text" && item.type !== "thinking",
);
streamedBlocksOf(message.parent_tool_use_id).length = 0;
} else if (
message.parent_tool_use_id !== null &&
Array.isArray(message.message.content)
) {
// A foreground fork inherits a placeholder result for its own
// launch. Only the launching side can complete that Agent call
// with the fork's actual report.
const launchId = message.parent_tool_use_id;
content = message.message.content.filter(
(block) => block.type !== "tool_result" || block.tool_use_id !== launchId,
);
// The message-level sidecar cannot describe a remaining result
// after its launch result was removed.
if (content.length !== message.message.content.length) toolUseResult = undefined;
} else {
content = message.message.content;
}
@@ -8234,7 +8252,7 @@ export class ClaudeAcpAgent {
);
const backgroundBashTask = backgroundBashTaskFromToolResult(
content,
message.tool_use_result,
toolUseResult,
session.toolUseCache,
);
if (backgroundBashTask) await asyncTasks.taskBackgrounded(backgroundBashTask);
@@ -8263,7 +8281,7 @@ export class ClaudeAcpAgent {
asyncTasks,
backgroundBashTask,
);
const resumedAgentId = resumedNativeSubagentId(message.tool_use_result);
const resumedAgentId = resumedNativeSubagentId(toolUseResult);
if (resumedAgentId) {
const resume = sendMessageResume(
session.toolUseCache,
@@ -8304,7 +8322,7 @@ export class ClaudeAcpAgent {
emittedToolCalls: session.emittedToolCalls,
toolCallFields: toolCallFieldsOf(session),
messageId: messageIdForGrouping(message),
toolUseResult: message.type === "user" ? message.tool_use_result : undefined,
toolUseResult,
// On the wire since CLI 2.1.216 but not in SDKUserMessage's
// type, hence the cast. Validated by parseToolResultMeta.
toolResultMeta:
@@ -9449,7 +9467,8 @@ export class ClaudeAcpAgent {
? new RfdSubagentReplay(
rfdHistory,
(notification) => this.client.sessionUpdate(asSdkSessionNotification(notification)),
replayModels,
this.subagentModels(sessionId),
!pending,
)
: undefined;
rfdReplay?.scan(messages, sessionId);

View File

@@ -90,6 +90,8 @@ export type SubagentModels = {
type ChildConfiguration = {
options: Map<string, SessionConfigOption>;
published?: string;
/** The `model` option value and name that the client last got. */
publishedModel?: ModelEntry;
publishing: Promise<void>;
};
@@ -285,13 +287,13 @@ export class NativeSubagentRuntime {
if (!entry) return;
// Every response of a child reports its model: most repeat the model that
// the client already has, and need no routing.
// A publication that failed sent nothing, so only the published model counts.
const child = this.childByParentToolUse.get(parentToolUseId!);
const current = child?.announced
? this.configBySession.get(child.sessionId)?.options.get(MODEL_CONFIG_ID)
: undefined;
if (current?.type === "select" && current.currentValue === entry.value) {
const shown = current.options[0];
if (shown && "name" in shown && shown.name === entry.name) return;
if (
child?.announced &&
sameModelEntry(this.configBySession.get(child.sessionId)?.publishedModel, entry)
) {
return;
}
await deliver({
sessionId: this.rootSessionId,
@@ -384,6 +386,11 @@ export class NativeSubagentRuntime {
},
});
config.published = snapshot;
const model = configOptions.find((option) => option.id === MODEL_CONFIG_ID);
const shown = model?.type === "select" ? model.options[0] : undefined;
if (model && shown && "name" in shown && typeof model.currentValue === "string") {
config.publishedModel = { value: model.currentValue, name: shown.name };
}
});
// The caller receives the error; later observations must still be able to retry.
config.publishing = publishing.catch(() => {});
@@ -1418,12 +1425,15 @@ export class NativeSubagentRuntime {
private buffer(parentToolUseId: string, notification: AcpSessionNotification): void {
const updates = this.pending.get(parentToolUseId);
// A child reports its model with each response before its announcement.
// Only the latest configuration waits, so they do not fill the buffer.
if (notification.update.sessionUpdate === "config_option_update" && updates) {
const index = updates.findIndex(
({ update }) => update.sessionUpdate === "config_option_update",
);
// A child reports its model with each response before its announcement
// (see `reportModel`). Only the latest such report waits, so they do not
// fill the buffer. Any other configuration update waits as it is.
const modelOnly = ({ update }: AcpSessionNotification) =>
update.sessionUpdate === "config_option_update" &&
update.configOptions.length === 1 &&
update.configOptions[0]?.id === MODEL_CONFIG_ID;
if (updates && modelOnly(notification)) {
const index = updates.findIndex(modelOnly);
if (index >= 0) {
updates[index] = notification;
return;

View File

@@ -112,6 +112,12 @@ export class RfdSubagentReplay {
private readonly history: SubagentHistory,
private readonly publish: Publish,
private readonly models?: SubagentModels,
/**
* Whether the replay maps a model to the session's model list as it goes.
* A load lacks the list until the CLI started, so it maps only in
* {@link currentStates}, after the load response.
*/
private readonly mapsDuringReplay = true,
) {}
/**
@@ -189,7 +195,7 @@ export class RfdSubagentReplay {
* whose model changes gets its `model` option again.
*/
async responseModel(owner: ReplayChild, model: string | undefined): Promise<void> {
const entry = responseModelEntry(model, this.models);
const entry = responseModelEntry(model, this.mapsDuringReplay ? this.models : undefined);
if (!entry || !owner.announced) return;
owner.responseModel = model;
await this.sendModel(owner, entry);

View File

@@ -3596,11 +3596,19 @@ describe("subagent transcript replay", () => {
{ log: () => {}, error: () => {} },
);
await initializeClient(agent, AIR_NATIVE_SUBAGENTS);
vi.mocked(getSessionMessages).mockResolvedValueOnce(replayHistory);
// A load replays while the CLI starts: the session and its model list come later.
await (
agent as unknown as { replaySessionHistory(sessionId: string): Promise<void> }
).replaySessionHistory("s1");
agent as unknown as {
replaySessionHistory(
sessionId: string,
messages: unknown[],
pending: unknown,
): Promise<void>;
}
).replaySessionHistory("s1", replayHistory, {
state: { cwd: "/tmp", taskState: new Map(), messageIdToUuid: new Map() },
stopped: false,
});
const child = "s1:replay-subagent:parent-agent-call";
const models = () =>
updates.flatMap(({ sessionId, update }) =>
@@ -3877,7 +3885,14 @@ describe("subagent transcript replay", () => {
/** The session's config options and model list, known after the response. */
models?: { configOptions: SessionConfigOption[]; modelInfos: ModelInfo[] };
},
{ traceModels = false }: { traceModels?: boolean } = {},
{
traceModels = false,
load = false,
}: {
traceModels?: boolean;
/** Replay as a load does, while the CLI starts: without the session record. */
load?: boolean;
} = {},
): Promise<string[]> {
const configDir = await mkdtemp(path.join(os.tmpdir(), "claude-acp-rfd-replay-"));
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
@@ -3915,9 +3930,21 @@ describe("subagent transcript replay", () => {
);
await initializeClient(agent, capabilities);
vi.mocked(getSessionInfo).mockResolvedValueOnce({ cwd: "/tmp/proj" } as any);
const pending = load
? {
state: { cwd: "/tmp/proj", taskState: new Map(), messageIdToUuid: new Map() },
stopped: false,
}
: undefined;
await (
agent as unknown as { replaySessionHistory(sessionId: string): Promise<void> }
).replaySessionHistory(sessionId);
agent as unknown as {
replaySessionHistory(
sessionId: string,
messages?: unknown[],
pending?: unknown,
): Promise<void>;
}
).replaySessionHistory(sessionId, undefined, pending);
const responseAt = updates.length;
if (afterResponse) {
// Stands in for the live session's runtime, which knows its running children.
@@ -4268,7 +4295,7 @@ describe("subagent transcript replay", () => {
],
},
},
{ traceModels: true },
{ traceModels: true, load: true },
);
const opus = JSON.stringify([{ value: "opus", name: "Opus 5" }]);
@@ -15937,6 +15964,132 @@ describe("usage_update computation", () => {
});
});
describe("foreground fork tool results", () => {
it.each([
["plain", {}, null],
["AIR", AIR_CLIENT_CAPABILITIES, null],
["plain nested", {}, "toolu_outer"],
["AIR nested", AIR_CLIENT_CAPABILITIES, "toolu_outer"],
] as const)(
"waits for the launching side's result for a %s client",
async (_, capabilities, parent) => {
const updates: SessionNotification[] = [];
const logger = { log: vi.fn(), error: vi.fn() };
const agent = new ClaudeAcpAgent(
{
sessionUpdate: async (notification: SessionNotification) => {
updates.push(notification);
},
} as unknown as AcpClient,
logger,
);
await initializeClient(agent, capabilities);
const input = new Pushable<any>();
const assistant = (content: unknown[], parentToolUseId: string | null) => ({
type: "assistant",
parent_tool_use_id: parentToolUseId,
uuid: randomUUID(),
session_id: "test-session",
message: {
id: randomUUID(),
role: "assistant",
model: "claude-sonnet-4-6",
content,
usage: successfulResultMessage().usage,
},
});
const user = (content: unknown[], parentToolUseId: string | null) => ({
type: "user",
parent_tool_use_id: parentToolUseId,
uuid: randomUUID(),
session_id: "test-session",
message: { role: "user", content },
});
const toolResult = (id: string, text: string) => ({
type: "tool_result",
tool_use_id: id,
content: [{ type: "text", text }],
});
async function* messages() {
const { value } = await input[Symbol.asyncIterator]().next();
yield userEcho(value);
yield assistant(
[
{
type: "tool_use",
id: "toolu_fork",
name: "Agent",
input: {
subagent_type: "fork",
run_in_background: false,
prompt: "Inspect the parser",
},
},
],
parent,
);
// The fork inherits a result for its own launch. It cannot complete
// that call: the launching side sends the actual report later.
yield user([toolResult("toolu_fork", "Fork started — that fork is you.")], "toolu_fork");
yield assistant(
[
{
type: "tool_use",
id: "toolu_child",
name: "Agent",
input: { subagent_type: "general-purpose", prompt: "Read the parser" },
},
],
"toolu_fork",
);
// Keep results of tools the fork itself called, even in a message
// that also carries the copied launch result.
yield {
...user(
[
toolResult("toolu_fork", "Fork started — processing in background"),
toolResult("toolu_child", "child report"),
],
"toolu_fork",
),
// Filtering must not attribute the inherited launch sidecar to
// the one result that remains in this message.
tool_use_result: {
status: "completed",
content: [{ type: "text", text: "inherited launch report" }],
},
};
yield user([toolResult("toolu_fork", "fork report")], parent);
yield successfulResultMessage();
yield { type: "system", subtype: "session_state_changed", state: "idle" };
}
agent.sessions["test-session"] = mockSessionState({ query: wrapQuery(messages()), input });
await expect(
agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "Inspect it" }] }),
).resolves.toMatchObject({ stopReason: "end_turn" });
const completed = updates.flatMap(({ update }) =>
update.sessionUpdate === "tool_call_update" && update.status === "completed"
? [{ id: update.toolCallId, content: update.content, rawOutput: update.rawOutput }]
: [],
);
expect(completed.map(({ id }) => id)).toEqual(["toolu_child", "toolu_fork"]);
expect(completed.map(({ content }) => content)).toEqual([
[{ type: "content", content: { type: "text", text: "child report" } }],
[{ type: "content", content: { type: "text", text: "fork report" } }],
]);
if (!("_meta" in capabilities)) {
expect(completed.map(({ rawOutput }) => rawOutput)).toEqual([
[{ type: "text", text: "child report" }],
[{ type: "text", text: "fork report" }],
]);
}
expect(logger.error.mock.calls.flat().join("\n")).not.toContain("wasn't tracked");
},
);
});
describe("assembled assistant text fallback", () => {
const ZERO_USAGE = {
input_tokens: 0,

View File

@@ -2678,6 +2678,44 @@ describe("NativeSubagentRuntime child models", () => {
},
);
it.each(["rfd", "air"] as const)(
"%s: sends a model again after its publication failed",
async (form) => {
const published: AcpSessionNotification[] = [];
let failures = 1;
const runtime = new NativeSubagentRuntime(
form,
"root",
{},
async (notification) => {
if (notification.update.sessionUpdate === "config_option_update" && failures-- > 0) {
throw new Error("client gone");
}
published.push(notification);
},
{ log: () => {} },
undefined,
undefined,
models,
);
const deliver = async (notification: AcpSessionNotification) => {
const routed = await runtime.route(notification, deliver);
if (routed) published.push(routed);
};
await deliver(agentCall("launch-1", {}));
await runtime.taskStarted(
{ taskId: "worker-1", toolUseId: "launch-1", subagentType: "Explore" },
deliver,
);
await expect(runtime.reportModel("launch-1", "claude-sonnet-4-6", deliver)).rejects.toThrow(
"client gone",
);
expect(modelUpdates(published)).toEqual([]);
await runtime.reportModel("launch-1", "claude-sonnet-4-6", deliver);
expect(modelUpdates(published)).toEqual([["worker-1", "sonnet", "Sonnet 4.6"]]);
},
);
it("air: forgets the configuration of an ended child", async () => {
const { runtime, published, deliver } = await spawn("air");
await runtime.reportModel("launch-1", "claude-sonnet-4-6", deliver);