docs(session): scope the todo/write JSDoc to the event's own contract

Codex Phase 1 review: the event JSDoc described Phase 2 consumers (the
todo_write tool, stdio printing, ACP plan mapping) as current state, and put an
@mode tag on a SessionEventMap member. @mode is for first-class Cordis
`interface Events` entries the catalog generator reads — this event rides the
existing session/event emit and has no catalog row, so the tag was wrong.

Trim the JSDoc to the event's own contract (snapshot data shape,
last-write-wins, not-a-surface-event) and drop @mode; phrase TodoItem in terms
of its own purpose rather than a not-yet-present tool.
This commit is contained in:
Tianyi Cui
2026-06-29 01:50:35 +08:00
parent 22a89847ac
commit 4f09157612
2 changed files with 23 additions and 29 deletions

View File

@@ -36,20 +36,17 @@ interface SessionEventMap {
/** Steering content injected between steps of a running turn. */ /** Steering content injected between steps of a running turn. */
'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource } 'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource }
/** /**
* The agent's whole todo list, replaced wholesale on each write * The agent's whole todo list, carried as a full snapshot and replaced
* (last-write-wins on replay — the current list is the last `todo/write`). * wholesale on each write — the current list is the most recent `todo/write`
* Written by the `todo_write` tool via * (last-write-wins on replay, no fold). Appended by an owning agent via
* `agent.session.append('todo/write', { todos })`. * `session.append('todo/write', { todos })`.
* *
* NOT a {@link SurfaceEventType}: it produces no LLM message and never reaches * NOT a {@link SurfaceEventType}: it produces no LLM message and never reaches
* `deriveMessages()` — it is durable, replayable UI state. The full snapshot * `deriveMessages()`, so it carries no `surfaceOp` and stays off the surface —
* travels each time, so a resume re-derives the current list from the last * it is durable, replayable UI state, distinct from the conversation history.
* event with no fold. UIs render off `session/event`: the stdio UI prints the * It is a `SessionEventMap` member riding the existing `session/event` emit,
* list; the ACP bridge maps it to a `plan` sessionUpdate. This is a * not a first-class Cordis `interface Events` notification, so it has no
* `SessionEventMap` member (it rides the existing `session/event` emit), not a * cordis-catalog row.
* first-class `interface Events` notification, so the cordis catalog gains no
* row for it.
* @mode emit
*/ */
'todo/write': { todos: TodoItem[] } 'todo/write': { todos: TodoItem[] }
} }
@@ -57,7 +54,7 @@ interface SessionEventMap {
### `TodoItem` — one todo-list entry ### `TodoItem` — one todo-list entry
The unit of the `todo_write` tool's whole-list state. Deliberately minimal — a `content` line and a three-state `status` (no id, priority, or `activeForm`): the list is replaced wholesale on every write, so entries need no stable identity, and the status triple is exactly the ACP `PlanEntryStatus`, so the ACP bridge maps a todo list to a `plan` update 1:1 (synthesizing the priority ACP additionally requires). The unit of the `todo/write` event's whole-list snapshot. Deliberately minimal — a `content` line and a three-state `status` (no id, priority, or `activeForm`): the list is replaced wholesale on every write, so entries need no stable identity, and the status triple is exactly the ACP `PlanEntryStatus`, so a UI bridge can map a todo list onto an ACP `plan` 1:1 (synthesizing the priority ACP additionally requires).
```ts type-equiv ```ts type-equiv
export interface TodoItem { export interface TodoItem {

View File

@@ -150,14 +150,14 @@ export interface TurnEndReasonMap {
export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap] export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap]
/** /**
* One entry in an agent's todo list — the unit of the `todo_write` tool's * One entry in an agent's todo list — the unit of the `todo/write`
* whole-list state (the `todo/write` {@link SessionEventMap} event). * {@link SessionEventMap} event's whole-list snapshot.
* *
* Deliberately minimal: a human-readable `content` line and a three-state * Deliberately minimal: a human-readable `content` line and a three-state
* `status`. No id, priority, or `activeForm` — the list is replaced wholesale * `status`. No id, priority, or `activeForm` — the list is replaced wholesale
* on every write (last-write-wins), so entries need no stable identity, and the * on every write (last-write-wins), so entries need no stable identity, and the
* status triple is exactly the ACP `PlanEntryStatus` (so the ACP bridge maps a * status triple is exactly the ACP `PlanEntryStatus`, so a UI bridge can map a
* todo list to a `plan` update 1:1, synthesizing the priority ACP additionally * todo list onto an ACP `plan` 1:1 (synthesizing the priority ACP additionally
* requires). * requires).
*/ */
export interface TodoItem { export interface TodoItem {
@@ -213,20 +213,17 @@ export interface SessionEventMap {
/** Steering content injected between steps of a running turn. */ /** Steering content injected between steps of a running turn. */
'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource } 'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource }
/** /**
* The agent's whole todo list, replaced wholesale on each write * The agent's whole todo list, carried as a full snapshot and replaced
* (last-write-wins on replay — the current list is the last `todo/write`). * wholesale on each write — the current list is the most recent `todo/write`
* Written by the `todo_write` tool via * (last-write-wins on replay, no fold). Appended by an owning agent via
* `agent.session.append('todo/write', { todos })`. * `session.append('todo/write', { todos })`.
* *
* NOT a {@link SurfaceEventType}: it produces no LLM message and never reaches * NOT a {@link SurfaceEventType}: it produces no LLM message and never reaches
* `deriveMessages()` — it is durable, replayable UI state. The full snapshot * `deriveMessages()`, so it carries no `surfaceOp` and stays off the surface —
* travels each time, so a resume re-derives the current list from the last * it is durable, replayable UI state, distinct from the conversation history.
* event with no fold. UIs render off `session/event`: the stdio UI prints the * It is a `SessionEventMap` member riding the existing `session/event` emit,
* list; the ACP bridge maps it to a `plan` sessionUpdate. This is a * not a first-class Cordis `interface Events` notification, so it has no
* `SessionEventMap` member (it rides the existing `session/event` emit), not a * cordis-catalog row.
* first-class `interface Events` notification, so the cordis catalog gains no
* row for it.
* @mode emit
*/ */
'todo/write': { todos: TodoItem[] } 'todo/write': { todos: TodoItem[] }
} }