Files
claude-agent-acp/src
Ben Brandt a4a8534c23 feat: serve session/set_title on ACP v1 and v2 (#1585)
Implements the [`session/set_title`
RFD](https://agentclientprotocol.com/rfds/session-set-title) on ACP v1
and v2.

## What clients get

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

## Structure

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

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

## Tests

- v1 (`session-index.test.ts`): a stored session for a client without
the session index; an archived session for three clients (AIR with
`sessionIndex`, AIR without it, a client that is not AIR); unknown
session and empty title.
- v2 (`acp-v2.test.ts`): an active session through its CLI with the
published update; a stored session's records; the rename that a resume
overtakes; unknown session and empty title.
- Golden files: only the `initialize` capability line changes.
`compare.ts` now allows the `title` capability against the origin/main
baseline.
- Not run: `npm run test:integration` against the real CLI. The
delete/rename ordering has no test, since it is a timing race.
2026-11-10 18:28:46 +00:00
..