浏览代码

refactor(agent, session-controller): drop the unread startedTime frame field

The `start` frame and the reconnect baseline attempt carried a wall-clock
`startedTime` that no Host or Client consumer read: the Host accumulator
and the Client reconciler copied it into their attempt maps and nothing
looked at it again. Presentation frames now carry only the coordinates
consumers use (attempt, revision, turn, step, chunk index, and v1 seq
provenance); the type-equivalence block, event graph, READMEs, and the
live-stream Agent Note follow the type.
Tianyi Cui 1 月之前
父节点
当前提交
fe9c2d7053
共有 27 个文件被更改,包括 76 次插入 和 103 次删除
  1. 2 2
      .agents/notes/implemented/architecture/2026-08-31-live-assistant-stream-frames.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-08-31-live-assistant-stream-frames.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-08-31-live-assistant-stream-frames.zh.md
  4. 2 2
      docs/event-producer-consumer.i18n.yaml
  5. 18 18
      docs/event-producer-consumer.md
  6. 18 18
      docs/event-producer-consumer.zh.md
  7. 2 2
      docs/subsystems/core.i18n.yaml
  8. 0 2
      docs/subsystems/core.md
  9. 0 2
      docs/subsystems/core.zh.md
  10. 2 2
      packages/api/session-controller/README.i18n.yaml
  11. 0 0
      packages/api/session-controller/README.md
  12. 0 0
      packages/api/session-controller/README.zh.md
  13. 0 3
      packages/api/session-controller/src/assistant-stream.ts
  14. 0 3
      packages/api/session-controller/src/client/sessions/assistant-stream.ts
  15. 0 3
      packages/api/session-controller/src/types.ts
  16. 2 2
      packages/api/session-controller/tests/assistant-stream.client.spec.ts
  17. 13 15
      packages/api/session-controller/tests/session-history-journal.host.spec.ts
  18. 4 6
      packages/api/session-controller/tests/sessions-service.client.spec.ts
  19. 3 7
      packages/api/session-controller/tests/transport.client.spec.ts
  20. 0 1
      packages/core/agent-loop/src/assistant-stream.ts
  21. 0 2
      packages/core/agent-loop/tests/loop.spec.ts
  22. 2 2
      packages/core/agent/README.i18n.yaml
  23. 1 1
      packages/core/agent/README.md
  24. 1 1
      packages/core/agent/README.zh.md
  25. 0 2
      packages/core/agent/src/runtime-types.ts
  26. 1 2
      packages/core/scope/tests/invariant.spec.ts
  27. 3 3
      packages/extensions/tool-cordis/src/api-catalog.ts

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-31-live-assistant-stream-frames.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-31-live-assistant-stream-frames.md
-2026-08-31-live-assistant-stream-frames.md: c01a919b1c331f82e9c25cc01625f412414591fd
-2026-08-31-live-assistant-stream-frames.zh.md: 0c1a21a09dff2c3d092f707a8970617002e25282
+2026-08-31-live-assistant-stream-frames.md: 0f32f7a32712cc99998815df06320312cd692e6f
+2026-08-31-live-assistant-stream-frames.zh.md: fd15f3e8ca356465fa3bf7c8d344966248e71bba

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-31-live-assistant-stream-frames.md

@@ -10,7 +10,7 @@ The session log keeps every `assistant/chunk` so replay, cold reads, telemetry,
 
 ## Decision
 
