Compare commits

..

5 Commits

Author SHA1 Message Date
Ben Brandt
f5d79167a6 fix: keep a v2 session idle when the model changes between turns (#1586)
On ACP v2, changing the model of an idle session left the session
reporting `running` with no work in progress, and it never reported
`idle` again until some later turn ended. A client that refuses prompts
while the session is `running` could then never send the prompt that
would end it.

## Cause

After `setModel`, Claude Code echoes a user message that carries only
the local command's output:

```json
{
  "type": "user",
  "message": {
    "role": "user",
    "content": "<local-command-stdout>Set model to `haiku (claude-haiku-5-5)`</local-command-stdout>"
  },
  "parent_tool_use_id": null,
  "uuid": "…",
  "isReplay": true
}
```

The v2 session read every top-level echo as a prompt that Claude Code
took in, and opened a turn for it. A model change runs no turn: no
`command_lifecycle` frame, `result` or `session_state_changed` follows
the echo, so nothing closed that turn.

Captured against the bundled CLI (adapter 0.104.1): `state_update
running` went out 18 ms after `session/set_config_option`, and no `idle`
followed in the next 75 seconds.

## Fix

An echo whose content is only local command output, and whose uuid is
not one of the session's pending prompts, no longer opens a turn. The
check is `isLocalCommandOutput` in `message-filters.ts`, next to the
helpers that already recognize these markers.

The pending-prompt exception keeps a client's own prompt working with a
CLI that sends no `command_lifecycle` frames, where the echo is the only
sign that the prompt was inserted.

## Scope

- v2 only. The v1 core takes its state from `session_state_changed` and
does not open turns at echoes.
- I captured a model change. Other local commands that Claude Code runs
outside a turn and echoes the same way are covered by the same check,
but I did not capture them.

## Testing

- New case in `src/tests/v2/v2.test.ts` sends the echo as the CLI sent
it. Without the fix it fails with `expected [ 'running' ] to deeply
equal []`.
- Reran the capture against the bundled CLI with the fix: no `running`
after either model change, and the next prompt ran `running` then
`idle`.
- `npm run check` and `npm run test:run` pass.
2026-11-10 18:49:50 +00:00
Ben Brandt
a4a8534c23 feat: serve session/set_title on ACP v1 and v2 (#1585)
Implements the [`session/set_title`
RFD](https://agentclientprotocol.com/rfds/session-set-title) on ACP v1
and v2.

## What clients get

- v1 advertises `agentCapabilities.sessionCapabilities.title: {}` and v2
advertises `capabilities.session.title: {}`. Both serve
`session/set_title` for every client.
- The session does not need to be loaded. A session that runs on this
connection is renamed through its CLI (`rename_session`). Any other
session gets the `custom-title` and `agent-name` records in every copy
of its transcript, plus the `custom-title.json` sidecar, as the CLI's
`/rename` writes them.
- The title is normalized the way `_session/rename` normalizes it:
whitespace collapsed, cut to the CLI's 200 characters. The response
carries the stored title, as that client's `session/list` shows it. On
v1 an AIR client gets it without the `[archived] ` prefix. An archived
session stays archived.
- A loaded session publishes `session_info_update { title }`. A title
generation in flight finishes first, and no generated title replaces the
client's.
- Errors: a title that is empty after normalization returns `-32602`. A
session that is not loaded and has no transcript returns `-32002`. On
v2, a subagent session is refused, as close and delete refuse it.

## Structure

- The transcript writer behind `_session/rename` lived in
`src/v1/session-index/`, which v2 cannot import. Everything there except
`connection.ts` moves to the shared `src/session-index/` with no change
beyond import paths. `connection.ts` becomes `src/v1/session-index.ts`.
- `src/session-index/rename.ts` holds the rename logic that used to be
in `SessionIndexConnection.rename`/`retitle`. `_session/rename` and
`session/set_title` on both versions call it.
- Clients without `sessionIndex` (and every v2 client) use a
`SessionIndexService` with no list and no watcher (`transcriptWriter`).
Their `session/delete` now runs inside that service's per-session
`exclusive()`, so a delete cannot race a rename that is still appending
records.
- If a restart or resume starts a new query of a v2 session while a
rename runs, v2 renames through the new query too. Its CLI could
otherwise hold the old title and write it back.

This doesn't use the SDK's top-level `renameSession()`. It writes only a
`custom-title` record to the first copy of the transcript it finds, with
no `agent-name` and no sidecar, so a session renamed earlier through
`/rename` would keep its old name in the CLI and in AIR.

## Tests

- v1 (`session-index.test.ts`): a stored session for a client without
the session index; an archived session for three clients (AIR with
`sessionIndex`, AIR without it, a client that is not AIR); unknown
session and empty title.
- v2 (`acp-v2.test.ts`): an active session through its CLI with the
published update; a stored session's records; the rename that a resume
overtakes; unknown session and empty title.
- Golden files: only the `initialize` capability line changes.
`compare.ts` now allows the `title` capability against the origin/main
baseline.
- Not run: `npm run test:integration` against the real CLI. The
delete/rename ordering has no test, since it is a timing race.
2026-11-10 18:28:46 +00:00
Ben Brandt
6bc2e058cb feat: serve session/status on v1 and v2 (#1584)
Implements the unstable [Session status
RFD](https://agentclientprotocol.com/rfds/session-status) on both
agents, and advertises `status: {}` in the v1 `sessionCapabilities` and
the v2 `capabilities.session`.

## What it answers

| Session | v1 | v2 |
| --- | --- | --- |
| Held by this connection | `open` | `open` with `state` |
| Not held, non-empty transcript stored | `saved` | `saved` |
| Not held, no transcript | `-32002` with `data.sessionId` | same |
| Subagent session | `-32002` | `-32602`, as resume, close, fork and
delete of one |

- The request starts no query, sends no update, and waits for no resume,
restart or close.
- On v2, `state` is the foreground state as a `state_update` snapshot
holds it: with the session's `background` list and without a stop
reason. `V2Background.snapshot` builds it without marking the list as
reported, so a status request does not change which later
`state_update`s go out.
- `saved` or unknown is decided in the new shared
`src/session-status.ts`: a non-empty `<sessionId>.jsonl` in any project
directory. The request names no `cwd`, so a resume with another `cwd`
can still fail with `-32002`. A directory that cannot be read for
another reason than being absent fails the request and does not answer
`-32002`.

## Behaviour to review

- **A v1 session whose query ended reads `open`.** The connection still
holds it, and a `session/load` with the same params returns it as it is,
so `saved` would point the client at a resume that revives nothing. Its
prompts fail with the "session ended" error. v2 drops such a session and
answers from its transcript.
- **A session that was never prompted is unknown once it is closed.**
Claude Code writes the transcript at the first prompt, and a resume of
it fails with `-32002` too.
- **A subagent session does not advertise `status`,** so v2 refuses the
request like its other requests, and v1 does not know the id.
- **`open` is per connection.** Each connection has its own agent and
its own Claude Code processes.

## Not done

- The lookup does one `readdir` and one `stat` per project directory for
each probe of a session that is not held. Sharing the v1 session index's
cached scan is a follow-up.
- Not run against the real CLI (`npm run test:integration`).

## Other changes

- `isSessionId` and `projectsRoot` moved from
`src/v1/session-index/project-dirs.ts` to `src/paths.ts`, so the shared
module can use them.
- The 40 v2 and 2 AIR scenario golden files gain the capability in
`initialize`, and the origin/main comparison allows it, next to
`systemPrompt` and `auth.status`.
- The README lists `session/status` in the v2 surface.
2026-11-10 18:11:39 +00:00
Ben Brandt
99cc1f3245 fix: report Claude Code's API error message as a notice in v2 (#1582)
## Problem

When an API request fails, Claude Code writes its own assistant message
into the conversation, e.g. "API Error: Connection lost mid-response.
The response above may be incomplete." The SDK marks it with `error` on
a `<synthetic>` message.

The v2 agent forwarded that message as an ordinary
`agent_message_chunk`, so clients showed it as Claude's own prose. Right
after a thinking block it reads as if Claude wrote it, and a client
cannot tell it apart without matching the string.

## Change

The v2 agent no longer sends that message as Claude's text:

- When the turn fails, the idle's `error` already carries the failure,
so nothing else is sent.
- Otherwise the text goes out as a `notice` with severity `error`, once,
before the turn's idle. A result that repeats the same text is not shown
again.

Claude Code's sign-in message is handled as before.

## Not changed

- v1 keeps its current behaviour: clients with the session-failure
extension already drop this text, and others keep the transcript line.
- `session/load` replay still shows these messages as text.
`getSessionMessages` strips the `error` marker, so replay cannot
identify them without matching prose.

## Testing

- New v2 cases: a notice when the turn ends normally, one notice when
the result repeats the text, and no notice when the idle fails. They
fail without the change.
- `npm run check` and `npm run test:run` pass.
2026-11-10 18:51:11 +01:00
Mark Tkachenko
bce309f482 refactor: move the v1 client connection out of acp-agent.ts (#1576)
Moves `ClientConnection` and `ChangedMetaClient` verbatim from
`src/v1/acp-agent.ts` into `src/v1/client-connection.ts`. No behaviour
change.

- Both classes gain `export`; nothing else in them changes.
- `client-connection.ts` imports nothing from `acp-agent.ts`.
- The `acp-client.ts` comment that names where `ClientConnection` lives
now points to the new file.
- Emitted JS differs from `main` only in `dist/v1/acp-agent.js` (the
import and the two classes) plus the new `dist/v1/client-connection.js`.

Review with `git diff --color-moved` to see the move.
2026-11-10 17:54:17 +01:00
80 changed files with 1169 additions and 325 deletions

View File

@@ -140,7 +140,7 @@ for v2 is negotiated down to v1. With `v2`, the adapter rejects the `initialize`
On v2 the adapter serves the session baseline (`session/new`, `session/list`, `session/resume`
with replay, `session/close`, `session/prompt`, `session/cancel`), `session/delete`,
`session/fork`, additional directories, config options (the mode is one), plans, notices,
`session/fork`, `session/status`, `session/set_title`, additional directories, config options (the mode is one), plans, notices,
compaction, display terminals for commands, permission requests, elicitation, providers, auth, and
[subagents](#subagent-sessions). Steering is v1 only for now.

View File

@@ -1408,6 +1408,8 @@ Each row carries `_meta.jetbrains.air` with flat row fields of the RFDs. `archiv
`_session/rename { "sessionId": "…", "title": "…" }` returns `{}`.
ACP's `session/set_title` renames the same way for every client, on v1 and v2, without `sessionIndex`, and returns `{ "title": "…" }`: the title as `session/list` shows it to the client. On v1 an AIR client gets it without the archive prefix, and every other client, and every v2 client, with it.
- A blank title is `-32602`. The adapter collapses whitespace and keeps the first 200 characters, trimmed, as the CLI does. An `[archived] ` prefix of the requested title is dropped: a rename never changes the archive state.
- The title is stored as the title records of [Archive](#archive): a `custom-title` and an `agent-name` record. An archived session keeps its `[archived] ` prefix (and the title is cut to 200 characters with it), so a rename does not unarchive it.
- A session that runs here on a CLI without the `rename_session` control request is refused with `-32600`: its CLI holds the title and would write it back.

View File

@@ -16,7 +16,7 @@ import type { AcpPermissionRequest } from "./permissions/presentation.js";
* Client-facing surface the agent calls back into. This is the subset of ACP
* client methods the agent actually uses, expressed as a narrow interface so
* tests can supply lightweight mocks. In the v1 agent it is backed by
* `ClientConnection` (`v1/acp-agent.ts`) over the SDK's typed `AgentContext`.
* `ClientConnection` (`v1/client-connection.ts`) over the SDK's typed `AgentContext`.
* The v2 session output passes one that only takes `sessionUpdate`
* (`v2/stream.ts`).
*/

View File

@@ -87,6 +87,22 @@ function hasMarkerTag(text: string, tag: string): boolean {
return markerTagText(text, tag) !== undefined;
}
/**
* Whether the content of a user message is only the output of a local
* command, such as the `<local-command-stdout>Set model to …</local-command-stdout>`
* that Claude Code echoes when the model changes.
*/
export function isLocalCommandOutput(content: unknown): boolean {
if (typeof content !== "string") return false;
if (
!hasMarkerTag(content, "local-command-stdout") &&
!hasMarkerTag(content, "local-command-stderr")
) {
return false;
}
return stripMarkerTags(content).trim() === "";
}
function commandInvocationFromMarkerOnlyText(text: string): string | null {
if (hasMarkerTag(text, "local-command-stdout") || hasMarkerTag(text, "local-command-stderr")) {
return null;

View File

@@ -5,3 +5,16 @@ import * as path from "node:path";
export function claudeConfigDir(): string {
return process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
}
/** `<config>/projects`, normalized like the SDK does. */
export function projectsRoot(): string {
return path.join(claudeConfigDir(), "projects").normalize("NFC");
}
const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/** Whether `value` is a Claude session id (a UUID). Only such ids name a
* transcript, and only such ids are safe to use as a file name. */
export function isSessionId(value: unknown): value is string {
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
}

View File

@@ -40,7 +40,7 @@
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
import { TASK_NOTIFICATION_TAG, taskNotificationsOf } from "../../task-notifications.js";
import { TASK_NOTIFICATION_TAG, taskNotificationsOf } from "../task-notifications.js";
export type BackgroundTasks = {
/** The ids of the tasks that started and did not end, in start order. */

View File

@@ -49,7 +49,8 @@ import {
type LiveSessionRegistry,
type LiveSnapshot,
} from "./live-registry.js";
import { isExactProjectDir, isSessionId, projectDirMatches, projectsRoot } from "./project-dirs.js";
import { isSessionId, projectsRoot } from "../paths.js";
import { isExactProjectDir, projectDirMatches } from "./project-dirs.js";
import { type IndexRow, type SessionIndex, type TranscriptCandidate } from "./session-index.js";
import { changeSignature, sessionInfoOf } from "./session-info.js";

View File

@@ -23,7 +23,7 @@ import { execFile } from "node:child_process";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { claudeConfigDir } from "../../paths.js";
import { claudeConfigDir } from "../paths.js";
import { errorCode } from "./project-dirs.js";
const SNAPSHOT_TTL_MS = 5_000;

View File

@@ -14,24 +14,10 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { claudeConfigDir } from "../../paths.js";
/** The SDK cuts an encoded path at this length and appends a hash. */
const MAX_SANITIZED_LENGTH = 200;
const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/** Whether `value` is a Claude session id (a UUID). Only such ids name a
* transcript, and only such ids are safe to use as a file name. */
export function isSessionId(value: unknown): value is string {
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
}
/** `<config>/projects`, normalized like the SDK does. */
export function projectsRoot(): string {
return path.join(claudeConfigDir(), "projects").normalize("NFC");
}
function sanitize(value: string): string {
return value.replace(/[^a-zA-Z0-9]/g, "-");
}

115
src/session-index/rename.ts Normal file
View File

@@ -0,0 +1,115 @@
/**
* Client renames of a session: `session/set_title` (v1 and v2) and AIR's
* `_session/rename` (v1). Both cores call {@link renameSession}, which stores
* the title in every transcript of the session (see
* {@link SessionIndexService.retitle}) and, for a session loaded on the
* connection, makes it the client title that no generated title replaces.
*/
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { Query } from "@anthropic-ai/claude-agent-sdk";
import type { SessionTitles, TitledSession } from "../session-titles.js";
import { storedTitle } from "./archive-title.js";
import {
renameTo,
type RenameSessionRequest,
type RetitleOptions,
type SessionIndexService,
type TitleChange,
} from "./service.js";
/** A session loaded on the connection, and its title state. */
export interface RetitledSession {
readonly session: TitledSession;
readonly titles: Pick<
SessionTitles,
"setExplicitTitle" | "storedTitle" | "rememberStoredTitle" | "shownTitle"
>;
}
type RenamingQuery = Query & {
renameSession?: (title: string, sessionId?: string) => Promise<void>;
};
/**
* Names a session; no generated title replaces it. An archived session stays
* archived. `loaded` is the session loaded on this connection, if any;
* `clientTitle` turns a stored title into the one the client shows (an AIR
* client's drops the archive prefix). Returns that title, which the loaded
* session also publishes as a `session_info_update`.
*
* A session that is not loaded and has no transcript is unknown (`-32002`).
*/
export async function renameSession(
index: SessionIndexService,
request: RenameSessionRequest,
loaded: RetitledSession | undefined,
clientTitle: (stored: string) => string = (stored) => stored,
): Promise<string> {
const { sessionId } = request;
// As the CLI keeps it, and never with the archive prefix: a rename does
// not change the archive state.
const title = storedTitle(request.title, false, sessionId);
// The title as stored: an archived one is cut with its prefix.
const shown = (stored: string | undefined) => clientTitle(stored ?? title);
const stored = await retitleSession(index, sessionId, renameTo(title), shown, loaded, {
mayBeUnwritten: true,
sidecar: "always",
});
return shown(stored);
}
/**
* Retitles a session (see {@link SessionIndexService.retitle}) and returns
* the title stored. A session that runs here is titled through its CLI
* (`rename_session`). A loaded session's title state takes the change as a
* client title, so no generated title replaces it; `publish` is the title it
* then publishes.
*/
export async function retitleSession(
index: SessionIndexService,
sessionId: string,
change: TitleChange,
publish: Parameters<SessionTitles["setExplicitTitle"]>[0],
loaded: RetitledSession | undefined,
options: Pick<RetitleOptions, "mayBeUnwritten" | "sidecar">,
): Promise<string | undefined> {
let stored: string | undefined;
// Decided once a title generation in flight has ended: the query may
// have closed meanwhile.
const persist = async () => {
const session = loaded?.session;
const query = session?.query as RenamingQuery | undefined;
// A closed session's CLI is gone here: only a running query titles it.
const running = session !== undefined && !session.queryClosed;
if (running && typeof query?.renameSession !== "function") {
// Its CLI holds the title and would write it back over ours.
throw RequestError.invalidRequest(
{ sessionId },
"The Claude Code CLI of this session cannot change its title while it runs.",
);
}
const live = running
? {
cwd: session.cwd,
rename: (title: string) => query!.renameSession!(title, sessionId),
stored: () => loaded!.titles.storedTitle,
remember: (title: string) => loaded!.titles.rememberStoredTitle(title),
shown: loaded!.titles.shownTitle,
}
: undefined;
stored = await index.retitle(sessionId, change, {
...options,
// Without its CLI, a session needs its transcript.
mayBeUnwritten: live !== undefined && options.mayBeUnwritten,
live,
});
return stored;
};
if (loaded) {
await loaded.titles.setExplicitTitle(publish, persist);
} else {
await persist();
}
return stored;
}

View File

@@ -18,8 +18,8 @@ import {
type SessionInfo,
} from "@agentclientprotocol/sdk/unstable";
import { deleteSession as sdkDeleteSession } from "@anthropic-ai/claude-agent-sdk";
import { airExtensionMeta } from "../../air-extension.js";
import { sanitizeTitle } from "../../session-titles.js";
import { airExtensionMeta } from "../air-extension.js";
import { sanitizeTitle } from "../session-titles.js";
import type { OwnSessionState } from "./activity.js";
import {
effectiveTitle,
@@ -36,11 +36,11 @@ import {
type ListSubscriptionDeps,
} from "./list-subscriptions.js";
import { LiveSessionRegistry } from "./live-registry.js";
import { isSessionId } from "../paths.js";
import {
canonicalPath,
exists,
isExactProjectDir,
isSessionId,
projectDirMatches,
sameProjectPath,
} from "./project-dirs.js";
@@ -466,6 +466,18 @@ export type SessionIndexDeps = {
logError: (message: string, error: unknown) => void;
};
/** The transcript reads and writes of the session index, without its list
* or watcher: what a client without `sessionIndex` uses for
* `session/set_title`, AIR's archive on delete, and delete's ordering
* after them (see {@link SessionIndexService.exclusive}). */
export function transcriptWriter(logger: {
error: (message: string, error: unknown) => void;
}): SessionIndexService {
return new SessionIndexService({
logError: (message, error) => logger.error(`[session-index] ${message}:`, error),
});
}
export class SessionIndexService {
readonly index: SessionIndex;
readonly registry: LiveSessionRegistry;
@@ -541,8 +553,10 @@ export class SessionIndexService {
};
}
/** Runs `mutation` after the previous mutation of the session ended. */
private exclusive<T>(sessionId: string, mutation: () => Promise<T>): Promise<T> {
/** Runs `mutation` after the previous mutation of the session ended, such
* as a delete that does not go through {@link delete}, so that a retitle
* cannot append to a transcript the delete is removing. */
exclusive<T>(sessionId: string, mutation: () => Promise<T>): Promise<T> {
const previous = this.mutations.get(sessionId) ?? Promise.resolve();
const next = previous.catch(() => undefined).then(mutation);
const settled = next.catch(() => undefined);

View File

@@ -21,20 +21,19 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { sanitizeTitle } from "../../session-titles.js";
import { sanitizeTitle } from "../session-titles.js";
import { effectiveTitle, visibleTitle } from "./archive-title.js";
import { isSessionId, projectsRoot } from "../paths.js";
import {
canonicalPath,
encodeProjectPath,
errorCode,
exists,
isExactProjectDir,
isSessionId,
normalizePath,
pathAndAncestors,
projectDirMatches,
projectDirsOf,
projectsRoot,
sameProjectPath,
} from "./project-dirs.js";
import {

View File

@@ -5,7 +5,7 @@
*/
import type { SessionInfo } from "@agentclientprotocol/sdk/unstable";
import { withAirMeta } from "../../air-extension.js";
import { withAirMeta } from "../air-extension.js";
import { deriveActivity, selectCost, type OwnSessionState } from "./activity.js";
import type { LiveRecord } from "./live-registry.js";
import type { IndexRow } from "./session-index.js";

68
src/session-status.ts Normal file
View File

@@ -0,0 +1,68 @@
/**
* `session/status` (the unstable "Session status" RFD) of a session that the
* connection does not hold: `saved` or unknown. Each core answers `open`
* itself for the sessions it holds.
*/
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import { isSessionId, projectsRoot } from "./paths.js";
/** What a core advertises as `status` in its session capabilities. */
export const SESSION_STATUS_CAPABILITY = {};
/**
* `saved` when a project directory holds a transcript file of `sessionId`
* that is not empty. Its content is not read, so a transcript without a
* conversation is `saved` too, and its resume fails with `-32002`. Fails with
* `-32002` and `data.sessionId` otherwise:
*
* - an id that is not a UUID, such as a subagent's session id;
* - an id without a transcript: one never used, a session never prompted
* (Claude Code writes the transcript at the first prompt), and a session
* whose transcripts were removed.
*
* The request names no `cwd`, so a transcript of any project counts: a
* resume with another `cwd` can still fail with `-32002`.
*
* It lists the project directories and stats the one file name in each. No
* query starts and no session is loaded. A directory or file that cannot be
* read for another reason than being absent fails the request: it does not
* make the session unknown.
*/
export async function savedSessionStatus(sessionId: string): Promise<{ status: "saved" }> {
if (!isSessionId(sessionId) || !(await hasTranscript(sessionId))) {
throw RequestError.sessionNotFound(sessionId);
}
return { status: "saved" };
}
async function hasTranscript(sessionId: string): Promise<boolean> {
const root = projectsRoot();
let projects: string[];
try {
projects = await fs.readdir(root);
} catch (error) {
// No session was ever stored.
if (isAbsent(error)) return false;
throw error;
}
const found = await Promise.all(
projects.map((project) =>
fs.stat(path.join(root, project, `${sessionId}.jsonl`)).then(
(stats) => stats.isFile() && stats.size > 0,
(error: unknown) => {
// No such transcript, or `project` is a file.
if (isAbsent(error)) return false;
throw error;
},
),
),
);
return found.includes(true);
}
function isAbsent(error: unknown): boolean {
const code = (error as { code?: unknown }).code;
return code === "ENOENT" || code === "ENOTDIR";
}

View File

@@ -1,7 +1,7 @@
/**
* The `<task-notification>` inputs that Claude Code writes to a transcript
* when a background task stops. The replay of a session (`v1/acp-agent.ts`) and
* the session index (`v1/session-index/background-tasks.ts`) read them here, so
* the session index (`session-index/background-tasks.ts`) read them here, so
* both read the same fields.
*/

View File

@@ -1,4 +1,4 @@
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Check the file.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_thought_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"I will edit it.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_message_chunk"}}}

View File

@@ -1,3 +1,3 @@
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"newSession","payload":{"configOptions":[{"category":"mode","currentValue":"auto","description":"Session permission mode","id":"mode","name":"Mode","options":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","name":"Manual","value":"default"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","id":"model","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"modes":{"availableModes":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","id":"default","name":"Manual"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","id":"acceptEdits","name":"Accept edits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","id":"plan","name":"Plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","id":"auto","name":"Auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","id":"bypassPermissions","name":"Bypass permissions"}],"currentModeId":"auto"},"sessionId":"<session>"}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"/goal Ship the feature","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"msg_r1","sessionUpdate":"agent_thought"}}}

View File

@@ -1,2 +1,2 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -1,4 +1,4 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}

View File

@@ -28,6 +28,12 @@
* - The `initialize` response also advertises `agentCapabilities.auth.status`,
* which origin/main did not: the agent now answers `auth/status` and sends
* `auth/status_update`.
* - The `initialize` response also advertises
* `agentCapabilities.sessionCapabilities.status`, which origin/main did
* not: the agent now serves `session/status`.
* - The `initialize` response also advertises
* `agentCapabilities.sessionCapabilities.title`, which origin/main did not:
* the agent now serves `session/set_title`.
* - A SandboxNetworkAccess tool call is settled by a `tool_call_update` with
* only its final `status` once its permission request is answered. The
* callback runs nothing, so origin/main never settled the call (see
@@ -229,19 +235,17 @@ function isMeta(key: string): boolean {
return key.startsWith("_meta.");
}
/** `record` without the session capability `key`, when it is an `initialize` response. */
function withoutSessionCapability(record: Recorded, key: string): Recorded {
/** `record` without the session capabilities `keys`, when it is an `initialize` response. */
function withoutSessionCapabilities(record: Recorded, keys: readonly string[]): Recorded {
if (record.kind !== "initialize") return record;
const payload = record.payload as Json;
const agent = payload.agentCapabilities as Json | undefined;
const session = agent?.sessionCapabilities as Json | undefined;
if (!session || !(key in session)) return record;
if (!session || !keys.some((key) => key in session)) return record;
const remaining = keys.reduce((rest, key) => without(rest, key), session);
return {
...record,
payload: {
...payload,
agentCapabilities: { ...agent, sessionCapabilities: without(session, key) },
},
payload: { ...payload, agentCapabilities: { ...agent, sessionCapabilities: remaining } },
} as Recorded;
}
@@ -264,14 +268,18 @@ function withoutAuthStatusCapability(record: Recorded): Recorded {
*/
export function compareWithBaseline(baseline: Recorded[], traffic: Recorded[]): string[] {
const expected = baseline
.map((record) => withoutSessionCapability(withoutAirOnlyKeys(record) as Recorded, "subagents"))
.map((record) =>
withoutSessionCapabilities(withoutAirOnlyKeys(record) as Recorded, ["subagents"]),
)
.filter((record) => {
const update = updateOf(record);
return !(update?.sessionUpdate === "session_info_update" && Object.keys(update).length === 1);
});
const current = withoutSandboxNetworkSettlements(
traffic.map((record) =>
withoutAuthStatusCapability(withoutSessionCapability(record, "systemPrompt")),
withoutAuthStatusCapability(
withoutSessionCapabilities(record, ["systemPrompt", "status", "title"]),
),
),
);
const violations: string[] = [];

View File

@@ -2,28 +2,28 @@ import { describe, expect, it } from "vitest";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { deriveActivity, selectCost, type ActivityState } from "../../v1/session-index/activity.js";
import { LiveSessionRegistry, type LiveRecord } from "../../v1/session-index/live-registry.js";
import { deriveActivity, selectCost, type ActivityState } from "../session-index/activity.js";
import { LiveSessionRegistry, type LiveRecord } from "../session-index/live-registry.js";
import { isSessionId } from "../paths.js";
import {
encodeProjectPath,
isSessionId,
pathAndAncestors,
projectDirMatches,
} from "../../v1/session-index/project-dirs.js";
} from "../session-index/project-dirs.js";
import {
decodeListCursor,
encodeListCursor,
parseListOptions,
parseRenameSessionRequest,
type ListScope,
} from "../../v1/session-index/service.js";
} from "../session-index/service.js";
import {
effectiveTitle,
isArchivedTitle,
storedTitle,
titleRecords,
visibleTitle,
} from "../../v1/session-index/archive-title.js";
} from "../session-index/archive-title.js";
import {
agentWorktreeOf,
continuedInSessionId,
@@ -33,11 +33,11 @@ import {
sdkTitles,
transcriptAgentName,
type TranscriptFacts,
} from "../../v1/session-index/transcript-scan.js";
import { firstPrompt } from "../../v1/session-index/first-prompt.js";
import { DirListings, statFiles } from "../../v1/session-index/dir-listing.js";
import { SessionIndex, type IndexRow } from "../../v1/session-index/session-index.js";
import { changeSignature, sessionInfoOf } from "../../v1/session-index/session-info.js";
} from "../session-index/transcript-scan.js";
import { firstPrompt } from "../session-index/first-prompt.js";
import { DirListings, statFiles } from "../session-index/dir-listing.js";
import { SessionIndex, type IndexRow } from "../session-index/session-index.js";
import { changeSignature, sessionInfoOf } from "../session-index/session-info.js";
const SESSION = "11111111-1111-4111-8111-111111111111";
const lines = (...entries: object[]) => entries.map((entry) => JSON.stringify(entry)).join("\n");

View File

@@ -77,7 +77,7 @@ import {
type SDKControlGetUsageResponse,
} from "@anthropic-ai/claude-agent-sdk";
import { createHash, randomUUID } from "crypto";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {
@@ -14953,6 +14953,79 @@ describe("session/fork", () => {
});
});
describe("session/status", () => {
it("answers open for the sessions it holds, and the others from their stored transcripts", async () => {
const configDir = await mkdtemp(path.join(os.tmpdir(), "session-status-"));
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
try {
const agent = new ClaudeAcpAgent({ sessionUpdate: async () => {} } as unknown as AcpClient, {
log: () => {},
error: () => {},
});
const { agentCapabilities } = await agent.initialize({
protocolVersion: 1,
clientCapabilities: {},
});
expect(agentCapabilities?.sessionCapabilities?.status).toEqual({});
const status = (sessionId: string) => agent.sessionStatus({ sessionId });
const unknown = (sessionId: string) => ({ code: -32002, data: { sessionId } });
const sessionId = randomUUID();
agent.sessions[sessionId] = {} as ClaudeAcpAgent["sessions"][string];
await expect(status(sessionId)).resolves.toEqual({ status: "open" });
// A held session whose query ended stays open: a load returns it as it is.
agent.sessions[sessionId]!.queryClosed = true;
await expect(status(sessionId)).resolves.toEqual({ status: "open" });
// A subagent's session id, and a name that every object inherits.
await expect(status("agent-1")).rejects.toMatchObject(unknown("agent-1"));
await expect(status("constructor")).rejects.toMatchObject(unknown("constructor"));
// Closed, and never prompted: no projects directory exists yet.
delete agent.sessions[sessionId];
await expect(status(sessionId)).rejects.toMatchObject(unknown(sessionId));
// A transcript of any project counts, next to a project without one.
await mkdir(path.join(configDir, "projects", "-other"), { recursive: true });
const project = path.join(configDir, "projects", "-project");
// A directory of that name is no transcript.
await mkdir(path.join(project, `${sessionId}.jsonl`), { recursive: true });
await expect(status(sessionId)).rejects.toMatchObject(unknown(sessionId));
await rm(path.join(project, `${sessionId}.jsonl`), { recursive: true });
await writeFile(path.join(project, `${sessionId}.jsonl`), "{}\n");
await expect(status(sessionId)).resolves.toEqual({ status: "saved" });
// The request loaded nothing.
expect(agent.sessions[sessionId]).toBeUndefined();
} finally {
vi.unstubAllEnvs();
await rm(configDir, { recursive: true, force: true });
}
});
// Not on Windows, where `chmod` does not take the read permission away.
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
"fails, not -32002, when a project directory cannot be read",
async () => {
const configDir = await mkdtemp(path.join(os.tmpdir(), "session-status-"));
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
const project = path.join(configDir, "projects", "-project");
await mkdir(project, { recursive: true });
await chmod(project, 0o000);
try {
const agent = new ClaudeAcpAgent(
{ sessionUpdate: async () => {} } as unknown as AcpClient,
{ log: () => {}, error: () => {} },
);
await expect(agent.sessionStatus({ sessionId: randomUUID() })).rejects.toMatchObject({
code: "EACCES",
});
} finally {
await chmod(project, 0o700);
vi.unstubAllEnvs();
await rm(configDir, { recursive: true, force: true });
}
},
);
});
describe("session/close", () => {
function createMockAgent() {
const mockClient = {

View File

@@ -11,11 +11,11 @@ import * as fsSync from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { SessionInfo } from "@agentclientprotocol/sdk/unstable";
import type { ActivityState } from "../../v1/session-index/activity.js";
import { endedTaskId, searchBackgroundTasks } from "../../v1/session-index/background-tasks.js";
import { encodeProjectPath } from "../../v1/session-index/project-dirs.js";
import { SessionIndex } from "../../v1/session-index/session-index.js";
import { SessionIndexService } from "../../v1/session-index/service.js";
import type { ActivityState } from "../../session-index/activity.js";
import { endedTaskId, searchBackgroundTasks } from "../../session-index/background-tasks.js";
import { encodeProjectPath } from "../../session-index/project-dirs.js";
import { SessionIndex } from "../../session-index/session-index.js";
import { SessionIndexService } from "../../session-index/service.js";
let configDir: string;
let workspace: string;

View File

@@ -6,7 +6,7 @@ import * as path from "node:path";
import * as os from "node:os";
import { messageIdForGrouping } from "../../sdk-to-acp.js";
import { forkSession } from "../../fork-session.js";
import { encodeProjectPath } from "../../v1/session-index/project-dirs.js";
import { encodeProjectPath } from "../../session-index/project-dirs.js";
// Use the real SDK against temporary transcripts, without starting Claude Code.
let root: string;

View File

@@ -23,7 +23,7 @@ import {
type InitializeResponse,
type SessionInfo,
} from "@agentclientprotocol/sdk/unstable";
import { encodeProjectPath } from "../../v1/session-index/project-dirs.js";
import { encodeProjectPath } from "../../session-index/project-dirs.js";
import {
archiveInsteadOfDelete,
archiveTo,
@@ -31,16 +31,16 @@ import {
parseBatchGetSessionsRequest,
SessionIndexService,
writeCustomTitleSidecar,
} from "../../v1/session-index/service.js";
import { scanTranscriptFile } from "../../v1/session-index/transcript-scan.js";
import { repositoryWorktrees } from "../../v1/session-index/worktrees.js";
} from "../../session-index/service.js";
import { scanTranscriptFile } from "../../session-index/transcript-scan.js";
import { repositoryWorktrees } from "../../session-index/worktrees.js";
import {
ListSubscriptions,
MAX_SUBSCRIPTIONS,
parseListSubscribeRequest,
parseListUnsubscribeRequest,
type ListChanges,
} from "../../v1/session-index/list-subscriptions.js";
} from "../../session-index/list-subscriptions.js";
import { SessionTitles } from "../../session-titles.js";
import { initializeClient } from "../helpers.js";
import {
@@ -1350,6 +1350,48 @@ describe("_session/rename", () => {
});
});
describe("session/set_title", () => {
it("titles a stored session for a client without the session index", async () => {
const session = await writeTranscript({});
const { agent } = createAgent();
await initializeClient(agent, {});
expect(
await agent.setSessionTitle({ sessionId: session.id, title: " Investigate\n OAuth " }),
).toEqual({ title: "Investigate OAuth" });
expect(await lastRecords(session.file)).toEqual(titleRecords(session.id, "Investigate OAuth"));
const page = await agent.listSessions({ cwd: workspace });
expect(page.sessions[0]!.title).toBe("Investigate OAuth");
});
it.each([
["an AIR client with sessionIndex", air("sessionIndex"), "Renamed"],
["an AIR client without sessionIndex", air(), "Renamed"],
["a client that is not AIR", {}, "[archived] Renamed"],
])(
"keeps an archived session archived and answers the title %s shows",
async (_client, capabilities, shown) => {
const session = await writeTranscript({ trailer: titleRecords("", "[archived] Fix it") });
const { agent } = createAgent();
await initializeClient(agent, capabilities);
expect(await agent.setSessionTitle({ sessionId: session.id, title: "Renamed" })).toEqual({
title: shown,
});
expect(await lastRecords(session.file)).toEqual(
titleRecords(session.id, "[archived] Renamed"),
);
},
);
it.each([
["an unknown session", () => ({ sessionId: randomUUID(), title: "x" }), -32002],
["an empty title", () => ({ sessionId: randomUUID(), title: " \n " }), -32602],
])("refuses %s", async (_case, params, code) => {
const { agent } = createAgent();
await initializeClient(agent, {});
await expect(agent.setSessionTitle(params())).rejects.toMatchObject({ code });
});
});
describe("_session/archive and _session/unarchive", () => {
it("append AIR's archive title records, are idempotent and keep updatedAt", async () => {
const session = await writeTranscript({ prompt: "Fix the\nparser" });
@@ -1854,7 +1896,7 @@ describe("_session/list/subscribe", () => {
});
it("clears the failed turn of a session that runs here when a new turn starts", async () => {
const { noteSessionState } = await import("../../v1/session-index/connection.js");
const { noteSessionState } = await import("../../v1/session-index.js");
const session = { lastTurnFailed: true } as any;
noteSessionState(session, "idle", "running");
expect(session.lastTurnFailed).toBe(false);

View File

@@ -5,7 +5,7 @@
* schema, so a malformed v2 message fails the test.
*/
import { randomUUID } from "node:crypto";
import { appendFile, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { appendFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -35,6 +35,7 @@ import {
v1SetSessionConfigOptionRequest,
v2ConfigOptions,
} from "../../v2/session-requests.js";
import { encodeProjectPath } from "../../session-index/project-dirs.js";
import { startupFailure } from "../helpers.js";
import packageJson from "../../../package.json" with { type: "json" };
@@ -108,6 +109,8 @@ const sdk = vi.hoisted(() => ({
accountInfo: async (): Promise<Record<string, unknown>> => ({}),
/** The query switches its model (`setModel`). */
setModel: async (_model?: string): Promise<void> => {},
/** Claude Code stores a session title (`rename_session`). */
renameSession: async (_title: string, _sessionId?: string): Promise<void> => {},
/** Set to serve Remote Control, as a query of a CLI that supports it does. */
enableRemoteControl: undefined as
((enabled: boolean, name?: string) => Promise<{ session_url?: string }>) | undefined,
@@ -142,6 +145,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
accountInfo: () => sdk.accountInfo(),
setMcpServers: (servers: Record<string, unknown>) => sdk.setMcpServers(servers),
setModel: (model?: string) => sdk.setModel(model),
renameSession: (title: string, sessionId?: string) => sdk.renameSession(title, sessionId),
...(sdk.enableRemoteControl ? { enableRemoteControl: sdk.enableRemoteControl } : {}),
});
return Object.assign(messages, controls);
@@ -394,6 +398,8 @@ describe("ACP protocol routing", () => {
fork: {},
additionalDirectories: {},
systemPrompt: { mode: { override: {} } },
status: {},
title: {},
});
expect(response.capabilities?.providers).toEqual({});
// Not `_session/steering`, which v1 advertises in the top-level `_meta`.
@@ -747,6 +753,236 @@ describe("ACP v2 sessions", () => {
});
});
describe("session/status", () => {
/** Stores a transcript of `sessionId`, as Claude Code does at the first prompt. */
async function storeTranscript(sessionId: string, content = "{}\n") {
const project = path.join(tempDir, "claude", "projects", "-project");
await mkdir(project, { recursive: true });
await writeFile(path.join(project, `${sessionId}.jsonl`), content);
}
const status = (agent: v2.ClientContext, sessionId: string) =>
agent.request(v2.methods.agent.session.status, { sessionId });
const unknown = (sessionId: string) => ({ code: -32002, data: { sessionId } });
it("answers from memory and the stored transcript, and starts or sends nothing", async () => {
const client = v2Client();
await client.app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
const neverUsed = randomUUID();
await expect(status(agent, neverUsed)).rejects.toMatchObject(unknown(neverUsed));
// Not a UUID: no file is looked up under such a name.
await expect(status(agent, "../settings")).rejects.toMatchObject(unknown("../settings"));
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
await client.authUpdate(1);
// The agent's post-setup work runs on a timer.
await new Promise((resolve) => setTimeout(resolve, 20));
const sent = client.sessionUpdates.length;
// The state as a `state_update` snapshot holds it, with the session's list.
expect(await status(agent, sessionId)).toEqual({
status: "open",
state: { state: "idle", background: [] },
});
await agent.request(v2.methods.agent.session.close, { sessionId });
// Claude Code writes the transcript at the first prompt: none is stored.
await expect(status(agent, sessionId)).rejects.toMatchObject(unknown(sessionId));
// An empty transcript holds no conversation to resume.
await storeTranscript(sessionId, "");
await expect(status(agent, sessionId)).rejects.toMatchObject(unknown(sessionId));
await storeTranscript(sessionId);
expect(await status(agent, sessionId)).toEqual({ status: "saved" });
expect(sdk.queryOptions).toHaveLength(1);
expect(client.sessionUpdates).toHaveLength(sent);
});
});
it("answers a session whose query ended without session/close from its transcript", async () => {
// Claude Code exits right after it started.
sdk.run = async function* () {};
const client = v2Client();
await client.app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
await vi.waitFor(() => expect(v2Session(sessionId)).toBeUndefined());
await expect(status(agent, sessionId)).rejects.toMatchObject(unknown(sessionId));
await storeTranscript(sessionId);
expect(await status(agent, sessionId)).toEqual({ status: "saved" });
await client.authUpdate(1);
});
});
it("answers saved while a resume still starts the session, without waiting for it", async () => {
const sessionId = randomUUID();
await storeTranscript(sessionId);
let started!: () => void;
const starting = new Promise<void>((resolve) => {
started = resolve;
});
const initialization = sdk.initializationResult;
sdk.initializationResult = async () => {
await starting;
return initialization();
};
const client = v2Client();
await client.app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
const resumed = agent.request(v2.methods.agent.session.resume, { sessionId, cwd });
await vi.waitFor(() => expect(sdk.queryOptions).toHaveLength(1));
expect(await status(agent, sessionId)).toEqual({ status: "saved" });
started();
await resumed;
expect(await status(agent, sessionId)).toMatchObject({ status: "open" });
await client.authUpdate(1);
});
});
it("reports a session that waits for a permission as requires_action", async () => {
let answer!: (response: v2.RequestPermissionResponse) => void;
scriptTurns(async function* (options) {
await options.canUseTool!("Bash", { command: "ls" }, {
signal: new AbortController().signal,
suggestions: [],
toolUseID: "toolu_ls",
requestId: randomUUID(),
} as Parameters<NonNullable<Options["canUseTool"]>>[2]);
yield result(options);
yield idleState(options);
});
const client = v2Client({
answerPermission: (() =>
new Promise<v2.RequestPermissionResponse>((resolve) => {
answer = resolve;
})) as unknown as () => v2.RequestPermissionResponse,
});
await client.app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
await agent.request(v2.methods.agent.session.prompt, { sessionId, prompt: text("go") });
await vi.waitFor(() => expect(client.permissionRequests).toHaveLength(1));
await vi.waitFor(() =>
expect(turnTrace(client.sessionUpdates).at(-1)).toBe("requires_action"),
);
expect(await agent.request(v2.methods.agent.session.status, { sessionId })).toEqual({
status: "open",
state: { state: "requires_action", background: [] },
});
answer({ outcome: { outcome: "selected", optionId: "allow-once" } });
await vi.waitFor(() => expect(turnTrace(client.sessionUpdates).at(-1)).toMatch(/^idle/));
await client.authUpdate(1);
});
// The status request added no state to the turn's.
expect(turnTrace(client.sessionUpdates)).toEqual([
"user_message",
"running",
"requires_action",
"running",
"idle end_turn",
]);
});
});
describe("session/set_title", () => {
it("titles an active session through Claude Code and publishes the stored title", async () => {
const renameSession = vi.fn(sdkDefaults.renameSession);
sdk.renameSession = renameSession;
const client = v2Client();
await client.app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
client.sessionUpdates.length = 0;
const response = await agent.request(v2.methods.agent.session.setTitle, {
sessionId,
title: " Investigate\n OAuth timeout ",
});
expect(response).toEqual({ title: "Investigate OAuth timeout" });
expect(renameSession).toHaveBeenCalledWith("Investigate OAuth timeout", sessionId);
expect(client.sessionUpdates).toEqual([
{
sessionId,
update: { sessionUpdate: "session_info_update", title: "Investigate OAuth timeout" },
},
]);
await client.authUpdate(1);
});
});
it("titles the query that a resume started while the rename ran", async () => {
let release!: () => void;
const renameSession = vi.fn(async (_title: string, _sessionId?: string) => {
if (renameSession.mock.calls.length === 1) {
await new Promise<void>((resolve) => (release = resolve));
}
});
sdk.renameSession = renameSession;
const client = v2Client();
await client.app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
const renamed = agent.request(v2.methods.agent.session.setTitle, {
sessionId,
title: "Mine",
});
await vi.waitFor(() => expect(renameSession).toHaveBeenCalledTimes(1));
await agent.request(v2.methods.agent.session.close, { sessionId });
await agent.request(v2.methods.agent.session.resume, { sessionId, cwd });
release();
expect(await renamed).toEqual({ title: "Mine" });
expect(renameSession.mock.calls).toEqual([
["Mine", sessionId],
["Mine", sessionId],
]);
await client.authUpdate(1);
});
});
it("titles a stored session that is not active in its transcript", async () => {
const sessionId = randomUUID();
const dir = path.join(tempDir, "claude", "projects", encodeProjectPath(cwd));
await mkdir(dir, { recursive: true });
const file = path.join(dir, `${sessionId}.jsonl`);
const prompt = { type: "user", sessionId, cwd, message: { role: "user", content: "hi" } };
await writeFile(file, JSON.stringify(prompt) + "\n");
const { app, authUpdate } = v2Client();
await app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
expect(
await agent.request(v2.methods.agent.session.setTitle, { sessionId, title: "Stored" }),
).toEqual({ title: "Stored" });
await authUpdate(1);
});
const records = (await readFile(file, "utf8"))
.trim()
.split("\n")
.slice(-2)
.map((line) => JSON.parse(line));
expect(records).toEqual([
{ type: "custom-title", customTitle: "Stored", sessionId },
{ type: "agent-name", agentName: "Stored", sessionId },
]);
});
it.each([
["an unknown session", { sessionId: randomUUID(), title: "Title" }, -32002],
["a title that is empty once normalized", { sessionId: randomUUID(), title: " \n " }, -32602],
])("refuses %s", async (_case, params, code) => {
const { app, authUpdate } = v2Client();
await app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
await expect(
agent.request(v2.methods.agent.session.setTitle, params),
).rejects.toMatchObject({ code });
await authUpdate(1);
});
});
});
it("offers a resumed Fable 5.2 session's model as `fable`, as a new session lists it", async () => {
// On resume, the CLI lists the transcript's Fable row under its id, with no `fable` row.
const row = (value: string, resolvedModel: string, displayName: string) => ({
@@ -5677,6 +5913,57 @@ describe("ACP v2 requests that subagents make", () => {
]);
});
it("refuses session/status of a subagent's session, which does not advertise it", async () => {
let finish!: () => void;
const finished = new Promise<void>((resolve) => {
finish = resolve;
});
scriptTurns(async function* (options) {
yield toolUseMessage(options, "toolu_agent-1", "Agent", {
description: "Explore the project",
prompt: "Look around",
subagent_type: "Explore",
});
yield taskStarted(options, "agent-1", { prompt: "Look around" });
await finished;
yield taskNotification(options, "agent-1", {});
yield result(options);
yield idleState(options);
});
const client = v2Client();
await client.app.connectWith(connectRouter(), async (agent) => {
await initializeV2(agent);
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
await agent.request(v2.methods.agent.session.prompt, { sessionId, prompt: text("hi") });
await vi.waitFor(() =>
expect(
client.sessionUpdates.some(
(notification) =>
notification.sessionId === "agent-1" &&
notification.update.sessionUpdate === "state_update",
),
).toBe(true),
);
await expect(
agent.request(v2.methods.agent.session.status, { sessionId: "agent-1" }),
).rejects.toMatchObject({
code: -32602,
message: expect.stringContaining("agent-1 is a subagent session"),
});
finish();
await vi.waitFor(() =>
expect(
client
.updates("state_update")
.some((update) => (update as { state: string }).state === "idle"),
).toBe(true),
);
await client.authUpdate(1);
});
});
it("sends a live child's configuration again after a replay's response, when the replay sent another", async () => {
scriptTurns(async function* (options) {
yield toolUseMessage(options, "toolu_agent-1", "Agent", {

View File

@@ -1012,6 +1012,30 @@ describe("ACP v2 core", () => {
});
});
it("stays idle at the echo of a model change's local command output", async () => {
const client = v2Client();
await client.connect(async (agent) => {
const { claude } = await openSession(agent);
// As the bundled CLI sends it after `setModel`, with no turn: no
// `command_lifecycle` frame, result or `session_state_changed` follows.
claude.send({
type: "user",
message: {
role: "user",
content:
"<local-command-stdout>Set model to `haiku (claude-haiku-5-5)`</local-command-stdout>",
},
parent_tool_use_id: null,
uuid: randomUUID(),
session_id: "session",
timestamp: new Date().toISOString(),
isReplay: true,
});
await new Promise((resolve) => setTimeout(resolve, 20));
expect(client.states()).toEqual([]);
});
});
it("forwards the result of a local command, which streamed no text", async () => {
const client = v2Client();
await client.connect(async (agent) => {
@@ -2119,6 +2143,44 @@ describe("ACP v2 core", () => {
});
});
it.each([
["a notice when the turn goes on to end", { result: "" }, "end_turn", 1],
["a notice once when the result repeats its text", {}, "end_turn", 1],
["the idle's error when the turn fails", { is_error: true }, "error", 0],
])(
"reports Claude Code's message for a failed API request as %s, not as Claude's text",
async (_, fields, stopReason, notices) => {
const text = "API Error: Connection lost mid-response. The response above may be incomplete.";
const client = v2Client();
await client.connect(async (agent) => {
const { claude } = await openSession(agent);
claude.send(cycleStart());
claude.send({
...assistant("msg_api_error", [{ type: "text", text }]),
message: {
id: "msg_api_error",
type: "message",
role: "assistant",
model: "<synthetic>",
content: [{ type: "text", text }],
usage: {},
},
error: "server_error",
});
claude.send(result({ result: text, ...fields }));
await vi.waitFor(() => expect(client.states()).toEqual(["running", "idle"]));
});
expect(
client.sessionUpdates.filter((update) => update.sessionUpdate === "agent_message_chunk"),
).toEqual([]);
expect(client.sessionUpdates.filter((update) => update.sessionUpdate === "notice")).toEqual(
Array(notices).fill({ sessionUpdate: "notice", severity: "error", title: text }),
);
expect(client.sessionUpdates.at(-1)).toMatchObject({ state: "idle", stopReason });
},
);
it.each([
[
"an error at the turn limit",

View File

@@ -1,15 +1,12 @@
import {
agent as acpAgent,
AgentApp,
AgentContext,
AuthenticateRequest,
AuthMethod,
AuthStatusResponse,
AuthStatusUpdateNotification,
AvailableCommand,
CancelNotification,
ClientCapabilities,
CompleteElicitationNotification,
CreateElicitationRequest,
CreateElicitationResponse,
DisableProviderRequest,
@@ -36,7 +33,6 @@ import {
RequestError,
SetProviderRequest,
SetProviderResponse,
RequestPermissionRequest,
RequestPermissionResponse,
ResumeSessionRequest,
ResumeSessionResponse,
@@ -47,9 +43,13 @@ import {
SetSessionConfigOptionResponse,
SetSessionModeRequest,
SetSessionModeResponse,
SessionStatusRequest,
SessionStatusResponse,
CloseSessionRequest,
CloseSessionResponse,
DeleteSessionRequest,
SetSessionTitleRequest,
SetSessionTitleResponse,
DeleteSessionResponse,
WriteTextFileRequest,
WriteTextFileResponse,
@@ -95,6 +95,7 @@ import {
} from "./goal-extension.js";
import { checklistPlanUpdate, resetPlanUpdates, type SubagentChecklists } from "../plans.js";
import { PromptSuggestions } from "../prompt-suggestions.js";
import { savedSessionStatus, SESSION_STATUS_CAPABILITY } from "../session-status.js";
import { sanitizeTitle, SessionTitles } from "../session-titles.js";
import {
AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY,
@@ -134,16 +135,16 @@ import {
clientSupportsAirCapability,
withAirMeta,
} from "../air-extension.js";
import * as sessionIndex from "./session-index/connection.js";
import * as sessionIndex from "./session-index.js";
import type {
BatchGetSessionsRequest,
RenameSessionRequest,
SessionIdRequest,
} from "./session-index/service.js";
} from "../session-index/service.js";
import type {
ListSubscribeRequest,
ListUnsubscribeRequest,
} from "./session-index/list-subscriptions.js";
} from "../session-index/list-subscriptions.js";
import {
AsyncTaskRuntime,
backgroundBashTaskFromToolResult,
@@ -305,7 +306,7 @@ import {
} from "../session-usage.js";
import { previewPatchContent } from "../diff.js";
import { backgroundedBashToolCall, withRunningTerminal } from "../tool-calls/background.js";
import { ChangedMetaFilter } from "./tool-calls/changed-meta-filter.js";
import { ChangedMetaClient, ClientConnection } from "./client-connection.js";
import { ToolCallFieldTracker } from "../tool-calls/field-tracker.js";
import { ClientCapabilities as ToolCallClientCapabilities } from "../tool-calls/client-capabilities.js";
import {
@@ -1751,116 +1752,6 @@ const LIMIT_FAILURE_CATEGORY = {
error_max_structured_output_retries: "provider_error",
} as const satisfies Record<string, ClaudeFailureKind>;
/**
* Bridges {@link AcpClient} to the connection-scoped {@link AgentContext}
* exposed by `AgentApp.connect(...)` as `connection.client`. The peer handle is
* valid for the entire connection lifetime, so it is captured once at
* construction.
*/
class ClientConnection implements AcpClient {
constructor(private readonly ctx: AgentContext) {}
sessionUpdate(params: AcpSessionNotification): Promise<void> {
return this.ctx.notify(methods.client.session.update, asSdkSessionNotification(params));
}
requestPermission(
params: RequestPermissionRequest,
signal?: AbortSignal,
): Promise<RequestPermissionResponse> {
return this.ctx.request(methods.client.session.requestPermission, params, {
cancellationSignal: signal,
stopWaitingOnCancel: true,
});
}
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
return this.ctx.request(methods.client.fs.readTextFile, params);
}
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
return this.ctx.request(methods.client.fs.writeTextFile, params);
}
createElicitation(
params: CreateElicitationRequest,
signal?: AbortSignal,
): Promise<CreateElicitationResponse> {
return this.ctx.request(methods.client.elicitation.create, params, {
cancellationSignal: signal,
stopWaitingOnCancel: true,
});
}
completeElicitation(params: CompleteElicitationNotification): Promise<void> {
return this.ctx.notify(methods.client.elicitation.complete, params);
}
authStatusUpdate(params: AuthStatusUpdateNotification): Promise<void> {
return this.ctx.notify(methods.client.auth.statusUpdate, params);
}
extNotification(method: string, params: Record<string, unknown>): Promise<void> {
return this.ctx.notify(method, params);
}
}
/**
* The client that the agent talks to. For an AIR client, every session update
* passes the {@link ChangedMetaFilter} last, after the native subagent routing,
* so a `tool_call_update` carries only the `_meta` keys that changed. ACP
* merges only the top-level tool call fields, not the `_meta` keys. So another
* client gets the full `_meta` on each update.
*/
class ChangedMetaClient implements AcpClient {
private readonly filter = new ChangedMetaFilter();
constructor(
private readonly inner: AcpClient,
private readonly airClient: () => boolean,
) {}
async sessionUpdate(params: AcpSessionNotification): Promise<void> {
if (!this.airClient()) return this.inner.sessionUpdate(params);
const update = this.filter.apply(params.update as SessionNotification["update"]);
if (update) await this.inner.sessionUpdate({ ...params, update } as AcpSessionNotification);
}
requestPermission(
params: AcpPermissionRequest,
signal?: AbortSignal,
): Promise<RequestPermissionResponse> {
return this.inner.requestPermission(params, signal);
}
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
return this.inner.readTextFile(params);
}
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
return this.inner.writeTextFile(params);
}
createElicitation(
params: CreateElicitationRequest,
signal?: AbortSignal,
): Promise<CreateElicitationResponse> {
return this.inner.createElicitation(params, signal);
}
completeElicitation(params: CompleteElicitationNotification): Promise<void> {
return this.inner.completeElicitation(params);
}
authStatusUpdate(params: AuthStatusUpdateNotification): Promise<void> {
return this.inner.authStatusUpdate(params);
}
extNotification(method: string, params: Record<string, unknown>): Promise<void> {
return this.inner.extNotification(method, params);
}
}
function raceWithAbort<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {
return new Promise<T>((resolve, reject) => {
const cleanup = () => signal.removeEventListener("abort", onAbort);
@@ -2134,7 +2025,10 @@ export class ClaudeAcpAgent {
fork: {},
list: {},
resume: {},
status: SESSION_STATUS_CAPABILITY,
systemPrompt: SYSTEM_PROMPT_CAPABILITY,
// `session/set_title`.
title: {},
...(this.toolCallCapabilities.air.client ? { subagents: {} } : {}),
};
@@ -7973,6 +7867,24 @@ export class ClaudeAcpAgent {
return {};
}
/**
* `session/status`: `open` for a session that this connection holds. A held
* session whose query ended (`queryClosed`) is `open` too: a `session/load`
* with the same params returns it as it is, and its prompts fail with what
* happened. Every other session is `saved` or unknown by its stored
* transcript (see `session-status.ts`). A subagent's session is unknown,
* as for the other session requests.
*/
async sessionStatus({ sessionId }: SessionStatusRequest): Promise<SessionStatusResponse> {
if (Object.hasOwn(this.sessions, sessionId)) return { status: "open" };
return savedSessionStatus(sessionId);
}
/** See {@link sessionIndex.SessionIndexConnection.setTitle}. */
setSessionTitle(params: SetSessionTitleRequest): Promise<SetSessionTitleResponse> {
return this.sessionIndex.setTitle(params);
}
/** Per client: see {@link sessionIndex.SessionIndexConnection.deleteSession}. */
async deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
return this.sessionIndex.deleteSession(params);
@@ -11246,8 +11158,10 @@ export function v1AgentApp(
)
.onRequest(methods.agent.session.list, (ctx) => agent.listSessions(ctx.params))
.onRequest(methods.agent.session.delete, (ctx) => agent.deleteSession(ctx.params))
.onRequest(methods.agent.session.setTitle, (ctx) => agent.setSessionTitle(ctx.params))
.onRequest(methods.agent.session.resume, (ctx) => agent.resumeSession(ctx.params))
.onRequest(methods.agent.session.close, (ctx) => agent.closeSession(ctx.params))
.onRequest(methods.agent.session.status, (ctx) => agent.sessionStatus(ctx.params))
.onRequest(methods.agent.session.setMode, (ctx) => agent.setSessionMode(ctx.params))
.onRequest(methods.agent.session.setConfigOption, (ctx) =>
agent.setSessionConfigOption(ctx.params),

129
src/v1/client-connection.ts Normal file
View File

@@ -0,0 +1,129 @@
import {
AgentContext,
AuthStatusUpdateNotification,
CompleteElicitationNotification,
CreateElicitationRequest,
CreateElicitationResponse,
methods,
ReadTextFileRequest,
ReadTextFileResponse,
RequestPermissionRequest,
RequestPermissionResponse,
SessionNotification,
WriteTextFileRequest,
WriteTextFileResponse,
} from "@agentclientprotocol/sdk/unstable";
import type { AcpClient } from "../acp-client.js";
import { AcpSessionNotification, asSdkSessionNotification } from "../acp-subagents.js";
import { ChangedMetaFilter } from "./tool-calls/changed-meta-filter.js";
import type { AcpPermissionRequest } from "../permissions/presentation.js";
/**
* Bridges {@link AcpClient} to the connection-scoped {@link AgentContext}
* exposed by `AgentApp.connect(...)` as `connection.client`. The peer handle is
* valid for the entire connection lifetime, so it is captured once at
* construction.
*/
export class ClientConnection implements AcpClient {
constructor(private readonly ctx: AgentContext) {}
sessionUpdate(params: AcpSessionNotification): Promise<void> {
return this.ctx.notify(methods.client.session.update, asSdkSessionNotification(params));
}
requestPermission(
params: RequestPermissionRequest,
signal?: AbortSignal,
): Promise<RequestPermissionResponse> {
return this.ctx.request(methods.client.session.requestPermission, params, {
cancellationSignal: signal,
stopWaitingOnCancel: true,
});
}
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
return this.ctx.request(methods.client.fs.readTextFile, params);
}
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
return this.ctx.request(methods.client.fs.writeTextFile, params);
}
createElicitation(
params: CreateElicitationRequest,
signal?: AbortSignal,
): Promise<CreateElicitationResponse> {
return this.ctx.request(methods.client.elicitation.create, params, {
cancellationSignal: signal,
stopWaitingOnCancel: true,
});
}
completeElicitation(params: CompleteElicitationNotification): Promise<void> {
return this.ctx.notify(methods.client.elicitation.complete, params);
}
authStatusUpdate(params: AuthStatusUpdateNotification): Promise<void> {
return this.ctx.notify(methods.client.auth.statusUpdate, params);
}
extNotification(method: string, params: Record<string, unknown>): Promise<void> {
return this.ctx.notify(method, params);
}
}
/**
* The client that the agent talks to. For an AIR client, every session update
* passes the {@link ChangedMetaFilter} last, after the native subagent routing,
* so a `tool_call_update` carries only the `_meta` keys that changed. ACP
* merges only the top-level tool call fields, not the `_meta` keys. So another
* client gets the full `_meta` on each update.
*/
export class ChangedMetaClient implements AcpClient {
private readonly filter = new ChangedMetaFilter();
constructor(
private readonly inner: AcpClient,
private readonly airClient: () => boolean,
) {}
async sessionUpdate(params: AcpSessionNotification): Promise<void> {
if (!this.airClient()) return this.inner.sessionUpdate(params);
const update = this.filter.apply(params.update as SessionNotification["update"]);
if (update) await this.inner.sessionUpdate({ ...params, update } as AcpSessionNotification);
}
requestPermission(
params: AcpPermissionRequest,
signal?: AbortSignal,
): Promise<RequestPermissionResponse> {
return this.inner.requestPermission(params, signal);
}
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
return this.inner.readTextFile(params);
}
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
return this.inner.writeTextFile(params);
}
createElicitation(
params: CreateElicitationRequest,
signal?: AbortSignal,
): Promise<CreateElicitationResponse> {
return this.inner.createElicitation(params, signal);
}
completeElicitation(params: CompleteElicitationNotification): Promise<void> {
return this.inner.completeElicitation(params);
}
authStatusUpdate(params: AuthStatusUpdateNotification): Promise<void> {
return this.inner.authStatusUpdate(params);
}
extNotification(method: string, params: Record<string, unknown>): Promise<void> {
return this.inner.extNotification(method, params);
}
}

View File

@@ -15,11 +15,12 @@ import {
type InitializeRequest,
type ListSessionsRequest,
type ListSessionsResponse,
type SetSessionTitleRequest,
type SetSessionTitleResponse,
} from "@agentclientprotocol/sdk/unstable";
import type { Query } from "@anthropic-ai/claude-agent-sdk";
import type { Session } from "../acp-agent.js";
import type { AcpClient } from "../../acp-client.js";
import type { Logger } from "../../logger.js";
import type { Session } from "./acp-agent.js";
import type { AcpClient } from "../acp-client.js";
import type { Logger } from "../logger.js";
import {
AIR_SESSION_ARCHIVE_CAPABILITY,
AIR_SESSION_BATCH_GET_CAPABILITY,
@@ -29,9 +30,10 @@ import {
AIR_SESSION_WORKTREES_CAPABILITY,
clientSupportsAirCapability,
withAirMeta,
} from "../../air-extension.js";
import type { OwnSessionState } from "./activity.js";
import { isArchivedTitle, storedTitle, visibleTitle } from "./archive-title.js";
} from "../air-extension.js";
import type { OwnSessionState } from "../session-index/activity.js";
import { isArchivedTitle, visibleTitle } from "../session-index/archive-title.js";
import { renameSession, retitleSession, type RetitledSession } from "../session-index/rename.js";
import {
LIST_CHANGES_METHOD,
LIST_SUBSCRIBE_METHOD,
@@ -41,12 +43,11 @@ import {
type ListSubscribeRequest,
type ListSubscribeResponse,
type ListUnsubscribeRequest,
} from "./list-subscriptions.js";
} from "../session-index/list-subscriptions.js";
import {
archiveInsteadOfDelete,
deleteSessionIfStored,
archiveTo,
renameTo,
parseRenameSessionRequest,
parseSessionIdRequest,
parseBatchGetSessionsRequest,
@@ -55,13 +56,12 @@ import {
SESSION_RENAME_METHOD,
SESSION_UNARCHIVE_METHOD,
SessionIndexService,
transcriptWriter,
type RenameSessionRequest,
type RetitleOptions,
type SessionIdRequest,
type TitleChange,
type BatchGetSessionsRequest,
type BatchGetSessionsResponse,
} from "./service.js";
} from "../session-index/service.js";
/** What the session index keeps on each `Session`. */
export type SessionIndexFields = {
@@ -115,9 +115,9 @@ export class SessionIndexConnection {
* exactly as before.
*/
service?: SessionIndexService;
/** The archive behind `session/delete` of an AIR client without
* `sessionIndex`: the same writes, without a list or a watcher. */
private archiver?: SessionIndexService;
/** The transcript writes of a client without `sessionIndex` (see
* {@link transcriptWriter}). */
private transcripts?: SessionIndexService;
/** When the query of a session failed here, by session id. */
private readonly failedQueries = new Map<string, number>();
@@ -250,24 +250,34 @@ export class SessionIndexConnection {
return sessions.filter((session) => !isArchivedTitle(session.customTitle));
}
/** `_session/rename`: names a session; no generated title replaces it.
* An archived session stays archived. */
/** `_session/rename` of a `sessionIndex` client (see {@link renameSession}). */
async rename(request: RenameSessionRequest): Promise<EmptyResponse> {
const index = this.requireService(SESSION_RENAME_METHOD);
const { sessionId } = request;
// As the CLI keeps it, and never with the archive prefix: a rename does
// not change the archive state.
const title = storedTitle(request.title, false, sessionId);
// The title as stored: an archived one is cut with its prefix.
const shown = (stored: string | undefined) =>
stored === undefined ? title : visibleTitle(stored);
await this.retitle(index, sessionId, renameTo(title), shown, {
mayBeUnwritten: true,
sidecar: "always",
});
await this.renameWith(this.requireService(SESSION_RENAME_METHOD), request);
return {};
}
/** `session/set_title`, for every client: the rename of `_session/rename`,
* without the session index of a client that did not ask for it. */
async setTitle(request: SetSessionTitleRequest): Promise<SetSessionTitleResponse> {
const title = await this.renameWith(
this.service ?? this.writer(),
parseRenameSessionRequest(request),
);
return { title };
}
private renameWith(index: SessionIndexService, request: RenameSessionRequest): Promise<string> {
return renameSession(index, request, this.loaded(request.sessionId), (stored) =>
this.clientTitle(stored),
);
}
/** The session loaded here, as a rename reaches it. */
private loaded(sessionId: string): RetitledSession | undefined {
const session = this.host.agent.sessions[sessionId];
return session && { session, titles: session.titles };
}
/** `_session/archive`: hides a session from the default list and stops
* it. Idempotent; the session need not be loaded. A session loaded here
* is interrupted, retitled through its CLI while that still runs, then
@@ -279,7 +289,7 @@ export class SessionIndexConnection {
// No turn goes on after the archive: the interrupt goes to the CLI ahead
// of the rename.
if (loaded) await this.host.interruptSession(sessionId);
await this.retitle(index, sessionId, archiveTo(true), undefined, {
await retitleSession(index, sessionId, archiveTo(true), undefined, this.loaded(sessionId), {
mayBeUnwritten: this.isUnwrittenSession(sessionId),
sidecar: "existing",
});
@@ -308,7 +318,7 @@ export class SessionIndexConnection {
async unarchive(params: SessionIdRequest): Promise<EmptyResponse> {
const index = this.requireService(SESSION_UNARCHIVE_METHOD);
const { sessionId } = params;
await this.retitle(index, sessionId, archiveTo(false), undefined, {
await retitleSession(index, sessionId, archiveTo(false), undefined, this.loaded(sessionId), {
mayBeUnwritten: this.isUnwrittenSession(sessionId),
sidecar: "existing",
});
@@ -316,58 +326,6 @@ export class SessionIndexConnection {
return {};
}
/**
* Retitles a session (see {@link SessionIndexService.retitle}). A session
* this connection runs is titled through its CLI (`rename_session`). A
* loaded session's title state takes the change as a client title, so no
* generated title replaces it; `publish` is the title it then publishes.
*/
private async retitle(
index: SessionIndexService,
sessionId: string,
change: TitleChange,
publish: Parameters<Session["titles"]["setExplicitTitle"]>[0],
options: Pick<RetitleOptions, "mayBeUnwritten" | "sidecar">,
): Promise<void> {
const session = this.host.agent.sessions[sessionId];
// Decided once a title generation in flight has ended: the query may
// have closed meanwhile.
const persist = () => {
const query = session?.query as
| (Query & { renameSession?: (title: string, sessionId?: string) => Promise<void> })
| undefined;
// A closed session's CLI is gone here: only a running query titles it.
const running = session !== undefined && !session.queryClosed;
if (running && typeof query?.renameSession !== "function") {
// Its CLI holds the title and would write it back over ours.
throw RequestError.invalidRequest(
{ sessionId },
"The Claude Code CLI of this session cannot change its title while it runs.",
);
}
const live = running
? {
cwd: session.cwd,
rename: (title: string) => query!.renameSession!(title, sessionId),
stored: () => session.titles.storedTitle,
remember: (title: string) => session.titles.rememberStoredTitle(title),
shown: session.titles.shownTitle,
}
: undefined;
return index.retitle(sessionId, change, {
...options,
// Without its CLI, a session needs its transcript.
mayBeUnwritten: live !== undefined && options.mayBeUnwritten,
live,
});
};
if (session) {
await session.titles.setExplicitTitle(publish, persist);
} else {
await persist();
}
}
/**
* `session/delete`:
* - A `sessionIndex` client deletes for real: every transcript, whoever
@@ -388,19 +346,19 @@ export class SessionIndexConnection {
if (this.service) {
await this.service.delete(params.sessionId, loaded);
} else if (this.host.isAirClient()) {
await archiveInsteadOfDelete(params.sessionId, this.airArchiver());
await archiveInsteadOfDelete(params.sessionId, this.writer());
} else {
await deleteSessionIfStored(params.sessionId);
// After a `session/set_title` of the session in flight.
await this.writer().exclusive(params.sessionId, () =>
deleteSessionIfStored(params.sessionId),
);
}
return {};
}
private airArchiver(): SessionIndexService {
this.archiver ??= new SessionIndexService({
logError: (message, error) =>
this.host.agent.logger.error(`[session-index] ${message}:`, error),
});
return this.archiver;
private writer(): SessionIndexService {
this.transcripts ??= transcriptWriter(this.host.agent.logger);
return this.transcripts;
}
private requireService(method: string): SessionIndexService {

View File

@@ -44,6 +44,12 @@ import { mergeEffortSettings } from "../session-effort.js";
import { configuredSettings, resolveThinkingConfig } from "../session-env.js";
import { resolveInitialPermissionMode } from "../session-mode.js";
import { sanitizeTitle } from "../session-titles.js";
import { renameSession } from "../session-index/rename.js";
import {
parseRenameSessionRequest,
transcriptWriter,
type SessionIndexService,
} from "../session-index/service.js";
import { SettingsManager } from "../settings.js";
import { STARTUP_FAILURE_RESULTS_ENV, startupFailureError } from "../startup-failure.js";
import { createPostToolUseHook } from "../tools.js";
@@ -58,6 +64,7 @@ import { modelSwitchHook, SessionConfig } from "./config.js";
import { fileChangePreviewHook } from "./file-preview.js";
import { canUseTool, denyingStoppedWork } from "./permissions.js";
import { reportHooks } from "./reports.js";
import { savedSessionStatus } from "../session-status.js";
import { SessionRestarts, type SetupParams } from "./restarts.js";
import { V2Session, type SessionConnection } from "./session.js";
import {
@@ -106,7 +113,9 @@ export function v2AgentApp(
.onRequest(v2.methods.agent.session.fork, ({ params }) => agent.forkSession(params))
.onRequest(v2.methods.agent.session.list, ({ params }) => agent.listSessions(params))
.onRequest(v2.methods.agent.session.close, ({ params }) => agent.closeSession(params))
.onRequest(v2.methods.agent.session.status, ({ params }) => agent.sessionStatus(params))
.onRequest(v2.methods.agent.session.delete, ({ params }) => agent.deleteSession(params))
.onRequest(v2.methods.agent.session.setTitle, ({ params }) => agent.setTitle(params))
.onRequest(v2.methods.agent.session.setConfigOption, ({ params }) =>
agent.setConfigOption(params),
)
@@ -152,6 +161,8 @@ class V2Agent implements AgentHandle, SessionConnection {
private readonly guard: SubscriptionGuard;
/** The restarts of the sessions' queries, for a new route or account (see `restarts.ts`). */
private readonly restarts: SessionRestarts;
/** The transcript writes of `session/set_title`, made at its first call (see `transcriptWriter`). */
private transcripts: SessionIndexService | undefined;
/** What the client declared in `initialize`. */
private clientCapabilities: v2.ClientCapabilities | null | undefined;
/** The elicitation modes that the client advertised in `initialize`. */
@@ -452,6 +463,25 @@ class V2Agent implements AgentHandle, SessionConnection {
: { sessions };
}
/**
* `session/status`. `open`, with its foreground state, for a session whose
* query runs here. Every other session is `saved` or unknown by its stored
* transcript (see `session-status.ts`): one that a resume is still
* starting, and one whose query ended without `session/close`, which a
* resume starts again.
*
* A subagent's session does not advertise `status`, and is refused like
* its other requests. Its parent's `subagent_update` reports its state.
*
* It waits for no resume, restart or close, and changes nothing.
*/
async sessionStatus({ sessionId }: v2.SessionStatusRequest): Promise<v2.SessionStatusResponse> {
const session = this.sessions.get(sessionId);
if (session) return { status: "open", state: session.statusState() };
if (this.ownerOfChild(sessionId)) throw restrictedChild(sessionId, "ask the status of");
return savedSessionStatus(sessionId);
}
/** Closing is idempotent: a session that is not active closes at once. */
async closeSession({ sessionId }: v2.CloseSessionRequest): Promise<v2.CloseSessionResponse> {
if (this.ownerOfChild(sessionId)) throw restrictedChild(sessionId, "close");
@@ -481,7 +511,9 @@ class V2Agent implements AgentHandle, SessionConnection {
const live = this.sessions.get(sessionId);
if (live || this.stopped.has(sessionId)) await this.closeSession({ sessionId });
try {
await deleteSession(sessionId, live ? { dir: live.cwd } : undefined);
// After a `session/set_title` of the session in flight.
const remove = () => deleteSession(sessionId, live ? { dir: live.cwd } : undefined);
await (this.transcripts ? this.transcripts.exclusive(sessionId, remove) : remove());
} catch (error) {
// A session without a transcript, such as one never prompted, has nothing to delete.
if (error instanceof Error && /not found/i.test(error.message)) return {};
@@ -490,6 +522,30 @@ class V2Agent implements AgentHandle, SessionConnection {
return {};
}
/**
* `session/set_title`, as the v1 core does it (see `session-index/rename.ts`).
* The session need not be active. An active one publishes the title, and no
* generated title replaces it.
*/
async setTitle(params: v2.SetSessionTitleRequest): Promise<v2.SetSessionTitleResponse> {
const request = parseRenameSessionRequest(params);
const { sessionId } = request;
if (this.ownerOfChild(sessionId)) throw restrictedChild(sessionId, "retitle");
// Renamed through the query that runs once a restart or resume settled.
await this.restarts.settled(sessionId);
await this.setupSettled(sessionId);
const writer = (this.transcripts ??= transcriptWriter(this.logger));
for (;;) {
const session = this.sessions.get(sessionId);
const title = await renameSession(writer, request, session?.reports.retitled());
// A restart or resume started another query of the session while the
// rename ran: its CLI may hold the old title and write it back, so it
// is renamed too. A session that only closed took the title already.
const now = this.sessions.get(sessionId);
if (now === undefined || now === session) return { title };
}
}
async setConfigOption(
params: v2.SetSessionConfigOptionRequest,
): Promise<v2.SetSessionConfigOptionResponse> {

View File

@@ -103,6 +103,17 @@ export class V2Background {
return notification;
}
/**
* `state` as the current work state of the session `sessionId`, with its
* list when it reports one: what {@link stamp} makes of the session's
* `state_update`. Unlike `stamp`, it does not count as a state that carried
* the list, so a later change of the list is reported as before.
*/
snapshot(sessionId: string, state: WorkStateName): v2.StateUpdate {
const session = this.sessions.get(sessionId);
return session ? withBackground(currentState(state), session.items) : currentState(state);
}
/**
* Records the new list of a session, and returns the updates that report it:
*

View File

@@ -6,6 +6,7 @@
import type { AuthMethod } from "@agentclientprotocol/sdk/unstable";
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
import { SYSTEM_PROMPT_CAPABILITY } from "../client-system-prompt.js";
import { SESSION_STATUS_CAPABILITY } from "../session-status.js";
/**
* The session capabilities that the v2 core serves.
@@ -17,6 +18,7 @@ import { SYSTEM_PROMPT_CAPABILITY } from "../client-system-prompt.js";
* - The prompt content and MCP transports that the v1 `initialize` lists too:
* images, embedded context, and HTTP servers. v2 has no SSE transport.
* - `systemPrompt`, in `append` and `override` mode.
* - `session/status` (unstable), with the foreground state of an open session.
*/
export const V2_SESSION_CAPABILITIES: v2.SessionCapabilities = {
prompt: { image: {}, embeddedContext: {} },
@@ -25,6 +27,9 @@ export const V2_SESSION_CAPABILITIES: v2.SessionCapabilities = {
fork: {},
additionalDirectories: {},
systemPrompt: SYSTEM_PROMPT_CAPABILITY,
status: SESSION_STATUS_CAPABILITY,
// `session/set_title`.
title: {},
};
/** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */

View File

@@ -17,7 +17,7 @@ import type {
import { AUTONOMOUS_RESULT_ORIGINS } from "../result-end.js";
import { isSyntheticLoginMessage } from "../message-filters.js";
import { resultEnd } from "../result-end.js";
import { providerFailureCategory } from "../session-failure-extension.js";
import { assistantMessageText, providerFailureCategory } from "../session-failure-extension.js";
import {
getMatchingModelUsage,
modelUsageIncrement,
@@ -109,8 +109,8 @@ function failed(error: RequestError): IdleOutcome {
/**
* What the assistant messages of the open turn reported about its end: the
* error kind of a failed API request, Claude Code's sign-in message, and a
* refusal's explanation. Read from the main conversation's messages.
* error kind and text of a failed API request, Claude Code's sign-in message,
* and a refusal's explanation. Read from the main conversation's messages.
*/
export class TurnReport {
errorKind: SDKAssistantMessageError | undefined;
@@ -118,12 +118,22 @@ export class TurnReport {
loginRequired = false;
/** The explanation of the turn's refusal. */
refusalExplanation: string | undefined;
/**
* The text of the message that Claude Code wrote for a failed API request,
* e.g. "API Error: Connection lost mid-response. The response above may be
* incomplete.", until {@link takeApiError} reads it.
*/
private apiError: string | undefined;
/**
* An assistant message of the main conversation. Returns whether it is
* Claude Code's sign-in message, which the client is not shown: its
* `/login` instruction is for Claude Code's terminal, and the turn's idle
* fails with `authRequired` instead.
* An assistant message of the main conversation. Returns whether the
* client is not shown it as Claude's text:
*
* - Claude Code's sign-in message. Its `/login` instruction is for Claude
* Code's terminal, and the turn's idle fails with `authRequired` instead.
* - The message that Claude Code wrote for a failed API request (`error` on
* a `<synthetic>` message). The turn's idle carries it when the turn
* fails, and a notice otherwise (see {@link takeApiError}).
*/
assistant(message: SDKAssistantMessage): boolean {
if (message.error) this.errorKind = message.error;
@@ -131,11 +141,24 @@ export class TurnReport {
this.refusalExplanation =
message.message.stop_details?.explanation ?? this.refusalExplanation;
}
if (!isSyntheticLoginMessage(message.message)) return false;
this.loginRequired = true;
if (isSyntheticLoginMessage(message.message)) {
this.loginRequired = true;
return true;
}
if (!message.error || message.message.model !== "<synthetic>") return false;
const text = assistantMessageText(message.message);
if (!text) return false;
this.apiError = text;
return true;
}
/** The text of the turn's failed API request that no notice or idle reported yet. */
takeApiError(): string | undefined {
const text = this.apiError;
this.apiError = undefined;
return text;
}
/**
* Claude Code's `model_refusal_no_fallback`: the explanation when the
* refused message carried none (older CLIs, gateways that drop
@@ -155,6 +178,7 @@ export class TurnReport {
this.errorKind = undefined;
this.loginRequired = false;
this.refusalExplanation = undefined;
this.apiError = undefined;
}
}

View File

@@ -41,6 +41,7 @@ import {
mcpServerStartupNotice,
normalizeNoticeText,
noticeUpdate,
splitNoticeText,
} from "../session-notices.js";
import {
DEFAULT_CONTEXT_WINDOW,
@@ -52,6 +53,7 @@ import {
type UsageSnapshot,
} from "../session-usage.js";
import { SessionTitles, type TitledSession } from "../session-titles.js";
import type { RetitledSession } from "../session-index/rename.js";
import type { SettingsManager } from "../settings.js";
import { createTaskHook } from "../tools.js";
import type { SessionOutput } from "./stream.js";
@@ -223,6 +225,24 @@ export class SessionReports {
);
}
/**
* A notice of the message that Claude Code wrote for a failed API request
* (see `TurnReport.takeApiError`). It is a turn's notice, so a result that
* repeats its text is not shown again.
*/
apiError(text: string): void {
this.host.emit([
{
sessionId: this.host.sessionId,
update: noticeUpdate({
severity: "error",
...splitNoticeText(text, "Claude reported an API error"),
}),
},
]);
this.noticeTexts.push(normalizeNoticeText(text));
}
/**
* Claude Code's session state went `idle`: the idle compaction may arm, and
* the title may have landed (see {@link SessionTitles.onTurnEnd}).
@@ -263,6 +283,13 @@ export class SessionReports {
this.titles.onAssistantText(content);
}
/** The session and its title state, as a client rename sets it
* (`session/set_title`); undefined until it starts and once it ends. */
retitled(): RetitledSession | undefined {
const session = this.host.titledSession();
return session && { session, titles: this.titles };
}
/** Sends the session's current title and `updatedAt`, as `session/resume` asks. */
async publishTitle(): Promise<void> {
const session = this.host.titledSession();

View File

@@ -60,6 +60,7 @@ import { Pushable } from "../utils.js";
import { v1PromptRequest } from "./prompt.js";
import { V2Background, stateUpdate as currentStateUpdate } from "./background.js";
import type { V2UpdateTranslator } from "./update-translator.js";
import { isLocalCommandOutput } from "../message-filters.js";
import { MODEL_CONFIG_ID } from "../session-model.js";
import { modelRefusalFallback, noticeUpdate } from "../session-notices.js";
import { planModeUpdate } from "../exit-plan.js";
@@ -362,6 +363,15 @@ export class V2Session implements RequestTarget {
];
}
/**
* The `state` of `session/status`: the foreground state now, with the
* session's list and without a stop reason. It sends nothing and changes
* nothing.
*/
statusState(): v2.StateUpdate {
return this.background.snapshot(this.sessionId, this.foregroundState());
}
/**
* Replaces the session's MCP servers, as a resume of the active session
* asks. The same servers stay as they are, connected.
@@ -917,7 +927,8 @@ export class V2Session implements RequestTarget {
if (message.parent_tool_use_id === null) this.usage.assistant(message.message.model);
if (this.stopped.drops(message)) return;
if (message.parent_tool_use_id === null) {
// Claude Code's sign-in message is not shown (see `TurnReport`).
// Claude Code's sign-in message and its message for a failed API
// request are not shown as Claude's text (see `TurnReport`).
if (this.turnReport.assistant(message)) return;
this.outputStarts();
this.reports.assistant(message);
@@ -929,6 +940,12 @@ export class V2Session implements RequestTarget {
// in. A prompt Claude Code reported `started` for is inserted already.
if ("isReplay" in message && message.isReplay) {
if (message.uuid && message.parent_tool_use_id === null) {
// The output of a local command that Claude Code ran outside a
// turn, such as the "Set model to …" of a model change, is no
// prompt: no result or `command_lifecycle` frame would end its turn.
if (!this.pending.has(message.uuid) && isLocalCommandOutput(message.message.content)) {
return;
}
// A message typed in another client of the session.
const remote = this.remoteControl.remoteMessage(message);
if (remote !== undefined) this.output.userMessage(remote, message.uuid);
@@ -1121,6 +1138,8 @@ export class V2Session implements RequestTarget {
this.fastModeReported(message);
}
this.reports.result(message);
// Before the result's text below, which can repeat the failed request's.
this.reportApiError(end);
// The turn goes on in a fresh context (see `ClearContext`).
if (this.clearContext.holdsResult(message, end)) {
this.turnReport.reset();
@@ -1172,6 +1191,19 @@ export class V2Session implements RequestTarget {
return reporting.then(() => this.closeTurn());
}
/**
* Sends the text of a failed API request that Claude Code wrote into the
* conversation (see `TurnReport.assistant`) as a notice. A turn whose idle
* fails sends none: its error carries the failure. `end` is the turn's
* result, if it has one.
*/
private reportApiError(end: TurnEnd | undefined): void {
const text = this.turnReport.takeApiError();
if (!text) return;
if (idleOutcome(end, this.cancelledWork, this.turnReport).stopReason === "error") return;
this.reports.apiError(text);
}
/**
* Claude Code's `idle`. The one that follows a result is that result's, and
* can come after the next cycle's first output. Any other ends the open
@@ -1281,6 +1313,8 @@ export class V2Session implements RequestTarget {
if (request.coveredByCancel) request.abort.abort();
}
}
// A turn that ends without a result, such as an interrupted one.
this.reportApiError(this.lastEnd);
this.reports.turnEnded();
this.elicitations.turnEnded();
this.fileChanges?.turnClosed(cancelled);