feat(session): add TodoItem + todo/write event vocabulary
Add the TodoItem type and a todo/write SessionEventMap variant carrying the whole todo list as a snapshot (last-write-wins on replay). It is NOT a SurfaceEventType: it produces no LLM message and never reaches deriveMessages(), so it carries no surfaceOp and stays off the surface — it is durable, replayable UI state that rides the existing session/event emit. Tests cover the snapshot-clone-on-append contract, last-write-wins, the not-on-surface guarantee, and a seeded replay round-trip. Docs: session.md gains the TodoItem type-equiv block + the event member; core.md's variant count goes to twelve; the type-equiv manifest gains TodoItem.
This commit is contained in:
@@ -214,7 +214,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}[T]
|
||||
```
|
||||
|
||||
The eleven event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `context/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the turn-enclosure invariant are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**.
|
||||
The twelve event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `context/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`, `todo/write`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the turn-enclosure invariant are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**.
|
||||
|
||||
## The agent handle
|
||||
|
||||
|
||||
@@ -35,6 +35,34 @@ interface SessionEventMap {
|
||||
'tool/result': { turn: number; step: number; callId: CallId; content: ContentBlock[]; isError: boolean; error?: { name: string; code: string } }
|
||||
/** Steering content injected between steps of a running turn. */
|
||||
'steering/message': { turn: number; content: ContentBlock[]; source: MessageSource }
|
||||
/**
|
||||
* The agent's whole todo list, replaced wholesale on each write
|
||||
* (last-write-wins on replay — the current list is the last `todo/write`).
|
||||
* Written by the `todo_write` tool via
|
||||
* `agent.session.append('todo/write', { todos })`.
|
||||
*
|
||||
* NOT a {@link SurfaceEventType}: it produces no LLM message and never reaches
|
||||
* `deriveMessages()` — it is durable, replayable UI state. The full snapshot
|
||||
* travels each time, so a resume re-derives the current list from the last
|
||||
* event with no fold. UIs render off `session/event`: the stdio UI prints the
|
||||
* list; the ACP bridge maps it to a `plan` sessionUpdate. This is a
|
||||
* `SessionEventMap` member (it rides the existing `session/event` emit), not a
|
||||
* first-class `interface Events` notification, so the cordis catalog gains no
|
||||
* row for it.
|
||||
* @mode emit
|
||||
*/
|
||||
'todo/write': { todos: TodoItem[] }
|
||||
}
|
||||
```
|
||||
|
||||
### `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).
|
||||
|
||||
```ts type-equiv
|
||||
export interface TodoItem {
|
||||
content: string
|
||||
status: 'pending' | 'in_progress' | 'completed'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user