-`dsh-agent-loop` emits scoped `agent/assistant-stream` frames for each model attempt. `start`, `chunk`, and `end` carry a branded `LlmAttemptId` unique within one Agent lifecycle; every emitted frame advances one revision local to that lifecycle. The `start` frame captures a safe-integer wall-clock `startedTime`, chunk indexes are dense from zero, and `end.index` equals the next chunk position. Stream acquisition and its final cancellation check occur before `start`; a failure there emits no frame, while every started attempt emits a terminal `end`. The loop appends every v1 `assistant/chunk` before its matching live chunk frame, records that exact `legacyChunkSeq`, and appends the final `assistant/message` before a committed end frame. The existing authenticated Session-follow accepts an explicit Web opt-in, opens with a cached active-attempt baseline, and carries durable events and cursorless frames in one FIFO. Each follower captures a local arrival ordinal with the opening baseline and drops buffered frames at or before that cut; frame revisions can restart at one with a replacement Agent, so they do not define the opening cut. If the durable opening snapshot precedes the Assistant baseline, a baseline may already acknowledge a chunk whose durable event remains buffered; the Web Session publishes that event when it arrives because the baseline's exact `legacyChunkSeq` proves its matching frame. A final message arriving after an active opening remains staged until the matching `end.index` and ordered provenance arrive; an earlier retry at the same Turn and Step remains visible. Revision, dense-index, or provenance gaps for a known attempt re-open follow and replace the baseline; an unknown attempt uses the durable fallback. The TypeScript and Python SDK protocols do not expose these frames. The durable log remains the source of replay and model history.
+`dsh-agent-loop` emits scoped `agent/assistant-stream` frames for each model attempt. `start`, `chunk`, and `end` carry a branded `LlmAttemptId` unique within one Agent lifecycle; every emitted frame advances one revision local to that lifecycle. The `start` frame names the attempt's turn and step, chunk indexes are dense from zero, and `end.index` equals the next chunk position. Stream acquisition and its final cancellation check occur before `start`; a failure there emits no frame, while every started attempt emits a terminal `end`. The loop appends every v1 `assistant/chunk` before its matching live chunk frame, records that exact `legacyChunkSeq`, and appends the final `assistant/message` before a committed end frame. The existing authenticated Session-follow accepts an explicit Web opt-in, opens with a cached active-attempt baseline, and carries durable events and cursorless frames in one FIFO. Each follower captures a local arrival ordinal with the opening baseline and drops buffered frames at or before that cut; frame revisions can restart at one with a replacement Agent, so they do not define the opening cut. If the durable opening snapshot precedes the Assistant baseline, a baseline may already acknowledge a chunk whose durable event remains buffered; the Web Session publishes that event when it arrives because the baseline's exact `legacyChunkSeq` proves its matching frame. A final message arriving after an active opening remains staged until the matching `end.index` and ordered provenance arrive; an earlier retry at the same Turn and Step remains visible. Revision, dense-index, or provenance gaps for a known attempt re-open follow and replace the baseline; an unknown attempt uses the durable fallback. The TypeScript and Python SDK protocols do not expose these frames. The durable log remains the source of replay and model history.
 
 ## Alternatives considered
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-31-live-assistant-stream-frames.zh.md

@@ -10,7 +10,7 @@ Session log 保留每个 `assistant/chunk`,因此重放、冷读、遥测和
 
 ## 决定
 
-`dsh-agent-loop` 为每次模型尝试发出作用域内的 `agent/assistant-stream` 帧。`start`、`chunk` 和 `end` 带有在单个 Agent lifecycle 内唯一的 branded `LlmAttemptId`;每个已发出的帧都会推进一次该 lifecycle 本地 revision。`start` 帧会把壁钟时间捕获为安全整数 `startedTime`,chunk index 从零开始连续递增,`end.index` 等于下一个 chunk 位置。循环会先取得 stream 并执行最终取消检查,再发出 `start`;这些步骤失败时不发出任何帧,而每个已开始的尝试都会发出终态 `end`。循环在匹配的实时 chunk 帧之前追加每个 v1 `assistant/chunk`,记录精确的 `legacyChunkSeq`,并在已提交的 end 帧之前追加最终 `assistant/message`。现有的已认证 Session-follow 接受显式 Web opt-in,以缓存的活跃尝试 baseline 打开,并在一个 FIFO 中携带持久事件和无 cursor 的帧。每个 follower 会随 opening baseline 捕获本地到达序号,并丢弃该 cut 及之前的 buffered frame;replacement Agent 的 frame revision 可以从一重新开始,因此 revision 不定义 opening cut。如果持久 opening snapshot 早于 Assistant baseline,baseline 可能已经确认一个持久事件仍在 buffer 中的 chunk;该事件到达时,Web Session 会依据 baseline 中精确的 `legacyChunkSeq` 已证明其匹配帧而直接发布。活跃 opening 之后到达的最终 message 会保持暂存,直到匹配的 `end.index` 与有序来源到达;同一 Turn 和 Step 中更早的 retry 仍保持可见。已知 attempt 的 revision、连续 index 或来源缺口会重新打开 follow 并替换 baseline;unknown attempt 使用持久回退。TypeScript 和 Python SDK 协议不公开这些帧。持久 log 仍然是重放和模型历史的真源。
+`dsh-agent-loop` 为每次模型尝试发出作用域内的 `agent/assistant-stream` 帧。`start`、`chunk` 和 `end` 带有在单个 Agent lifecycle 内唯一的 branded `LlmAttemptId`;每个已发出的帧都会推进一次该 lifecycle 本地 revision。`start` 帧给出该尝试的 turn 与 step,chunk index 从零开始连续递增,`end.index` 等于下一个 chunk 位置。循环会先取得 stream 并执行最终取消检查,再发出 `start`;这些步骤失败时不发出任何帧,而每个已开始的尝试都会发出终态 `end`。循环在匹配的实时 chunk 帧之前追加每个 v1 `assistant/chunk`,记录精确的 `legacyChunkSeq`,并在已提交的 end 帧之前追加最终 `assistant/message`。现有的已认证 Session-follow 接受显式 Web opt-in,以缓存的活跃尝试 baseline 打开,并在一个 FIFO 中携带持久事件和无 cursor 的帧。每个 follower 会随 opening baseline 捕获本地到达序号,并丢弃该 cut 及之前的 buffered frame;replacement Agent 的 frame revision 可以从一重新开始,因此 revision 不定义 opening cut。如果持久 opening snapshot 早于 Assistant baseline,baseline 可能已经确认一个持久事件仍在 buffer 中的 chunk;该事件到达时,Web Session 会依据 baseline 中精确的 `legacyChunkSeq` 已证明其匹配帧而直接发布。活跃 opening 之后到达的最终 message 会保持暂存,直到匹配的 `end.index` 与有序来源到达;同一 Turn 和 Step 中更早的 retry 仍保持可见。已知 attempt 的 revision、连续 index 或来源缺口会重新打开 follow 并替换 baseline;unknown attempt 使用持久回退。TypeScript 和 Python SDK 协议不公开这些帧。持久 log 仍然是重放和模型历史的真源。
 
 ## 曾考虑的替代方案
 

