Quellcode durchsuchen

fix(ui-stdio): seed turn labels from the registry; drop stale taxonomy line

Address review on the event-taxonomy PR:

- ui-stdio built its session-id→agent-id label map only from live
  `agent/created` events, so an agent registered before the UI fiber
  installed — the pre-created `main` agent, or any agent surviving an HMR
  reload of just this fiber — was missed and its turns rendered the raw
  session id instead of `[main turn N]`. Seed the map from
  `ctx.agents.list()` at install, then keep it live. Regression test proven
  red without the seed.
- The agent event-domain doc still listed "the turn boundaries" among the
  TRANSIENT `agent/*` emits, contradicting the rule ten lines below that a
  turn/step boundary is a durable `session/event`, not an `agent/*` mirror.
Tianyi Cui vor 2 Monaten
Ursprung
Commit
1e87b6fea4

+ 11 - 11
docs/cordis-catalog/events-and-services.md

@@ -25,7 +25,7 @@ An agent was registered in the AgentRegistry and is ready to receive messages.
 
 Types: [Agent](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:164`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:165`](../../packages/core/agent/src/types.ts)
 
 #### `agent/disposed` — emit
 
@@ -37,7 +37,7 @@ An agent was disposed and removed from the registry; its fiber and any in-flight
 
 Types: [Agent](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:170`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:171`](../../packages/core/agent/src/types.ts)
 
 #### `agent/error` — emit
 
@@ -49,7 +49,7 @@ A step or turn errored. The loop reports a failure here (plus the logger) even w
 
 Types: [Agent](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:263`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:264`](../../packages/core/agent/src/types.ts)
 
 #### `agent/pre-step` — serial
 
@@ -63,7 +63,7 @@ Serial (awaited in registration order), not a waterfall: a listener mutates the
 
 Types: [Agent](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:223`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:224`](../../packages/core/agent/src/types.ts)
 
 #### `agent/queued` — emit
 
@@ -75,7 +75,7 @@ A message entered the agent's inbox (queued or steering). `source` is the resolv
 
 Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:183`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:184`](../../packages/core/agent/src/types.ts)
 
 #### `agent/request` — waterfall
 
