mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-13 21:02:08 +00:00
Compare commits
2 Commits
v0.106.1-p
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e857d59ec | ||
|
|
c5c120dba9 |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.106.0"
|
||||
".": "0.107.0"
|
||||
}
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## [0.107.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.106.0...v0.107.0) (2026-11-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* report the turn's rate limit in rate-limit failure errors ([#1610](https://github.com/agentclientprotocol/claude-agent-acp/issues/1610)) ([4f474d6](https://github.com/agentclientprotocol/claude-agent-acp/commit/4f474d655b88a075ec7d0124d45cafa0f5565af7))
|
||||
* report unknown subagent state at a conversation reset and reference children from tool calls ([#1608](https://github.com/agentclientprotocol/claude-agent-acp/issues/1608)) ([4804917](https://github.com/agentclientprotocol/claude-agent-acp/commit/4804917a6fa7050490d1d62c6689e78c231c752a))
|
||||
* say the cause of an api retry in the AIR warning details ([#1594](https://github.com/agentclientprotocol/claude-agent-acp/issues/1594)) ([4f690e1](https://github.com/agentclientprotocol/claude-agent-acp/commit/4f690e129c01f22e2c2a87e0e0a3c5b503241f15))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* assume a 1M context window until Claude Code reports it ([#1613](https://github.com/agentclientprotocol/claude-agent-acp/issues/1613)) ([9619805](https://github.com/agentclientprotocol/claude-agent-acp/commit/96198053067cf4d7b7440c52036174ad4f54183e))
|
||||
* end the tool calls of a response that Claude Code took back ([#1616](https://github.com/agentclientprotocol/claude-agent-acp/issues/1616)) ([21f3e04](https://github.com/agentclientprotocol/claude-agent-acp/commit/21f3e0430b693e6b04750ab3018e1eaf726af173))
|
||||
* explain why Claude Code turns Fast mode off ([#1602](https://github.com/agentclientprotocol/claude-agent-acp/issues/1602)) ([c331e17](https://github.com/agentclientprotocol/claude-agent-acp/commit/c331e178a5df47448be1a3ed2f71270c3aad2433))
|
||||
* follow Claude Code's rule for managed deniedModels in the model picker ([#1615](https://github.com/agentclientprotocol/claude-agent-acp/issues/1615)) ([c78bcc7](https://github.com/agentclientprotocol/claude-agent-acp/commit/c78bcc7f417fc8d3da66f51021b43f4528826ec0))
|
||||
* log a managed policy source that did not load ([#1614](https://github.com/agentclientprotocol/claude-agent-acp/issues/1614)) ([ca46b41](https://github.com/agentclientprotocol/claude-agent-acp/commit/ca46b412f51cdb7b5a303c751f5d48c216700887))
|
||||
* report a mid-turn API error notice where the response broke off ([#1599](https://github.com/agentclientprotocol/claude-agent-acp/issues/1599)) ([4df6999](https://github.com/agentclientprotocol/claude-agent-acp/commit/4df69996ca74adff6c42fc60e4b9b742f2d3fdf5))
|
||||
* say why the login failed in the v2 authRequired error ([#1598](https://github.com/agentclientprotocol/claude-agent-acp/issues/1598)) ([ecd066c](https://github.com/agentclientprotocol/claude-agent-acp/commit/ecd066c8700955e3456dd9387e6b8317f024e26c))
|
||||
* update to @anthropic-ai/claude-agent-sdk 0.3.323 ([#1612](https://github.com/agentclientprotocol/claude-agent-acp/issues/1612)) ([ddee24e](https://github.com/agentclientprotocol/claude-agent-acp/commit/ddee24ef8fd0abc0401fb32d567914030fbf6944))
|
||||
|
||||
## [0.106.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.105.0...v0.106.0) (2026-11-10)
|
||||
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.106.0",
|
||||
"version": "0.107.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.106.0",
|
||||
"version": "0.107.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.6.0",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.106.0",
|
||||
"version": "0.107.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
|
||||
@@ -143,6 +143,32 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("v2 core with Claude Code",
|
||||
]);
|
||||
}, 180_000);
|
||||
|
||||
it("ends the work once when Claude Code folds a prompt into it", async () => {
|
||||
const sessionId = await newSession("bypassPermissions");
|
||||
await prompt(
|
||||
sessionId,
|
||||
"Use the Bash tool in the foreground (not run_in_background) to run exactly: ping -c 8 127.0.0.1 > /dev/null . Then reply with just: woke",
|
||||
);
|
||||
await until(() =>
|
||||
updates.some(
|
||||
({ update }) =>
|
||||
update.sessionUpdate === "terminal_update" &&
|
||||
/ping/.test((update as v2.TerminalUpdate).command ?? ""),
|
||||
),
|
||||
);
|
||||
// Claude Code takes this prompt in after the command, into the same work.
|
||||
// It sends the prompt's `completed` frame before the work's result.
|
||||
await prompt(sessionId, "Reply with exactly the single word: pong");
|
||||
await until(() => states(sessionId).some((update) => update.state === "idle"));
|
||||
// A turn that the result opened would follow the idle at once.
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
expect(states(sessionId).map((update) => update.state)).toEqual(["running", "idle"]);
|
||||
// The idle is the result's: it carries what the work spent.
|
||||
const idle = states(sessionId).at(-1) as { stopReason?: string; usage?: v2.Usage };
|
||||
expect(idle.stopReason).toBe("end_turn");
|
||||
expect(idle.usage?.totalTokens).toBeGreaterThan(0);
|
||||
}, 240_000);
|
||||
|
||||
it("fails a queued prompt with -32800 at a cancel and goes idle with cancelled", async () => {
|
||||
const sessionId = await newSession("bypassPermissions");
|
||||
await prompt(
|
||||
|
||||
@@ -1706,6 +1706,62 @@ describe("ACP v2 core", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["the turn of another prompt", true],
|
||||
["a cycle that Claude Code started on its own", false],
|
||||
])("ends the work once, at its result, when a prompt is folded into %s", async (_, prompted) => {
|
||||
const client = v2Client();
|
||||
await client.connect(async (agent) => {
|
||||
const { response, claude } = await openSession(agent);
|
||||
const ask = () =>
|
||||
agent.request(v2.methods.agent.session.prompt, {
|
||||
sessionId: response.sessionId,
|
||||
prompt: PROMPT,
|
||||
});
|
||||
let opener: SDKUserMessage | undefined;
|
||||
if (prompted) {
|
||||
const first = ask();
|
||||
opener = await claude.prompt(0);
|
||||
claude.send(lifecycle(opener, "started"));
|
||||
await first;
|
||||
}
|
||||
claude.send(cycleStart());
|
||||
await vi.waitFor(() => expect(client.states()).toEqual(["running"]));
|
||||
// As the bundled CLI sends it: a folded prompt's echo comes before its
|
||||
// `started`, and its `completed` before the result of the work.
|
||||
const second = ask();
|
||||
const folded = await claude.prompt(prompted ? 1 : 0);
|
||||
claude.send(echo(folded));
|
||||
claude.send(lifecycle(folded, "started"));
|
||||
await second;
|
||||
for (const event of textDelta("msg_pong", "pong")) claude.send(event);
|
||||
claude.send(assistant("msg_pong", [{ type: "text", text: "pong" }]));
|
||||
claude.send(lifecycle(folded, "completed"));
|
||||
claude.send(
|
||||
result({
|
||||
result: "pong",
|
||||
origin: { kind: prompted ? "human" : "task-notification" },
|
||||
user_message_uuids: [...(opener ? [opener.uuid] : []), folded.uuid],
|
||||
}),
|
||||
);
|
||||
if (opener) claude.send(lifecycle(opener, "completed"));
|
||||
claude.send(sessionState("idle"));
|
||||
await vi.waitFor(() => expect(client.states()).toContain("idle"));
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
});
|
||||
|
||||
expect(client.states()).toEqual(["running", "idle"]);
|
||||
expect(
|
||||
client.sessionUpdates.filter((update) => update.sessionUpdate === "agent_message_chunk"),
|
||||
).toMatchObject([{ messageId: "msg_pong" }]);
|
||||
// The idle carries the result's usage: the result ended the work.
|
||||
expect(client.sessionUpdates.at(-1)).toMatchObject({
|
||||
state: "idle",
|
||||
stopReason: "end_turn",
|
||||
usage: { totalTokens: 6 },
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a tool call that Claude dropped before it ran as cancelled at the turn's end", async () => {
|
||||
const client = v2Client();
|
||||
await client.connect(async (agent) => {
|
||||
|
||||
@@ -141,6 +141,14 @@ export class V2Session implements RequestTarget {
|
||||
private foreground = false;
|
||||
/** The prompt whose `started` frame came last in the open turn. */
|
||||
private turnCommand: string | undefined;
|
||||
/**
|
||||
* Whether a cycle of Claude Code runs in the open turn: one started (see
|
||||
* {@link openTurn}) and its result has not come. Claude Code folds a prompt
|
||||
* that starts meanwhile into that cycle.
|
||||
*/
|
||||
private cycleOpen = false;
|
||||
/** The prompt that started the running cycle, if one did. */
|
||||
private cycleCommand: string | undefined;
|
||||
/** The state the client was told last. */
|
||||
private reportedState: ForegroundState = "idle";
|
||||
/** The open permission requests and elicitations of the session. */
|
||||
@@ -1088,9 +1096,13 @@ export class V2Session implements RequestTarget {
|
||||
);
|
||||
return;
|
||||
case "completed":
|
||||
// Comes after the turn's result. A turn that ended without one ends here.
|
||||
// Claude Code sends it after the result for the prompt that started
|
||||
// the cycle, so a cycle that ended without a result ends here. For a
|
||||
// prompt folded into the cycle it sends it before the result, which
|
||||
// then ends the turn. Nothing but Claude Code's order tells the two
|
||||
// apart.
|
||||
this.stopped.promptEnded(uuid);
|
||||
if (uuid === this.turnCommand) this.closeTurn();
|
||||
if (uuid === this.cycleCommand) this.closeTurn();
|
||||
return;
|
||||
default:
|
||||
// `queued`: the prompt waits in Claude Code's queue.
|
||||
@@ -1153,6 +1165,10 @@ export class V2Session implements RequestTarget {
|
||||
if (interrupted) this.cancelledWork = true;
|
||||
// A result with no open turn: a turn that sent nothing before it.
|
||||
this.openTurn(undefined);
|
||||
// The result ends the cycle. A prompt that starts next in the open turn,
|
||||
// such as the `/clear` of a fresh context, starts its own.
|
||||
this.cycleOpen = false;
|
||||
this.cycleCommand = undefined;
|
||||
// A cycle that Claude Code started on its own leaves its Fast mode state
|
||||
// to the next result of a prompt.
|
||||
if (!(message.origin && AUTONOMOUS_RESULT_ORIGINS.has(message.origin.kind))) {
|
||||
@@ -1309,8 +1325,8 @@ export class V2Session implements RequestTarget {
|
||||
// --- State ---------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Opens a turn, if none is open. `command` is the prompt that started it,
|
||||
* if one did.
|
||||
* Opens a turn, if none is open, and starts a cycle in it, if none runs
|
||||
* there. `command` is the prompt that started it, if one did.
|
||||
*/
|
||||
private openTurn(command: string | undefined): void {
|
||||
if (command !== undefined) {
|
||||
@@ -1319,6 +1335,10 @@ export class V2Session implements RequestTarget {
|
||||
this.localCommands.delete(command);
|
||||
if (local) this.turnLocalCommand = { command: local, abort: new AbortController() };
|
||||
}
|
||||
if (!this.cycleOpen) {
|
||||
this.cycleOpen = true;
|
||||
this.cycleCommand = command;
|
||||
}
|
||||
if (this.foreground) return;
|
||||
this.foreground = true;
|
||||
this.lastEnd = undefined;
|
||||
@@ -1356,6 +1376,8 @@ export class V2Session implements RequestTarget {
|
||||
this.output.cancelled = false;
|
||||
this.lastEnd = undefined;
|
||||
this.turnCommand = undefined;
|
||||
this.cycleOpen = false;
|
||||
this.cycleCommand = undefined;
|
||||
this.turnReport.reset();
|
||||
const ownCommandDone = this.ownCommandDone;
|
||||
this.ownCommandDone = undefined;
|
||||
|
||||
Reference in New Issue
Block a user