+ 2 - 2
docs/event-producer-consumer.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
-event-producer-consumer.md: f411abc7d72ea64e55203052adcc6f3a249cfb34
-event-producer-consumer.zh.md: 24afb48478605d08d800b92d67dda040805812cf
+event-producer-consumer.md: 69e83ec5c8e66bb24f6cd721789233c82b5d3a55
+event-producer-consumer.zh.md: 69b0001993e670d11cec5bababa956b54bcb8843

+ 18 - 18
docs/event-producer-consumer.md

@@ -9,24 +9,24 @@ This matrix shows which packages dispatch each harness-owned event and which pac
 | --- | --- | --- | --- | --- |
 | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:246`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
 | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:80`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `remotes` |
-| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:313`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `session-controller` |
-| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:202`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:211`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:343`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) |
-| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:240`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
-| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:248`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
-| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:229`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
-| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:274`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:287`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) |
-| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:303`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) |
-| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `agent-team`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
-| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:221`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server`, `session-controller` |
-| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:331`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
-| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:592`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:572`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:599`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:578`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:585`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `session-controller` |
+| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:200`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:209`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:341`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) |
+| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:238`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
+| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:246`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
+| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:227`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
+| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:272`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:285`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) |
+| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:301`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) |
+| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:258`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `agent-team`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
+| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:219`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server`, `session-controller` |
+| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:329`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
+| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:589`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:569`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:596`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:575`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:582`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
 | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:85`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
 | `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) |
 | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:81`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |

+ 18 - 18
docs/event-producer-consumer.zh.md

@@ -11,24 +11,24 @@
 | --- | --- | --- | --- | --- |
 | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:246`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
 | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:80`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `remotes` |
-| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:313`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `session-controller` |
-| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:202`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:211`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:343`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) |
-| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:240`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
-| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:248`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
-| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:229`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
-| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:274`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) |
-| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:287`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) |
-| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:303`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) |
-| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `agent-team`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
-| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:221`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server`, `session-controller` |
-| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:331`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
-| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:592`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:572`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:599`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:578`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
-| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:585`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `session-controller` |
+| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:200`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:209`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:341`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) |
+| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:238`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
+| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:246`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
+| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:227`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
+| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:272`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) |
+| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:285`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) |
+| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:301`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) |
+| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:258`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `agent-team`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
+| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:219`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server`, `session-controller` |
+| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:329`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
+| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:589`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:569`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:596`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:575`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
+| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:582`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
 | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:85`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
 | `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) |
 | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:81`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |

+ 2 - 2
docs/subsystems/core.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/subsystems/core.md
-core.md: 7965e90bfb8641c8403663041694501b5575e64e
-core.zh.md: b2a68328fcdbf19cf1c031d8c56b716f73ab12c5
+core.md: c397c5a770626221d4cdfc69d29c3ea6d6d6dda5
+core.zh.md: e8296dad4c333779fec17e4c8464d2c4cc901103

+ 0 - 2
docs/subsystems/core.md

@@ -160,8 +160,6 @@ type AssistantStreamFrame =
     readonly attemptId: LlmAttemptId
     /** Monotone within one attached Agent lifecycle; replacement restarts at 1. */
     readonly revision: number