@@ -87,7 +87,7 @@ Waterfall: mutate the fully-assembled GenerateOptions before the model call (hoo
 
 Types: [Agent](../core-data-structures/core.md) · [GenerateOptions](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:232`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:233`](../../packages/core/agent/src/types.ts)
 
 #### `agent/status` — emit
 
@@ -99,7 +99,7 @@ Agent status changed (`idle` ⇄ `running`, or → `disposed`). Drive lifecycle
 
 Types: [Agent](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:178`](../../packages/core/agent/src/types.ts)
 
 #### `agent/steering` — emit
 
@@ -111,7 +111,7 @@ Steering content was injected into a running turn.
 
 Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:257`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:258`](../../packages/core/agent/src/types.ts)
 
 #### `agent/step-result` — waterfall
 
@@ -123,7 +123,7 @@ Waterfall: post-process the assembled assistant Message before tool dispatch (va
 
 Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:238`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:239`](../../packages/core/agent/src/types.ts)
 
 #### `agent/stream-chunk` — emit
 
@@ -135,7 +135,7 @@ A raw StreamChunk arrived from the model (token-level UI/log feed).
 
 Types: [Agent](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md)
 
-Source: [`packages/core/agent/src/types.ts:252`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:253`](../../packages/core/agent/src/types.ts)
 
 #### `agent/turn-continuation` — waterfall
 
@@ -147,7 +147,7 @@ Waterfall: override the turn-continuation decision. The default (computed by the
 
 Types: [Agent](../core-data-structures/core.md)
 
-Source: [`packages/core/agent/src/types.ts:245`](../../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:246`](../../packages/core/agent/src/types.ts)
 
 ### `llm/*`
 

+ 4 - 3
packages/core/agent/src/types.ts

@@ -19,9 +19,10 @@
  *   live `Agent`. Two shapes: INTERCEPTION waterfalls (`agent/request`,
  *   `agent/step-result`, `agent/turn-continuation`) that mutate/veto, and
  *   TRANSIENT emits (`agent/status`, `agent/stream-chunk`, `agent/error`,
- *   `agent/created`/`agent/disposed`, `agent/queued`, `agent/steering`, and the
- *   turn boundaries) that notify with the `Agent` in hand. Answers "right now,
- *   with the agent object — intercept or observe."
+ *   `agent/created`/`agent/disposed`, `agent/queued`, `agent/steering`) that
+ *   notify with the `Agent` in hand. Turn/step boundaries are NOT here — they
+ *   are durable `session/event` records (see the rule below). Answers "right
+ *   now, with the agent object — intercept or observe."
  * - **`tools/*`** (`@deepseek-ai/dsh-tools`) — the tool registry + execution.
  *
  * **The rule:** a durable, replayable fact is a SessionEvent; a live

+ 1 - 1
packages/support/ui-stdio/README.md

@@ -25,7 +25,7 @@ This package consolidates what were two near-identical copies under `examples/ec
 Rendering is **global** — every agent's events are written to stdout, not just `config.agent`'s. `config.agent` scopes only *input* (which agent stdin drives) and the EOF-exit gate; the single-agent demos this serves have just one agent, so the distinction is moot for them. (A multi-agent UI that needs per-agent panes would filter these handlers by the agent argument — deliberately out of scope here.)
 
 - `agent/stream-chunk` — `text-delta` is written verbatim; `reasoning-delta` is wrapped in the dim SGR (`\x1B[2m … \x1B[0m`) so the chain-of-thought is visually subordinate to the answer. Reasoning rendering is inert when no `reasoning-delta` chunks arrive (e.g. a mock model), so it is always on.
-- `session/event` — the durable transcript feed drives all boundary and content rendering: `turn/start` prints a `[<agent> turn N]` header (the short agent label comes from an `agent/created`→id map, since the turn event carries only the turn number), `turn/end` prints the trailing `> ` prompt, `tool/call` renders `[tool call] name(args)`, `tool/result` renders the joined text blocks as `[tool result] …`, and `todo/write` renders a glyphed checklist.
+- `session/event` — the durable transcript feed drives all boundary and content rendering: `turn/start` prints a `[<agent> turn N]` header (the short agent label comes from a session-id→agent-id map seeded from `ctx.agents.list()` at install and kept live via `agent/created`/`agent/disposed`, since the turn event carries only the turn number), `turn/end` prints the trailing `> ` prompt, `tool/call` renders `[tool call] name(args)`, `tool/result` renders the joined text blocks as `[tool result] …`, and `todo/write` renders a glyphed checklist.
 
 ## The I/O seam
 

+ 7 - 1
packages/support/ui-stdio/src/index.ts

@@ -80,8 +80,14 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
   // number, so to print the short agent id (`[main turn 1]`) we map the
   // session's id to its agent's id. The session id is not reliably the agent id
   // (a session can be created with an explicit/client-supplied id), so build the
-  // map from `agent/created` rather than parsing the id string.
+  // map from `agent/created` rather than parsing the id string. Seed from the
+  // registry's current agents first: an agent registered before this plugin
+  // installed (e.g. the pre-created `main` agent, or any agent surviving an HMR
+  // reload of just this fiber) already fired its `agent/created`, so the live
+  // listener alone would miss it and its turns would fall back to the raw
+  // session id.
   const labelBySession = new Map<string, string>()
+  for (const agent of ctx.agents.list()) labelBySession.set(agent.session.header.id, agent.id)
   ctx.on('agent/created', (agent) => { labelBySession.set(agent.session.header.id, agent.id) })
   ctx.on('agent/disposed', (agent) => { labelBySession.delete(agent.session.header.id) })
 

+ 3 - 0
packages/support/ui-stdio/tests/readline.spec.ts

@@ -16,6 +16,9 @@ function fakeContext(): Context {
   return {
     on: vi.fn(() => vi.fn()),
     effect: vi.fn((callback: () => () => void) => callback()),
+    // The UI seeds its label map from the registry at install; this suite only
+    // exercises readline terminal-mode selection, so an empty roster suffices.
+    agents: { list: vi.fn(() => []) },
   } as unknown as Context
 }
 

+ 20 - 0
packages/support/ui-stdio/tests/ui-stdio.spec.ts

@@ -149,6 +149,26 @@ describe('createStdioChat rendering', () => {
     expect(out.text()).toContain('[orphan-session turn 1] ')
   })
 
+  it('seeds labels for agents already registered before the UI installs', async () => {
+    // The pre-created `main` agent (and any agent surviving an HMR reload of just
+    // this fiber) fired its `agent/created` before the UI's listener existed, so
+    // the live listener alone would miss it. Seeding from `ctx.agents.list()` at
+    // install time is what keeps its turn header showing `[main turn N]` instead
+    // of the raw session id.
+    const ctx = new Context()
+    await ctx.plugin(AgentRegistry)
+    const agent = makeAgent('main')
+    ctx.agents.register(agent) // registered BEFORE the UI plugin below
+    const { runtime, out } = makeRuntime()
+    await ctx.plugin(Object.assign((inner: Context) => {
+      createStdioChat(inner, CONFIG, runtime)
+    }, { inject: ['agents'] }))
+    ctx.emit('session/event', makeSession('main'), {
+      type: 'turn/start', seq: 1, time: 0, data: { turn: 5, trigger: { kind: 'message' } },
+    } as SessionEvent)
+    expect(out.text()).toContain('[main turn 5] ')
+  })
+
   it('resets dim styling at turn/end if a turn ends mid-reasoning', async () => {
     const { ctx, out } = await setup()
     const agent = makeAgent('main')