mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-11 03:41:17 +00:00
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.
48 lines
1.9 KiB
TypeScript
48 lines
1.9 KiB
TypeScript
/**
|
|
* What the ACP v2 `initialize` response advertises: the session capabilities,
|
|
* and the v2 form of the auth methods, which the shared auth modules build in
|
|
* v1 terms (`auth-methods.ts`).
|
|
*/
|
|
import type { AuthMethod } from "@agentclientprotocol/sdk/unstable";
|
|
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
|
|
import { SYSTEM_PROMPT_CAPABILITY } from "../client-system-prompt.js";
|
|
import { SESSION_STATUS_CAPABILITY } from "../session-status.js";
|
|
|
|
/**
|
|
* The session capabilities that the v2 core serves.
|
|
*
|
|
* - The session baseline: `session/new`, `list`, `resume` (with replay),
|
|
* `close`, `prompt`, `cancel`, and `update`.
|
|
* - `session/delete`, `session/fork` (which returns a session the client can
|
|
* prompt at once), and `additionalDirectories`.
|
|
* - The prompt content and MCP transports that the v1 `initialize` lists too:
|
|
* images, embedded context, and HTTP servers. v2 has no SSE transport.
|
|
* - `systemPrompt`, in `append` and `override` mode.
|
|
* - `session/status` (unstable), with the foreground state of an open session.
|
|
*/
|
|
export const V2_SESSION_CAPABILITIES: v2.SessionCapabilities = {
|
|
prompt: { image: {}, embeddedContext: {} },
|
|
mcp: { stdio: {}, http: {} },
|
|
delete: {},
|
|
fork: {},
|
|
additionalDirectories: {},
|
|
systemPrompt: SYSTEM_PROMPT_CAPABILITY,
|
|
status: SESSION_STATUS_CAPABILITY,
|
|
// `session/set_title`.
|
|
title: {},
|
|
};
|
|
|
|
/** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */
|
|
export function v2AuthMethod(method: AuthMethod): v2.AuthMethod {
|
|
if ("type" in method && method.type === "terminal") {
|
|
const { id, env, ...terminal } = method;
|
|
return {
|
|
...terminal,
|
|
methodId: id,
|
|
...(env ? { env: Object.entries(env).map(([name, value]) => ({ name, value })) } : {}),
|
|
};
|
|
}
|
|
const { id, ...agent } = method;
|
|
return { ...agent, methodId: id, type: "agent" };
|
|
}
|