-    /** Safe-integer wall-clock time captured when this attempt started. */
-    readonly startedTime: number
     readonly turn: number
     readonly step: number
   }

+ 0 - 2
docs/subsystems/core.zh.md

@@ -164,8 +164,6 @@ type AssistantStreamFrame =
     readonly attemptId: LlmAttemptId
     /** Monotone within one attached Agent lifecycle; replacement restarts at 1. */
     readonly revision: number
-    /** Safe-integer wall-clock time captured when this attempt started. */
-    readonly startedTime: number
     readonly turn: number
     readonly step: number
   }

+ 2 - 2
packages/api/session-controller/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/api/session-controller/README.md
-README.md: 627b614bc89514cc26e8588e342a5ef076ab571b
-README.zh.md: ace5318a40b6f8673c5d0c671a7f6ad3fd5ac98d
+README.md: 29f94393709387a896fae8460e9bdbaba6aac2b3
+README.zh.md: 2d5c84ae914be9a6f908324544663018781da451

文件差异内容过多而无法显示
+ 0 - 0
packages/api/session-controller/README.md


文件差异内容过多而无法显示
+ 0 - 0
packages/api/session-controller/README.zh.md


+ 0 - 3
packages/api/session-controller/src/assistant-stream.ts

@@ -11,7 +11,6 @@ type ChunkFrame = Extract<AssistantStreamFrame, { type: 'chunk' }>
 
 interface MutableAttempt {
   readonly attemptId: SessionAssistantStreamAttempt['attemptId']
-  readonly startedTime: number
   readonly turn: number
   readonly step: number
   readonly chunks: ChunkFrame[]
@@ -50,7 +49,6 @@ export class SessionAssistantStreamAccumulator {
       case 'start':
         this.attempts.set(String(frame.attemptId), {
           attemptId: frame.attemptId,
-          startedTime: frame.startedTime,
           turn: frame.turn,
           step: frame.step,
           chunks: [],
@@ -84,7 +82,6 @@ export class SessionAssistantStreamAccumulator {
       revision: this.revision,
       attempts: [...this.attempts.values()].map(attempt => ({
         attemptId: attempt.attemptId,
-        startedTime: attempt.startedTime,
         turn: attempt.turn,
         step: attempt.step,
         chunks: attempt.chunks.map(frame => frame.chunk as JsonValue),

+ 0 - 3
packages/api/session-controller/src/client/sessions/assistant-stream.ts

@@ -10,7 +10,6 @@ import type {
 } from '../contract/events.ts'
 
 interface ActiveAttempt {
-  readonly startedTime: number
   readonly turn: number
   readonly step: number
   readonly legacyChunkSeqs: Set<number>
@@ -62,7 +61,6 @@ export class ClientAssistantStream {
     if (baseline !== undefined) {
       for (const attempt of baseline.attempts) {
         this.attempts.set(String(attempt.attemptId), {
-          startedTime: attempt.startedTime,
           turn: attempt.turn,
           step: attempt.step,
           legacyChunkSeqs: new Set(attempt.legacyChunkSeqs),
@@ -111,7 +109,6 @@ export class ClientAssistantStream {
     switch (frame.type) {
       case 'start':
         this.attempts.set(String(frame.attemptId), {
-          startedTime: frame.startedTime,
           turn: frame.turn,
           step: frame.step,
           legacyChunkSeqs: new Set(),

+ 0 - 3
packages/api/session-controller/src/types.ts

@@ -453,8 +453,6 @@ export interface SessionFollowRequest {
 /** One active assistant attempt in a reconnect opening snapshot. */
 export interface SessionAssistantStreamAttempt {
   readonly attemptId: LlmAttemptId
-  /** Safe-integer wall-clock time copied from the attempt's start frame. */
-  readonly startedTime: number
   readonly turn: number
   readonly step: number
   readonly chunks: readonly JsonValue[]
@@ -474,7 +472,6 @@ export type SessionAssistantStreamFrame =
     readonly type: 'start'
     readonly attemptId: LlmAttemptId
     readonly revision: number
-    readonly startedTime: number
     readonly turn: number
     readonly step: number
   }

+ 2 - 2
packages/api/session-controller/tests/assistant-stream.client.spec.ts

@@ -54,7 +54,7 @@ function message(
 }
 
 function start(attemptId = ATTEMPT): SessionAssistantStreamFrame {
-  return { type: 'start', attemptId, revision: 1, startedTime: 1, turn: 1, step: 1 }
+  return { type: 'start', attemptId, revision: 1, turn: 1, step: 1 }
 }
 
 function chunkFrame(
@@ -87,7 +87,7 @@ function baseline(
 ): SessionAssistantStreamBaseline {
   return {
     revision: chunks.length + 1,
-    attempts: [{ attemptId: ATTEMPT, startedTime: 1, turn: 1, step: 1, chunks, legacyChunkSeqs }],
+    attempts: [{ attemptId: ATTEMPT, turn: 1, step: 1, chunks, legacyChunkSeqs }],
   }
 }
 

+ 13 - 15
packages/api/session-controller/tests/session-history-journal.host.spec.ts

@@ -123,7 +123,7 @@ describe('Session history raw journal', () => {
       ctx.emit('agent/assistant-stream', { agent, frame })
     }
     emit({
-      type: 'start', attemptId, revision: 1, startedTime: 100,
+      type: 'start', attemptId, revision: 1,
       turn: 1, step: 1,
     })
     const firstChunk = session.append('assistant/chunk', {
@@ -147,7 +147,6 @@ describe('Session history raw journal', () => {
           revision: 2,
           attempts: [{
             attemptId,
-            startedTime: 100,
             turn: 1,
             step: 1,
             chunks: [firstChunk.data.chunk],
@@ -197,7 +196,7 @@ describe('Session history raw journal', () => {
     ctx.emit('agent/assistant-stream', {
       agent,
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 100,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })
@@ -226,7 +225,6 @@ describe('Session history raw journal', () => {
             revision: 2,
             attempts: [{
               attemptId,
-              startedTime: 100,
               turn: 1,
               step: 1,
               chunks: [oldChunk.data.chunk],
@@ -239,7 +237,7 @@ describe('Session history raw journal', () => {
       ctx.emit('agent/disposed', { agent })
       const replacementAgent = { id: session.id, session, status: 'running', ctx } as Agent
       const replacement: AssistantStreamFrame = {
-        type: 'start', attemptId, revision: 1, startedTime: 200,
+        type: 'start', attemptId, revision: 1,
         turn: 2, step: 1,
       }
       ctx.emit('agent/assistant-stream', { agent: replacementAgent, frame: replacement })
@@ -261,7 +259,7 @@ describe('Session history raw journal', () => {
     ctx.emit('agent/assistant-stream', {
       agent,
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 100,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })
@@ -303,7 +301,7 @@ describe('Session history raw journal', () => {
     ctx.emit('agent/assistant-stream', {
       agent,
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 100,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })
@@ -345,7 +343,7 @@ describe('Session history raw journal', () => {
     ctx.emit('agent/assistant-stream', {
       agent,
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 100,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })
@@ -421,7 +419,7 @@ describe('Session history raw journal', () => {
         agent: otherAgent,
         frame: {
           type: 'start', attemptId: LlmAttemptId('other-session-attempt'),
-          revision: 1, startedTime: 100, turn: 1, step: 1,
+          revision: 1, turn: 1, step: 1,
         },
       })
       const targetEvent = target.append('turn/start', { turn: 1 })
@@ -458,7 +456,7 @@ describe('Session history raw journal', () => {
       await observationStarted.promise
       const frame: AssistantStreamFrame = {
         type: 'start', attemptId: LlmAttemptId('opening-cut-attempt'),
-        revision: 1, startedTime: 100, turn: 1, step: 1,
+        revision: 1, turn: 1, step: 1,
       }
       ctx.emit('agent/assistant-stream', { agent, frame })
       releaseObservation.resolve(undefined)
@@ -493,7 +491,7 @@ describe('Session history raw journal', () => {
     ctx.emit('agent/assistant-stream', {
       agent,
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 100,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })
@@ -561,7 +559,7 @@ describe('Session history raw journal', () => {
     ctx.emit('agent/assistant-stream', {
       agent,
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 100,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })
@@ -595,7 +593,7 @@ describe('Session history raw journal', () => {
       ctx.emit('agent/assistant-stream', {
         agent,
         frame: {
-          type: 'start', attemptId, revision: 1, startedTime: 200,
+          type: 'start', attemptId, revision: 1,
           turn: 2, step: 1,
         },
       })
@@ -606,7 +604,7 @@ describe('Session history raw journal', () => {
           type: 'snapshot',
           assistantStream: {
             revision: 1,
-            attempts: [{ attemptId, startedTime: 200, turn: 2, step: 1, chunks: [] }],
+            attempts: [{ attemptId, turn: 2, step: 1, chunks: [] }],
           },
         },
       })
@@ -641,7 +639,7 @@ describe('Session history raw journal', () => {
     ctx.emit('agent/assistant-stream', {
       agent,
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 200,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })

+ 4 - 6
packages/api/session-controller/tests/sessions-service.client.spec.ts

@@ -177,7 +177,7 @@ describe('scope tree', () => {
     await b.api.pushFollow(sid('s1'), {
       type: 'assistant-stream',
       frame: {
-        type: 'start', attemptId, revision: 1, startedTime: 1,
+        type: 'start', attemptId, revision: 1,
         turn: 1, step: 1,
       },
     })
@@ -241,7 +241,7 @@ describe('scope tree', () => {
     b.api.assistantStreamBaseline = {
       revision: 2,
       attempts: [{
-        attemptId, startedTime: 1, turn: 1, step: 1,
+        attemptId, turn: 1, step: 1,
         chunks: [first.event.data.chunk], legacyChunkSeqs: [0],
       }],
     }
@@ -257,7 +257,7 @@ describe('scope tree', () => {
     b.api.assistantStreamBaseline = {
       revision: 3,
       attempts: [{
-        attemptId, startedTime: 1, turn: 1, step: 1,
+        attemptId, turn: 1, step: 1,
         chunks: [first.event.data.chunk, second.event.data.chunk],
         legacyChunkSeqs: [0, 1],
       }],
@@ -286,7 +286,7 @@ describe('scope tree', () => {
     b.api.assistantStreamBaseline = {
       revision: 2,
       attempts: [{
-        attemptId, startedTime: 1, turn: 1, step: 1,
+        attemptId, turn: 1, step: 1,
         chunks: [chunk.event.data.chunk], legacyChunkSeqs: [0],
       }],
     }
@@ -366,7 +366,6 @@ describe('scope tree', () => {
       revision: 2,
       attempts: [{
         attemptId,
-        startedTime: 20,
         turn: 1,
         step: 1,
         chunks: [currentChunk.event.data.chunk],
@@ -441,7 +440,6 @@ describe('scope tree', () => {
       revision: 2,
       attempts: [{
         attemptId,
-        startedTime: 20,
         turn: 1,
         step: 1,
         chunks: [chunk.event.data.chunk],

+ 3 - 7
packages/api/session-controller/tests/transport.client.spec.ts

@@ -156,7 +156,6 @@ describe('Session Client stream adapters', () => {
       revision: 2,
       attempts: [{
         attemptId,
-        startedTime: 1,
         turn: 1,
         step: 1,
         chunks: [{ type: 'text-delta', index: 0, text: 'a' }],
@@ -222,7 +221,7 @@ describe('Session Client stream adapters', () => {
     const remote = new ScriptedSessionRemote([{
       frames: [assistantFrame({
         type: 'start', attemptId: LlmAttemptId('pre-opening-attempt'),
-        revision: 1, startedTime: 1, turn: 1, step: 1,
+        revision: 1, turn: 1, step: 1,
       })],
     }], [])
     const stream = new SessionEventStream(sessionClient(remote), ADDRESS, {
@@ -244,7 +243,7 @@ describe('Session Client stream adapters', () => {
   it('rebaselines after a transient assistant revision gap without advancing the durable cursor', async () => {
     const attemptId = LlmAttemptId('gapped-attempt')
     const start: SessionAssistantStreamFrame = {
-      type: 'start', attemptId, revision: 1, startedTime: 1,
+      type: 'start', attemptId, revision: 1,
       turn: 1, step: 1,
     }
     const gap: SessionAssistantStreamFrame = {
@@ -256,7 +255,6 @@ describe('Session Client stream adapters', () => {
       revision: 3,
       attempts: [{
         attemptId,
-        startedTime: 1,
         turn: 1,
         step: 1,
         chunks: [gap.chunk],
@@ -299,7 +297,6 @@ describe('Session Client stream adapters', () => {
       revision: 2,
       attempts: [{
         attemptId,
-        startedTime: 1,
         turn: 1,
         step: 1,
         chunks: [{ type: 'text-delta', index: 0, text: 'old' }],
@@ -307,14 +304,13 @@ describe('Session Client stream adapters', () => {
       }],
     }
     const replacementStart: SessionAssistantStreamFrame = {
-      type: 'start', attemptId, revision: 1, startedTime: 2,
+      type: 'start', attemptId, revision: 1,
       turn: 2, step: 1,
     }
     const replacement: SessionAssistantStreamBaseline = {
       revision: 1,
       attempts: [{
         attemptId,
-        startedTime: 2,
         turn: 2,
         step: 1,
         chunks: [],

+ 0 - 1
packages/core/agent-loop/src/assistant-stream.ts

@@ -40,7 +40,6 @@ export class AssistantStreamAttempt {
       type: 'start',
       attemptId: this.attemptId,
       revision: this.nextRevision(),
-      startedTime: Date.now(),
       turn: this.turn,
       step: this.step,
     })

+ 0 - 2
packages/core/agent-loop/tests/loop.spec.ts

@@ -79,8 +79,6 @@ describe('agent loop', () => {
     expect(frames.at(0)?.type).toBe('start')
     const start = frames.at(0)
     if (start?.type === 'start') {
-      expect(typeof start.startedTime).toBe('number')
-      expect(Number.isSafeInteger(start.startedTime)).toBe(true)
     }
     expect(frames.at(-1)?.type).toBe('end')
     expect(frames.map(frame => frame.revision)).toEqual(

+ 2 - 2
packages/core/agent/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/core/agent/README.md
-README.md: 11218f796675d4361e507ab4305c926c2756edb2
-README.zh.md: e15f2fd8295a5891c69814a3e02beff1e2c2b5a1
+README.md: 941a6de532b4c92118a747f300af4428608d73db
+README.zh.md: 959c61a79adda85960aa2bce6814dd71b8f88a71

+ 1 - 1
packages/core/agent/README.md

@@ -64,7 +64,7 @@ await handle.agent.whenIdle()
 
 ### Intercept or observe work in flight
 
-The `agent/*` events let plugins act on live work without depending on the loop package. `agent/pre-step` can reject a proposed step or replace the messages entering it; `agent/request-error` lets a listener retry a failed model request; `agent/turn-stopping` runs before an otherwise completed turn closes and can steer to keep it open. `agent/assistant-stream` carries one process-local assistant attempt's ordered frames after their durable v1 records append; `start` records the attempt's safe-integer wall-clock `startedTime`, chunk indexes are dense from zero, and `end.index` is the next chunk position. These frames are presentation data, not a replay source. `agent/status`, `agent/created`, and `agent/disposed` drive UI and coordination state, and the per-message `agent/inbox/*` notifications keep inbox projections in sync. Exact signatures, dispatch modes, and payload contracts live in the generated region of the [core subsystem page](../../../docs/subsystems/core.md#cordis-surface).
+The `agent/*` events let plugins act on live work without depending on the loop package. `agent/pre-step` can reject a proposed step or replace the messages entering it; `agent/request-error` lets a listener retry a failed model request; `agent/turn-stopping` runs before an otherwise completed turn closes and can steer to keep it open. `agent/assistant-stream` carries one process-local assistant attempt's ordered frames after their durable v1 records append; `start` names the attempt's turn and step, chunk indexes are dense from zero, and `end.index` is the next chunk position. These frames are presentation data, not a replay source. `agent/status`, `agent/created`, and `agent/disposed` drive UI and coordination state, and the per-message `agent/inbox/*` notifications keep inbox projections in sync. Exact signatures, dispatch modes, and payload contracts live in the generated region of the [core subsystem page](../../../docs/subsystems/core.md#cordis-surface).
 
 -----
 

+ 1 - 1
packages/core/agent/README.zh.md

@@ -64,7 +64,7 @@ await handle.agent.whenIdle()
 
 ### 拦截或观察进行中的工作
 
-`agent/*` 事件让插件无需依赖循环包即可作用于实时工作。`agent/pre-step` 可以拒绝拟进入的步骤或替换进入它的消息;`agent/request-error` 让监听器重试失败的模型请求;`agent/turn-stopping` 在本可完成的轮次关闭前运行,并可通过 steer 使其保持打开。`agent/assistant-stream` 携带一个进程本地 assistant 尝试在其持久 v1 记录追加后的有序帧;`start` 把该尝试的壁钟时间记录为安全整数 `startedTime`,chunk index 从零开始连续递增,`end.index` 则是下一个 chunk 位置。这些帧是呈现数据,不是重放来源。`agent/status`、`agent/created` 与 `agent/disposed` 驱动 UI 与协调状态,逐消息的 `agent/inbox/*` 通知则让收件箱投影保持同步。确切签名、分发 mode 与 payload 约定见 [core 子系统页](../../../docs/subsystems/core.zh.md#cordis-surface) 的生成区块。
+`agent/*` 事件让插件无需依赖循环包即可作用于实时工作。`agent/pre-step` 可以拒绝拟进入的步骤或替换进入它的消息;`agent/request-error` 让监听器重试失败的模型请求;`agent/turn-stopping` 在本可完成的轮次关闭前运行,并可通过 steer 使其保持打开。`agent/assistant-stream` 携带一个进程本地 assistant 尝试在其持久 v1 记录追加后的有序帧;`start` 给出该尝试的 turn 与 step,chunk index 从零开始连续递增,`end.index` 则是下一个 chunk 位置。这些帧是呈现数据,不是重放来源。`agent/status`、`agent/created` 与 `agent/disposed` 驱动 UI 与协调状态,逐消息的 `agent/inbox/*` 通知则让收件箱投影保持同步。确切签名、分发 mode 与 payload 约定见 [core 子系统页](../../../docs/subsystems/core.zh.md#cordis-surface) 的生成区块。
 
 -----
 

+ 0 - 2
packages/core/agent/src/runtime-types.ts

@@ -77,8 +77,6 @@ export type AssistantStreamFrame =
     readonly attemptId: LlmAttemptId
     /** Monotone within one attached Agent lifecycle; replacement restarts at 1. */
     readonly revision: number
-    /** Safe-integer wall-clock time captured when this attempt started. */
-    readonly startedTime: number
     readonly turn: number
     readonly step: number
   }

+ 1 - 2
packages/core/scope/tests/invariant.spec.ts

@@ -57,8 +57,7 @@ describe('scoped-dispatch invariants', () => {
       'agent/assistant-stream': [{
         agent,
         frame: {
-          type: 'start', attemptId: 'attempt-1' as never, revision: 1,
-          startedTime: 1, turn: 1, step: 1,
+          type: 'start', attemptId: 'attempt-1' as never, revision: 1, turn: 1, step: 1,
         },
       }],
       'agent/request-error': [

+ 3 - 3
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -3512,7 +3512,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
   },
   {
     name: 'AssistantStreamFrame',
-    declaration: 'export type AssistantStreamFrame = {\n    readonly type: \'start\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly startedTime: number;\n    readonly turn: number;\n    readonly step: number;\n} | {\n    readonly type: \'chunk\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly chunk: StreamChunk;\n    readonly legacyChunkSeq: SessionSeq;\n} | {\n    readonly type: \'end\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly outcome: \'committed\' | \'aborted\';\n    readonly legacyChunkSeqs: readonly SessionSeq[];\n};',
+    declaration: 'export type AssistantStreamFrame = {\n    readonly type: \'start\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly turn: number;\n    readonly step: number;\n} | {\n    readonly type: \'chunk\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly chunk: StreamChunk;\n    readonly legacyChunkSeq: SessionSeq;\n} | {\n    readonly type: \'end\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly outcome: \'committed\' | \'aborted\';\n    readonly legacyChunkSeqs: readonly SessionSeq[];\n};',
   },
   {
     name: 'AttachmentId',
@@ -4784,7 +4784,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
   },
   {
     name: 'SessionAssistantStreamAttempt',
-    declaration: 'export interface SessionAssistantStreamAttempt {\n    readonly attemptId: LlmAttemptId;\n    readonly startedTime: number;\n    readonly turn: number;\n    readonly step: number;\n    readonly chunks: readonly JsonValue[];\n    readonly legacyChunkSeqs: readonly number[];\n}',
+    declaration: 'export interface SessionAssistantStreamAttempt {\n    readonly attemptId: LlmAttemptId;\n    readonly turn: number;\n    readonly step: number;\n    readonly chunks: readonly JsonValue[];\n    readonly legacyChunkSeqs: readonly number[];\n}',
   },
   {
     name: 'SessionAssistantStreamBaseline',
@@ -4792,7 +4792,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
   },
   {
     name: 'SessionAssistantStreamFrame',
-    declaration: 'export type SessionAssistantStreamFrame = {\n    readonly type: \'start\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly startedTime: number;\n    readonly turn: number;\n    readonly step: number;\n} | {\n    readonly type: \'chunk\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly chunk: JsonValue;\n    readonly legacyChunkSeq: number;\n} | {\n    readonly type: \'end\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly outcome: \'committed\' | \'aborted\';\n    readonly legacyChunkSeqs: readonly number[];\n};',
+    declaration: 'export type SessionAssistantStreamFrame = {\n    readonly type: \'start\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly turn: number;\n    readonly step: number;\n} | {\n    readonly type: \'chunk\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly chunk: JsonValue;\n    readonly legacyChunkSeq: number;\n} | {\n    readonly type: \'end\';\n    readonly attemptId: LlmAttemptId;\n    readonly revision: number;\n    readonly index: number;\n    readonly outcome: \'committed\' | \'aborted\';\n    readonly legacyChunkSeqs: readonly number[];\n};',
   },
   {
     name: 'SessionAttachmentRequest',

部分文件因为文件数量过多而无法显示