From a4a8534c23b08cdfd7f656d25f9ab16068fd8ffd Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 10 Nov 2026 19:28:46 +0100 Subject: [PATCH] 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. --- README.md | 2 +- docs/air-extensions.md | 2 + src/{v1 => }/session-index/activity.ts | 0 src/{v1 => }/session-index/archive-title.ts | 0 .../session-index/background-tasks.ts | 2 +- src/{v1 => }/session-index/dir-listing.ts | 0 src/{v1 => }/session-index/first-prompt.ts | 0 .../session-index/list-subscriptions.ts | 2 +- src/{v1 => }/session-index/live-registry.ts | 2 +- src/{v1 => }/session-index/project-dirs.ts | 0 src/session-index/rename.ts | 115 ++++++++++++++ src/{v1 => }/session-index/service.ts | 24 ++- src/{v1 => }/session-index/session-index.ts | 4 +- src/{v1 => }/session-index/session-info.ts | 2 +- src/{v1 => }/session-index/transcript-scan.ts | 0 src/{v1 => }/session-index/worktrees.ts | 0 src/task-notifications.ts | 2 +- .../air/session-load-replay.jsonl | 2 +- .../__snapshots__/air/session-setup.jsonl | 2 +- .../__snapshots__/v2/ask-user-question.jsonl | 2 +- .../v2/async-task-ends-before-tool-id.jsonl | 2 +- .../v2/async-task-held-until-tool-id.jsonl | 2 +- .../v2/async-task-released-at-turn-end.jsonl | 2 +- .../__snapshots__/v2/bash-background.jsonl | 2 +- .../__snapshots__/v2/bash-error.jsonl | 2 +- .../__snapshots__/v2/bash-foreground.jsonl | 2 +- .../v2/compaction-failed-legacy.jsonl | 2 +- .../__snapshots__/v2/compaction-legacy.jsonl | 2 +- .../__snapshots__/v2/compaction-update.jsonl | 2 +- .../__snapshots__/v2/edit-rejected.jsonl | 2 +- .../v2/edit-with-permission.jsonl | 2 +- .../__snapshots__/v2/exit-plan-approve.jsonl | 2 +- .../__snapshots__/v2/exit-plan-reject.jsonl | 2 +- .../acp-scenarios/__snapshots__/v2/goal.jsonl | 2 +- .../__snapshots__/v2/grep-and-glob.jsonl | 2 +- .../__snapshots__/v2/mcp-tool.jsonl | 2 +- .../__snapshots__/v2/memory-recall.jsonl | 2 +- .../__snapshots__/v2/network-permission.jsonl | 2 +- .../__snapshots__/v2/notebook-edit.jsonl | 2 +- .../__snapshots__/v2/permission-denied.jsonl | 2 +- .../v2/rate-limit-and-origin.jsonl | 2 +- .../acp-scenarios/__snapshots__/v2/read.jsonl | 2 +- .../v2/session-load-replay.jsonl | 2 +- .../__snapshots__/v2/session-setup.jsonl | 2 +- .../__snapshots__/v2/skill.jsonl | 2 +- .../v2/subagent-agent-async.jsonl | 2 +- .../v2/subagent-late-child-update.jsonl | 2 +- .../v2/subagent-native-sessions.jsonl | 2 +- .../__snapshots__/v2/subagent-nested.jsonl | 2 +- .../v2/subagent-task-legacy.jsonl | 2 +- .../v2/subagent-transcript-extension.jsonl | 2 +- .../__snapshots__/v2/task-create-update.jsonl | 2 +- .../v2/task-output-and-stop.jsonl | 2 +- .../__snapshots__/v2/text-and-thinking.jsonl | 2 +- .../__snapshots__/v2/todo-write.jsonl | 2 +- .../__snapshots__/v2/tool-progress.jsonl | 2 +- .../v2/web-fetch-and-search.jsonl | 2 +- .../__snapshots__/v2/write-existing.jsonl | 2 +- .../__snapshots__/v2/write-new.jsonl | 2 +- src/tests/acp-scenarios/compare.ts | 21 +-- .../{v1 => }/session-index-units.test.ts | 22 +-- src/tests/v1/background-tasks.test.ts | 10 +- src/tests/v1/fork-session.test.ts | 2 +- src/tests/v1/session-index.test.ts | 54 ++++++- src/tests/v2/acp-v2.test.ts | 102 ++++++++++++- src/v1/acp-agent.ts | 16 +- .../connection.ts => session-index.ts} | 140 ++++++------------ src/v2/agent.ts | 37 ++++- src/v2/initialize.ts | 2 + src/v2/reports.ts | 8 + 70 files changed, 472 insertions(+), 183 deletions(-) rename src/{v1 => }/session-index/activity.ts (100%) rename src/{v1 => }/session-index/archive-title.ts (100%) rename src/{v1 => }/session-index/background-tasks.ts (98%) rename src/{v1 => }/session-index/dir-listing.ts (100%) rename src/{v1 => }/session-index/first-prompt.ts (100%) rename src/{v1 => }/session-index/list-subscriptions.ts (99%) rename src/{v1 => }/session-index/live-registry.ts (99%) rename src/{v1 => }/session-index/project-dirs.ts (100%) create mode 100644 src/session-index/rename.ts rename src/{v1 => }/session-index/service.ts (97%) rename src/{v1 => }/session-index/session-index.ts (99%) rename src/{v1 => }/session-index/session-info.ts (97%) rename src/{v1 => }/session-index/transcript-scan.ts (100%) rename src/{v1 => }/session-index/worktrees.ts (100%) rename src/tests/{v1 => }/session-index-units.test.ts (98%) rename src/v1/{session-index/connection.ts => session-index.ts} (80%) diff --git a/README.md b/README.md index edc1c917..112e8478 100644 --- a/README.md +++ b/README.md @@ -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`, `session/status`, 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. diff --git a/docs/air-extensions.md b/docs/air-extensions.md index 6e238c0e..ba34315b 100644 --- a/docs/air-extensions.md +++ b/docs/air-extensions.md @@ -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. diff --git a/src/v1/session-index/activity.ts b/src/session-index/activity.ts similarity index 100% rename from src/v1/session-index/activity.ts rename to src/session-index/activity.ts diff --git a/src/v1/session-index/archive-title.ts b/src/session-index/archive-title.ts similarity index 100% rename from src/v1/session-index/archive-title.ts rename to src/session-index/archive-title.ts diff --git a/src/v1/session-index/background-tasks.ts b/src/session-index/background-tasks.ts similarity index 98% rename from src/v1/session-index/background-tasks.ts rename to src/session-index/background-tasks.ts index 2231893e..a0e14999 100644 --- a/src/v1/session-index/background-tasks.ts +++ b/src/session-index/background-tasks.ts @@ -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. */ diff --git a/src/v1/session-index/dir-listing.ts b/src/session-index/dir-listing.ts similarity index 100% rename from src/v1/session-index/dir-listing.ts rename to src/session-index/dir-listing.ts diff --git a/src/v1/session-index/first-prompt.ts b/src/session-index/first-prompt.ts similarity index 100% rename from src/v1/session-index/first-prompt.ts rename to src/session-index/first-prompt.ts diff --git a/src/v1/session-index/list-subscriptions.ts b/src/session-index/list-subscriptions.ts similarity index 99% rename from src/v1/session-index/list-subscriptions.ts rename to src/session-index/list-subscriptions.ts index 6a5f85ff..9abc6f45 100644 --- a/src/v1/session-index/list-subscriptions.ts +++ b/src/session-index/list-subscriptions.ts @@ -49,7 +49,7 @@ import { type LiveSessionRegistry, type LiveSnapshot, } from "./live-registry.js"; -import { isSessionId, projectsRoot } from "../../paths.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"; diff --git a/src/v1/session-index/live-registry.ts b/src/session-index/live-registry.ts similarity index 99% rename from src/v1/session-index/live-registry.ts rename to src/session-index/live-registry.ts index f8462d5a..13e4023b 100644 --- a/src/v1/session-index/live-registry.ts +++ b/src/session-index/live-registry.ts @@ -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; diff --git a/src/v1/session-index/project-dirs.ts b/src/session-index/project-dirs.ts similarity index 100% rename from src/v1/session-index/project-dirs.ts rename to src/session-index/project-dirs.ts diff --git a/src/session-index/rename.ts b/src/session-index/rename.ts new file mode 100644 index 00000000..0c117967 --- /dev/null +++ b/src/session-index/rename.ts @@ -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; +}; + +/** + * 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 { + 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[0], + loaded: RetitledSession | undefined, + options: Pick, +): Promise { + 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; +} diff --git a/src/v1/session-index/service.ts b/src/session-index/service.ts similarity index 97% rename from src/v1/session-index/service.ts rename to src/session-index/service.ts index 603eaf3f..75aaa786 100644 --- a/src/v1/session-index/service.ts +++ b/src/session-index/service.ts @@ -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,7 +36,7 @@ import { type ListSubscriptionDeps, } from "./list-subscriptions.js"; import { LiveSessionRegistry } from "./live-registry.js"; -import { isSessionId } from "../../paths.js"; +import { isSessionId } from "../paths.js"; import { canonicalPath, exists, @@ -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(sessionId: string, mutation: () => Promise): Promise { + /** 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(sessionId: string, mutation: () => Promise): Promise { const previous = this.mutations.get(sessionId) ?? Promise.resolve(); const next = previous.catch(() => undefined).then(mutation); const settled = next.catch(() => undefined); diff --git a/src/v1/session-index/session-index.ts b/src/session-index/session-index.ts similarity index 99% rename from src/v1/session-index/session-index.ts rename to src/session-index/session-index.ts index ea57a190..dee69287 100644 --- a/src/v1/session-index/session-index.ts +++ b/src/session-index/session-index.ts @@ -21,9 +21,9 @@ 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 { isSessionId, projectsRoot } from "../paths.js"; import { canonicalPath, encodeProjectPath, diff --git a/src/v1/session-index/session-info.ts b/src/session-index/session-info.ts similarity index 97% rename from src/v1/session-index/session-info.ts rename to src/session-index/session-info.ts index 0a97fefc..901816ed 100644 --- a/src/v1/session-index/session-info.ts +++ b/src/session-index/session-info.ts @@ -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"; diff --git a/src/v1/session-index/transcript-scan.ts b/src/session-index/transcript-scan.ts similarity index 100% rename from src/v1/session-index/transcript-scan.ts rename to src/session-index/transcript-scan.ts diff --git a/src/v1/session-index/worktrees.ts b/src/session-index/worktrees.ts similarity index 100% rename from src/v1/session-index/worktrees.ts rename to src/session-index/worktrees.ts diff --git a/src/task-notifications.ts b/src/task-notifications.ts index 5168d496..def71c7c 100644 --- a/src/task-notifications.ts +++ b/src/task-notifications.ts @@ -1,7 +1,7 @@ /** * The `` 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. */ diff --git a/src/tests/acp-scenarios/__snapshots__/air/session-load-replay.jsonl b/src/tests/acp-scenarios/__snapshots__/air/session-load-replay.jsonl index 1b038342..13f6d976 100644 --- a/src/tests/acp-scenarios/__snapshots__/air/session-load-replay.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/air/session-load-replay.jsonl @@ -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":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"authMethods":[],"protocolVersion":1}} {"kind":"sessionUpdate","payload":{"sessionId":"","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}} {"kind":"sessionUpdate","payload":{"sessionId":"","update":{"content":{"text":"Check the file.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_thought_chunk"}}} {"kind":"sessionUpdate","payload":{"sessionId":"","update":{"content":{"text":"I will edit it.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_message_chunk"}}} diff --git a/src/tests/acp-scenarios/__snapshots__/air/session-setup.jsonl b/src/tests/acp-scenarios/__snapshots__/air/session-setup.jsonl index 3e5be576..df27d702 100644 --- a/src/tests/acp-scenarios/__snapshots__/air/session-setup.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/air/session-setup.jsonl @@ -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":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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":""}} {"kind":"sessionUpdate","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl index 1274ecf8..615399d6 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/async-task-ends-before-tool-id.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/async-task-ends-before-tool-id.jsonl index 38055579..fc090049 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/async-task-ends-before-tool-id.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/async-task-ends-before-tool-id.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/async-task-held-until-tool-id.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/async-task-held-until-tool-id.jsonl index 38055579..fc090049 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/async-task-held-until-tool-id.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/async-task-held-until-tool-id.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/async-task-released-at-turn-end.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/async-task-released-at-turn-end.jsonl index 38055579..fc090049 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/async-task-released-at-turn-end.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/async-task-released-at-turn-end.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl index 9fa6c2c2..63731033 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl index 96e1a0ac..69dc907b 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/bash-foreground.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/bash-foreground.jsonl index d6d0280e..2d5162e3 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/bash-foreground.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/bash-foreground.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl index 6988b8cd..98cb7a9c 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl index 1f6083ad..dbb8f3e2 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl index 1f6083ad..dbb8f3e2 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/edit-rejected.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/edit-rejected.jsonl index 56c701ba..bb8b995f 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/edit-rejected.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/edit-rejected.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/edit-with-permission.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/edit-with-permission.jsonl index f434e660..560d3886 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/edit-with-permission.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/edit-with-permission.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-approve.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-approve.jsonl index bcdcd8bb..685937f6 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-approve.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-approve.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-reject.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-reject.jsonl index 6b54c853..59fdd49a 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-reject.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/exit-plan-reject.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/goal.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/goal.jsonl index 40c4f5fb..20f2fdce 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/goal.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/goal.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"/goal Ship the feature","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl index fdae355a..2ffd6c00 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/mcp-tool.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/mcp-tool.jsonl index b90a5452..c2dd8169 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/mcp-tool.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/mcp-tool.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl index 2d423e15..755819aa 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/network-permission.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/network-permission.jsonl index d77ba60c..e257333e 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/network-permission.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/network-permission.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/notebook-edit.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/notebook-edit.jsonl index 0bb71ba3..2f3c6cf4 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/notebook-edit.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/notebook-edit.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl index e682c9ad..9db065fe 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/rate-limit-and-origin.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/rate-limit-and-origin.jsonl index 3d327778..5f5d1121 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/rate-limit-and-origin.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/rate-limit-and-origin.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/read.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/read.jsonl index 7078fec8..2fddf7f8 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/read.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/read.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/session-load-replay.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/session-load-replay.jsonl index 50e50740..e9be82e7 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/session-load-replay.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/session-load-replay.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"protocolVersion":2}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[],"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message"}}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","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":"","update":{"content":[],"messageId":"msg_r1","sessionUpdate":"agent_thought"}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/session-setup.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/session-setup.jsonl index 1eb189e1..d101e598 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/session-setup.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/session-setup.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl index b75e45d4..322fd237 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl index bbaaf2c3..e07be2f1 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl index 94359468..709b77a2 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-native-sessions.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-native-sessions.jsonl index e08d0504..4d9d2f15 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/subagent-native-sessions.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-native-sessions.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl index f81c376e..4f891049 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-task-legacy.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-task-legacy.jsonl index 85ee9187..147a82ca 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/subagent-task-legacy.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-task-legacy.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl index 126101f5..88a3e10c 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/task-create-update.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/task-create-update.jsonl index 4ee626d4..917f740f 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/task-create-update.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/task-create-update.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl index cf854b3e..36c968cd 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/text-and-thinking.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/text-and-thinking.jsonl index 5e64ee36..d4d633c8 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/text-and-thinking.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/text-and-thinking.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/todo-write.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/todo-write.jsonl index c9bb8e67..7c2bc880 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/todo-write.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/todo-write.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl index 840fa8bb..2dc6c176 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/web-fetch-and-search.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/web-fetch-and-search.jsonl index f47505c9..62e43924 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/web-fetch-and-search.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/web-fetch-and-search.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/write-existing.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/write-existing.jsonl index 742fabaa..20deb0a8 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/write-existing.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/write-existing.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/write-new.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/write-new.jsonl index 206a01a1..f0c066b6 100644 --- a/src/tests/acp-scenarios/__snapshots__/v2/write-new.jsonl +++ b/src/tests/acp-scenarios/__snapshots__/v2/write-new.jsonl @@ -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":{}},"status":{},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","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":""},"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 [|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":""}} {"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} {"kind":"response","method":"session/prompt","payload":{"messageId":""}} diff --git a/src/tests/acp-scenarios/compare.ts b/src/tests/acp-scenarios/compare.ts index 086e9fee..7ea29595 100644 --- a/src/tests/acp-scenarios/compare.ts +++ b/src/tests/acp-scenarios/compare.ts @@ -31,6 +31,9 @@ * - 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 @@ -232,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; } @@ -267,7 +268,9 @@ 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); @@ -275,7 +278,7 @@ export function compareWithBaseline(baseline: Recorded[], traffic: Recorded[]): const current = withoutSandboxNetworkSettlements( traffic.map((record) => withoutAuthStatusCapability( - withoutSessionCapability(withoutSessionCapability(record, "systemPrompt"), "status"), + withoutSessionCapabilities(record, ["systemPrompt", "status", "title"]), ), ), ); diff --git a/src/tests/v1/session-index-units.test.ts b/src/tests/session-index-units.test.ts similarity index 98% rename from src/tests/v1/session-index-units.test.ts rename to src/tests/session-index-units.test.ts index 56bd8d4a..08393c2a 100644 --- a/src/tests/v1/session-index-units.test.ts +++ b/src/tests/session-index-units.test.ts @@ -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 { isSessionId } from "../../paths.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, 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"); diff --git a/src/tests/v1/background-tasks.test.ts b/src/tests/v1/background-tasks.test.ts index a6578470..2e5ffb13 100644 --- a/src/tests/v1/background-tasks.test.ts +++ b/src/tests/v1/background-tasks.test.ts @@ -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; diff --git a/src/tests/v1/fork-session.test.ts b/src/tests/v1/fork-session.test.ts index b5533cb0..7215fdd5 100644 --- a/src/tests/v1/fork-session.test.ts +++ b/src/tests/v1/fork-session.test.ts @@ -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; diff --git a/src/tests/v1/session-index.test.ts b/src/tests/v1/session-index.test.ts index ea83cd8c..9d9642ef 100644 --- a/src/tests/v1/session-index.test.ts +++ b/src/tests/v1/session-index.test.ts @@ -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); diff --git a/src/tests/v2/acp-v2.test.ts b/src/tests/v2/acp-v2.test.ts index ddc07212..4872da0c 100644 --- a/src/tests/v2/acp-v2.test.ts +++ b/src/tests/v2/acp-v2.test.ts @@ -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> => ({}), /** The query switches its model (`setModel`). */ setModel: async (_model?: string): Promise => {}, + /** Claude Code stores a session title (`rename_session`). */ + renameSession: async (_title: string, _sessionId?: string): Promise => {}, /** 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) => 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); @@ -395,6 +399,7 @@ describe("ACP protocol routing", () => { additionalDirectories: {}, systemPrompt: { mode: { override: {} } }, status: {}, + title: {}, }); expect(response.capabilities?.providers).toEqual({}); // Not `_session/steering`, which v1 advertises in the top-level `_meta`. @@ -883,6 +888,101 @@ describe("ACP v2 sessions", () => { }); }); + 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((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) => ({ diff --git a/src/v1/acp-agent.ts b/src/v1/acp-agent.ts index da7f2715..65c48be1 100644 --- a/src/v1/acp-agent.ts +++ b/src/v1/acp-agent.ts @@ -48,6 +48,8 @@ import { CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, + SetSessionTitleRequest, + SetSessionTitleResponse, DeleteSessionResponse, WriteTextFileRequest, WriteTextFileResponse, @@ -133,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, @@ -2025,6 +2027,8 @@ export class ClaudeAcpAgent { resume: {}, status: SESSION_STATUS_CAPABILITY, systemPrompt: SYSTEM_PROMPT_CAPABILITY, + // `session/set_title`. + title: {}, ...(this.toolCallCapabilities.air.client ? { subagents: {} } : {}), }; @@ -7876,6 +7880,11 @@ export class ClaudeAcpAgent { return savedSessionStatus(sessionId); } + /** See {@link sessionIndex.SessionIndexConnection.setTitle}. */ + setSessionTitle(params: SetSessionTitleRequest): Promise { + return this.sessionIndex.setTitle(params); + } + /** Per client: see {@link sessionIndex.SessionIndexConnection.deleteSession}. */ async deleteSession(params: DeleteSessionRequest): Promise { return this.sessionIndex.deleteSession(params); @@ -11149,6 +11158,7 @@ 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)) diff --git a/src/v1/session-index/connection.ts b/src/v1/session-index.ts similarity index 80% rename from src/v1/session-index/connection.ts rename to src/v1/session-index.ts index e940bbb7..5eea707b 100644 --- a/src/v1/session-index/connection.ts +++ b/src/v1/session-index.ts @@ -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(); @@ -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 { - 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 { + const title = await this.renameWith( + this.service ?? this.writer(), + parseRenameSessionRequest(request), + ); + return { title }; + } + + private renameWith(index: SessionIndexService, request: RenameSessionRequest): Promise { + 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 { 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[0], - options: Pick, - ): Promise { - 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 }) - | 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 { diff --git a/src/v2/agent.ts b/src/v2/agent.ts index d1810526..e94b99fd 100644 --- a/src/v2/agent.ts +++ b/src/v2/agent.ts @@ -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"; @@ -109,6 +115,7 @@ export function v2AgentApp( .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), ) @@ -154,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`. */ @@ -502,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 {}; @@ -511,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 { + 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 { diff --git a/src/v2/initialize.ts b/src/v2/initialize.ts index bc0f7346..85f919be 100644 --- a/src/v2/initialize.ts +++ b/src/v2/initialize.ts @@ -28,6 +28,8 @@ export const V2_SESSION_CAPABILITIES: v2.SessionCapabilities = { 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. */ diff --git a/src/v2/reports.ts b/src/v2/reports.ts index dd2bb4dc..25ea32d2 100644 --- a/src/v2/reports.ts +++ b/src/v2/reports.ts @@ -53,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"; @@ -282,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 { const session = this.host.titledSession();