diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml index cb4a14f421..ef4f8d14d9 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.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-07-31-claimed-pre-step-inbox-lifecycle.md -2026-07-31-claimed-pre-step-inbox-lifecycle.md: 06a806375b5f4fe67271ebab842e9e27d5e4824d -2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md: ad6da9285a5a9f6882b887f2c4e62903ae2b0f09 +2026-07-31-claimed-pre-step-inbox-lifecycle.md: ed56723ab8dcaaba9fbcd8e4c52ecc264141df2e +2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md: aecfece99c49a31d429fc3229432911dc4b83f82 diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md index 06a806375b..ed56723ab8 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md +++ b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md @@ -14,7 +14,7 @@ Occurrence-local inbox wrappers also duplicated the identity already carried by Before every proposed step, `Inbox.claim(target)` atomically removes the complete batch: all `next-step` messages and, at a turn boundary, one `next-turn` message. At the initial boundary the loop first commits `turn/start`, so the claim and its single `agent/pre-step` decision have durable turn ownership. Claiming records normalized `agent/inbox/spliced` pure deletions with no outcome. The loop then emits `agent/inbox/claimed { message, turn }` once per claimed message and awaits the waterfall with that exclusive batch and `{ turn, step, signal }`. -`PreStepDecision` is `{ kind: 'reject' } | { kind: 'enter'; messages: UserMessage[] }`. Reject opens no step, leaves the claimed batch removed, and closes the turn as blocked with `step: 0`. Empty entry, cancellation, and failure before `step/start` likewise close a balanced no-step turn. Enter supplies the complete batch appended as `user/message` events after `step/start`. A listener wrapping `next()` preserves downstream changes unless it intentionally replaces them, so all message rewrites settle once in the final return value. There is no `agent/prompt-prepare`, `agent/prompt-submit`, or `agent/step` seam. +`PreStepDecision` is `{ kind: 'reject' } | { kind: 'enter'; messages: UserMessage[] }`. Reject opens no step, leaves the claimed batch removed, and closes the turn as blocked without any step events. Empty entry, cancellation, and failure before `step/start` likewise close a balanced no-step turn. Enter supplies the complete batch appended as `user/message` events after `step/start`. A listener wrapping `next()` preserves downstream changes unless it intentionally replaces them, so all message rewrites settle once in the final return value. There is no `agent/prompt-prepare`, `agent/prompt-submit`, or `agent/step` seam. The durable inbox remains two `UserMessage[]` lists addressed by `MessageId`. `append`, `prepend`, and `splice` take a target, while `replace(messageId, newMessage)` and `remove(messageId)` locate the pending message across both lists before committing a normalized splice. Replacement may change identity and emits the old message as discarded followed by the new message as inserted. Every insertion emits `agent/inbox/inserted { message }`; an ordinary removal records `outcome: 'canceled'` and emits `agent/inbox/discarded { message }`. These live events add no placement, outcome, or batch fields. diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md index ad6da9285a..aecfece99c 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md @@ -14,7 +14,7 @@ Status: implemented 每个拟议步骤之前,`Inbox.claim(target)` 会原子移除完整批次:全部 `next-step` 消息,以及轮次边界上的一条 `next-turn` 消息。在首次边界,循环会先提交 `turn/start`,使领取及其唯一一次 `agent/pre-step` 决策拥有持久轮次归属。领取会记录规范化、不带 outcome 的纯删除 `agent/inbox/spliced`。随后,循环针对每条已领取消息发出一次 `agent/inbox/claimed { message, turn }`,并用该独占批次与 `{ turn, step, signal }` 等待 waterfall(瀑布式事件)。 -`PreStepDecision` 为 `{ kind: 'reject' } | { kind: 'enter'; messages: UserMessage[] }`。reject 不会打开步骤,会让已领取批次保持已删除,并以 `step: 0` 将轮次关闭为 blocked。空的 enter、取消以及 `step/start` 前的失败同样会关闭一个边界平衡的无步骤轮次。enter 提供在 `step/start` 后以 `user/message` 追加的完整批次。包装 `next()` 的监听器会保留下游变更,除非有意替换,因此全部消息改写只在最终返回值中一次性结算。系统不再存在 `agent/prompt-prepare`、`agent/prompt-submit` 或 `agent/step` seam。 +`PreStepDecision` 为 `{ kind: 'reject' } | { kind: 'enter'; messages: UserMessage[] }`。reject 不会打开步骤,会让已领取批次保持已删除,并将轮次关闭为 blocked,且不产生任何步骤事件。空的 enter、取消以及 `step/start` 前的失败同样会关闭一个边界平衡的无步骤轮次。enter 提供在 `step/start` 后以 `user/message` 追加的完整批次。包装 `next()` 的监听器会保留下游变更,除非有意替换,因此全部消息改写只在最终返回值中一次性结算。系统不再存在 `agent/prompt-prepare`、`agent/prompt-submit` 或 `agent/step` seam。 持久 inbox 仍是两份通过 `MessageId` 寻址的 `UserMessage[]` 列表。`append`、`prepend` 与 `splice` 接受 target;`replace(messageId, newMessage)` 与 `remove(messageId)` 则在提交规范化 splice 前,通过 `MessageId` 跨两份列表定位待处理消息。替换可以改变标识,并先将旧消息作为 discarded 发布,再将新消息作为 inserted 发布。每次插入发出 `agent/inbox/inserted { message }`;普通删除记录 `outcome: 'canceled'` 并发出 `agent/inbox/discarded { message }`。这些实时事件不增加 placement、outcome 或批次字段。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.i18n.yaml index c2a49d6b53..02ea7ca431 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.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/bug-fix/2026-08-04-load-pre-react-loop-sessions.md -2026-08-04-load-pre-react-loop-sessions.md: da204822251ef646b982683e74413a001a19873a -2026-08-04-load-pre-react-loop-sessions.zh.md: 1c2818fd3afbefb3af91f53514b9cfc2baa01739 +2026-08-04-load-pre-react-loop-sessions.md: b7dcad1ff7fe0aa8239ac03f52b50dfa55417515 +2026-08-04-load-pre-react-loop-sessions.zh.md: 14c6dcc7b4da016ebef424a66fae0b96fb469cd4 diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md b/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md index da20482225..b7dcad1ff7 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md +++ b/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md @@ -6,21 +6,21 @@ English | [中文](2026-08-04-load-pre-react-loop-sessions.zh.md) ## Problem -The react-loop simplification changed durable events while retaining `SESSION_FORMAT_VERSION` 0. Stored sessions from the change's base contain `steering/message`, a `turn/start.trigger`, and `turn/end` records without the current top-level `step`; their terminal reasons also use coarse `aborted`, separate `disposed`, and two older error payloads. Current surface and turn invariants cannot replay those records directly. +The react-loop simplification changed durable events while retaining `SESSION_FORMAT_VERSION` 0. Stored sessions from the change's base contain `steering/message` and `turn/start.trigger`; their terminal reasons also use coarse `aborted`, separate `disposed`, and two older error payloads. Current surface and turn invariants cannot replay those records directly. The new durable inbox is not part of this compatibility problem. The base emitted process-local inbox notifications but no `agent/inbox/*` session events, so replaying old history as pending work would resurrect already claimed or discarded prompts. ## Decision -`PersistenceCoordinator` recognizes the exact pre-react-loop shapes after backend decoding and projects them into the current read view. It removes the obsolete `turn/start.trigger`, converts `steering/message` to the same identified `user/message`, adds the last entered step to `turn/end`, maps old failure facts into the current structured error, folds `disposed` into an aborted turn with the `disposed` cause, and represents coarse aborted records with the persistence-only `{ kind: 'legacy' }` cause because their caller is unavailable. +`PersistenceCoordinator` recognizes the exact pre-react-loop shapes after backend decoding and projects them into the current read view. It removes the obsolete `turn/start.trigger`, converts `steering/message` to the same identified `user/message`, maps old failure facts into the current structured error, folds `disposed` into an aborted turn with the `disposed` cause, and represents coarse aborted records with the persistence-only `{ kind: 'legacy' }` cause because their caller is unavailable. -The coordinator applies the projection to `load`, `inspect`, adoption, HMR prefix comparison, and `readFrom`. A seek-capable `readFrom` normally reads only its suffix; when that suffix contains a legacy event needing an earlier step or replacement identity, the coordinator loads and normalizes the complete prefix before returning the requested seq range. +The coordinator applies the projection to `load`, `inspect`, adoption, HMR prefix comparison, and `readFrom`. A seek-capable `readFrom` normally reads only its suffix; when that suffix contains a legacy event needing an earlier replacement identity, the coordinator loads and normalizes the complete prefix before returning the requested seq range. The importer does not synthesize inbox splices. A resumed pre-react-loop agent begins with empty pending lists, matching the base runtime's inability to persist pending inbox work. The stored artifact remains append-only and later events use the current format. ## Alternatives considered -**Treat the same-version records as unsupported.** This follows the pre-release default but strands sessions produced by the PR base even though the removed steering content, turn step, and terminal facts have complete mappings. +**Treat the same-version records as unsupported.** This follows the pre-release default but strands sessions produced by the PR base even though the removed steering content and terminal facts have complete mappings. **Replay old inbox notifications into durable splices.** Those notifications were not session events and do not provide a trustworthy pending-state snapshot. Inferring insertions without every claim and discard would re-run consumed work. diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.zh.md b/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.zh.md index 1c2818fd3a..14c6dcc7b4 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.zh.md @@ -6,21 +6,21 @@ Status: implemented ## 问题 -react-loop 简化在保持 `SESSION_FORMAT_VERSION` 为 0 的同时更改了持久事件。该变更基线所存储的会话包含 steering(中途引导)事件 `steering/message`、`turn/start.trigger`,以及缺少当前顶层 `step` 的 `turn/end` 记录;其终止原因还使用粗粒度 `aborted`、独立的 `disposed` 和两种旧版错误载荷。当前 surface 和轮次不变量无法直接回放这些记录。 +react-loop 简化在保持 `SESSION_FORMAT_VERSION` 为 0 的同时更改了持久事件。该变更基线所存储的会话包含 steering(中途引导)事件 `steering/message` 和 `turn/start.trigger`;其终止原因还使用粗粒度 `aborted`、独立的 `disposed` 和两种旧版错误载荷。当前 surface 和轮次不变量无法直接回放这些记录。 新的持久 inbox 不属于此兼容性问题。该基线会发出进程本地 inbox 通知,但不会产生 `agent/inbox/*` 会话事件,因此将旧历史回放为待处理工作会让已经领取或丢弃的提示词再次执行。 ## 决策 -`PersistenceCoordinator` 会在后端解码后识别 react-loop 重构前的确切形状,并将其投影为当前读取视图。它移除已废弃的 `turn/start.trigger`,把 `steering/message` 转换为同一条带标识的 `user/message`,为 `turn/end` 补上最后进入的步骤,将旧版失败事实映射为当前结构化错误,把 `disposed` 折叠为带 `disposed` 原因的已中止轮次,并用仅供持久化导入使用的 `{ kind: 'legacy' }` 原因表示粗粒度中止记录,因为无法获得其调用方。 +`PersistenceCoordinator` 会在后端解码后识别 react-loop 重构前的确切形状,并将其投影为当前读取视图。它移除已废弃的 `turn/start.trigger`,把 `steering/message` 转换为同一条带标识的 `user/message`,将旧版失败事实映射为当前结构化错误,把 `disposed` 折叠为带 `disposed` 原因的已中止轮次,并用仅供持久化导入使用的 `{ kind: 'legacy' }` 原因表示粗粒度中止记录,因为无法获得其调用方。 -协调器会把该投影应用于 `load`、`inspect`、接管、HMR 前缀比较和 `readFrom`。可寻址的 `readFrom` 通常只读取后缀;如果后缀包含需要更早步骤或替换标识的旧版事件,协调器会先加载并规范化完整前缀,再返回所请求的 seq 范围。 +协调器会把该投影应用于 `load`、`inspect`、接管、HMR 前缀比较和 `readFrom`。可寻址的 `readFrom` 通常只读取后缀;如果后缀包含需要更早替换标识的旧版事件,协调器会先加载并规范化完整前缀,再返回所请求的 seq 范围。 导入器不会合成 inbox splice。恢复后的 react-loop 重构前 agent 从空的待处理列表开始,这与基线运行时无法持久化待处理 inbox 工作的行为一致。已存储产物仍然仅追加,后续事件使用当前格式。 ## 考虑过的替代方案 -**将同版本记录视为不受支持。** 这符合预发布阶段的默认立场,但会使 PR 基线产生的会话无法恢复,尽管已移除的 steering 内容、轮次步骤和终止事实都有完整映射。 +**将同版本记录视为不受支持。** 这符合预发布阶段的默认立场,但会使 PR 基线产生的会话无法恢复,尽管已移除的 steering 内容和终止事实都有完整映射。 **将旧 inbox 通知回放为持久 splice。** 这些通知不是会话事件,也无法提供可信的待处理状态快照。如果无法获知每一次领取和丢弃,就推断插入操作,会让已消费的工作再次执行。 diff --git a/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml index e8b93f7d34..4c0e59425e 100644 --- a/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.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/feature/2026-07-19-same-session-goal-round-driver.md -2026-07-19-same-session-goal-round-driver.md: 29f804ff9de5e69eb8af3063af1bed023fef1146 -2026-07-19-same-session-goal-round-driver.zh.md: bcee71e28c61dfd43b1b2b797503554b3397bd73 +2026-07-19-same-session-goal-round-driver.md: a989b9d3487ebbf8ff11a4e34cb08029f1e4ace4 +2026-07-19-same-session-goal-round-driver.zh.md: 6fb97a8beb477479adb37596708ff5cc7fa05dc6 diff --git a/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.md b/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.md index 29f804ff9d..a989b9d348 100644 --- a/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.md +++ b/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.md @@ -53,9 +53,9 @@ No abnormal outcome requests an automatic retry. A later human prompt can ask to Every `goal/changed` notification creates a checkpoint obligation. The driver awaits `ctx.sessions.flush(session)` before reserving work, then checks for a newer mutation, agent lifecycle change, or competing prompt. Turn-end flush failure is reported by the existing `agent/error` notification after `turn/end`; the driver finds that exact closed turn even when a concurrent one-shot injection appended a later turn, associates the failure with the exact attempt, and disarms before the next idle decision. -Broad cancellation previously exposed only its effects after queues were cleared or the request aborted. The public agent vocabulary now includes observe-only `agent/cancel-requested(agent, reason)`. The concrete loop emits it for effective cancellation before either action; fused notification containment means a broken listener cannot veto cancellation. The goal driver uses this edge to clear its reservation before the loop destroys the queued-work evidence. When that reservation is a queued or admitted goal attempt, cancellation durably pauses the goal; when cancellation belongs to unrelated human work with no goal attempt, it only removes process-local activation. If the pause mutation throws, the driver falls back to disarming rather than allowing cancelled automatic work to restart. +Broad cancellation clears pending inbox work and aborts the active loop phase. The goal driver follows the reserved message through inbox claim/discard events and the durable aborted turn ending. Because a turn now opens before its initial claim, cancellation can close a claimed no-step attempt; the driver marks that attempt cancelled and lets the following idle edge pause the goal, just as it does for an admitted attempt. Cancellation with no matching goal attempt only removes process-local activation. If the pause mutation throws, the driver falls back to disarming rather than allowing cancelled automatic work to restart. -This is a coordination notification, not a second stop API. `Agent.cancel()` remains the only public broad cancellation verb, idle calls remain no-ops, and custom `Agent` implementations that claim the interface must honor the event ordering if consumers depend on it. +`Agent.cancel()` remains the only public broad cancellation verb. Custom `Agent` implementations that claim the interface must honor the inbox, turn-ending, status, and quiescence ordering if consumers depend on it. ### Process lifecycle diff --git a/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.zh.md b/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.zh.md index bcee71e28c..6fb97a8beb 100644 --- a/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.zh.md @@ -53,9 +53,9 @@ Status: implemented 每次 `goal/changed` 通知都会产生一个检查点义务。驱动器在预留工作前等待 `ctx.sessions.flush(session)`,随后检查是否出现了更新的变更、agent 生命周期变化或竞争提示词。轮次结束时的 flush 失败会在 `turn/end` 之后通过现有 `agent/error` 通知报告;即使并发的一次性注入已追加后续轮次,驱动器仍会找到该精确的已关闭轮次,把失败关联到精确尝试,并在下一次空闲决策前解除激活。 -广义取消此前只在队列已清除或请求已中止后暴露结果。公共 agent 词汇现在新增只观察的 `agent/cancel-requested(agent, reason)`。具体循环仅在取消有效时发出该事件,并且发生在清除队列和中止步骤之前;融合通知会隔离失败,因此损坏的监听器不能否决取消。目标驱动器利用该边沿在循环销毁排队工作证据前清除预留。若该预留是排队中或已接纳的目标尝试,取消会持久暂停目标;若取消属于没有目标尝试的无关人类工作,则只移除进程内激活态。若暂停变更抛错,驱动器会回退到解除激活,避免已取消的自动工作重新启动。 +广义取消会清除待处理 inbox 工作,并中止活跃 loop 阶段。目标驱动器通过 inbox 的领取/丢弃事件和持久的 aborted 轮次结束来跟踪预留消息。由于轮次现在会在首次领取前打开,取消可以关闭已领取的无步骤尝试;驱动器会把该尝试标记为已取消,并让随后的 idle 边沿暂停目标,与已准入尝试的处理方式相同。没有匹配目标尝试的取消只会移除进程内激活态。若暂停变更抛错,驱动器会回退到解除激活,避免已取消的自动工作重新启动。 -该通知是协调事件,不是第二个停止 API。`Agent.cancel()` 仍是唯一的公共广义取消动词,空闲调用仍是无操作;若消费者依赖此接缝,自定义 `Agent` 实现就必须满足该事件顺序。 +`Agent.cancel()` 仍是唯一的公共广义取消动词。若消费方依赖其顺序,实现该接口的自定义 `Agent` 必须遵守 inbox、轮次结束、status 与完全停稳的顺序。 ### 进程生命周期 diff --git a/apps/cli/tests/tui-keyless-smoke.e2e.ts b/apps/cli/tests/tui-keyless-smoke.e2e.ts index d4f052a4b0..222bc47228 100644 --- a/apps/cli/tests/tui-keyless-smoke.e2e.ts +++ b/apps/cli/tests/tui-keyless-smoke.e2e.ts @@ -129,7 +129,7 @@ async function seedResumeSession(cwd: string): Promise { { type: 'step/end', seq: 5, time: 1_700_000_000_006, data: { turn: 1, step: 1 } }, { type: 'session/title', seq: 6, time: 1_700_000_000_007, data: { title: 'Resume selector design', messageSeqs: [1], source: { kind: 'fallback' } } }, { type: 'todo/write', seq: 7, time: 1_700_000_000_008, data: { todos: [{ content: 'Preserve restored state', status: 'in_progress' }] } }, - { type: 'turn/end', seq: 8, time: 1_700_000_000_009, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 8, time: 1_700_000_000_009, data: { turn: 1, reason: { kind: 'completed' } } }, ] const file = logPath(join(cwd, '.sessions'), sessionCwd, id, 'none') await mkdir(dirname(file), { recursive: true }) diff --git a/apps/web/tests/markdown-images.e2e.ts b/apps/web/tests/markdown-images.e2e.ts index bd05bd4746..781876a9d7 100644 --- a/apps/web/tests/markdown-images.e2e.ts +++ b/apps/web/tests/markdown-images.e2e.ts @@ -115,7 +115,7 @@ function markdownImageFixture(remoteUrl: string): string { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const header = { type: 'session', diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 5576af22a5..5a681b85ca 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.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/architecture.md -architecture.md: 401dc4e0a1875488fa3107e9f50a6a25c0e8aa89 -architecture.zh.md: ee666e593e2c38c4ede4f64254de0755cc95290e +architecture.md: fe125063c8215d18b2aec7444fc20fa8d9a24028 +architecture.zh.md: d661cd2d450ce8111b5df5b85286d93b92aa6969 diff --git a/docs/architecture.md b/docs/architecture.md index 401dc4e0a1..fe125063c8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -122,7 +122,7 @@ Adapter selection, dispatch, and iteration failures become terminal error or abo Other failures use `agent/error`; cancellation and disposal beat recovery. Before request-header commit, the turn signal cancels capability preparation; undispatched tools get synthetic `tool/call`/`ABORTED_BEFORE_DISPATCH` pairs. Effective `cancel(cause)` reports its cause before clearing and aborting; idle calls emit nothing. Durability distinguishes `aborted` cancellation from `disposed` teardown, which awaits quiescence ([decision](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md)). -Turn and step events are turn-enclosed; the loop appends `user/message` events only from entered batches inside a turn. A turn opens before the initial claim and pre-step, so rejection, empty input, cancellation, or failure closes a durable turn with `step: 0`. Standalone `compact/* { turn: null }` events consume no turn, and their lock-time markers may interleave with inbox splices. Reload synthesizes interrupted turn ends; `session/end-seed` distinguishes stale compaction orphans from live locks. After close, only `agent/error` reports failures. Each turn has one [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). +Turn and step events are turn-enclosed; the loop appends `user/message` events only from entered batches inside a turn. A turn opens before the initial claim and pre-step, so rejection, empty input, cancellation, or failure closes a durable turn without any step events. Standalone `compact/* { turn: null }` events consume no turn, and their lock-time markers may interleave with inbox splices. Reload synthesizes interrupted turn ends; `session/end-seed` distinguishes stale compaction orphans from live locks. After close, only `agent/error` reports failures. Each turn has one [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). ### Agent Handles diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index ee666e593e..d661cd2d45 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -122,7 +122,7 @@ idle inject: 其他故障使用 `agent/error`;取消和资源释放优先于恢复。在提交请求头之前,轮次信号会取消功能准备;尚未分派的工具会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。实际生效的 `cancel(cause)` 会在清空队列和中止前报告原因;空闲调用不发事件。持久化层以 `aborted` 区分取消,以 `disposed` 区分会等待完全停稳的拆卸([决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。 -轮次和步骤事件均位于轮次边界内;loop 只会在轮次内从进入步骤的批次追加 `user/message`。轮次会在首次领取与 pre-step 之前打开,因此拒绝、空输入、取消或失败会以 `step: 0` 关闭一个持久轮次。独立的 `compact/* { turn: null }` 事件不占用轮次,其锁定时刻标记可以与 inbox splice 交错。重新加载会为中断的轮次合成结束事件;`session/end-seed` 区分陈旧的压缩遗留项与活跃锁。关闭后仅由 `agent/error` 报告故障。每个轮次有一个 [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap)。 +轮次和步骤事件均位于轮次边界内;loop 只会在轮次内从进入步骤的批次追加 `user/message`。轮次会在首次领取与 pre-step 之前打开,因此拒绝、空输入、取消或失败会关闭一个不包含任何步骤事件的持久轮次。独立的 `compact/* { turn: null }` 事件不占用轮次,其锁定时刻标记可以与 inbox splice 交错。重新加载会为中断的轮次合成结束事件;`session/end-seed` 区分陈旧的压缩遗留项与活跃锁。关闭后仅由 `agent/error` 报告故障。每个轮次有一个 [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap)。 ### Agent 句柄 diff --git a/docs/core-data-structures/session.i18n.yaml b/docs/core-data-structures/session.i18n.yaml index ec3d9c78ca..621476e9eb 100644 --- a/docs/core-data-structures/session.i18n.yaml +++ b/docs/core-data-structures/session.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/core-data-structures/session.md -session.md: 998e6770a63b6afdb679ddd186da473e35927a02 -session.zh.md: 44e945a2b705846c30c7cfe4c25cb983dc00bee0 +session.md: 3e79a9278578e9fe3860bdbe72ff61319bbfa2c5 +session.zh.md: d701e35cde5a7ae8732efb9081d373410581d6e4 diff --git a/docs/core-data-structures/session.md b/docs/core-data-structures/session.md index 998e6770a6..3e79a92785 100644 --- a/docs/core-data-structures/session.md +++ b/docs/core-data-structures/session.md @@ -33,14 +33,14 @@ interface SessionEventMap { */ 'turn/start': { turn: number } /** - * Closes turn `turn` after `step`, the last entered step (`0` when none), - * with the {@link TurnEndReason} that ended it. The loop does not await a + * Closes turn `turn` with the {@link TurnEndReason} that ended it. A turn + * with no entered step has no `step/start` or `step/end`. The loop does not await a * flush at turn boundaries: `dsh-session-checkpoint-policy` owns the * per-request durability checkpoint, and consumers that read storage after * `whenIdle()` flush themselves. Success commits the turn; rejection is * reported live and does not prevent later work. */ - 'turn/end': { turn: number; step: number; reason: TurnEndReason } + 'turn/end': { turn: number; reason: TurnEndReason } /** Opens step `step` of turn `turn` — one model call plus the tool executions it requested. */ 'step/start': { turn: number; step: number } /** Closes step `step` of turn `turn`. */ diff --git a/docs/core-data-structures/session.zh.md b/docs/core-data-structures/session.zh.md index 44e945a2b7..d701e35cde 100644 --- a/docs/core-data-structures/session.zh.md +++ b/docs/core-data-structures/session.zh.md @@ -33,14 +33,14 @@ interface SessionEventMap { */ 'turn/start': { turn: number } /** - * Closes turn `turn` after `step`, the last entered step (`0` when none), - * with the {@link TurnEndReason} that ended it. The loop does not await a + * Closes turn `turn` with the {@link TurnEndReason} that ended it. A turn + * with no entered step has no `step/start` or `step/end`. The loop does not await a * flush at turn boundaries: `dsh-session-checkpoint-policy` owns the * per-request durability checkpoint, and consumers that read storage after * `whenIdle()` flush themselves. Success commits the turn; rejection is * reported live and does not prevent later work. */ - 'turn/end': { turn: number; step: number; reason: TurnEndReason } + 'turn/end': { turn: number; reason: TurnEndReason } /** Opens step `step` of turn `turn` — one model call plus the tool executions it requested. */ 'step/start': { turn: number; step: number } /** Closes step `step` of turn `turn`. */ diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 34d787269c..a0790be7ad 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -657,14 +657,14 @@ Source: [`packages/core/session/src/types.ts:245`](../packages/core/session/src/ ```ts persistence-catalog /** - * Closes turn `turn` after `step`, the last entered step (`0` when none), - * with the {@link TurnEndReason} that ended it. The loop does not await a + * Closes turn `turn` with the {@link TurnEndReason} that ended it. A turn + * with no entered step has no `step/start` or `step/end`. The loop does not await a * flush at turn boundaries: `dsh-session-checkpoint-policy` owns the * per-request durability checkpoint, and consumers that read storage after * `whenIdle()` flush themselves. Success commits the turn; rejection is * reported live and does not prevent later work. */ -'turn/end': { turn: number; step: number; reason: TurnEndReason } +'turn/end': { turn: number; reason: TurnEndReason } ``` Types: [TurnEndReason](core-data-structures/session.md) diff --git a/examples/acp-agent/tests/goal-snapshots/goal-session/session.expected.jsonl b/examples/acp-agent/tests/goal-snapshots/goal-session/session.expected.jsonl index e72ad637c5..5f5b8971ce 100644 --- a/examples/acp-agent/tests/goal-snapshots/goal-session/session.expected.jsonl +++ b/examples/acp-agent/tests/goal-snapshots/goal-session/session.expected.jsonl @@ -36,7 +36,7 @@ {"type":"assistant/chunk","seq":34,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":35,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL READY"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":35,"outputTokens":2}},"sourceEventSeqs":[30,31,32,33,34],"surfaceOp":"append"} {"type":"step/end","seq":36,"time":0,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":37,"time":0,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":37,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":38,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"\nObjective: \"Finish the ACP goal-session snapshot proof\"\nRound: 1/2\n\nContinue working toward the objective in this same session. Treat the current workspace, tool results, and durable session state as authoritative; inspect them instead of assuming earlier narration is still current. Make concrete progress and verify the result. Before claiming completion, gather evidence that the whole objective is achieved, read the current goal, and mark it complete. If work remains, leave the goal active for the next round. Follow the configured goal-tool policy before reporting a blocker.\n"}],"source":{"kind":"goal","goalId":"goal-{{sessionId}}","revision":1,"round":1},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","seq":39,"time":0,"data":{"turn":2}} {"type":"agent/inbox/spliced","seq":40,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} @@ -49,7 +49,7 @@ {"type":"assistant/chunk","seq":47,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":48,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL ROUND ONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":40,"outputTokens":3}},"sourceEventSeqs":[43,44,45,46,47],"surfaceOp":"append"} {"type":"step/end","seq":49,"time":0,"data":{"turn":2,"step":1}} -{"type":"turn/end","seq":50,"time":0,"data":{"turn":2,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":50,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":51,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"\nObjective: \"Finish the ACP goal-session snapshot proof\"\nRound: 2/2\n\nContinue working toward the objective in this same session. Treat the current workspace, tool results, and durable session state as authoritative; inspect them instead of assuming earlier narration is still current. Make concrete progress and verify the result. Before claiming completion, gather evidence that the whole objective is achieved, read the current goal, and mark it complete. If work remains, leave the goal active for the next round. Follow the configured goal-tool policy before reporting a blocker.\n"}],"source":{"kind":"goal","goalId":"goal-{{sessionId}}","revision":1,"round":2},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","seq":52,"time":0,"data":{"turn":3}} {"type":"agent/inbox/spliced","seq":53,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} @@ -58,5 +58,5 @@ {"type":"assistant/chunk","seq":56,"time":0,"data":{"turn":3,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} {"type":"assistant/chunk","seq":57,"time":0,"data":{"turn":3,"step":1,"chunk":{"type":"text-delta","index":0,"text":"partial"}}} {"type":"step/end","seq":58,"time":0,"data":{"turn":3,"step":1}} -{"type":"turn/end","seq":59,"time":0,"data":{"turn":3,"step":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} +{"type":"turn/end","seq":59,"time":0,"data":{"turn":3,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} {"type":"goal/change","seq":60,"time":0,"data":{"kind":"goal/change","version":1,"operation":"pause","goal":{"id":"goal-{{sessionId}}","revision":2,"objective":"Finish the ACP goal-session snapshot proof","phase":"paused","maxGoalRounds":2},"roundsStarted":2,"createdAt":0,"updatedAt":0}} diff --git a/examples/acp-agent/tests/goal-snapshots/goal-wrapup/session.expected.jsonl b/examples/acp-agent/tests/goal-snapshots/goal-wrapup/session.expected.jsonl index 27d2b492f1..60335e83b4 100644 --- a/examples/acp-agent/tests/goal-snapshots/goal-wrapup/session.expected.jsonl +++ b/examples/acp-agent/tests/goal-snapshots/goal-wrapup/session.expected.jsonl @@ -26,7 +26,7 @@ {"type":"assistant/chunk","seq":24,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":25,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL READY"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":28,"outputTokens":2}},"sourceEventSeqs":[20,21,22,23,24],"surfaceOp":"append"} {"type":"step/end","seq":26,"time":0,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":27,"time":0,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":27,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":28,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"\nObjective: \"Finish the ACP goal wrap-up snapshot proof\"\nRound: 1/2\n\nContinue working toward the objective in this same session. Treat the current workspace, tool results, and durable session state as authoritative; inspect them instead of assuming earlier narration is still current. Make concrete progress and verify the result. Before claiming completion, gather evidence that the whole objective is achieved, read the current goal, and mark it complete. If work remains, leave the goal active for the next round. Follow the configured goal-tool policy before reporting a blocker.\n"}],"source":{"kind":"goal","goalId":"goal-{{sessionId}}","revision":1,"round":1},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","seq":29,"time":0,"data":{"turn":2}} {"type":"agent/inbox/spliced","seq":30,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} @@ -53,4 +53,4 @@ {"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":52,"time":0,"data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL WRAP-UP: the snapshot objective is achieved and this closing message reaches the user."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":52,"outputTokens":14}},"sourceEventSeqs":[47,48,49,50,51],"surfaceOp":"append"} {"type":"step/end","seq":53,"time":0,"data":{"turn":2,"step":2}} -{"type":"turn/end","seq":54,"time":0,"data":{"turn":2,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":54,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl b/examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl index 1bedc9dd16..7e9f42d6fb 100644 --- a/examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl @@ -16,4 +16,4 @@ {"type":"assistant/chunk","seq":14,"time":1785730458561,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":15,"time":1785730458561,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"b9c977ca-2c1a-4a5e-8397-e0b9381a9943"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"} {"type":"step/end","seq":16,"time":1785730458561,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":17,"time":1785730458561,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":17,"time":1785730458561,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl b/examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl index 9158a1ea6c..38c5dfae47 100644 --- a/examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl +++ b/examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl @@ -16,4 +16,4 @@ {"type":"assistant/chunk","seq":14,"time":1785730458709,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":15,"time":1785730458709,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5c33b525-4844-4272-b6f2-e036356d0e22"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"} {"type":"step/end","seq":16,"time":1785730458709,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":17,"time":1785730458709,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":17,"time":1785730458709,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl b/examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl index c9a22d29ba..ec5c5756f2 100644 --- a/examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl +++ b/examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl @@ -67,4 +67,4 @@ {"type":"assistant/chunk","seq":65,"time":1785730458751,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":66,"time":1785730458751,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_ACP_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"a32b89ce-13ed-48ba-a7f9-24144b94ec56"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[61,62,63,64,65],"surfaceOp":"append"} {"type":"step/end","seq":67,"time":1785730458751,"data":{"turn":1,"step":6}} -{"type":"turn/end","seq":68,"time":1785730458751,"data":{"turn":1,"step":6,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":68,"time":1785730458751,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/bash-spill/session.jsonl b/examples/acp-agent/tests/snapshots/bash-spill/session.jsonl index eab5cad16e..075b166442 100644 --- a/examples/acp-agent/tests/snapshots/bash-spill/session.jsonl +++ b/examples/acp-agent/tests/snapshots/bash-spill/session.jsonl @@ -25,4 +25,4 @@ {"type":"assistant/chunk","seq":23,"time":1785730421085,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":24,"time":1785730421085,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"64dccb5c-e621-47f1-af30-04dc7f4ba59d"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} {"type":"step/end","seq":25,"time":1785730421086,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":26,"time":1785730421086,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":26,"time":1785730421086,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/bash-tool-turn/session.jsonl b/examples/acp-agent/tests/snapshots/bash-tool-turn/session.jsonl index b91e5ac37c..2d49114651 100644 --- a/examples/acp-agent/tests/snapshots/bash-tool-turn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/bash-tool-turn/session.jsonl @@ -32,4 +32,4 @@ {"type":"assistant/chunk","seq":98,"time":1785730424681,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":99,"time":1785730424681,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The command ran successfully and output \"TERMINAL_OK\". I should now reply with just \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"aa705bf0-9b5b-4af3-9763-dbf93c98e4c4"},"usage":{"inputTokens":168,"outputTokens":25,"cacheReadTokens":2816,"reasoningTokens":22}},"sourceEventSeqs":[69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98],"surfaceOp":"append"} {"type":"step/end","seq":100,"time":1785730424682,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":101,"time":1785730424682,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":101,"time":1785730424682,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl b/examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl index bacd0f5330..9341d8f61a 100644 --- a/examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl @@ -33,4 +33,4 @@ {"type":"assistant/chunk","seq":150,"time":1785730479437,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":151,"time":1785730479437,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The output is \"BOTH_OK\" (with a trailing newline, but that's fine). The user asked me to reply with that output only."},{"type":"text","text":"BOTH_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"dbd0a9c9-1f19-405d-ad05-86f90447e006"},"usage":{"inputTokens":50,"outputTokens":35,"cacheReadTokens":10496,"reasoningTokens":31}},"sourceEventSeqs":[111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150],"surfaceOp":"append"} {"type":"step/end","seq":152,"time":1785730479437,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":153,"time":1785730479437,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":153,"time":1785730479437,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/cancel-tool-calls/session.jsonl b/examples/acp-agent/tests/snapshots/cancel-tool-calls/session.jsonl index fbeb6ddbcc..631f97f98a 100644 --- a/examples/acp-agent/tests/snapshots/cancel-tool-calls/session.jsonl +++ b/examples/acp-agent/tests/snapshots/cancel-tool-calls/session.jsonl @@ -22,4 +22,4 @@ {"type":"tool/call","seq":20,"time":1785730445645,"data":{"turn":1,"step":1,"callId":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}} {"type":"tool/result","seq":21,"time":1785730445645,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_skipped"},"content":[{"type":"tool-result","toolCallId":"call_skipped","content":[{"type":"text","text":"Error: tool call aborted before dispatch"}],"isError":true}],"role":"user","id":"55c65cec-41ad-4361-bc86-e82b7726d445"},"error":{"name":"AbortError","code":"ABORTED_BEFORE_DISPATCH"}},"sourceEventSeqs":[20],"surfaceOp":"append"} {"type":"step/end","seq":22,"time":1785730445645,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":23,"time":1785730445645,"data":{"turn":1,"step":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} +{"type":"turn/end","seq":23,"time":1785730445645,"data":{"turn":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} diff --git a/examples/acp-agent/tests/snapshots/cancel/session.jsonl b/examples/acp-agent/tests/snapshots/cancel/session.jsonl index 2c8c816797..f28bb4a224 100644 --- a/examples/acp-agent/tests/snapshots/cancel/session.jsonl +++ b/examples/acp-agent/tests/snapshots/cancel/session.jsonl @@ -11,4 +11,4 @@ {"type":"assistant/chunk","seq":9,"time":1785498791456,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} {"type":"assistant/chunk","seq":10,"time":1785730444541,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"partial"}}} {"type":"step/end","seq":11,"time":1785730444547,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":12,"time":1785730444547,"data":{"turn":1,"step":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} +{"type":"turn/end","seq":12,"time":1785730444547,"data":{"turn":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} diff --git a/examples/acp-agent/tests/snapshots/code-mode-turn/session.jsonl b/examples/acp-agent/tests/snapshots/code-mode-turn/session.jsonl index b3e60f5a6f..81422f7470 100644 --- a/examples/acp-agent/tests/snapshots/code-mode-turn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/code-mode-turn/session.jsonl @@ -35,4 +35,4 @@ {"type":"assistant/chunk","seq":251,"time":1785730477165,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":252,"time":1785730477165,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The program ran successfully. The console.log output \"captured output\" appeared, and the return value is \"CODE_ONE+CODE_TWO\". The user asked me to reply with that joined string only."},{"type":"text","text":"CODE_ONE+CODE_TWO"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"e435b807-b35f-48d3-846f-a5c59333c316"},"usage":{"inputTokens":117,"outputTokens":50,"cacheReadTokens":6272,"reasoningTokens":42}},"sourceEventSeqs":[197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251],"surfaceOp":"append"} {"type":"step/end","seq":253,"time":1785730477165,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":254,"time":1785730477165,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":254,"time":1785730477165,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl b/examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl index cf1318ae47..f7da13d67f 100644 --- a/examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl +++ b/examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl @@ -38,4 +38,4 @@ {"type":"assistant/chunk","seq":36,"time":1785014475930,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":37,"time":1785733131134,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"**Code Mode workspace handshake:** `CODE_MODE_CONTEXT_OK`"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"2cf342f3-fa45-4472-8304-f8c947e825f8"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[32,33,34,35,36],"surfaceOp":"append"} {"type":"step/end","seq":38,"time":1785733131135,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":39,"time":1785733131135,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":39,"time":1785733131135,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl b/examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl index 1697862fa5..c4ae0e2738 100644 --- a/examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl +++ b/examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl @@ -15,7 +15,7 @@ {"type":"assistant/chunk","seq":13,"time":1785730459883,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} {"type":"assistant/message","seq":14,"time":1785730459883,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect","arguments":"{\"what\":\"api\",\"name\":\"tools\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"6b62bed7-113a-4d2e-a6aa-b935a1063ee2"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"} {"type":"tool/call","seq":15,"time":1785730459883,"data":{"turn":1,"step":1,"callId":"inspect-tools-api","name":"cordis_inspect","arguments":"{\"what\":\"api\",\"name\":\"tools\"}"}} -{"type":"tool/result","seq":16,"time":1785730459904,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"## api\n- tools — Tool registry and execution pipeline.\n /**\n * Register globally or in the calling agent scope. Scoped tools shadow\n * globals; duplicates within one layer and the reserved `run_code` name fail.\n * @param definition - tool schema, execution, and optional finalization/presentation callbacks.\n * @returns the exact disposer that unregisters the tool.\n */\n register(definition: ToolDefinition): () => void\n /**\n * Restrict global tools for the calling agent scope. Empty filters, unknown\n * names, scope-local names, and reserved transport names fail. Restrictions\n * intersect; scoped registrations remain visible.\n * @param filter - global-surface mask: `allow` (keep only) and/or `deny` (remove).\n * @returns the exact disposer that lifts this restriction.\n */\n restrict(filter: ToolRestriction): () => void\n /**\n * Register a monotonic guard after the extensible `tools/pre-execute`\n * waterfall. A plain-context guard applies globally; one registered through\n * `agent.ctx` applies only to that agent. Any matching guard may deny by\n * returning a reason, while no guard can force-allow a call another guard\n * denied. The exact effect disposer is returned for ordered ownership and\n * HMR cleanup.\n * @param guard - synchronous check; a returned string denies the execution.\n * @returns the exact disposer that unregisters the guard.\n */\n guard(guard: ToolGuard): () => void\n /**\n * Look up a tool as one scope sees it (scoped\n * shadows global; a restricted-away global reads as absent). Presenters pass\n * the calling agent so the rendered card matches the definition that\n * actually executed.\n * @param name - the tool name as registered.\n * @param scope - the viewing scope (the agent); omitted = the global view.\n * @returns the definition the scope resolves, or undefined when none is visible.\n */\n get(name: string, scope?: ScopeKey): ToolDefinition | undefined\n /**\n * Project visible definitions onto the allowlisted model-facing schema fields,\n * excluding execution and presentation callbacks.\n * @param scope - the viewing scope (the agent); omitted = the global view.\n * @returns one deep-cloned schema per visible tool.\n */\n schemas(scope?: ScopeKey): ToolSchema[]\n /**\n * Classify a pending call through the caller's visible tool definition. Only\n * an exact `true` is parallel; unknown, hidden, undeclared, invalid, or\n * throwing classifiers are exclusive.\n * @param exec - call name, parsed arguments, and optional agent scope.\n * @returns the fail-closed scheduling mode.\n */\n executionMode(exec: ToolExecutionInput): ToolExecutionMode\n /**\n * Execute through pre-policy, guards, around-dispatch, post-policy,\n * definition-owned content finalization, and final notification. Tool and\n * listener failures resolve as materialized error results; an invisible tool\n * reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen\n * snapshot final observers receive. Cancellation\n * arriving after entry and before final result materialization skips a\n * not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a\n * successful started outcome with `ABORTED`; already-started work is still\n * drained and may retain a tool-owned structured error.\n * @param exec - the typed same-process call input. The registry assigns its\n * correlation token before policy begins.\n * @returns the materialized final result.\n */\n async execute(exec: ToolExecutionInput): Promise\ntype shapes (referenced by the signatures above — read these before assuming a field is a string):\n export interface Agent {\n readonly id: SessionId;\n readonly options: AgentOptions;\n readonly session: Session;\n readonly inbox: Inbox;\n readonly status: AgentStatus;\n readonly ctx: Context;\n cancel(cause: AgentCancelCause, options?: CancelOptions): void;\n whenIdle(): Promise;\n runMaintenance(task: (signal: AbortSignal) => Promise): Promise;\n send(message: UserMessage, target: InboxTarget, wakeup: boolean): void;\n followup(message: UserMessage): void;\n steer(message: UserMessage): void;\n inject(message: UserMessage): void;\n }\n export type AgentCancelCause = {\n readonly kind: 'user';\n } | {\n readonly kind: 'parent';\n } | {\n readonly kind: 'hook';\n readonly reason: string;\n } | {\n readonly kind: 'disposed';\n };\n export interface AgentOptions {\n provider?: string;\n model?: string;\n maxTokens?: number;\n }\n export type AgentStatus = 'idle' | 'running';\n export interface AssistantMessage extends Message {\n readonly role: 'assistant';\n readonly source: ModelMessageSource;\n }\n export interface AssistantProvenance {\n provider: string;\n model: string;\n replayState?: unknown;\n }\n export type Branded = string & {\n readonly [BRAND]: B;\n };\n export type CallId = Branded<'CallId'>;\n export interface CancelOptions {\n keepInbox?: boolean | undefined;\n }\n export interface ContentBlockMap {\n 'text': TextBlock;\n 'reasoning': ReasoningBlock;\n 'tool-call': ToolCallBlock;\n 'tool-result': ToolResultBlock;\n }\n export type ContentBlockType = keyof ContentBlockMap;\n export interface DiffCallView {\n card: 'diff';\n title: string;\n diffs: FileDiff[];\n locations?: FileLocation[];\n }\n export interface DiffResultView {\n card: 'diff';\n title?: string;\n diffs: FileDiff[];\n }\n export interface EpochHeader {\n config: LlmCallConfig;\n adapterDefaults?: LlmCallConfigAdapterDefaults;\n system?: string;\n tools?: ToolSchema[];\n }\n export interface FileDiff {\n path: string;\n oldText: string | null;\n newText: string;\n }\n export interface FileLocation {\n path: string;\n line?: number;\n }\n export type FinishReason = FinishReasonMap[keyof FinishReasonMap];\n export interface FinishReasonMap {\n 'stop': {\n kind: 'stop';\n };\n 'tool-calls': {\n kind: 'tool-calls';\n };\n 'max-tokens': {\n kind: 'max-tokens';\n };\n 'aborted': {\n kind: 'aborted';\n failure: LlmFailure;\n };\n 'error': {\n kind: 'error';\n failure: LlmFailure;\n };\n }\n export interface GenericCallView {\n card: 'generic';\n title: string;\n kind?: ToolCallKind;\n rawInput?: unknown;\n content?: ContentBlock[];\n locations?: FileLocation[];\n }\n export interface GenericResultView {\n card: 'generic';\n title?: string;\n content?: ContentBlock[];\n }\n export class Inbox {\n constructor(private readonly session: Session, private readonly notifications: InboxNotifications);\n get nextTurn(): readonly UserMessage[];\n get nextStep(): readonly UserMessage[];\n get hasPending(): boolean;\n clear(): void;\n claim(target: InboxTarget): UserMessage[];\n append(target: InboxTarget, message: UserMessage): void;\n prepend(target: InboxTarget, message: UserMessage): void;\n replace(messageId: MessageId, newMessage: UserMessage): boolean;\n remove(messageId: MessageId): boolean;\n splice(target: InboxTarget, start: number, deleteCount: number, inserted: UserMessage[]): UserMessage[];\n }\n export interface InboxNotifications {\n inserted(message: UserMessage): void;\n discarded(message: UserMessage): void;\n }\n export type InboxTarget = 'next-turn' | 'next-step';\n export interface JsonSchemaNode {\n type?: JsonSchemaType;\n oneOf?: JsonSchemaNode[];\n properties?: Record;\n required?: string[];\n additionalProperties?: boolean;\n items?: JsonSchemaNode;\n enum?: JsonSchemaScalar[];\n const?: JsonSchemaScalar;\n description?: string;\n title?: string;\n default?: JsonValue;\n examples?: JsonValue;\n }\n export type JsonSchemaScalar = string | number | boolean | null;\n export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\n export type JsonValue = null | boolean | number | string | JsonValue[] | {\n [key: string]: JsonValue;\n };\n export interface LlmCallConfig {\n provider: string;\n model: string;\n reasoningEffort?: ReasoningEffortId;\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n }\n export interface LlmCallConfigAdapterDefaults {\n reasoningEffort?: true;\n maxTokens?: true;\n }\n export interface LlmFailure {\n readonly message: string;\n readonly code: string;\n readonly status?: number;\n readonly providerRetryAfterMs?: number;\n readonly requestId?: ProviderRequestId;\n }\n export interface Message {\n readonly id: MessageId;\n readonly role: 'system' | 'user' | 'assistant';\n readonly content: ContentBlock[];\n readonly source: MessageSource;\n }\n export type MessageId = Branded<'MessageId'>;\n export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\n export interface MessageSourceMap {\n user: {\n kind: 'user';\n };\n plugin: {\n kind: 'plugin';\n plugin: string;\n };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n }\n export interface ModelMessageSource extends AssistantProvenance {\n kind: 'model';\n }\n export type ProviderRequestId = Branded<'ProviderRequestId'>;\n export interface ReadFileLine {\n number: number;\n text: string;\n }\n export interface ReadResultView {\n card: 'read';\n title?: string;\n path: string;\n offset: number;\n lines: ReadFileLine[];\n totalLines: number;\n lang?: string;\n content?: ContentBlock[];\n }\n export interface ReasoningBlock {\n type: 'reasoning';\n text: string;\n }\n export type ReasoningEffortId = Branded<'ReasoningEffortId'>;\n export interface RequestContext {\n provider: string;\n model: string;\n contextWindow?: number;\n }\n export type RequestHeaderReason = 'initial' | 'resume' | 'change';\n export type ScopeKey = object;\n export interface SearchFileMatches {\n path: string;\n matches: SearchLineMatch[];\n }\n export interface SearchLineMatch {\n lineNumber: number;\n line: string;\n }\n export interface SearchMatchesResultView {\n card: 'search';\n shape: 'matches';\n title?: string;\n files: SearchFileMatches[];\n truncated: boolean;\n total: number;\n }\n export interface SearchPathsResultView {\n card: 'search';\n shape: 'paths';\n title?: string;\n paths: string[];\n truncated: boolean;\n total: number;\n }\n export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\n export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n get id(): SessionId;\n readonly firstLiveSeq: number;\n constructor(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader);\n get events(): readonly SessionEvent[];\n get seq(): number;\n append(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [\n opts: SurfaceIntent\n ] : [\n ]): SessionEvent;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n deriveMessages(): Message[];\n deriveEventMessage(event: SessionEvent): Message | null;\n }\n export type SessionEvent = {\n [K in SessionEventType]: {\n type: K;\n seq: number;\n time: number;\n data: SessionEventMap[K];\n } & (K extends SurfaceEventType ? {\n sourceEventSeqs?: number[];\n surfaceOp?: SurfaceOp;\n } : object);\n }[T];\n export interface SessionEventMap {\n 'turn/start': {\n turn: number;\n };\n 'turn/end': {\n turn: number;\n step: number;\n reason: TurnEndReason;\n };\n 'step/start': {\n turn: number;\n step: number;\n };\n 'step/end': {\n turn: number;\n step: number;\n };\n 'user/message': UserMessage;\n 'assistant/chunk': {\n turn: number;\n step: number;\n chunk: StreamChunk;\n };\n 'assistant/message': {\n turn: number;\n step: number;\n message: AssistantMessage;\n usage?: TokenUsage;\n };\n 'tool/call': {\n turn: number;\n step: number;\n callId: CallId;\n name: string;\n arguments: string;\n };\n 'tool/result': {\n turn: number;\n step: number;\n message: ToolResultMessage;\n error?: {\n name: string;\n code: string;\n };\n meta?: JsonValue;\n };\n 'todo/write': {\n todos: TodoItem[];\n };\n 'request/header': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n };\n 'request/context': RequestContext;\n 'session/end-seed': Record;\n }\n export type SessionEventType = keyof SessionEventMap;\n export interface SessionHeader {\n readonly version: number;\n readonly id: SessionId;\n readonly createdAt: number;\n readonly cwd?: string;\n readonly parentSession?: SessionId;\n readonly seedLength?: number;\n readonly origin?: 'subagent';\n readonly delegationDepth?: number;\n }\n export type SessionId = Branded<'SessionId'>;\n export interface SessionSurface {\n readonly nodes: readonly number[];\n readonly replaceGeneration: number;\n }\n export type StreamChunk = {\n type: 'block-start';\n index: number;\n blockType: ContentBlockType;\n } | {\n type: 'text-delta';\n index: number;\n text: string;\n } | {\n type: 'reasoning-delta';\n index: number;\n text: string;\n } | {\n type: 'tool-call-delta';\n index: number;\n id: CallId;\n name?: string;\n argumentsDelta: string;\n } | {\n type: 'block-end';\n index: number;\n block: ContentBlock;\n } | {\n type: 'usage';\n usage: TokenUsage;\n } | {\n type: 'finish';\n reason: FinishReason;\n replayState?: unknown;\n };\n export type SurfaceEventType = 'user/message' | 'assistant/message' | 'tool/result';\n export interface SurfaceIntent {\n surfaceOp: SurfaceOp;\n sourceEventSeqs?: number[];\n }\n export type SurfaceOp = 'append' | {\n op: 'replace';\n start: number;\n end: number;\n };\n export interface TerminalCallView {\n card: 'terminal';\n title: string;\n description?: string;\n cwd?: string;\n }\n export interface TerminalResultView {\n card: 'terminal';\n title?: string;\n output?: string;\n exitCode?: number;\n signal?: string;\n }\n export interface TodoItem {\n content: string;\n status: 'pending' | 'in_progress' | 'completed';\n }\n export interface TokenUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n reasoningTokens?: number;\n }\n export interface ToolCallBlock {\n type: 'tool-call';\n id: CallId;\n name: string;\n arguments: string;\n }\n export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\n export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\n export interface ToolDefinition extends ToolSchema {\n readonly output: ToolOutputDefinition;\n execute(args: unknown, exec: ToolRunContext): Promise;\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\n timeoutMs?: number;\n isConcurrencySafe?(args: unknown): boolean;\n presentCall?(args: unknown): ToolCallView | undefined;\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\n }\n export interface ToolErrorInfo {\n name: string;\n code: string;\n }\n export interface ToolExecution extends ToolExecutionInput {\n readonly token: ToolExecutionToken;\n }\n export interface ToolExecutionFailure {\n readonly isError: true;\n readonly error: ToolFailure;\n readonly value?: never;\n readonly content: ContentBlock[];\n readonly meta?: JsonValue;\n readonly additionalContexts?: UserMessage[];\n readonly concludesTurn?: never;\n }\n export interface ToolExecutionInput {\n readonly callId: CallId;\n readonly name: string;\n readonly arguments: unknown;\n readonly agent?: Agent;\n readonly parent?: ToolExecutionToken;\n readonly signal: AbortSignal;\n }\n export type ToolExecutionMode = {\n kind: 'parallel';\n } | {\n kind: 'exclusive';\n };\n export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\n export interface ToolExecutionSuccess {\n readonly isError: false;\n readonly value: JsonValue;\n readonly content: ContentBlock[];\n readonly error?: never;\n readonly meta?: JsonValue;\n readonly additionalContexts?: UserMessage[];\n readonly concludesTurn?: true;\n }\n export type ToolExecutionToken = symbol & {\n readonly [toolExecutionTokenBrand]: true;\n };\n export interface ToolFailure {\n message: string;\n info?: ToolErrorInfo;\n }\n export type ToolGuard = (execution: Readonly) => string | undefined;\n export interface ToolMessageSource {\n kind: 'tool';\n callId: CallId;\n }\n export interface ToolOutputDefinition {\n readonly schema: JsonSchemaNode;\n render(args: unknown, value: JsonValue): ContentBlock[];\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\n }\n export interface ToolRestriction {\n readonly allow?: readonly string[];\n readonly deny?: readonly string[];\n }\n export interface ToolResult {\n content: ContentBlock[];\n isError: boolean;\n meta?: JsonValue;\n }\n export interface ToolResultBlock {\n type: 'tool-result';\n toolCallId: CallId;\n content: ContentBlock[];\n isError?: boolean;\n }\n export interface ToolResultMessage extends Message {\n readonly role: 'user';\n readonly content: [\n ToolResultBlock\n ];\n readonly source: ToolMessageSource;\n }\n export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\n export interface ToolRunContext extends ToolExecution {\n deferContext(context: UserMessage): void;\n concludeTurn(): void;\n }\n export interface ToolSchema {\n name: string;\n description: string;\n parameters: Record;\n }\n export type TurnEndCancelCause = AgentCancelCause | {\n readonly kind: 'legacy';\n };\n export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap];\n export interface TurnEndReasonMap {\n completed: {\n kind: 'completed';\n };\n aborted: {\n kind: 'aborted';\n reason: TurnEndCancelCause;\n };\n blocked: {\n kind: 'blocked';\n };\n error: {\n kind: 'error';\n error: LlmFailure;\n };\n 'max-tokens': {\n kind: 'max-tokens';\n };\n interrupted: {\n kind: 'interrupted';\n };\n }\n export interface UserMessage extends Message {\n readonly role: 'user';\n }\n export interface WebFetchResultView {\n card: 'web';\n kind: 'fetch';\n title?: string;\n url: string;\n statusCode: number;\n truncated: boolean;\n }\n export type WebResultView = WebSearchResultView | WebFetchResultView;\n export interface WebSearchResultView {\n card: 'web';\n kind: 'search';\n title?: string;\n sources: WebSource[];\n answer?: string;\n truncated: boolean;\n }\n export interface WebSource {\n url: string;\n title?: string;\n snippet?: string;\n publishedAt?: string;\n }"}],"isError":false}],"role":"user","id":"847bf2e6-59da-4621-946d-06932a78f0ce"}},"sourceEventSeqs":[15],"surfaceOp":"append"} +{"type":"tool/result","seq":16,"time":1785730459904,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"## api\n- tools — Tool registry and execution pipeline.\n /**\n * Register globally or in the calling agent scope. Scoped tools shadow\n * globals; duplicates within one layer and the reserved `run_code` name fail.\n * @param definition - tool schema, execution, and optional finalization/presentation callbacks.\n * @returns the exact disposer that unregisters the tool.\n */\n register(definition: ToolDefinition): () => void\n /**\n * Restrict global tools for the calling agent scope. Empty filters, unknown\n * names, scope-local names, and reserved transport names fail. Restrictions\n * intersect; scoped registrations remain visible.\n * @param filter - global-surface mask: `allow` (keep only) and/or `deny` (remove).\n * @returns the exact disposer that lifts this restriction.\n */\n restrict(filter: ToolRestriction): () => void\n /**\n * Register a monotonic guard after the extensible `tools/pre-execute`\n * waterfall. A plain-context guard applies globally; one registered through\n * `agent.ctx` applies only to that agent. Any matching guard may deny by\n * returning a reason, while no guard can force-allow a call another guard\n * denied. The exact effect disposer is returned for ordered ownership and\n * HMR cleanup.\n * @param guard - synchronous check; a returned string denies the execution.\n * @returns the exact disposer that unregisters the guard.\n */\n guard(guard: ToolGuard): () => void\n /**\n * Look up a tool as one scope sees it (scoped\n * shadows global; a restricted-away global reads as absent). Presenters pass\n * the calling agent so the rendered card matches the definition that\n * actually executed.\n * @param name - the tool name as registered.\n * @param scope - the viewing scope (the agent); omitted = the global view.\n * @returns the definition the scope resolves, or undefined when none is visible.\n */\n get(name: string, scope?: ScopeKey): ToolDefinition | undefined\n /**\n * Project visible definitions onto the allowlisted model-facing schema fields,\n * excluding execution and presentation callbacks.\n * @param scope - the viewing scope (the agent); omitted = the global view.\n * @returns one deep-cloned schema per visible tool.\n */\n schemas(scope?: ScopeKey): ToolSchema[]\n /**\n * Classify a pending call through the caller's visible tool definition. Only\n * an exact `true` is parallel; unknown, hidden, undeclared, invalid, or\n * throwing classifiers are exclusive.\n * @param exec - call name, parsed arguments, and optional agent scope.\n * @returns the fail-closed scheduling mode.\n */\n executionMode(exec: ToolExecutionInput): ToolExecutionMode\n /**\n * Execute through pre-policy, guards, around-dispatch, post-policy,\n * definition-owned content finalization, and final notification. Tool and\n * listener failures resolve as materialized error results; an invisible tool\n * reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen\n * snapshot final observers receive. Cancellation\n * arriving after entry and before final result materialization skips a\n * not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a\n * successful started outcome with `ABORTED`; already-started work is still\n * drained and may retain a tool-owned structured error.\n * @param exec - the typed same-process call input. The registry assigns its\n * correlation token before policy begins.\n * @returns the materialized final result.\n */\n async execute(exec: ToolExecutionInput): Promise\ntype shapes (referenced by the signatures above — read these before assuming a field is a string):\n export interface Agent {\n readonly id: SessionId;\n readonly options: AgentOptions;\n readonly session: Session;\n readonly inbox: Inbox;\n readonly status: AgentStatus;\n readonly ctx: Context;\n cancel(cause: AgentCancelCause, options?: CancelOptions): void;\n whenIdle(): Promise;\n runMaintenance(task: (signal: AbortSignal) => Promise): Promise;\n send(message: UserMessage, target: InboxTarget, wakeup: boolean): void;\n followup(message: UserMessage): void;\n steer(message: UserMessage): void;\n inject(message: UserMessage): void;\n }\n export type AgentCancelCause = {\n readonly kind: 'user';\n } | {\n readonly kind: 'parent';\n } | {\n readonly kind: 'hook';\n readonly reason: string;\n } | {\n readonly kind: 'disposed';\n };\n export interface AgentOptions {\n provider?: string;\n model?: string;\n maxTokens?: number;\n }\n export type AgentStatus = 'idle' | 'running';\n export interface AssistantMessage extends Message {\n readonly role: 'assistant';\n readonly source: ModelMessageSource;\n }\n export interface AssistantProvenance {\n provider: string;\n model: string;\n replayState?: unknown;\n }\n export type Branded = string & {\n readonly [BRAND]: B;\n };\n export type CallId = Branded<'CallId'>;\n export interface CancelOptions {\n keepInbox?: boolean | undefined;\n }\n export interface ContentBlockMap {\n 'text': TextBlock;\n 'reasoning': ReasoningBlock;\n 'tool-call': ToolCallBlock;\n 'tool-result': ToolResultBlock;\n }\n export type ContentBlockType = keyof ContentBlockMap;\n export interface DiffCallView {\n card: 'diff';\n title: string;\n diffs: FileDiff[];\n locations?: FileLocation[];\n }\n export interface DiffResultView {\n card: 'diff';\n title?: string;\n diffs: FileDiff[];\n }\n export interface EpochHeader {\n config: LlmCallConfig;\n adapterDefaults?: LlmCallConfigAdapterDefaults;\n system?: string;\n tools?: ToolSchema[];\n }\n export interface FileDiff {\n path: string;\n oldText: string | null;\n newText: string;\n }\n export interface FileLocation {\n path: string;\n line?: number;\n }\n export type FinishReason = FinishReasonMap[keyof FinishReasonMap];\n export interface FinishReasonMap {\n 'stop': {\n kind: 'stop';\n };\n 'tool-calls': {\n kind: 'tool-calls';\n };\n 'max-tokens': {\n kind: 'max-tokens';\n };\n 'aborted': {\n kind: 'aborted';\n failure: LlmFailure;\n };\n 'error': {\n kind: 'error';\n failure: LlmFailure;\n };\n }\n export interface GenericCallView {\n card: 'generic';\n title: string;\n kind?: ToolCallKind;\n rawInput?: unknown;\n content?: ContentBlock[];\n locations?: FileLocation[];\n }\n export interface GenericResultView {\n card: 'generic';\n title?: string;\n content?: ContentBlock[];\n }\n export class Inbox {\n constructor(private readonly session: Session, private readonly notifications: InboxNotifications);\n get nextTurn(): readonly UserMessage[];\n get nextStep(): readonly UserMessage[];\n get hasPending(): boolean;\n clear(): void;\n claim(target: InboxTarget): UserMessage[];\n append(target: InboxTarget, message: UserMessage): void;\n prepend(target: InboxTarget, message: UserMessage): void;\n replace(messageId: MessageId, newMessage: UserMessage): boolean;\n remove(messageId: MessageId): boolean;\n splice(target: InboxTarget, start: number, deleteCount: number, inserted: UserMessage[]): UserMessage[];\n }\n export interface InboxNotifications {\n inserted(message: UserMessage): void;\n discarded(message: UserMessage): void;\n }\n export type InboxTarget = 'next-turn' | 'next-step';\n export interface JsonSchemaNode {\n type?: JsonSchemaType;\n oneOf?: JsonSchemaNode[];\n properties?: Record;\n required?: string[];\n additionalProperties?: boolean;\n items?: JsonSchemaNode;\n enum?: JsonSchemaScalar[];\n const?: JsonSchemaScalar;\n description?: string;\n title?: string;\n default?: JsonValue;\n examples?: JsonValue;\n }\n export type JsonSchemaScalar = string | number | boolean | null;\n export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\n export type JsonValue = null | boolean | number | string | JsonValue[] | {\n [key: string]: JsonValue;\n };\n export interface LlmCallConfig {\n provider: string;\n model: string;\n reasoningEffort?: ReasoningEffortId;\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n }\n export interface LlmCallConfigAdapterDefaults {\n reasoningEffort?: true;\n maxTokens?: true;\n }\n export interface LlmFailure {\n readonly message: string;\n readonly code: string;\n readonly status?: number;\n readonly providerRetryAfterMs?: number;\n readonly requestId?: ProviderRequestId;\n }\n export interface Message {\n readonly id: MessageId;\n readonly role: 'system' | 'user' | 'assistant';\n readonly content: ContentBlock[];\n readonly source: MessageSource;\n }\n export type MessageId = Branded<'MessageId'>;\n export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\n export interface MessageSourceMap {\n user: {\n kind: 'user';\n };\n plugin: {\n kind: 'plugin';\n plugin: string;\n };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n }\n export interface ModelMessageSource extends AssistantProvenance {\n kind: 'model';\n }\n export type ProviderRequestId = Branded<'ProviderRequestId'>;\n export interface ReadFileLine {\n number: number;\n text: string;\n }\n export interface ReadResultView {\n card: 'read';\n title?: string;\n path: string;\n offset: number;\n lines: ReadFileLine[];\n totalLines: number;\n lang?: string;\n content?: ContentBlock[];\n }\n export interface ReasoningBlock {\n type: 'reasoning';\n text: string;\n }\n export type ReasoningEffortId = Branded<'ReasoningEffortId'>;\n export interface RequestContext {\n provider: string;\n model: string;\n contextWindow?: number;\n }\n export type RequestHeaderReason = 'initial' | 'resume' | 'change';\n export type ScopeKey = object;\n export interface SearchFileMatches {\n path: string;\n matches: SearchLineMatch[];\n }\n export interface SearchLineMatch {\n lineNumber: number;\n line: string;\n }\n export interface SearchMatchesResultView {\n card: 'search';\n shape: 'matches';\n title?: string;\n files: SearchFileMatches[];\n truncated: boolean;\n total: number;\n }\n export interface SearchPathsResultView {\n card: 'search';\n shape: 'paths';\n title?: string;\n paths: string[];\n truncated: boolean;\n total: number;\n }\n export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\n export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n get id(): SessionId;\n readonly firstLiveSeq: number;\n constructor(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader);\n get events(): readonly SessionEvent[];\n get seq(): number;\n append(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [\n opts: SurfaceIntent\n ] : [\n ]): SessionEvent;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n deriveMessages(): Message[];\n deriveEventMessage(event: SessionEvent): Message | null;\n }\n export type SessionEvent = {\n [K in SessionEventType]: {\n type: K;\n seq: number;\n time: number;\n data: SessionEventMap[K];\n } & (K extends SurfaceEventType ? {\n sourceEventSeqs?: number[];\n surfaceOp?: SurfaceOp;\n } : object);\n }[T];\n export interface SessionEventMap {\n 'turn/start': {\n turn: number;\n };\n 'turn/end': {\n turn: number;\n reason: TurnEndReason;\n };\n 'step/start': {\n turn: number;\n step: number;\n };\n 'step/end': {\n turn: number;\n step: number;\n };\n 'user/message': UserMessage;\n 'assistant/chunk': {\n turn: number;\n step: number;\n chunk: StreamChunk;\n };\n 'assistant/message': {\n turn: number;\n step: number;\n message: AssistantMessage;\n usage?: TokenUsage;\n };\n 'tool/call': {\n turn: number;\n step: number;\n callId: CallId;\n name: string;\n arguments: string;\n };\n 'tool/result': {\n turn: number;\n step: number;\n message: ToolResultMessage;\n error?: {\n name: string;\n code: string;\n };\n meta?: JsonValue;\n };\n 'todo/write': {\n todos: TodoItem[];\n };\n 'request/header': {\n header: EpochHeader;\n reason: RequestHeaderReason;\n };\n 'request/context': RequestContext;\n 'session/end-seed': Record;\n }\n export type SessionEventType = keyof SessionEventMap;\n export interface SessionHeader {\n readonly version: number;\n readonly id: SessionId;\n readonly createdAt: number;\n readonly cwd?: string;\n readonly parentSession?: SessionId;\n readonly seedLength?: number;\n readonly origin?: 'subagent';\n readonly delegationDepth?: number;\n }\n export type SessionId = Branded<'SessionId'>;\n export interface SessionSurface {\n readonly nodes: readonly number[];\n readonly replaceGeneration: number;\n }\n export type StreamChunk = {\n type: 'block-start';\n index: number;\n blockType: ContentBlockType;\n } | {\n type: 'text-delta';\n index: number;\n text: string;\n } | {\n type: 'reasoning-delta';\n index: number;\n text: string;\n } | {\n type: 'tool-call-delta';\n index: number;\n id: CallId;\n name?: string;\n argumentsDelta: string;\n } | {\n type: 'block-end';\n index: number;\n block: ContentBlock;\n } | {\n type: 'usage';\n usage: TokenUsage;\n } | {\n type: 'finish';\n reason: FinishReason;\n replayState?: unknown;\n };\n export type SurfaceEventType = 'user/message' | 'assistant/message' | 'tool/result';\n export interface SurfaceIntent {\n surfaceOp: SurfaceOp;\n sourceEventSeqs?: number[];\n }\n export type SurfaceOp = 'append' | {\n op: 'replace';\n start: number;\n end: number;\n };\n export interface TerminalCallView {\n card: 'terminal';\n title: string;\n description?: string;\n cwd?: string;\n }\n export interface TerminalResultView {\n card: 'terminal';\n title?: string;\n output?: string;\n exitCode?: number;\n signal?: string;\n }\n export interface TodoItem {\n content: string;\n status: 'pending' | 'in_progress' | 'completed';\n }\n export interface TokenUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n reasoningTokens?: number;\n }\n export interface ToolCallBlock {\n type: 'tool-call';\n id: CallId;\n name: string;\n arguments: string;\n }\n export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\n export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\n export interface ToolDefinition extends ToolSchema {\n readonly output: ToolOutputDefinition;\n execute(args: unknown, exec: ToolRunContext): Promise;\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\n timeoutMs?: number;\n isConcurrencySafe?(args: unknown): boolean;\n presentCall?(args: unknown): ToolCallView | undefined;\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\n }\n export interface ToolErrorInfo {\n name: string;\n code: string;\n }\n export interface ToolExecution extends ToolExecutionInput {\n readonly token: ToolExecutionToken;\n }\n export interface ToolExecutionFailure {\n readonly isError: true;\n readonly error: ToolFailure;\n readonly value?: never;\n readonly content: ContentBlock[];\n readonly meta?: JsonValue;\n readonly additionalContexts?: UserMessage[];\n readonly concludesTurn?: never;\n }\n export interface ToolExecutionInput {\n readonly callId: CallId;\n readonly name: string;\n readonly arguments: unknown;\n readonly agent?: Agent;\n readonly parent?: ToolExecutionToken;\n readonly signal: AbortSignal;\n }\n export type ToolExecutionMode = {\n kind: 'parallel';\n } | {\n kind: 'exclusive';\n };\n export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\n export interface ToolExecutionSuccess {\n readonly isError: false;\n readonly value: JsonValue;\n readonly content: ContentBlock[];\n readonly error?: never;\n readonly meta?: JsonValue;\n readonly additionalContexts?: UserMessage[];\n readonly concludesTurn?: true;\n }\n export type ToolExecutionToken = symbol & {\n readonly [toolExecutionTokenBrand]: true;\n };\n export interface ToolFailure {\n message: string;\n info?: ToolErrorInfo;\n }\n export type ToolGuard = (execution: Readonly) => string | undefined;\n export interface ToolMessageSource {\n kind: 'tool';\n callId: CallId;\n }\n export interface ToolOutputDefinition {\n readonly schema: JsonSchemaNode;\n render(args: unknown, value: JsonValue): ContentBlock[];\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\n }\n export interface ToolRestriction {\n readonly allow?: readonly string[];\n readonly deny?: readonly string[];\n }\n export interface ToolResult {\n content: ContentBlock[];\n isError: boolean;\n meta?: JsonValue;\n }\n export interface ToolResultBlock {\n type: 'tool-result';\n toolCallId: CallId;\n content: ContentBlock[];\n isError?: boolean;\n }\n export interface ToolResultMessage extends Message {\n readonly role: 'user';\n readonly content: [\n ToolResultBlock\n ];\n readonly source: ToolMessageSource;\n }\n export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\n export interface ToolRunContext extends ToolExecution {\n deferContext(context: UserMessage): void;\n concludeTurn(): void;\n }\n export interface ToolSchema {\n name: string;\n description: string;\n parameters: Record;\n }\n export type TurnEndCancelCause = AgentCancelCause | {\n readonly kind: 'legacy';\n };\n export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap];\n export interface TurnEndReasonMap {\n completed: {\n kind: 'completed';\n };\n aborted: {\n kind: 'aborted';\n reason: TurnEndCancelCause;\n };\n blocked: {\n kind: 'blocked';\n };\n error: {\n kind: 'error';\n error: LlmFailure;\n };\n 'max-tokens': {\n kind: 'max-tokens';\n };\n interrupted: {\n kind: 'interrupted';\n };\n }\n export interface UserMessage extends Message {\n readonly role: 'user';\n }\n export interface WebFetchResultView {\n card: 'web';\n kind: 'fetch';\n title?: string;\n url: string;\n statusCode: number;\n truncated: boolean;\n }\n export type WebResultView = WebSearchResultView | WebFetchResultView;\n export interface WebSearchResultView {\n card: 'web';\n kind: 'search';\n title?: string;\n sources: WebSource[];\n answer?: string;\n truncated: boolean;\n }\n export interface WebSource {\n url: string;\n title?: string;\n snippet?: string;\n publishedAt?: string;\n }"}],"isError":false}],"role":"user","id":"847bf2e6-59da-4621-946d-06932a78f0ce"}},"sourceEventSeqs":[15],"surfaceOp":"append"} {"type":"step/end","seq":17,"time":1785730459904,"data":{"turn":1,"step":1}} {"type":"step/start","seq":18,"time":1785730459916,"data":{"turn":1,"step":2}} {"type":"assistant/chunk","seq":19,"time":1784449176734,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} @@ -35,4 +35,4 @@ {"type":"assistant/chunk","seq":33,"time":1785730459943,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":34,"time":1785730459943,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"CORDIS_INSPECT_JSDOC_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"a4aa43b5-240e-423a-bc03-0abed8d890e4"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[29,30,31,32,33],"surfaceOp":"append"} {"type":"step/end","seq":35,"time":1785730459943,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":36,"time":1785730459943,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":36,"time":1785730459943,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/empty-response-retry/session.jsonl b/examples/acp-agent/tests/snapshots/empty-response-retry/session.jsonl index 1b4447f3aa..741fb4d797 100644 --- a/examples/acp-agent/tests/snapshots/empty-response-retry/session.jsonl +++ b/examples/acp-agent/tests/snapshots/empty-response-retry/session.jsonl @@ -18,4 +18,4 @@ {"type":"assistant/chunk","seq":16,"time":1785730441209,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":17,"time":1785730441209,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"Recovered."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"422eae65-9975-4a95-8cde-1ddfe21fff4e"},"usage":{"inputTokens":12,"outputTokens":3}},"sourceEventSeqs":[12,13,14,15,16],"surfaceOp":"append"} {"type":"step/end","seq":18,"time":1785730441209,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":19,"time":1785730441209,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":19,"time":1785730441209,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/error-finish/session.jsonl b/examples/acp-agent/tests/snapshots/error-finish/session.jsonl index c90cf99ce3..12afe1fa3e 100644 --- a/examples/acp-agent/tests/snapshots/error-finish/session.jsonl +++ b/examples/acp-agent/tests/snapshots/error-finish/session.jsonl @@ -10,4 +10,4 @@ {"type":"request/context","seq":8,"time":1785730686100,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","seq":9,"time":1785730686108,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}}} {"type":"step/end","seq":10,"time":1785730686108,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":11,"time":1785730686109,"data":{"turn":1,"step":1,"reason":{"kind":"error","error":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}} +{"type":"turn/end","seq":11,"time":1785730686109,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}} diff --git a/examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl b/examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl index 2fbe8db357..591cd7e4d6 100644 --- a/examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl +++ b/examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl @@ -34,4 +34,4 @@ {"type":"assistant/chunk","seq":185,"time":1785730480556,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":186,"time":1785730480556,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The command succeeded — it wrote the file, read it back (output \"escalated\"), and removed it. The user asked me to reply with the single word DONE after the result."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"feade984-75a1-44dc-aed5-7cb93736c376"},"usage":{"inputTokens":27,"outputTokens":42,"cacheReadTokens":1664,"reasoningTokens":39}},"sourceEventSeqs":[139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185],"surfaceOp":"append"} {"type":"step/end","seq":187,"time":1785730480557,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":188,"time":1785730480557,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":188,"time":1785730480557,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl b/examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl index 3e5de1420a..4f05bbfbcf 100644 --- a/examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl +++ b/examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl @@ -33,4 +33,4 @@ {"type":"assistant/chunk","seq":212,"time":1785730481628,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":213,"time":1785730481628,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user rejected the escalation. As instructed, I will not retry and will not work around it — just explain in one short sentence and stop."},{"type":"text","text":"The user rejected the permission escalation, so this command cannot be run."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"780bcab9-e903-46c1-befa-a72b6cf93dcb"},"usage":{"inputTokens":69,"outputTokens":45,"cacheReadTokens":1664,"reasoningTokens":30}},"sourceEventSeqs":[163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212],"surfaceOp":"append"} {"type":"step/end","seq":214,"time":1785730481629,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":215,"time":1785730481629,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":215,"time":1785730481629,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-edit/session.jsonl b/examples/acp-agent/tests/snapshots/fs-edit/session.jsonl index f1721ea4a4..18125bea36 100644 --- a/examples/acp-agent/tests/snapshots/fs-edit/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-edit/session.jsonl @@ -45,4 +45,4 @@ {"type":"assistant/chunk","seq":159,"time":1785730434565,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":160,"time":1785730434565,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Done. The user wants me to reply with exactly \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"1b426931-4d0f-4595-af9d-6eb1f5241f92"},"usage":{"inputTokens":244,"outputTokens":17,"cacheReadTokens":2944,"reasoningTokens":14}},"sourceEventSeqs":[138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159],"surfaceOp":"append"} {"type":"step/end","seq":161,"time":1785730434565,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":162,"time":1785730434565,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":162,"time":1785730434565,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl b/examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl index 74c5f87407..b5feb459d3 100644 --- a/examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl @@ -34,4 +34,4 @@ {"type":"assistant/chunk","seq":124,"time":1785730482702,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":125,"time":1785730482702,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The file was created successfully. The user asked me to reply with exactly the single word DONE."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"8d322465-9e9a-4872-a0d5-f920a666153c"},"usage":{"inputTokens":107,"outputTokens":23,"cacheReadTokens":3968,"reasoningTokens":20}},"sourceEventSeqs":[97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124],"surfaceOp":"append"} {"type":"step/end","seq":126,"time":1785730482702,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":127,"time":1785730482702,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":127,"time":1785730482702,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-glob-sampling/session.jsonl b/examples/acp-agent/tests/snapshots/fs-glob-sampling/session.jsonl index 7c73be83ef..a712edb3ea 100644 --- a/examples/acp-agent/tests/snapshots/fs-glob-sampling/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-glob-sampling/session.jsonl @@ -30,4 +30,4 @@ {"type":"assistant/chunk","seq":117,"time":1785815911893,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":118,"time":1785815911894,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The glob result shows it was sampled - 4 of 8 paths across 4 of 6 top-level entries. I need to reply with exactly \"GLOB_SAMPLED\" as instructed."},{"type":"text","text":"GLOB_SAMPLED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"f11fc733-498d-44a3-9fc5-07fead8c0a68"},"usage":{"inputTokens":188,"outputTokens":48,"cacheReadTokens":1280,"reasoningTokens":41}},"sourceEventSeqs":[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117],"surfaceOp":"append"} {"type":"step/end","seq":119,"time":1785815911894,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":120,"time":1785815911894,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":120,"time":1785815911894,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-policy-reject/session.jsonl b/examples/acp-agent/tests/snapshots/fs-policy-reject/session.jsonl index 79aa0c0cf0..112a2b6cab 100644 --- a/examples/acp-agent/tests/snapshots/fs-policy-reject/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-policy-reject/session.jsonl @@ -58,4 +58,4 @@ {"type":"assistant/chunk","seq":258,"time":1785730437959,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":259,"time":1785730437959,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The replacement was successful. I'll reply with just \"DONE\" as instructed."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5ccbca9e-74e5-45d1-b3c8-5c4c2edc19c3"},"usage":{"inputTokens":202,"outputTokens":20,"cacheReadTokens":3456,"reasoningTokens":17}},"sourceEventSeqs":[234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258],"surfaceOp":"append"} {"type":"step/end","seq":260,"time":1785730437960,"data":{"turn":1,"step":4}} -{"type":"turn/end","seq":261,"time":1785730437960,"data":{"turn":1,"step":4,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":261,"time":1785730437960,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-read-window/session.jsonl b/examples/acp-agent/tests/snapshots/fs-read-window/session.jsonl index 77c5d29ce5..ea8e65620c 100644 --- a/examples/acp-agent/tests/snapshots/fs-read-window/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-read-window/session.jsonl @@ -32,4 +32,4 @@ {"type":"assistant/chunk","seq":134,"time":1785730436802,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":135,"time":1785730436803,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The read tool returned lines 5 through 8 as expected. Now I need to reply with exactly the single word \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"4f7f5f2a-8fbd-4221-b813-b2a5272e4d4e"},"usage":{"inputTokens":292,"outputTokens":30,"cacheReadTokens":2816,"reasoningTokens":27}},"sourceEventSeqs":[100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134],"surfaceOp":"append"} {"type":"step/end","seq":136,"time":1785730436803,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":137,"time":1785730436803,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":137,"time":1785730436803,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-read/session.jsonl b/examples/acp-agent/tests/snapshots/fs-read/session.jsonl index cbb6c9cfbe..0373748f2c 100644 --- a/examples/acp-agent/tests/snapshots/fs-read/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-read/session.jsonl @@ -32,4 +32,4 @@ {"type":"assistant/chunk","seq":106,"time":1785730432330,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":107,"time":1785730432330,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asked me to read the file and then reply with exactly the single word \"DONE\". I've read the file. Now I just need to reply with \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"90e72cf4-dc61-4349-8c5e-6b835ea94f4d"},"usage":{"inputTokens":200,"outputTokens":40,"cacheReadTokens":2816,"reasoningTokens":37}},"sourceEventSeqs":[62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106],"surfaceOp":"append"} {"type":"step/end","seq":108,"time":1785730432330,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":109,"time":1785730432330,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":109,"time":1785730432330,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-write-overwrite/session.jsonl b/examples/acp-agent/tests/snapshots/fs-write-overwrite/session.jsonl index 743494f2f7..67c4a6c2be 100644 --- a/examples/acp-agent/tests/snapshots/fs-write-overwrite/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-write-overwrite/session.jsonl @@ -45,4 +45,4 @@ {"type":"assistant/chunk","seq":146,"time":1785730435701,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":147,"time":1785730435701,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The file has been replaced successfully. Now I just reply with \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"1411eb9b-9cc6-48fa-8d1e-2f4b91b8b9aa"},"usage":{"inputTokens":225,"outputTokens":19,"cacheReadTokens":2944,"reasoningTokens":16}},"sourceEventSeqs":[123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146],"surfaceOp":"append"} {"type":"step/end","seq":148,"time":1785730435701,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":149,"time":1785730435702,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":149,"time":1785730435702,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/fs-write/session.jsonl b/examples/acp-agent/tests/snapshots/fs-write/session.jsonl index 47fbaea0f9..48a3c835c9 100644 --- a/examples/acp-agent/tests/snapshots/fs-write/session.jsonl +++ b/examples/acp-agent/tests/snapshots/fs-write/session.jsonl @@ -32,4 +32,4 @@ {"type":"assistant/chunk","seq":95,"time":1785730433427,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":96,"time":1785730433428,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The file has been created. Now I just need to reply with \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"91664038-fb2c-4305-b1a5-02daaf93aeca"},"usage":{"inputTokens":214,"outputTokens":20,"cacheReadTokens":2816,"reasoningTokens":17}},"sourceEventSeqs":[71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95],"surfaceOp":"append"} {"type":"step/end","seq":97,"time":1785730433428,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":98,"time":1785730433428,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":98,"time":1785730433428,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-cc-invalid-matcher/session.jsonl b/examples/acp-agent/tests/snapshots/hook-cc-invalid-matcher/session.jsonl index 05c251d728..11409c2a03 100644 --- a/examples/acp-agent/tests/snapshots/hook-cc-invalid-matcher/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-cc-invalid-matcher/session.jsonl @@ -19,4 +19,4 @@ {"type":"assistant/chunk","seq":36,"time":1785730463106,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":37,"time":1785730463106,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."},{"type":"text","text":"PONG"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"09e21cd4-86fd-4088-9419-54f7e95ee4da"},"usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"surfaceOp":"append"} {"type":"step/end","seq":38,"time":1785730463106,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":39,"time":1785730463106,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":39,"time":1785730463106,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-cc-posttool-block/session.jsonl b/examples/acp-agent/tests/snapshots/hook-cc-posttool-block/session.jsonl index 0d2cd5f069..c8c3b91c7d 100644 --- a/examples/acp-agent/tests/snapshots/hook-cc-posttool-block/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-cc-posttool-block/session.jsonl @@ -48,4 +48,4 @@ {"type":"assistant/chunk","seq":177,"time":1785730468639,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":178,"time":1785730468639,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The second attempt succeeded. The final result is \"HELLO\"."},{"type":"text","text":"The final tool result verbatim:\n\n```\nHELLO\n```"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"144a17d3-106c-4f62-867d-a9d4d97aceab"},"usage":{"inputTokens":36,"outputTokens":28,"cacheReadTokens":3456,"reasoningTokens":14}},"sourceEventSeqs":[145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177],"surfaceOp":"append"} {"type":"step/end","seq":179,"time":1785730468639,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":180,"time":1785730468639,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":180,"time":1785730468639,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-cc-posttool-context/session.jsonl b/examples/acp-agent/tests/snapshots/hook-cc-posttool-context/session.jsonl index f5c7ab0bbe..597b1eaa28 100644 --- a/examples/acp-agent/tests/snapshots/hook-cc-posttool-context/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-cc-posttool-context/session.jsonl @@ -36,4 +36,4 @@ {"type":"assistant/chunk","seq":128,"time":1785730469730,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":129,"time":1785730469730,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asked me to report the tool result verbatim. The result was \"HELLO\" with an exit code of 0 (success)."},{"type":"text","text":"The tool result was:\n\n```\nHELLO\n```\n\nIt completed successfully with exit code 0."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"33bc2b6b-1d60-4143-971a-8ea2dab595bd"},"usage":{"inputTokens":188,"outputTokens":51,"cacheReadTokens":2816,"reasoningTokens":30}},"sourceEventSeqs":[73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128],"surfaceOp":"append"} {"type":"step/end","seq":130,"time":1785730469730,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":131,"time":1785730469730,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":131,"time":1785730469730,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl b/examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl index 3d579bbfb1..1ee79964b0 100644 --- a/examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl @@ -35,4 +35,4 @@ {"type":"assistant/chunk","seq":115,"time":1785730467525,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":116,"time":1785730467526,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The bash tool returned an error saying it requires manual approval in this session. I'll report this verbatim."},{"type":"text","text":"The tool result I got back verbatim is:\n\n```\nError: bash requires manual approval in this session\n```"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"707dacf7-7d41-4906-92f7-25656fdb1b4f"},"usage":{"inputTokens":166,"outputTokens":45,"cacheReadTokens":2816,"reasoningTokens":22}},"sourceEventSeqs":[66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115],"surfaceOp":"append"} {"type":"step/end","seq":117,"time":1785730467526,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":118,"time":1785730467526,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":118,"time":1785730467526,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/session.jsonl b/examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/session.jsonl index 1d91769b58..880eb36f8c 100644 --- a/examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/session.jsonl @@ -33,4 +33,4 @@ {"type":"assistant/chunk","seq":120,"time":1785730466401,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":121,"time":1785730466401,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The bash tool is disabled by policy. I need to report this error verbatim back to the user."},{"type":"text","text":"The tool returned:\n\n> Error: bash is disabled by policy in this session\n\nI cannot run the command because the bash tool is disabled by policy."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cb2cc300-1026-4bb9-8cc2-3c8869d13528"},"usage":{"inputTokens":167,"outputTokens":52,"cacheReadTokens":2816,"reasoningTokens":21}},"sourceEventSeqs":[64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120],"surfaceOp":"append"} {"type":"step/end","seq":122,"time":1785730466401,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":123,"time":1785730466401,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":123,"time":1785730466401,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/session.jsonl b/examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/session.jsonl index 5e0dd331d4..e2a05b7d1d 100644 --- a/examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/session.jsonl @@ -22,4 +22,4 @@ {"type":"assistant/chunk","seq":38,"time":1785821426965,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":39,"time":1785821426965,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user's favorite color is teal, as stated in the context provided by the plugin."},{"type":"text","text":"teal"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"097b2896-4bc1-4d33-be4b-5b7f4fc6dd41"},"usage":{"inputTokens":2892,"outputTokens":22,"cacheReadTokens":0,"reasoningTokens":19}},"sourceEventSeqs":[12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"surfaceOp":"append"} {"type":"step/end","seq":40,"time":1785821426965,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":41,"time":1785821426966,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":41,"time":1785821426966,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-cc-stop-continue/session.jsonl b/examples/acp-agent/tests/snapshots/hook-cc-stop-continue/session.jsonl index 460352812e..96fe89d332 100644 --- a/examples/acp-agent/tests/snapshots/hook-cc-stop-continue/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-cc-stop-continue/session.jsonl @@ -38,4 +38,4 @@ {"type":"step/end","seq":69,"time":1785730470785,"data":{"turn":1,"step":2}} {"type":"hook/invoked","seq":70,"time":1785730470785,"data":{"turn":1,"point":"Stop","dialect":"claude","handlerId":"claude:Stop:2"}} {"type":"hook/result","seq":71,"time":1785730470788,"data":{"turn":1,"point":"Stop","handlerId":"claude:Stop:2","decision":"pass","exitCode":0,"durationMs":2.633624999999938}} -{"type":"turn/end","seq":72,"time":1785730470788,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":72,"time":1785730470788,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-codex-invalid-matcher/session.jsonl b/examples/acp-agent/tests/snapshots/hook-codex-invalid-matcher/session.jsonl index 11a0b7cf88..a161045ea5 100644 --- a/examples/acp-agent/tests/snapshots/hook-codex-invalid-matcher/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-codex-invalid-matcher/session.jsonl @@ -19,4 +19,4 @@ {"type":"assistant/chunk","seq":36,"time":1785730464177,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":37,"time":1785730464177,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."},{"type":"text","text":"PONG"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"c6f7b850-9c28-41a0-ae85-27c03578ecba"},"usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"surfaceOp":"append"} {"type":"step/end","seq":38,"time":1785730464177,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":39,"time":1785730464177,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":39,"time":1785730464177,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-codex-posttool-block/session.jsonl b/examples/acp-agent/tests/snapshots/hook-codex-posttool-block/session.jsonl index e1fae5ef02..2903c7a247 100644 --- a/examples/acp-agent/tests/snapshots/hook-codex-posttool-block/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-codex-posttool-block/session.jsonl @@ -33,4 +33,4 @@ {"type":"assistant/chunk","seq":118,"time":1785730473933,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":119,"time":1785730473933,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The tool output was rejected by codex policy. Let me quote what I got back."},{"type":"text","text":"```\ntool output rejected by codex policy: summarize instead\n```"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ad398545-2fd8-419c-937b-44c6387c11e3"},"usage":{"inputTokens":44,"outputTokens":39,"cacheReadTokens":3328,"reasoningTokens":18}},"sourceEventSeqs":[75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118],"surfaceOp":"append"} {"type":"step/end","seq":120,"time":1785730473933,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":121,"time":1785730473933,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":121,"time":1785730473933,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-codex-posttool-context/session.jsonl b/examples/acp-agent/tests/snapshots/hook-codex-posttool-context/session.jsonl index 62af0d6bcf..2813196245 100644 --- a/examples/acp-agent/tests/snapshots/hook-codex-posttool-context/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-codex-posttool-context/session.jsonl @@ -36,4 +36,4 @@ {"type":"assistant/chunk","seq":118,"time":1785730474989,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":119,"time":1785730474989,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asked me to report the tool result verbatim. The result I got back is:\n\nHELLO\n\nThat's it."},{"type":"text","text":"The tool result I received is:\n\n```\nHELLO\n```"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"12bf71d7-c8bb-404f-84fb-e5964de5c19f"},"usage":{"inputTokens":188,"outputTokens":41,"cacheReadTokens":2816,"reasoningTokens":27}},"sourceEventSeqs":[73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118],"surfaceOp":"append"} {"type":"step/end","seq":120,"time":1785730474989,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":121,"time":1785730474989,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":121,"time":1785730474989,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-codex-pretool-block/session.jsonl b/examples/acp-agent/tests/snapshots/hook-codex-pretool-block/session.jsonl index 932ced462e..a329513cee 100644 --- a/examples/acp-agent/tests/snapshots/hook-codex-pretool-block/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-codex-pretool-block/session.jsonl @@ -33,4 +33,4 @@ {"type":"assistant/chunk","seq":117,"time":1785730472863,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":118,"time":1785730472863,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The bash tool is disabled by policy in this session. I need to report this result verbatim to the user."},{"type":"text","text":"The tool result I got back verbatim is:\n\n```\nError: bash is disabled by codex policy in this session\n```"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"f996eea7-d53a-42a6-a0bf-a7b16bcb49d2"},"usage":{"inputTokens":171,"outputTokens":49,"cacheReadTokens":2816,"reasoningTokens":23}},"sourceEventSeqs":[64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117],"surfaceOp":"append"} {"type":"step/end","seq":119,"time":1785730472864,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":120,"time":1785730472864,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":120,"time":1785730472864,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/session.jsonl b/examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/session.jsonl index c4191d19de..b2a6d287da 100644 --- a/examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/session.jsonl @@ -22,4 +22,4 @@ {"type":"assistant/chunk","seq":57,"time":1785821434055,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":58,"time":1785821434055,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asked about their favorite color, and the context tells me they previously stated it's teal. They asked me to reply with just the color and stop, without using any tools."},{"type":"text","text":"teal"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"05782b9b-b4ce-4a05-abce-50c05c8a9259"},"usage":{"inputTokens":2891,"outputTokens":41,"cacheReadTokens":0,"reasoningTokens":38}},"sourceEventSeqs":[12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57],"surfaceOp":"append"} {"type":"step/end","seq":59,"time":1785821434055,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":60,"time":1785821434055,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":60,"time":1785821434055,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/hook-codex-stop-continue/session.jsonl b/examples/acp-agent/tests/snapshots/hook-codex-stop-continue/session.jsonl index 3b92a939cc..b28ab2e081 100644 --- a/examples/acp-agent/tests/snapshots/hook-codex-stop-continue/session.jsonl +++ b/examples/acp-agent/tests/snapshots/hook-codex-stop-continue/session.jsonl @@ -38,4 +38,4 @@ {"type":"step/end","seq":69,"time":1785730476033,"data":{"turn":1,"step":2}} {"type":"hook/invoked","seq":70,"time":1785730476033,"data":{"turn":1,"point":"Stop","dialect":"codex","handlerId":"codex:Stop:2"}} {"type":"hook/result","seq":71,"time":1785730476036,"data":{"turn":1,"point":"Stop","handlerId":"codex:Stop:2","decision":"pass","exitCode":0,"durationMs":2.646165999999994}} -{"type":"turn/end","seq":72,"time":1785730476036,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":72,"time":1785730476036,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/lsp-definition/session.jsonl b/examples/acp-agent/tests/snapshots/lsp-definition/session.jsonl index fcdf24eb72..9f48213a1c 100644 --- a/examples/acp-agent/tests/snapshots/lsp-definition/session.jsonl +++ b/examples/acp-agent/tests/snapshots/lsp-definition/session.jsonl @@ -25,4 +25,4 @@ {"type":"assistant/chunk","seq":23,"time":1785730428108,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":24,"time":1785730428108,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"94b551d6-7dc5-41fb-b898-42e8f44bfe4e"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} {"type":"step/end","seq":25,"time":1785730428108,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":26,"time":1785730428108,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":26,"time":1785730428108,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/multi-turn/session.jsonl b/examples/acp-agent/tests/snapshots/multi-turn/session.jsonl index 61a0364541..6392fbf640 100644 --- a/examples/acp-agent/tests/snapshots/multi-turn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/multi-turn/session.jsonl @@ -18,7 +18,7 @@ {"type":"assistant/chunk","seq":33,"time":1785730439022,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":34,"time":1785730439022,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."},{"type":"text","text":"ONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"4ce3ae64-c2c0-407e-8aa9-46b65ecb0145"},"usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"surfaceOp":"append"} {"type":"step/end","seq":35,"time":1785730439022,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":36,"time":1785730439022,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":36,"time":1785730439022,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":37,"time":1785498786019,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly the word: TWO. No tools."}],"source":{"kind":"user"},"role":"user","id":"99ed2338-f25f-47c5-b2d9-17f9f73f90f8"}]}} {"type":"turn/start","seq":38,"time":1785821395209,"data":{"turn":2}} {"type":"agent/inbox/spliced","seq":39,"time":1785821395209,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} @@ -35,4 +35,4 @@ {"type":"assistant/chunk","seq":67,"time":1785730439038,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":68,"time":1785730439038,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."},{"type":"text","text":"TWO"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"62c5b1a1-dfbb-4b31-af28-346d1ad87333"},"usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}},"sourceEventSeqs":[42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67],"surfaceOp":"append"} {"type":"step/end","seq":69,"time":1785730439038,"data":{"turn":2,"step":1}} -{"type":"turn/end","seq":70,"time":1785730439038,"data":{"turn":2,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":70,"time":1785730439038,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/packed-chunks/session.jsonl b/examples/acp-agent/tests/snapshots/packed-chunks/session.jsonl index d2b0a95baa..4d951eebf9 100644 --- a/examples/acp-agent/tests/snapshots/packed-chunks/session.jsonl +++ b/examples/acp-agent/tests/snapshots/packed-chunks/session.jsonl @@ -33,4 +33,4 @@ {"type":"assistant/chunk","seq":120,"time":1785730418716,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":121,"time":1785730418716,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The bash tool is disabled by policy. I need to report this error verbatim back to the user."},{"type":"text","text":"The tool returned:\n\n> Error: bash is disabled by policy in this session\n\nI cannot run the command because the bash tool is disabled by policy."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0bea7b77-242e-4399-bd10-90324a37fff0"},"usage":{"inputTokens":167,"outputTokens":52,"cacheReadTokens":2816,"reasoningTokens":21}},"sourceEventSeqs":[64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120],"surfaceOp":"append"} {"type":"step/end","seq":122,"time":1785730418716,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":123,"time":1785730418717,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":123,"time":1785730418717,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/parallel-tool-calls/session.jsonl b/examples/acp-agent/tests/snapshots/parallel-tool-calls/session.jsonl index 892f859c6f..251fcaf938 100644 --- a/examples/acp-agent/tests/snapshots/parallel-tool-calls/session.jsonl +++ b/examples/acp-agent/tests/snapshots/parallel-tool-calls/session.jsonl @@ -30,4 +30,4 @@ {"type":"assistant/chunk","seq":28,"time":1785730419922,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":29,"time":1785730419922,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"fdbb9418-bd61-4ec5-9bb9-fa73f632b242"},"usage":{"inputTokens":10,"outputTokens":1}},"sourceEventSeqs":[24,25,26,27,28],"surfaceOp":"append"} {"type":"step/end","seq":30,"time":1785730419922,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":31,"time":1785730419922,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":31,"time":1785730419922,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/pty-tools/session.jsonl b/examples/acp-agent/tests/snapshots/pty-tools/session.jsonl index a89f0c1191..8e5a8a2601 100644 --- a/examples/acp-agent/tests/snapshots/pty-tools/session.jsonl +++ b/examples/acp-agent/tests/snapshots/pty-tools/session.jsonl @@ -75,4 +75,4 @@ {"type":"assistant/chunk","seq":73,"time":1785730423550,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":74,"time":1785730423550,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"1d660de9-1864-4c09-82d7-e3ac9da8c7fe"},"usage":{"inputTokens":10,"outputTokens":3}},"sourceEventSeqs":[69,70,71,72,73],"surfaceOp":"append"} {"type":"step/end","seq":75,"time":1785730423551,"data":{"turn":1,"step":7}} -{"type":"turn/end","seq":76,"time":1785730423551,"data":{"turn":1,"step":7,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":76,"time":1785730423551,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/repeat-tool-guard/session.jsonl b/examples/acp-agent/tests/snapshots/repeat-tool-guard/session.jsonl index 3c42143f61..64bdcbd4fb 100644 --- a/examples/acp-agent/tests/snapshots/repeat-tool-guard/session.jsonl +++ b/examples/acp-agent/tests/snapshots/repeat-tool-guard/session.jsonl @@ -76,4 +76,4 @@ {"type":"assistant/chunk","seq":74,"time":1785730442389,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":75,"time":1785730442389,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"DONE."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"48236fa5-4888-4e27-9e17-05bc246ea622"},"usage":{"inputTokens":10,"outputTokens":3}},"sourceEventSeqs":[70,71,72,73,74],"surfaceOp":"append"} {"type":"step/end","seq":76,"time":1785730442389,"data":{"turn":1,"step":6}} -{"type":"turn/end","seq":77,"time":1785730442389,"data":{"turn":1,"step":6,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":77,"time":1785730442389,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/session-query-spill/session.jsonl b/examples/acp-agent/tests/snapshots/session-query-spill/session.jsonl index 013384aeff..b5b7d42671 100644 --- a/examples/acp-agent/tests/snapshots/session-query-spill/session.jsonl +++ b/examples/acp-agent/tests/snapshots/session-query-spill/session.jsonl @@ -35,4 +35,4 @@ {"type":"assistant/chunk","seq":33,"time":1785730422336,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":34,"time":1785730422337,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"59890792-9e9c-4be8-b4f4-d25ff06855d2"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[29,30,31,32,33],"surfaceOp":"append"} {"type":"step/end","seq":35,"time":1785730422337,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":36,"time":1785730422337,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":36,"time":1785730422337,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/session-sandbox-root/session.jsonl b/examples/acp-agent/tests/snapshots/session-sandbox-root/session.jsonl index 2b83bc1199..22b34e01e1 100644 --- a/examples/acp-agent/tests/snapshots/session-sandbox-root/session.jsonl +++ b/examples/acp-agent/tests/snapshots/session-sandbox-root/session.jsonl @@ -25,4 +25,4 @@ {"type":"assistant/chunk","seq":23,"time":1785730483827,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":24,"time":1785730483827,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"87b694cc-1b3d-4b38-9d2c-1a902556327a"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} {"type":"step/end","seq":25,"time":1785730483827,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":26,"time":1785730483828,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":26,"time":1785730483828,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/session-title-after-turn/session.jsonl b/examples/acp-agent/tests/snapshots/session-title-after-turn/session.jsonl index bc8b555388..a2654b2fde 100644 --- a/examples/acp-agent/tests/snapshots/session-title-after-turn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/session-title-after-turn/session.jsonl @@ -16,5 +16,5 @@ {"type":"assistant/chunk","seq":14,"time":1785730416406,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":15,"time":1785730416406,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"TITLE_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"2c014efb-65c8-4d17-aa95-b535f7f9ff64"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"} {"type":"step/end","seq":16,"time":1785730416406,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":17,"time":1785730416406,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":17,"time":1785730416406,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/title","seq":18,"time":1785730416406,"data":{"title":"Late durable session title","messageSeqs":[4],"source":{"kind":"provider","provider":"session-title-first-message-llm","model":{"provider":"title-replay","model":"title-model"}}}} diff --git a/examples/acp-agent/tests/snapshots/skill-load/session.jsonl b/examples/acp-agent/tests/snapshots/skill-load/session.jsonl index f9be3bdc04..5083f57ef9 100644 --- a/examples/acp-agent/tests/snapshots/skill-load/session.jsonl +++ b/examples/acp-agent/tests/snapshots/skill-load/session.jsonl @@ -32,4 +32,4 @@ {"type":"assistant/chunk","seq":30,"time":1785730426852,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":31,"time":1785730426853,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The skill is loaded."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"abdbdc3b-06a3-4b5f-b807-15d6566154a0"},"usage":{"inputTokens":180,"outputTokens":10,"cacheReadTokens":0,"reasoningTokens":4}},"sourceEventSeqs":[23,24,25,26,27,28,29,30],"surfaceOp":"append"} {"type":"step/end","seq":32,"time":1785730426853,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":33,"time":1785730426853,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":33,"time":1785730426853,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-continuable/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-continuable/session.1.jsonl index f8b9885f33..ccaf248ca3 100644 --- a/examples/acp-agent/tests/snapshots/subagent-continuable/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-continuable/session.1.jsonl @@ -19,7 +19,7 @@ {"type":"assistant/chunk","seq":17,"time":1785730451397,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":18,"time":1785730696668,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"178ea526-9e19-49d2-b3b0-57b682320028"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[13,14,15,16,17],"surfaceOp":"append"} {"type":"step/end","seq":19,"time":1785730696668,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":20,"time":1785730696669,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":20,"time":1785730696669,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"turn/start","seq":21,"time":1785821409092,"data":{"turn":2}} {"type":"agent/inbox/spliced","seq":22,"time":1785821409092,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","seq":23,"time":1785730696682,"data":{"turn":2,"step":1}} @@ -31,7 +31,7 @@ {"type":"assistant/chunk","seq":29,"time":1785730451421,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":30,"time":1785730696686,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SECOND_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ced209bf-5d6d-4880-b187-18cb816a150c"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[25,26,27,28,29],"surfaceOp":"append"} {"type":"step/end","seq":31,"time":1785730696686,"data":{"turn":2,"step":1}} -{"type":"turn/end","seq":32,"time":1785730696686,"data":{"turn":2,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":32,"time":1785730696686,"data":{"turn":2,"reason":{"kind":"completed"}}} {"type":"turn/start","seq":33,"time":1785821409110,"data":{"turn":3}} {"type":"agent/inbox/spliced","seq":34,"time":1785821409110,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} -{"type":"turn/end","seq":35,"time":1785821409122,"data":{"turn":3,"step":0,"reason":{"kind":"error","error":{"message":"snapshot disk full","code":"UNKNOWN"}}}} +{"type":"turn/end","seq":35,"time":1785821409122,"data":{"turn":3,"reason":{"kind":"error","error":{"message":"snapshot disk full","code":"UNKNOWN"}}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-continuable/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-continuable/session.jsonl index 6cfeb1e281..3c8efb1ddd 100644 --- a/examples/acp-agent/tests/snapshots/subagent-continuable/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-continuable/session.jsonl @@ -55,4 +55,4 @@ {"type":"assistant/chunk","seq":53,"time":1785730451453,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":54,"time":1785730451453,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"fbea6b64-84cf-4411-abec-48d26b3801da"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[49,50,51,52,53],"surfaceOp":"append"} {"type":"step/end","seq":55,"time":1785730451454,"data":{"turn":1,"step":5}} -{"type":"turn/end","seq":56,"time":1785730451454,"data":{"turn":1,"step":5,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":56,"time":1785730451454,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.1.jsonl index 03ea20b744..e1362f5cdb 100644 --- a/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.1.jsonl @@ -26,4 +26,4 @@ {"type":"assistant/chunk","seq":24,"time":1785730456086,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":25,"time":1785730456086,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DEPTH_ONE_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5a1911c6-f487-458c-b802-4a66221ec046"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[20,21,22,23,24],"surfaceOp":"append"} {"type":"step/end","seq":26,"time":1785730456086,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":27,"time":1785730456086,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":27,"time":1785730456086,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.2.jsonl b/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.2.jsonl index 8c817c3d74..8a957dd636 100644 --- a/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.2.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.2.jsonl @@ -26,4 +26,4 @@ {"type":"assistant/chunk","seq":24,"time":1785730456070,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":25,"time":1785730456070,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DEPTH_REJECTED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"57c0ecaf-3f72-4da9-9eb9-a0726e8f097a"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[20,21,22,23,24],"surfaceOp":"append"} {"type":"step/end","seq":26,"time":1785730456071,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":27,"time":1785730456071,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":27,"time":1785730456071,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.jsonl index 680c20c643..37c7b008be 100644 --- a/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.jsonl @@ -25,4 +25,4 @@ {"type":"assistant/chunk","seq":23,"time":1785730456101,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":24,"time":1785730456101,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"ROOT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cf7259c7-e817-42a4-af8c-d63b755997da"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} {"type":"step/end","seq":25,"time":1785730456102,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":26,"time":1785730456102,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":26,"time":1785730456102,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl index 12e4a6bc14..b26e6e1227 100644 --- a/examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl @@ -18,7 +18,7 @@ {"type":"assistant/chunk","seq":38,"time":1785730448979,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":39,"time":1785730448979,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to remember the codeword \"MARMALADE\" and reply with just \"OK\"."},{"type":"text","text":"OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"7ac2e3d7-d558-4b24-b71e-40fc2f42216d"},"usage":{"inputTokens":2885,"outputTokens":25,"cacheReadTokens":0,"reasoningTokens":23}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"surfaceOp":"append"} {"type":"step/end","seq":40,"time":1785730448979,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":41,"time":1785730448979,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":41,"time":1785730448979,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/end-seed","seq":42,"time":1785730449008,"data":{}} {"type":"agent/inbox/spliced","seq":43,"time":1785498796160,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"What is the project codeword mentioned earlier in this conversation? Reply with exactly that one word and nothing else."}],"source":{"kind":"user"},"role":"user","id":"d037163e-ed56-4c9c-b5d1-57df017d618c"}]}} {"type":"turn/start","seq":44,"time":1785821406523,"data":{"turn":2}} @@ -37,4 +37,4 @@ {"type":"assistant/chunk","seq":93,"time":1785730449034,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":94,"time":1785730449034,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asked me to remember the project codeword \"MARMALADE\" and now they're asking what it is. I should just reply with that word."},{"type":"text","text":"MARMALADE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cdc56e00-c648-4669-92b2-7299e41cb743"},"usage":{"inputTokens":97,"outputTokens":39,"cacheReadTokens":2816,"reasoningTokens":34}},"sourceEventSeqs":[50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93],"surfaceOp":"append"} {"type":"step/end","seq":95,"time":1785730449035,"data":{"turn":2,"step":1}} -{"type":"turn/end","seq":96,"time":1785730449035,"data":{"turn":2,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":96,"time":1785730449035,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-fork/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-fork/session.jsonl index 0bf255cea2..7daf489e18 100644 --- a/examples/acp-agent/tests/snapshots/subagent-fork/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-fork/session.jsonl @@ -18,7 +18,7 @@ {"type":"assistant/chunk","seq":38,"time":1785730448979,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":39,"time":1785730448979,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to remember the codeword \"MARMALADE\" and reply with just \"OK\"."},{"type":"text","text":"OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"7ac2e3d7-d558-4b24-b71e-40fc2f42216d"},"usage":{"inputTokens":2885,"outputTokens":25,"cacheReadTokens":0,"reasoningTokens":23}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"surfaceOp":"append"} {"type":"step/end","seq":40,"time":1785730448979,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":41,"time":1785730448979,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":41,"time":1785730448979,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":42,"time":1785498796131,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Use the subagent_fork tool exactly once to delegate this subtask to a forked child agent: 'What is the project codeword mentioned earlier in this conversation? Reply with exactly that one word and nothing else.' The forked child inherits this conversation, so it can answer. After the subagent returns, reply with the single word PARENT_DONE and stop. Do not use the bash tool."}],"source":{"kind":"user"},"role":"user","id":"444d4dbd-e948-45ac-89a9-a56cf91c75e8"}]}} {"type":"turn/start","seq":43,"time":1785821406495,"data":{"turn":2}} {"type":"agent/inbox/spliced","seq":44,"time":1785821406496,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} @@ -47,4 +47,4 @@ {"type":"assistant/chunk","seq":196,"time":1785730449055,"data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":197,"time":1785730449055,"data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The forked child agent correctly returned \"MARMALADE\". Now I need to reply with \"PARENT_DONE\"."},{"type":"text","text":"PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"1dfdd09b-b2f8-4f93-903c-f9548433599f"},"usage":{"inputTokens":65,"outputTokens":30,"cacheReadTokens":3072,"reasoningTokens":25}},"sourceEventSeqs":[162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],"surfaceOp":"append"} {"type":"step/end","seq":198,"time":1785730449055,"data":{"turn":2,"step":2}} -{"type":"turn/end","seq":199,"time":1785730449055,"data":{"turn":2,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":199,"time":1785730449055,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-list-agents/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-list-agents/session.1.jsonl index 40fd29b21d..9a0ee79f04 100644 --- a/examples/acp-agent/tests/snapshots/subagent-list-agents/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-list-agents/session.1.jsonl @@ -17,4 +17,4 @@ {"type":"assistant/chunk","seq":15,"time":1785730454843,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":16,"time":1785730454843,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"f6a952dd-2d09-4b5c-b8ae-5456cfdfeab0"},"usage":{"inputTokens":10,"outputTokens":3}},"sourceEventSeqs":[11,12,13,14,15],"surfaceOp":"append"} {"type":"step/end","seq":17,"time":1785730454843,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":18,"time":1785730454844,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":18,"time":1785730454844,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-list-agents/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-list-agents/session.jsonl index 00da410d49..edf5ca3ea2 100644 --- a/examples/acp-agent/tests/snapshots/subagent-list-agents/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-list-agents/session.jsonl @@ -25,7 +25,7 @@ {"type":"assistant/chunk","seq":23,"time":1785730454820,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":24,"time":1785730454820,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"STARTED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"2ee459fa-21f9-48c6-a42e-3c38eca1e4c9"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} {"type":"step/end","seq":25,"time":1785730454821,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":26,"time":1785730454821,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":26,"time":1785730454821,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":27,"time":1785730454857,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Call list_agents once and observe the subagent you started. Then reply with the single word DONE. Do not call any other tool."}],"source":{"kind":"user"},"role":"user","id":"3bcac2a3-e0db-465b-94b4-4e2761236475"}]}} {"type":"turn/start","seq":28,"time":1785821412840,"data":{"turn":2}} {"type":"agent/inbox/spliced","seq":29,"time":1785821412840,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} @@ -48,4 +48,4 @@ {"type":"assistant/chunk","seq":46,"time":1785730454907,"data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":47,"time":1785730454907,"data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"17c1a7c5-84f5-493e-adb5-6a65219e6ad6"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[42,43,44,45,46],"surfaceOp":"append"} {"type":"step/end","seq":48,"time":1785730454908,"data":{"turn":2,"step":2}} -{"type":"turn/end","seq":49,"time":1785730454908,"data":{"turn":2,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":49,"time":1785730454908,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-mixed/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-mixed/session.1.jsonl index e5091842bb..fd2593bc1f 100644 --- a/examples/acp-agent/tests/snapshots/subagent-mixed/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-mixed/session.1.jsonl @@ -19,4 +19,4 @@ {"type":"assistant/chunk","seq":37,"time":1785730450194,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":38,"time":1785730450194,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asked me to reply with exactly the word \"ALPHA\" and nothing else."},{"type":"text","text":"ALPHA"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cfff210d-8dd3-4acc-bbc3-fa860baf88cf"},"usage":{"inputTokens":48,"outputTokens":23,"cacheReadTokens":2816,"reasoningTokens":19}},"sourceEventSeqs":[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"surfaceOp":"append"} {"type":"step/end","seq":39,"time":1785730450194,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":40,"time":1785730450195,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":40,"time":1785730450195,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl b/examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl index 901eb654fe..d24cb8a038 100644 --- a/examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl @@ -18,7 +18,7 @@ {"type":"assistant/chunk","seq":32,"time":1785730450146,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":33,"time":1785730450146,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to remember a codeword and just reply with \"OK\"."},{"type":"text","text":"OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cf8355ae-a447-4c41-b01f-beaf74c3e70e"},"usage":{"inputTokens":2883,"outputTokens":19,"cacheReadTokens":0,"reasoningTokens":17}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"surfaceOp":"append"} {"type":"step/end","seq":34,"time":1785730450146,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":35,"time":1785730450146,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":35,"time":1785730450146,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/end-seed","seq":36,"time":1785730450227,"data":{}} {"type":"agent/inbox/spliced","seq":37,"time":1785498797482,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"What is the project codeword mentioned earlier in this conversation? Reply with exactly that one word and nothing else."}],"source":{"kind":"user"},"role":"user","id":"86e9f144-764f-460d-b72b-262cffe43d77"}]}} {"type":"turn/start","seq":38,"time":1785821407808,"data":{"turn":2}} @@ -37,4 +37,4 @@ {"type":"assistant/chunk","seq":83,"time":1785730450254,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":84,"time":1785730450254,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user is asking me to recall the project codeword that was mentioned earlier in the conversation. I was told to remember it: SAFFRON."},{"type":"text","text":"SAFFRON"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"e1f347c1-ce65-4ca9-8a9e-05e4366ef365"},"usage":{"inputTokens":95,"outputTokens":35,"cacheReadTokens":2816,"reasoningTokens":31}},"sourceEventSeqs":[44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83],"surfaceOp":"append"} {"type":"step/end","seq":85,"time":1785730450254,"data":{"turn":2,"step":1}} -{"type":"turn/end","seq":86,"time":1785730450254,"data":{"turn":2,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":86,"time":1785730450254,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-mixed/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-mixed/session.jsonl index 0666301e1e..dc704cc32a 100644 --- a/examples/acp-agent/tests/snapshots/subagent-mixed/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-mixed/session.jsonl @@ -18,7 +18,7 @@ {"type":"assistant/chunk","seq":32,"time":1785730450146,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":33,"time":1785730450146,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to remember a codeword and just reply with \"OK\"."},{"type":"text","text":"OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cf8355ae-a447-4c41-b01f-beaf74c3e70e"},"usage":{"inputTokens":2883,"outputTokens":19,"cacheReadTokens":0,"reasoningTokens":17}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"surfaceOp":"append"} {"type":"step/end","seq":34,"time":1785730450146,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":35,"time":1785730450146,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":35,"time":1785730450146,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":36,"time":1785498797390,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Do these two delegations, once at a time. First, use the subagent tool (fresh child) exactly once: 'Reply with exactly the word ALPHA and nothing else.' Then, after it returns, use the subagent_fork tool (forked child that inherits this conversation) exactly once: 'What is the project codeword mentioned earlier in this conversation? Reply with exactly that one word and nothing else.' After both subagents return, reply with the single word PARENT_DONE and stop. Do not use the bash tool."}],"source":{"kind":"user"},"role":"user","id":"80c38716-32d9-4e42-8b93-a094a28ad39e"}]}} {"type":"turn/start","seq":37,"time":1785821407727,"data":{"turn":2}} {"type":"agent/inbox/spliced","seq":38,"time":1785821407727,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} @@ -60,4 +60,4 @@ {"type":"assistant/chunk","seq":290,"time":1785730450269,"data":{"turn":2,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":291,"time":1785730450270,"data":{"turn":2,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Both subagents returned:\n1. First (fresh child): \"ALPHA\"\n2. Second (forked child): \"SAFFRON\" - correctly inherited the conversation context where I was asked to remember the codeword \"SAFFRON\".\n\nNow I reply with \"PARENT_DONE\" as instructed."},{"type":"text","text":"PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"256c985a-449a-4176-9233-7d29cf47ba5e"},"usage":{"inputTokens":69,"outputTokens":69,"cacheReadTokens":3200,"reasoningTokens":64}},"sourceEventSeqs":[217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290],"surfaceOp":"append"} {"type":"step/end","seq":292,"time":1785730450270,"data":{"turn":2,"step":3}} -{"type":"turn/end","seq":293,"time":1785730450270,"data":{"turn":2,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":293,"time":1785730450270,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-multi/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-multi/session.1.jsonl index d1ef746580..d0b798bf1f 100644 --- a/examples/acp-agent/tests/snapshots/subagent-multi/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-multi/session.1.jsonl @@ -19,4 +19,4 @@ {"type":"assistant/chunk","seq":37,"time":1785730447834,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":38,"time":1785730447834,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ALPHA\" and nothing else."},{"type":"text","text":"ALPHA"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5f1e6087-da72-4a56-9bc0-ae1ac6618a8a"},"usage":{"inputTokens":49,"outputTokens":23,"cacheReadTokens":2816,"reasoningTokens":19}},"sourceEventSeqs":[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"surfaceOp":"append"} {"type":"step/end","seq":39,"time":1785730447834,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":40,"time":1785730447834,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":40,"time":1785730447834,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl b/examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl index 76932f10b9..51b8e8cca8 100644 --- a/examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl @@ -20,4 +20,4 @@ {"type":"assistant/chunk","seq":35,"time":1785730447887,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":36,"time":1785730447887,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"BETA\" and nothing else."},{"type":"text","text":"BETA"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"adc4527d-efd1-4c89-b42b-826c33f2bb12"},"usage":{"inputTokens":48,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}},"sourceEventSeqs":[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"surfaceOp":"append"} {"type":"step/end","seq":37,"time":1785730447887,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":38,"time":1785730447887,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":38,"time":1785730447887,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-multi/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-multi/session.jsonl index b6b0563c4f..66fedff41c 100644 --- a/examples/acp-agent/tests/snapshots/subagent-multi/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-multi/session.jsonl @@ -44,4 +44,4 @@ {"type":"assistant/chunk","seq":208,"time":1785730447907,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":209,"time":1785730447907,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Both subagents have returned: first with \"ALPHA\", second with \"BETA\". Now I should reply with \"PARENT_DONE\"."},{"type":"text","text":"PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"b51ff9b8-1c06-485e-8e42-5eac7675c590"},"usage":{"inputTokens":115,"outputTokens":35,"cacheReadTokens":3072,"reasoningTokens":30}},"sourceEventSeqs":[169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208],"surfaceOp":"append"} {"type":"step/end","seq":210,"time":1785730447907,"data":{"turn":1,"step":3}} -{"type":"turn/end","seq":211,"time":1785730447907,"data":{"turn":1,"step":3,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":211,"time":1785730447907,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-published-run-failure/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-published-run-failure/session.jsonl index dcf0ec4747..ef64d948af 100644 --- a/examples/acp-agent/tests/snapshots/subagent-published-run-failure/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-published-run-failure/session.jsonl @@ -25,4 +25,4 @@ {"type":"assistant/chunk","seq":23,"time":1785730452539,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":24,"time":1785730452540,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_OBSERVED_ERROR"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"04a4fe93-dd92-4f86-9376-9b3da097b2ce"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} {"type":"step/end","seq":25,"time":1785730452540,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":26,"time":1785730452540,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":26,"time":1785730452540,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-report/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-report/session.1.jsonl index 1e60d25479..27eb9b020f 100644 --- a/examples/acp-agent/tests/snapshots/subagent-report/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-report/session.1.jsonl @@ -27,4 +27,4 @@ {"type":"assistant/chunk","seq":25,"time":1785730453668,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":26,"time":1785730453668,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"Reported."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"96784835-2d0f-4d00-aef5-ee3a14820dd1"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[21,22,23,24,25],"surfaceOp":"append"} {"type":"step/end","seq":27,"time":1785730453668,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":28,"time":1785730453668,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":28,"time":1785730453668,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-report/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-report/session.jsonl index a11a31ba53..17f822c19e 100644 --- a/examples/acp-agent/tests/snapshots/subagent-report/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-report/session.jsonl @@ -25,7 +25,7 @@ {"type":"assistant/chunk","seq":23,"time":1785730453628,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":24,"time":1785730453628,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"STARTED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"571faad7-adbd-480c-922a-1499e1329ead"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} {"type":"step/end","seq":25,"time":1785730453628,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":26,"time":1785730453629,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":26,"time":1785730453629,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"agent/inbox/spliced","seq":27,"time":1785730453654,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Background subagent 33333333-3333-4333-8333-333333333333 reported:"},{"type":"text","text":"CHILD_REPORT_OK"}],"source":{"kind":"subagent-report","senderSessionId":"33333333-3333-4333-8333-333333333333"},"role":"user","id":"824dc60a-f9d7-48ea-a0d4-6d56df83bd4f"}]}} {"type":"agent/inbox/spliced","seq":28,"time":1785730453673,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Repeat back, verbatim, the exact output the background subagent reported to you. Reply with only that text. Do not use any tools."}],"source":{"kind":"user"},"role":"user","id":"43f17984-22c3-48b9-911e-923a2f68dce0"}]}} {"type":"turn/start","seq":29,"time":1785821411548,"data":{"turn":2}} @@ -41,4 +41,4 @@ {"type":"assistant/chunk","seq":39,"time":1785730453687,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":40,"time":1785730453687,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_REPORT_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"77bba235-d2b6-4a32-9ba3-ebb69d9b0654"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[35,36,37,38,39],"surfaceOp":"append"} {"type":"step/end","seq":41,"time":1785730453687,"data":{"turn":2,"step":1}} -{"type":"turn/end","seq":42,"time":1785730453687,"data":{"turn":2,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":42,"time":1785730453687,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-spawn/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-spawn/session.1.jsonl index 1c2de98410..aa6df16217 100644 --- a/examples/acp-agent/tests/snapshots/subagent-spawn/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-spawn/session.1.jsonl @@ -19,4 +19,4 @@ {"type":"assistant/chunk","seq":35,"time":1785730446727,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":36,"time":1785730446727,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word CHILD_OK and nothing else."},{"type":"text","text":"CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"16118fc6-2262-476e-9a4a-4b533cff09bc"},"usage":{"inputTokens":48,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":17}},"sourceEventSeqs":[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"surfaceOp":"append"} {"type":"step/end","seq":37,"time":1785730446727,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":38,"time":1785730446727,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":38,"time":1785730446727,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-spawn/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-spawn/session.jsonl index 0948f64521..9ae84809f1 100644 --- a/examples/acp-agent/tests/snapshots/subagent-spawn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/subagent-spawn/session.jsonl @@ -31,4 +31,4 @@ {"type":"assistant/chunk","seq":160,"time":1785730446743,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":161,"time":1785730446744,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The subagent returned \"CHILD_OK\" as expected. Now I need to reply with the single word \"PARENT_DONE\" and stop."},{"type":"text","text":"PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"82643563-e845-4bfa-9e47-98b353d54a39"},"usage":{"inputTokens":120,"outputTokens":35,"cacheReadTokens":2944,"reasoningTokens":30}},"sourceEventSeqs":[121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160],"surfaceOp":"append"} {"type":"step/end","seq":162,"time":1785730446744,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":163,"time":1785730446744,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":163,"time":1785730446744,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/text-turn/session.jsonl b/examples/acp-agent/tests/snapshots/text-turn/session.jsonl index 1af23f13b5..5da6e46877 100644 --- a/examples/acp-agent/tests/snapshots/text-turn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/text-turn/session.jsonl @@ -19,4 +19,4 @@ {"type":"assistant/chunk","seq":36,"time":1785730415297,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":37,"time":1785730415298,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."},{"type":"text","text":"PONG"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"3b028c0c-080e-4de0-8339-9aef7fa4769f"},"usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"surfaceOp":"append"} {"type":"step/end","seq":38,"time":1785730415298,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":39,"time":1785730415298,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":39,"time":1785730415298,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/todo-write/session.jsonl b/examples/acp-agent/tests/snapshots/todo-write/session.jsonl index 42a21301f5..32a6ab449c 100644 --- a/examples/acp-agent/tests/snapshots/todo-write/session.jsonl +++ b/examples/acp-agent/tests/snapshots/todo-write/session.jsonl @@ -33,4 +33,4 @@ {"type":"assistant/chunk","seq":134,"time":1785730425764,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":135,"time":1785730425764,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The todos have been written successfully. Now I just need to reply with the single word \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"c4e454ce-14cc-4030-be47-0395ac9f12fb"},"usage":{"inputTokens":237,"outputTokens":24,"cacheReadTokens":2816,"reasoningTokens":21}},"sourceEventSeqs":[106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134],"surfaceOp":"append"} {"type":"step/end","seq":136,"time":1785730425764,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":137,"time":1785730425764,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":137,"time":1785730425764,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/tool-call-turn/session.jsonl b/examples/acp-agent/tests/snapshots/tool-call-turn/session.jsonl index 520431ee55..15734149cf 100644 --- a/examples/acp-agent/tests/snapshots/tool-call-turn/session.jsonl +++ b/examples/acp-agent/tests/snapshots/tool-call-turn/session.jsonl @@ -32,4 +32,4 @@ {"type":"assistant/chunk","seq":100,"time":1785730417600,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":101,"time":1785730417600,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The command executed successfully and printed SNAPSHOT_OK. Now I need to reply with the single word DONE."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ad76b9dd-271f-4b2b-bcda-80bb9e169513"},"usage":{"inputTokens":170,"outputTokens":28,"cacheReadTokens":2816,"reasoningTokens":25}},"sourceEventSeqs":[68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100],"surfaceOp":"append"} {"type":"step/end","seq":102,"time":1785730417601,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":103,"time":1785730417601,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":103,"time":1785730417601,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/web-fetch/session.jsonl b/examples/acp-agent/tests/snapshots/web-fetch/session.jsonl index 094c57bc9c..b15f1a8e00 100644 --- a/examples/acp-agent/tests/snapshots/web-fetch/session.jsonl +++ b/examples/acp-agent/tests/snapshots/web-fetch/session.jsonl @@ -32,4 +32,4 @@ {"type":"assistant/chunk","seq":126,"time":1785730429294,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":127,"time":1785730429294,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asked me to fetch the URL, then reply with exactly \"DONE\". I've fetched it. Now I just reply with \"DONE\"."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"63a38279-bed6-48ff-8420-b8e72839f3be"},"usage":{"inputTokens":239,"outputTokens":34,"cacheReadTokens":5376,"reasoningTokens":31}},"sourceEventSeqs":[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126],"surfaceOp":"append"} {"type":"step/end","seq":128,"time":1785730429294,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":129,"time":1785730429294,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":129,"time":1785730429294,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/workflow-run/session.1.jsonl b/examples/acp-agent/tests/snapshots/workflow-run/session.1.jsonl index 415f5f6270..c6fb4f7983 100644 --- a/examples/acp-agent/tests/snapshots/workflow-run/session.1.jsonl +++ b/examples/acp-agent/tests/snapshots/workflow-run/session.1.jsonl @@ -19,4 +19,4 @@ {"type":"assistant/chunk","seq":37,"time":1785730457316,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":38,"time":1785730457316,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"WF_CHILD_OK\" and nothing else."},{"type":"text","text":"WF_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0ddaf3d1-53dc-45df-bc19-54ad72d6d7fb"},"usage":{"inputTokens":17,"outputTokens":23,"cacheReadTokens":3072,"reasoningTokens":18}},"sourceEventSeqs":[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"surfaceOp":"append"} {"type":"step/end","seq":39,"time":1785730457316,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":40,"time":1785730457316,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":40,"time":1785730457316,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/workflow-run/session.jsonl b/examples/acp-agent/tests/snapshots/workflow-run/session.jsonl index 02952529b4..fc488e435f 100644 --- a/examples/acp-agent/tests/snapshots/workflow-run/session.jsonl +++ b/examples/acp-agent/tests/snapshots/workflow-run/session.jsonl @@ -31,4 +31,4 @@ {"type":"assistant/chunk","seq":209,"time":1785730457339,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":210,"time":1785730457339,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The workflow returned successfully with the reply \"WF_CHILD_OK\". Now I need to reply with exactly \"WORKFLOW_DONE\" and stop."},{"type":"text","text":"WORKFLOW_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"265fc6fa-19e0-4df9-b4ea-f38141ba4efa"},"usage":{"inputTokens":328,"outputTokens":36,"cacheReadTokens":3072,"reasoningTokens":30}},"sourceEventSeqs":[169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209],"surfaceOp":"append"} {"type":"step/end","seq":211,"time":1785730457339,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":212,"time":1785730457339,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":212,"time":1785730457339,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/workspace-context/session.jsonl b/examples/acp-agent/tests/snapshots/workspace-context/session.jsonl index 29ebb7c10b..3e885e8cb7 100644 --- a/examples/acp-agent/tests/snapshots/workspace-context/session.jsonl +++ b/examples/acp-agent/tests/snapshots/workspace-context/session.jsonl @@ -52,4 +52,4 @@ {"type":"assistant/chunk","seq":50,"time":1785730689236,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":51,"time":1785730689236,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"4082d4e8-9244-45e6-b71d-cd6728f372a3"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[46,47,48,49,50],"surfaceOp":"append"} {"type":"step/end","seq":52,"time":1785730689237,"data":{"turn":1,"step":4}} -{"type":"turn/end","seq":53,"time":1785730689237,"data":{"turn":1,"step":4,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":53,"time":1785730689237,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/workspace-edit/session.jsonl b/examples/acp-agent/tests/snapshots/workspace-edit/session.jsonl index 416597cf36..3c60aa7c41 100644 --- a/examples/acp-agent/tests/snapshots/workspace-edit/session.jsonl +++ b/examples/acp-agent/tests/snapshots/workspace-edit/session.jsonl @@ -58,4 +58,4 @@ {"type":"assistant/chunk","seq":241,"time":1785730430457,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":242,"time":1785730430457,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The file now has two lines:\n1. hello\n2. WORLD\n\nI can reply with DONE."},{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0c8f9ddb-8946-494f-9249-9633e56482dd"},"usage":{"inputTokens":219,"outputTokens":25,"cacheReadTokens":3072,"reasoningTokens":22}},"sourceEventSeqs":[212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241],"surfaceOp":"append"} {"type":"step/end","seq":243,"time":1785730430457,"data":{"turn":1,"step":4}} -{"type":"turn/end","seq":244,"time":1785730430457,"data":{"turn":1,"step":4,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":244,"time":1785730430457,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/semantic-checkpoint-snapshots/tool-outcome-unknown/session.expected.jsonl b/examples/headless-agent/tests/semantic-checkpoint-snapshots/tool-outcome-unknown/session.expected.jsonl index 7d46f3828f..7af81fa449 100644 --- a/examples/headless-agent/tests/semantic-checkpoint-snapshots/tool-outcome-unknown/session.expected.jsonl +++ b/examples/headless-agent/tests/semantic-checkpoint-snapshots/tool-outcome-unknown/session.expected.jsonl @@ -6,7 +6,7 @@ {"type":"tool/call","seq":4,"time":0,"data":{"turn":1,"step":1,"callId":"unknown-outcome-call","name":"write_remote","arguments":"{\"value\":1}"}} {"type":"tool/result","seq":5,"time":0,"data":{"turn":1,"step":1,"message":{"id":"interrupted-tool-result-unknown-outcome-call-5","role":"user","source":{"kind":"tool","callId":"unknown-outcome-call"},"content":[{"type":"tool-result","toolCallId":"unknown-outcome-call","isError":true,"content":[{"type":"text","text":"The tool call was interrupted after it was recorded, but no result was durably recorded. Its outcome is unknown. Decide whether to retry from the tool semantics: retry only if the operation is read-only or idempotent; if it may have side effects, first verify external state or ask the user. Do not retry blindly."}]}]},"error":{"name":"ToolOutcomeUnknownError","code":"TOOL_OUTCOME_UNKNOWN"}},"surfaceOp":"append","sourceEventSeqs":[4]} {"type":"step/end","seq":6,"time":0,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":7,"time":0,"data":{"turn":1,"step":1,"reason":{"kind":"interrupted"}}} +{"type":"turn/end","seq":7,"time":0,"data":{"turn":1,"reason":{"kind":"interrupted"}}} {"type":"session/end-seed","seq":8,"time":0,"data":{}} {"type":"agent/inbox/spliced","seq":9,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue safely from the interrupted operation."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","seq":10,"time":0,"data":{"turn":2}} @@ -22,4 +22,4 @@ {"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":21,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"I will verify the external state before deciding whether to retry the side-effecting operation."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"}},"sourceEventSeqs":[17,18,19,20],"surfaceOp":"append"} {"type":"step/end","seq":22,"time":0,"data":{"turn":2,"step":1}} -{"type":"turn/end","seq":23,"time":0,"data":{"turn":2,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":23,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl index bf9f727989..10c380057d 100644 --- a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl +++ b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl @@ -15,4 +15,4 @@ {"type":"assistant/chunk","seq":13,"time":1785730501507,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":14,"time":1785730501507,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cac680cf-1d70-4fb2-91a3-da1e3a317d2e"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"} {"type":"step/end","seq":15,"time":1785730501507,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":16,"time":1785730501507,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":16,"time":1785730501507,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl index 1b5481bcf4..d514d19b96 100644 --- a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl +++ b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl @@ -15,4 +15,4 @@ {"type":"assistant/chunk","seq":13,"time":1785730501646,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":14,"time":1785730501646,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"2b31dae5-8939-44e1-bbcd-9f64aa637d76"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"} {"type":"step/end","seq":15,"time":1785730501646,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":16,"time":1785730501646,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":16,"time":1785730501646,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl index e2aae31f3c..fe5fa4dc8d 100644 --- a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl +++ b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl @@ -66,4 +66,4 @@ {"type":"assistant/chunk","seq":64,"time":1785730501679,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":65,"time":1785730501679,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_HEADLESS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0a4ca8f2-92c1-4dbc-beb8-923b8791c298"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[60,61,62,63,64],"surfaceOp":"append"} {"type":"step/end","seq":66,"time":1785730501679,"data":{"turn":1,"step":6}} -{"type":"turn/end","seq":67,"time":1785730501679,"data":{"turn":1,"step":6,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":67,"time":1785730501679,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/snapshots/advanced-toolchain/stream-json.expected.jsonl b/examples/headless-agent/tests/snapshots/advanced-toolchain/stream-json.expected.jsonl index 50703bdae4..c342059741 100644 --- a/examples/headless-agent/tests/snapshots/advanced-toolchain/stream-json.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/advanced-toolchain/stream-json.expected.jsonl @@ -65,5 +65,5 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":64,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":65,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_HEADLESS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[60,61,62,63,64],"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":66,"time":0,"data":{"turn":1,"step":6}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":67,"time":0,"data":{"turn":1,"step":6,"reason":{"kind":"completed"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":67,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} {"type":"result","sessionId":"{{sessionId}}","output":"ADVANCED_HEADLESS_OK","usage":{"inputTokens":18,"outputTokens":18}} diff --git a/examples/headless-agent/tests/snapshots/goal-tools/stream-json.expected.jsonl b/examples/headless-agent/tests/snapshots/goal-tools/stream-json.expected.jsonl index 84d9d41520..cf36d97e79 100644 --- a/examples/headless-agent/tests/snapshots/goal-tools/stream-json.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/goal-tools/stream-json.expected.jsonl @@ -44,5 +44,5 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":43,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":44,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL READY"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":35,"outputTokens":2}},"sourceEventSeqs":[39,40,41,42,43],"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":45,"time":0,"data":{"turn":1,"step":4}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":46,"time":0,"data":{"turn":1,"step":4,"reason":{"kind":"completed"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":46,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} {"type":"result","sessionId":"{{sessionId}}","output":"GOAL READY","usage":{"inputTokens":100,"outputTokens":20}} diff --git a/examples/headless-agent/tests/snapshots/missing-credential/stream-json.expected.jsonl b/examples/headless-agent/tests/snapshots/missing-credential/stream-json.expected.jsonl index 7c77ab805d..16a690dd88 100644 --- a/examples/headless-agent/tests/snapshots/missing-credential/stream-json.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/missing-credential/stream-json.expected.jsonl @@ -8,5 +8,5 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), export DEEPSEEK_API_KEY in the launching environment, or — as a last resort — set a literal \"apiKey\" in the llm-deepseek settings section","code":"MISSING_CREDENTIAL"}}}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":9,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":10,"time":0,"data":{"turn":1,"step":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), export DEEPSEEK_API_KEY in the launching environment, or — as a last resort — set a literal \"apiKey\" in the llm-deepseek settings section","code":"MISSING_CREDENTIAL"}}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":10,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), export DEEPSEEK_API_KEY in the launching environment, or — as a last resort — set a literal \"apiKey\" in the llm-deepseek settings section","code":"MISSING_CREDENTIAL"}}}}} {"type":"result","sessionId":"{{sessionId}}","output":""} diff --git a/examples/headless-agent/tests/snapshots/provider-retry/stream-json.expected.jsonl b/examples/headless-agent/tests/snapshots/provider-retry/stream-json.expected.jsonl index 5da4758d34..ca523f3665 100644 --- a/examples/headless-agent/tests/snapshots/provider-retry/stream-json.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/provider-retry/stream-json.expected.jsonl @@ -15,5 +15,5 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"RETRY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":4,"outputTokens":2}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":17,"time":0,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":17,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} {"type":"result","sessionId":"{{sessionId}}","output":"RETRY_OK","usage":{"inputTokens":4,"outputTokens":2}} diff --git a/examples/headless-agent/tests/snapshots/pty-tools/session.jsonl b/examples/headless-agent/tests/snapshots/pty-tools/session.jsonl index 45f300bd7c..67dbe5883b 100644 --- a/examples/headless-agent/tests/snapshots/pty-tools/session.jsonl +++ b/examples/headless-agent/tests/snapshots/pty-tools/session.jsonl @@ -75,4 +75,4 @@ {"type":"assistant/chunk","seq":73,"time":1785730504764,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":74,"time":1785730504764,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"23d457ba-b690-4bb9-b434-86f43c9f4da5"},"usage":{"inputTokens":10,"outputTokens":3}},"sourceEventSeqs":[69,70,71,72,73],"surfaceOp":"append"} {"type":"step/end","seq":75,"time":1785730504764,"data":{"turn":1,"step":7}} -{"type":"turn/end","seq":76,"time":1785730504764,"data":{"turn":1,"step":7,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":76,"time":1785730504764,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/snapshots/pty-tools/stream-json.expected.jsonl b/examples/headless-agent/tests/snapshots/pty-tools/stream-json.expected.jsonl index 661f6aaddc..5af477ac6b 100644 --- a/examples/headless-agent/tests/snapshots/pty-tools/stream-json.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/pty-tools/stream-json.expected.jsonl @@ -74,5 +74,5 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":73,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":74,"time":0,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":3}},"sourceEventSeqs":[69,70,71,72,73],"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":75,"time":0,"data":{"turn":1,"step":7}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":76,"time":0,"data":{"turn":1,"step":7,"reason":{"kind":"completed"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":76,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} {"type":"result","sessionId":"{{sessionId}}","output":"DONE","usage":{"inputTokens":70,"outputTokens":33}} diff --git a/examples/headless-agent/tests/snapshots/ralph-loop/stream-json.expected.jsonl b/examples/headless-agent/tests/snapshots/ralph-loop/stream-json.expected.jsonl index da79f0ea90..fe2c9df583 100644 --- a/examples/headless-agent/tests/snapshots/ralph-loop/stream-json.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/ralph-loop/stream-json.expected.jsonl @@ -23,5 +23,5 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":23,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"RALPH SNAPSHOT COMPLETE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":30,"outputTokens":4}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":24,"time":0,"data":{"turn":1,"step":2}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":25,"time":0,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":25,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} {"type":"result","sessionId":"{{sessionId}}","output":"RALPH SNAPSHOT COMPLETE","usage":{"inputTokens":50,"outputTokens":12}} diff --git a/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/child.expected.jsonl b/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/child.expected.jsonl index f028014560..d1d98988ce 100644 --- a/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/child.expected.jsonl +++ b/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/child.expected.jsonl @@ -27,4 +27,4 @@ {"type":"assistant/chunk","seq":25,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":26,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[21,22,23,24,25],"surfaceOp":"append"} {"type":"step/end","seq":27,"time":0,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":28,"time":0,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":28,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/parent.expected.jsonl b/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/parent.expected.jsonl index 704eb36bae..748e69dc28 100644 --- a/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/parent.expected.jsonl +++ b/examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/parent.expected.jsonl @@ -2,7 +2,7 @@ {"type":"turn/start","seq":0,"time":0,"data":{"turn":1}} {"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Tighten this session to read-only."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"sandbox/mode","seq":2,"time":0,"data":{"mode":"read-only"}} -{"type":"turn/end","seq":3,"time":0,"data":{"turn":1,"step":0,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":3,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}} {"type":"session/end-seed","seq":4,"time":0,"data":{}} {"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Delegate the write probe to a subagent."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","seq":6,"time":0,"data":{"turn":2}} @@ -30,4 +30,4 @@ {"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":29,"time":0,"data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The delegated child was denied by the sandbox. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[24,25,26,27,28],"surfaceOp":"append"} {"type":"step/end","seq":30,"time":0,"data":{"turn":2,"step":2}} -{"type":"turn/end","seq":31,"time":0,"data":{"turn":2,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":31,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/subagent-inheritance.snapshot.ts b/examples/headless-agent/tests/subagent-inheritance.snapshot.ts index 9bc13b9230..e5f6994d0c 100644 --- a/examples/headless-agent/tests/subagent-inheritance.snapshot.ts +++ b/examples/headless-agent/tests/subagent-inheritance.snapshot.ts @@ -42,7 +42,7 @@ async function seedReadOnlyParent(root: string, cwd: string): Promise { { type: 'turn/start', seq: 0, time: 10, data: { turn: 1 } }, { type: 'user/message', seq: 1, time: 11, data: createUserMessage({ content: [{ type: 'text', text: 'Tighten this session to read-only.' }], source: { kind: 'user' } }), surfaceOp: 'append' }, { type: 'sandbox/mode', seq: 2, time: 12, data: { mode: 'read-only' } }, - { type: 'turn/end', seq: 3, time: 13, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 3, time: 13, data: { turn: 1, reason: { kind: 'completed' } } }, ] try { await ctx.sessionPersistence.create(meta) diff --git a/examples/jsonrpc-agent/tests/snapshots/bash-tool/notifications.expected.jsonl b/examples/jsonrpc-agent/tests/snapshots/bash-tool/notifications.expected.jsonl index 066c790e90..4f2601b62c 100644 --- a/examples/jsonrpc-agent/tests/snapshots/bash-tool/notifications.expected.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/bash-tool/notifications.expected.jsonl @@ -97,5 +97,5 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":95,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":96,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The command produced the expected output. I'll reply with just that stdout."},{"type":"text","text":"dsh-sdk-proof-7391"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":233,"outputTokens":24,"cacheReadTokens":1664,"reasoningTokens":15}},"sourceEventSeqs":[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95],"surfaceOp":"append"}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":97,"time":0,"data":{"turn":1,"step":2}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":98,"time":0,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":98,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/examples/jsonrpc-agent/tests/snapshots/bash-tool/session.jsonl b/examples/jsonrpc-agent/tests/snapshots/bash-tool/session.jsonl index f8638e2257..c2ea52eb1b 100644 --- a/examples/jsonrpc-agent/tests/snapshots/bash-tool/session.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/bash-tool/session.jsonl @@ -30,4 +30,4 @@ {"type":"assistant/chunk","seq":95,"time":1785730506530,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":96,"time":1785730506530,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The command produced the expected output. I'll reply with just that stdout."},{"type":"text","text":"dsh-sdk-proof-7391"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"54a3c713-55c2-4e95-9437-e7e3680b18ae"},"usage":{"inputTokens":233,"outputTokens":24,"cacheReadTokens":1664,"reasoningTokens":15}},"sourceEventSeqs":[67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95],"surfaceOp":"append"} {"type":"step/end","seq":97,"time":1785730506531,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":98,"time":1785730506531,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":98,"time":1785730506531,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl index ec3fb3ff39..cbf8b34e75 100644 --- a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl @@ -75,5 +75,5 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":73,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":74,"time":0,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"PERSISTENT_TOOLS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[69,70,71,72,73],"surfaceOp":"append"}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":75,"time":0,"data":{"turn":1,"step":7}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":76,"time":0,"data":{"turn":1,"step":7,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":76,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl index 7aefbffee2..a436a9e5c2 100644 --- a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl @@ -75,4 +75,4 @@ {"type":"assistant/chunk","seq":73,"time":1785730508642,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":74,"time":1785730508642,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"PERSISTENT_TOOLS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"43efc58a-46a1-4813-995f-1dc489438942"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[69,70,71,72,73],"surfaceOp":"append"} {"type":"step/end","seq":75,"time":1785730508642,"data":{"turn":1,"step":7}} -{"type":"turn/end","seq":76,"time":1785730508642,"data":{"turn":1,"step":7,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":76,"time":1785730508642,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/notifications.expected.jsonl b/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/notifications.expected.jsonl index ec8596af58..434027310a 100644 --- a/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/notifications.expected.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/notifications.expected.jsonl @@ -136,7 +136,7 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":34,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"child answer 42.\""},{"type":"text","text":"child answer 42."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":107,"outputTokens":20,"cacheReadTokens":1664,"reasoningTokens":14}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"surfaceOp":"append"}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":35,"time":0,"data":{"turn":1,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":36,"time":0,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":36,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} {"method":"subagent.finished","params":{"provider":"spawn","agentId":"{{sessionId}}","parentSessionId":"{{sessionId}}","childSessionId":"{{sessionId}}","status":"ok","stopReason":"completed","lastAssistantMessage":[{"type":"reasoning","text":"The user wants me to reply with exactly \"child answer 42.\""},{"type":"text","text":"child answer 42."}]}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":99,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_oHPNQ1nLoakoaAGXIxCM7404"},"content":[{"type":"tool-result","toolCallId":"call_00_oHPNQ1nLoakoaAGXIxCM7404","content":[{"type":"text","text":"child answer 42."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[98],"surfaceOp":"append"}}} @@ -181,5 +181,5 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":138,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":139,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The subagent replied with \"child answer 42.\" Now I need to reply with the subagent's final answer verbatim."},{"type":"text","text":"child answer 42."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":19,"outputTokens":32,"cacheReadTokens":1920,"reasoningTokens":26}},"sourceEventSeqs":[102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138],"surfaceOp":"append"}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":140,"time":0,"data":{"turn":1,"step":2}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":141,"time":0,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":141,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.1.jsonl b/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.1.jsonl index 9700063d5e..9c45784001 100644 --- a/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.1.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.1.jsonl @@ -18,4 +18,4 @@ {"type":"assistant/chunk","seq":33,"time":1785730507343,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":34,"time":1785730507344,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"child answer 42.\""},{"type":"text","text":"child answer 42."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"3d9970cd-d000-4fd5-8712-a88c301ddb19"},"usage":{"inputTokens":107,"outputTokens":20,"cacheReadTokens":1664,"reasoningTokens":14}},"sourceEventSeqs":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"surfaceOp":"append"} {"type":"step/end","seq":35,"time":1785730507344,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":36,"time":1785730507344,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":36,"time":1785730507344,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.jsonl b/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.jsonl index 81f212abee..dc9c8febc2 100644 --- a/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.jsonl @@ -30,4 +30,4 @@ {"type":"assistant/chunk","seq":138,"time":1785730507362,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":139,"time":1785730507362,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The subagent replied with \"child answer 42.\" Now I need to reply with the subagent's final answer verbatim."},{"type":"text","text":"child answer 42."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"7e4e2067-1d5f-4009-a397-acd58c3b3ba3"},"usage":{"inputTokens":19,"outputTokens":32,"cacheReadTokens":1920,"reasoningTokens":26}},"sourceEventSeqs":[102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138],"surfaceOp":"append"} {"type":"step/end","seq":140,"time":1785730507362,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":141,"time":1785730507362,"data":{"turn":1,"step":2,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":141,"time":1785730507362,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/jsonrpc-agent/tests/snapshots/text-turn/notifications.expected.jsonl b/examples/jsonrpc-agent/tests/snapshots/text-turn/notifications.expected.jsonl index 0a28622597..eb60d67a0c 100644 --- a/examples/jsonrpc-agent/tests/snapshots/text-turn/notifications.expected.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/text-turn/notifications.expected.jsonl @@ -38,5 +38,5 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":36,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":37,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."},{"type":"text","text":"SDK snapshot OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19}},"sourceEventSeqs":[8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"surfaceOp":"append"}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":38,"time":0,"data":{"turn":1,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":39,"time":0,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":39,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/examples/jsonrpc-agent/tests/snapshots/text-turn/session.jsonl b/examples/jsonrpc-agent/tests/snapshots/text-turn/session.jsonl index 012cf36309..08b526a0ef 100644 --- a/examples/jsonrpc-agent/tests/snapshots/text-turn/session.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/text-turn/session.jsonl @@ -17,4 +17,4 @@ {"type":"assistant/chunk","seq":36,"time":1785730505710,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} {"type":"assistant/message","seq":37,"time":1785730505710,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."},{"type":"text","text":"SDK snapshot OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"3dd28f2f-9314-41a8-bf15-851be3652c14"},"usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19}},"sourceEventSeqs":[8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"surfaceOp":"append"} {"type":"step/end","seq":38,"time":1785730505710,"data":{"turn":1,"step":1}} -{"type":"turn/end","seq":39,"time":1785730505710,"data":{"turn":1,"step":1,"reason":{"kind":"completed"}}} +{"type":"turn/end","seq":39,"time":1785730505710,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/packages/client/runtime/tests/request-inspection.spec.ts b/packages/client/runtime/tests/request-inspection.spec.ts index 4e9094f9f5..d01d0569f0 100644 --- a/packages/client/runtime/tests/request-inspection.spec.ts +++ b/packages/client/runtime/tests/request-inspection.spec.ts @@ -225,7 +225,7 @@ describe('inspectRequests', () => { const snapshot = inspectRequests(entriesOf([ at(0, 'step/start', { turn: 1, step: 1 }), at(1, 'turn/end', { - turn: 1, step: 1, reason: { kind: 'error', error: { + turn: 1, reason: { kind: 'error', error: { code: 'AUTH', message: 'Authentication Fails, Your api key: sk-preview-secret is invalid', }, @@ -233,7 +233,7 @@ describe('inspectRequests', () => { }), at(2, 'step/start', { turn: 2, step: 1 }), at(3, 'turn/end', { - turn: 2, step: 1, reason: { kind: 'error', error: { message: 'plugin exploded', code: 'UNKNOWN' } }, + turn: 2, reason: { kind: 'error', error: { message: 'plugin exploded', code: 'UNKNOWN' } }, }), ])) diff --git a/packages/client/runtime/tests/session.spec.ts b/packages/client/runtime/tests/session.spec.ts index f371d037e9..cceedac704 100644 --- a/packages/client/runtime/tests/session.spec.ts +++ b/packages/client/runtime/tests/session.spec.ts @@ -222,7 +222,7 @@ describe('live event path', () => { ev.stepStart(8, 1), at(9, { type: 'turn/end', - data: { turn: 1, step: 0, reason: { kind: 'error', error: { + data: { turn: 1, reason: { kind: 'error', error: { code: 'AUTH', message: 'Authentication Fails, Your api key: sk-preview-secret is invalid', }, @@ -234,7 +234,7 @@ describe('live event path', () => { ev.stepStart(12, 2, 1), at(13, { type: 'turn/end', - data: { turn: 2, step: 1, reason: { kind: 'error', error: { message: 'plugin exploded', code: 'UNKNOWN' } } }, + data: { turn: 2, reason: { kind: 'error', error: { message: 'plugin exploded', code: 'UNKNOWN' } } }, }), ] for (const event of failedTurns) feed(event) @@ -411,7 +411,7 @@ describe('live event path', () => { feed(ev.retry(7, 1)) feed(at(8, { type: 'turn/end', - data: { turn: 1, step: 0, reason: { kind: 'error', error: { message: 'retry failed', code: 'UNKNOWN' } } }, + data: { turn: 1, reason: { kind: 'error', error: { message: 'retry failed', code: 'UNKNOWN' } } }, })) expect(session.getSnapshot().nodes.at(-1)).toMatchObject({ diff --git a/packages/compact/compact-basic/tests/compact-basic.spec.ts b/packages/compact/compact-basic/tests/compact-basic.spec.ts index 78489a42f2..9a4f3d2221 100644 --- a/packages/compact/compact-basic/tests/compact-basic.spec.ts +++ b/packages/compact/compact-basic/tests/compact-basic.spec.ts @@ -130,7 +130,7 @@ function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn, step: 1 }) - session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } session.append('turn/start', { turn: turns + 1, @@ -180,7 +180,7 @@ function toolConversation(): Session { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn, step: 1 }) - session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } session.append('turn/start', { turn: 4 }) return session @@ -226,7 +226,7 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess meta: { presentation: 'preserved' }, }, { surfaceOp: 'append' }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) session.append('turn/start', { turn: 2 }) return session } @@ -935,7 +935,7 @@ describe('compaction region transaction', () => { it('requires an open turn and an idle compaction bracket', async () => { const compact = service() const closed = conversation(1) - closed.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + closed.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) const nodes = closed.surface.nodes await expect(compact.compactRegion( nodes[0]!, diff --git a/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts b/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts index d43ce4f2c9..b3dc707b27 100644 --- a/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts +++ b/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts @@ -209,7 +209,7 @@ function overflowHistorySeed(): SessionEvent[] { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn, step: 1 }) - session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } return [...session.events] } diff --git a/packages/compact/compact-basic/tests/manual-compact.spec.ts b/packages/compact/compact-basic/tests/manual-compact.spec.ts index 147fb7df37..51ad8c4991 100644 --- a/packages/compact/compact-basic/tests/manual-compact.spec.ts +++ b/packages/compact/compact-basic/tests/manual-compact.spec.ts @@ -193,7 +193,7 @@ function closedConversation(turns = 2, lastTurnNumber = turns): Session { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn, step: 1 }) - session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } return session } @@ -437,7 +437,7 @@ describe('compactNow transaction and failure classification', () => { const original = closedConversation(2) original.append('compact/start', { turn: null }) original.append('turn/start', { turn: 3 }) - original.append('turn/end', { turn: 3, step: 0, reason: { kind: 'interrupted' } }) + original.append('turn/end', { turn: 3, reason: { kind: 'interrupted' } }) const reloaded = new Session(SessionId('reloaded-orphan'), [...original.events]) const agent = fakeAgent(reloaded, () => () => undefined) diff --git a/packages/compact/compact-tool-result-prune/tests/tool-result-prune.spec.ts b/packages/compact/compact-tool-result-prune/tests/tool-result-prune.spec.ts index e9190ca484..bd347e6d1e 100644 --- a/packages/compact/compact-tool-result-prune/tests/tool-result-prune.spec.ts +++ b/packages/compact/compact-tool-result-prune/tests/tool-result-prune.spec.ts @@ -60,7 +60,7 @@ function appendToolStep( ...extra, }, { surfaceOp: 'append' }) session.append('step/end', { turn, step: 1 }) - session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) return result.seq } diff --git a/packages/compact/compact/tests/invariant.spec.ts b/packages/compact/compact/tests/invariant.spec.ts index 57795d9d3f..9342fa193e 100644 --- a/packages/compact/compact/tests/invariant.spec.ts +++ b/packages/compact/compact/tests/invariant.spec.ts @@ -90,7 +90,7 @@ describe('compaction invariants', () => { expect(() => replayed.append( 'turn/end', - { turn: 1, step: 0, reason: { kind: 'interrupted' } }, + { turn: 1, reason: { kind: 'interrupted' } }, )).not.toThrow() }) @@ -100,7 +100,7 @@ describe('compaction invariants', () => { const source = new Session(SessionId('stale-repaired-compaction-source')) source.append('compact/start', { turn: null }) startTurn(source) - source.append('turn/end', { turn: 1, step: 0, reason: { kind: 'interrupted' } }) + source.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } }) const replayed = ctx.sessions.create(SessionId('stale-repaired-compaction-replay'), { seed: source.events, }) @@ -116,7 +116,7 @@ describe('compaction invariants', () => { expect(() => { startTurn(replayed, 2) - replayed.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + replayed.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) }).not.toThrow() }) @@ -126,7 +126,7 @@ describe('compaction invariants', () => { const source = new Session(SessionId('closed-nested-compaction-source')) source.append('compact/start', { turn: null }) startTurn(source) - source.append('turn/end', { turn: 1, step: 0, reason: { kind: 'interrupted' } }) + source.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } }) source.append('compact/end', { turn: null, error: 'failed after crossing turn' }) const replayed = ctx.sessions.create(SessionId('closed-nested-compaction-replay'), { seed: source.events, @@ -147,7 +147,7 @@ describe('compaction invariants', () => { await ctx.plugin(InvariantService) await ctx.plugin(CompactInvariant) expect(() => session.append('compact/end', { turn: 1, error: 'resume failed' })).not.toThrow() - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }) it('adopts a bare session and ignores unrelated committed events', async () => { @@ -200,7 +200,7 @@ describe('compaction invariants', () => { await ctx.plugin(SessionStore) const session = ctx.sessions.create() startTurn(session) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) session.append('compact/start', { turn: 1 }) await ctx.plugin(InvariantService) await expect(ctx.plugin(CompactInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/) @@ -215,7 +215,7 @@ describe('compaction invariants', () => { standalone.append('compact/end', { turn: null, error: 'cancelled' }) expect(() => { startTurn(standalone) - standalone.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + standalone.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }).not.toThrow() const numbered = ctx.sessions.create() @@ -223,12 +223,12 @@ describe('compaction invariants', () => { numbered.append('compact/start', { turn: 1 }) expect(() => numbered.append( 'turn/end', - { turn: 1, step: 0, reason: { kind: 'completed' } }, + { turn: 1, reason: { kind: 'completed' } }, )).toThrow(/turn\/end cannot cross an open compaction for turn 1/) numbered.append('compact/end', { turn: 1, error: 'cancelled' }) expect(() => numbered.append( 'turn/end', - { turn: 1, step: 0, reason: { kind: 'completed' } }, + { turn: 1, reason: { kind: 'completed' } }, )).not.toThrow() }) diff --git a/packages/compact/compact/tests/tool-pairing.spec.ts b/packages/compact/compact/tests/tool-pairing.spec.ts index e122682051..f9f4d661db 100644 --- a/packages/compact/compact/tests/tool-pairing.spec.ts +++ b/packages/compact/compact/tests/tool-pairing.spec.ts @@ -271,7 +271,7 @@ describe('tool-pairing cache refresh', () => { expect(eventIndexReads).toBe(3) events.push({ - type: 'turn/end', seq: 3, time: 3, data: { turn: 1, step: 0, reason: { kind: 'completed' } }, + type: 'turn/end', seq: 3, time: 3, data: { turn: 1, reason: { kind: 'completed' } }, }) expect(toolPairingBalancedAfter(session, nodes[2]!)).toBe(true) expect(eventCollectionReads).toBe(1) diff --git a/packages/context/time-context/tests/invariant.spec.ts b/packages/context/time-context/tests/invariant.spec.ts index 4364a83385..35beb67406 100644 --- a/packages/context/time-context/tests/invariant.spec.ts +++ b/packages/context/time-context/tests/invariant.spec.ts @@ -47,7 +47,7 @@ function preparing(turn: number, step: number): Session { const session = new Session(SessionId(`time-invariant-${turn}-${step}`)) for (let priorTurn = 1; priorTurn < turn; priorTurn += 1) { session.append('turn/start', { turn: priorTurn }) - session.append('turn/end', { turn: priorTurn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: priorTurn, reason: { kind: 'completed' } }) } session.append('turn/start', { turn }) session.append('user/message', createUserMessage({ @@ -127,7 +127,7 @@ describe('time-context invariants', () => { it('rejects a reading after cancellation closes the turn', async () => { const ctx = await setup() const session = preparing(1, 2) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'aborted', reason: { kind: 'user' } } }) + session.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } } }) expect(() => { ctx.emit('session/event', session, event(reading('1', '2', 'step context'))) }) .toThrow(/at a prompt boundary/) }) diff --git a/packages/context/time-context/tests/time-context.spec.ts b/packages/context/time-context/tests/time-context.spec.ts index 8dcaf19ec8..c75815508a 100644 --- a/packages/context/time-context/tests/time-context.spec.ts +++ b/packages/context/time-context/tests/time-context.spec.ts @@ -248,7 +248,7 @@ describe('durable step context', () => { surfaceOp: { op: 'replace', start: user.seq, end: reading.seq }, sourceEventSeqs: [user.seq, reading.seq], }) - original.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + original.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) expect(JSON.stringify(original.deriveMessages())).not.toContain('Time sampled while preparing') const resumed = new Session(SessionId('resumed'), [...original.events]) @@ -277,7 +277,7 @@ describe('durable step context', () => { const firstAgent = sessionAgent(first, 'first-agent') openMessageTurn(first, 1) await fire(ctx, firstAgent, 1, 1) - first.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + first.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) vi.setSystemTime(BASE + 500) openMessageTurn(first, 2) diff --git a/packages/context/tmux-context/tests/tmux-context.spec.ts b/packages/context/tmux-context/tests/tmux-context.spec.ts index 840e5f582c..36eba5f519 100644 --- a/packages/context/tmux-context/tests/tmux-context.spec.ts +++ b/packages/context/tmux-context/tests/tmux-context.spec.ts @@ -210,12 +210,12 @@ describe('tmux-context injection', () => { openMessageTurn(session, 1) await fire(ctx, agent, 1, 1) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Same state on turn 2: suppressed. openMessageTurn(session, 2) await fire(ctx, agent, 2, 1) - session.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) expect(contextTexts(session)).toHaveLength(1) // Moved pane on turn 3: re-injected. @@ -238,7 +238,7 @@ describe('tmux-context injection', () => { openMessageTurn(session, 1) await fire(ctx, agent, 1, 1) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Changed state but inside the interval: suppressed, and never queried. bash.result = runResult(`${tmuxLine({ paneId: '%99' })}\n`) diff --git a/packages/core/agent-loop/tests/contract-regressions.spec.ts b/packages/core/agent-loop/tests/contract-regressions.spec.ts index 0b7e96f08f..8d45f93085 100644 --- a/packages/core/agent-loop/tests/contract-regressions.spec.ts +++ b/packages/core/agent-loop/tests/contract-regressions.spec.ts @@ -201,7 +201,7 @@ describe('abort during tool execution ends the turn', () => { || event.type === 'step/start' || event.type === 'turn/end').map(event => event.type)) .toEqual(['turn/start', 'turn/end']) expect(agent.session.events.find(event => event.type === 'turn/end')?.data) - .toEqual({ turn: 1, step: 0, reason: { kind: 'completed' } }) + .toEqual({ turn: 1, reason: { kind: 'completed' } }) expect(agent.inbox.nextTurn).toHaveLength(0) }) @@ -357,7 +357,7 @@ describe('plugin exceptions are contained', () => { send(agent, 'first') await waitForIdle(ctx, agent) expect(agent.session.events.findLast(event => event.type === 'turn/end')).toMatchObject({ - data: { step: 1, reason: { kind: 'error', error: { message: 'broken continuation plugin', code: 'UNKNOWN' } } }, + data: { reason: { kind: 'error', error: { message: 'broken continuation plugin', code: 'UNKNOWN' } } }, }) // the loop is still alive: a second send works normally @@ -601,7 +601,7 @@ describe('a finish-error stream chunk ends the turn as error, not completed', () const events = [...agent.session.events] const turnEnd = events.find(event => event.type === 'turn/end') - expect(turnEnd).toMatchObject({ data: { step: 1, reason: { kind: 'error', error: failure } } }) + expect(turnEnd).toMatchObject({ data: { reason: { kind: 'error', error: failure } } }) // A failed step must not synthesize an assistant message. expect(events.some(event => event.type === 'assistant/message')).toBe(false) }) @@ -781,7 +781,7 @@ describe('turn and step boundary recovery', () => { errors: 1, }) expect(agent.session.events.findLast(event => event.type === 'turn/end')).toMatchObject({ - data: { step: 0, reason: { kind: 'error', error: { message: 'reject step-start before commit', code: 'UNKNOWN' } } }, + data: { reason: { kind: 'error', error: { message: 'reject step-start before commit', code: 'UNKNOWN' } } }, }) }) diff --git a/packages/core/agent-loop/tests/coverage-edges.spec.ts b/packages/core/agent-loop/tests/coverage-edges.spec.ts index 51488c9328..273ef022fd 100644 --- a/packages/core/agent-loop/tests/coverage-edges.spec.ts +++ b/packages/core/agent-loop/tests/coverage-edges.spec.ts @@ -134,7 +134,7 @@ describe('thrown-value propagation', () => { expect(starts).toHaveLength(0) expect(ends).toHaveLength(0) expect(messages).toHaveLength(0) - expect(agent.inbox.nextTurn).toHaveLength(1) + expect(agent.inbox.nextTurn).toHaveLength(2) }) it('preserves non-Error throws from the agent/request waterfall', async () => { diff --git a/packages/core/agent-loop/tests/loop.spec.ts b/packages/core/agent-loop/tests/loop.spec.ts index 382a134e66..561faa6ac6 100644 --- a/packages/core/agent-loop/tests/loop.spec.ts +++ b/packages/core/agent-loop/tests/loop.spec.ts @@ -565,14 +565,15 @@ describe('agent loop', () => { await waitForIdle(ctx, agent) expect(adapter.requests).toHaveLength(0) - expect(agent.session.events.filter(event => event.type === 'turn/start')).toHaveLength(0) + expect(agent.session.events.filter(event => event.type === 'turn/start')).toHaveLength(1) + expect(agent.session.events.filter(event => event.type === 'turn/end')).toHaveLength(1) expect(agent.inbox.nextStep).toHaveLength(1) send(agent, 'resume') await waitForIdle(ctx, agent) expect(adapter.requests).toHaveLength(1) - expect(agent.session.events.filter(event => event.type === 'turn/start')).toHaveLength(1) + expect(agent.session.events.filter(event => event.type === 'turn/start')).toHaveLength(2) expect(JSON.stringify(adapter.requests[0]?.messages)).toContain('pending steering') }) @@ -867,11 +868,11 @@ describe('agent loop', () => { send(agent, 'first') await waitForIdle(ctx, agent) - // The first proposal failed before opening a turn or calling the model. + // The first proposal failed inside a balanced turn without calling the model. expect(errors.map(error => error.message)).toEqual(['boom in pre-step']) expect(adapter.requests.length).toBe(0) - expect(agent.session.events.some(event => event.type === 'turn/start')).toBe(false) - expect(agent.session.events.some(event => event.type === 'turn/end')).toBe(false) + expect(agent.session.events.some(event => event.type === 'turn/start')).toBe(true) + expect(agent.session.events.some(event => event.type === 'turn/end')).toBe(true) // The loop survived: a second prompt runs a normal completed turn. send(agent, 'second') diff --git a/packages/core/agent-loop/tests/request-reconstruction.spec.ts b/packages/core/agent-loop/tests/request-reconstruction.spec.ts index 6e7d0fe15a..11bcbd233b 100644 --- a/packages/core/agent-loop/tests/request-reconstruction.spec.ts +++ b/packages/core/agent-loop/tests/request-reconstruction.spec.ts @@ -360,7 +360,6 @@ describe('request stability across the loop', () => { expect(agent.session.events.findLast(event => event.type === 'turn/end')).toMatchObject({ data: { - step: 1, reason: failure instanceof LlmError ? { kind: 'error', error: failure.failure } : { kind: 'error', error: { message: failure.message, code: 'UNKNOWN' } }, diff --git a/packages/core/agent-loop/tests/resume.spec.ts b/packages/core/agent-loop/tests/resume.spec.ts index 85798cfc48..e5b423c688 100644 --- a/packages/core/agent-loop/tests/resume.spec.ts +++ b/packages/core/agent-loop/tests/resume.spec.ts @@ -44,7 +44,7 @@ async function persistSession(sessionId: SessionId): Promise { // the model merely to construct this lifecycle fixture. const seed: SessionEvent[] = [ { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }, - { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } }, ] const session = ctx.sessions.create(sessionId, { seed }) await ctx.sessions.flush(session) @@ -198,7 +198,7 @@ describe('the session-persistence Agent Note: AgentLoop factory create/resume', await expect(ctx.agents.resume({ resumeSessionId: sessionId })) .rejects.toThrow(/live turn is open/) - first.session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + first.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(first.session) const loaded = await ctx.sessionPersistence.load(sessionId) expect(loaded.events.map(event => event.type)).toEqual(['turn/start', 'turn/end']) @@ -548,7 +548,7 @@ describe('the session-persistence Agent Note: AgentLoop factory create/resume', // materializes the fork (header + seed) on disk. const seed: SessionEvent[] = [ { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }, - { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } }, ] const adapter1 = new MockAdapter([textResponse('a')]) const { ctx: ctx1, root } = await persistentHarness(adapter1) diff --git a/packages/core/agent-loop/tests/tool-calls.spec.ts b/packages/core/agent-loop/tests/tool-calls.spec.ts index 10bf60875a..f3548cca52 100644 --- a/packages/core/agent-loop/tests/tool-calls.spec.ts +++ b/packages/core/agent-loop/tests/tool-calls.spec.ts @@ -683,7 +683,7 @@ describe('tool-call scheduler: failure quiescence', () => { expect(turnEndBeforeDrain).toBeUndefined() expect(gated.pending()).toEqual([]) expect(events(agent).findLast(event => event.type === 'turn/end')).toMatchObject({ - data: { step: 1, reason: { kind: 'error', error: { message: schedulerError.message, code: 'UNKNOWN' } } }, + data: { reason: { kind: 'error', error: { message: schedulerError.message, code: 'UNKNOWN' } } }, }) }) }) diff --git a/packages/core/agent-loop/tests/tool-order.spec.ts b/packages/core/agent-loop/tests/tool-order.spec.ts index 6b9ac94d4b..8aec38004f 100644 --- a/packages/core/agent-loop/tests/tool-order.spec.ts +++ b/packages/core/agent-loop/tests/tool-order.spec.ts @@ -93,7 +93,7 @@ describe('loop-level canonical tool order', () => { expect(Object.isFrozen(adapter.requests[0])).toBe(true) }) - it('fails before opening a turn when toolOrder names an unregistered tool', async () => { + it('closes a no-step turn when toolOrder names an unregistered tool', async () => { const adapter = new MockAdapter([textResponse('never sent')]) const ctx = await harness(adapter, ['ghost', TOOL_ORDER_REST]) registerNamed(ctx, 'alpha') @@ -102,8 +102,8 @@ describe('loop-level canonical tool order', () => { await waitForIdle(ctx, agent) expect(adapter.requests).toHaveLength(0) expect(foldRequestHeader(agent.session.events)).toBeUndefined() - expect(agent.session.events.some(e => e.type === 'turn/start')).toBe(false) - expect(agent.session.events.some(e => e.type === 'turn/end')).toBe(false) + expect(agent.session.events.some(e => e.type === 'turn/start')).toBe(true) + expect(agent.session.events.some(e => e.type === 'turn/end')).toBe(true) expect(agent.session.events.some(e => e.type === 'step/start')).toBe(false) expect(agent.session.events.some(e => e.type === 'step/end')).toBe(false) }) diff --git a/packages/core/session/tests/fork.spec.ts b/packages/core/session/tests/fork.spec.ts index efa6358e47..9fd072c325 100644 --- a/packages/core/session/tests/fork.spec.ts +++ b/packages/core/session/tests/fork.spec.ts @@ -29,7 +29,7 @@ function appendClosedTurn( content: [{ type: 'text', text }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, step: 0, reason }) + session.append('turn/end', { turn, reason }) } function appendOpenTurn(session: Session, turn: number): void { diff --git a/packages/core/session/tests/invariant.spec.ts b/packages/core/session/tests/invariant.spec.ts index 46d3cba950..cc5e21f57a 100644 --- a/packages/core/session/tests/invariant.spec.ts +++ b/packages/core/session/tests/invariant.spec.ts @@ -27,7 +27,7 @@ describe('session-log invariants', () => { const session = ctx.sessions.create(SessionId('global-under-scoped-invariants')) expect(() => { session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }).not.toThrow() }) @@ -63,7 +63,7 @@ describe('session-log invariants', () => { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }).not.toThrow() }) @@ -82,7 +82,7 @@ describe('session-log invariants', () => { expect(session.events).toEqual([]) expect(() => { session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }).not.toThrow() }) @@ -94,7 +94,7 @@ describe('session-log invariants', () => { ctx.on('session/event', () => { throw new Error('hostile observer') }, { prepend: true }) expect(() => { session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }).not.toThrow() expect(warnings).toHaveLength(2) }) @@ -112,7 +112,7 @@ describe('session-log invariants', () => { type: 'turn/end', seq: 0, time: 2, - data: { turn: 1, step: 0, reason: { kind: 'completed' } }, + data: { turn: 1, reason: { kind: 'completed' } }, } as never) }).toThrow(/seq must strictly increase/) }) @@ -122,12 +122,12 @@ describe('session-log invariants', () => { open.append('turn/start', { turn: 1 }) expect(() => open.append('turn/start', { turn: 2 })) .toThrow(/turn 1 is still open/) - expect(() => open.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } })) + expect(() => open.append('turn/end', { turn: 2, reason: { kind: 'completed' } })) .toThrow(/does not match open turn 1/) const second = (await setup()).ctx.sessions.create() second.append('turn/start', { turn: 1 }) - second.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + second.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) expect(() => second.append('turn/start', { turn: 3 })) .toThrow(/expected turn 2, got 3/) @@ -135,8 +135,8 @@ describe('session-log invariants', () => { third.append('turn/start', { turn: 1 }) third.append('step/start', { turn: 1, step: 1 }) third.append('step/end', { turn: 1, step: 1 }) - expect(() => third.append('turn/end', { turn: 1, step: 2, reason: { kind: 'completed' } })) - .toThrow(/expected last step 1, got 2/) + expect(() => third.append('turn/end', { turn: 1, reason: { kind: 'completed' } })) + .not.toThrow() const enclosed = (await setup()).ctx.sessions.create() enclosed.append('turn/start', { turn: 1 }) @@ -178,7 +178,7 @@ describe('session-log invariants', () => { nested.append('turn/start', { turn: 1 }) nested.append('step/start', { turn: 1, step: 1 }) expect(() => nested.append('step/start', { turn: 1, step: 2 })).toThrow(/while step 1 is still open/) - expect(() => nested.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } })) + expect(() => nested.append('turn/end', { turn: 1, reason: { kind: 'completed' } })) .toThrow(/while step 1 is still open/) expect(() => nested.append('step/end', { turn: 1, step: 2 })).toThrow(/open is turn 1\/step 1/) expect(() => nested.append('assistant/message', { @@ -203,9 +203,8 @@ describe('session-log invariants', () => { expect(() => skipped.append('turn/end', { turn: 1, - step: 0, reason: { kind: 'completed' }, - })).toThrow(/expected last step 1, got 0/) + })).not.toThrow() }) it('requires step-scoped stream and tool events to name the open step', async () => { @@ -268,7 +267,7 @@ describe('session-log invariants', () => { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) session.append('turn/start', { turn: 2 }) expect(() => session.append('tool/result', { @@ -308,7 +307,7 @@ describe('session-log invariants', () => { }), }, { surfaceOp: 'append' }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) expect(() => session.append('tool/result', { ...original.data, @@ -341,7 +340,7 @@ describe('session-log invariants', () => { error: { name: 'ToolNotStartedError', code: TOOL_NOT_STARTED }, }, { surfaceOp: 'append' }) repaired.append('step/end', { turn: 1, step: 1 }) - repaired.append('turn/end', { turn: 1, step: 1, reason: { kind: 'interrupted' } }) + repaired.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } }) }).not.toThrow() const unresolved = (await setup()).ctx.sessions.create() @@ -350,7 +349,7 @@ describe('session-log invariants', () => { unresolved.append('step/start', { turn: 1, step: 1 }) unresolved.append('tool/call', { turn: 1, step: 1, callId: CallId('c1'), name: 'echo', arguments: '{}' }) unresolved.append('step/end', { turn: 1, step: 1 }) - unresolved.append('turn/end', { turn: 1, step: 1, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } }) + unresolved.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } }) }).not.toThrow() }) @@ -409,7 +408,7 @@ describe('session-log invariants', () => { // Balanced seed: between turns. expect(() => ctx.sessions.create(SessionId('inherited-between-turns'), { seed: [ { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }, - { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } }, ] })).not.toThrow() // Unbalanced seed: inside the open turn, which the relation permits. const open = ctx.sessions.create(SessionId('inherited-inside-open-turn'), { seed: [ @@ -419,7 +418,7 @@ describe('session-log invariants', () => { // Still open afterwards: the boundary moves no cursor. expect(() => open.append('turn/start', { turn: 2 })) .toThrow(/turn 1 is still open/) - expect(() => open.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } })).not.toThrow() + expect(() => open.append('turn/end', { turn: 1, reason: { kind: 'completed' } })).not.toThrow() }) it('removes all listeners when the companion is disposed', async () => { diff --git a/packages/core/session/tests/properties.spec.ts b/packages/core/session/tests/properties.spec.ts index 52b5b8d96c..2cae9b3c4b 100644 --- a/packages/core/session/tests/properties.spec.ts +++ b/packages/core/session/tests/properties.spec.ts @@ -71,7 +71,7 @@ const messageEventArb: fc.Arbitrary = fc.oneof( // A non-message event (trace/replay data — must NOT affect derived history). const nonMessageEventArb: fc.Arbitrary = fc.oneof( fc.constant({ type: 'turn/start', data: { turn: 1 } }), - fc.constant({ type: 'turn/end', data: { turn: 1, step: 0, reason: { kind: 'completed' } } }), + fc.constant({ type: 'turn/end', data: { turn: 1, reason: { kind: 'completed' } } }), fc.constant({ type: 'step/start', data: { turn: 1, step: 1 } }), fc.constant({ type: 'step/end', data: { turn: 1, step: 1 } }), fc.string().map((text): Appendable => ({ type: 'assistant/chunk', data: { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text } } })), diff --git a/packages/core/session/tests/repair.spec.ts b/packages/core/session/tests/repair.spec.ts index d84133fa77..b656a7116c 100644 --- a/packages/core/session/tests/repair.spec.ts +++ b/packages/core/session/tests/repair.spec.ts @@ -19,7 +19,7 @@ describe('interruptedTurnClosers', () => { it('returns nothing for a balanced log (ends on turn/end)', () => { const balanced: SessionEvent[] = [ userTurnStart(1, 0), - { type: 'turn/end', seq: 1, time: 1, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: 1, data: { turn: 1, reason: { kind: 'completed' } } }, ] expect(interruptedTurnClosers(balanced)).toEqual([]) }) @@ -169,7 +169,7 @@ describe('interruptedTurnClosers', () => { }), } }, { type: 'step/end', seq: 4, time: 4, data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 5, time: 5, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 5, time: 5, data: { turn: 1, reason: { kind: 'completed' } } }, userTurnStart(2, 6), { type: 'step/start', seq: 7, time: 7, data: { turn: 2, step: 1 } }, { type: 'assistant/message', seq: 8, time: 8, data: { @@ -285,7 +285,7 @@ describe('lastActivityTime', () => { it('reports the log tail when no boundary is present', () => { const events: SessionEvent[] = [ userTurnStart(1, 0), - { type: 'turn/end', seq: 1, time: 500, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: 500, data: { turn: 1, reason: { kind: 'completed' } } }, ] expect(lastActivityTime(events)).toBe(500) }) @@ -293,7 +293,7 @@ describe('lastActivityTime', () => { it('skips a trailing boundary in favour of the last real work', () => { const events: SessionEvent[] = [ userTurnStart(1, 0), - { type: 'turn/end', seq: 1, time: 500, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: 500, data: { turn: 1, reason: { kind: 'completed' } } }, endSeedAt(2, 9_000), ] // Resumed long after the work, but never worked in again. @@ -304,7 +304,7 @@ describe('lastActivityTime', () => { const events: SessionEvent[] = [ userTurnStart(1, 0), endSeedAt(1, 9_000), - { type: 'turn/end', seq: 2, time: 9_500, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 2, time: 9_500, data: { turn: 1, reason: { kind: 'completed' } } }, ] expect(lastActivityTime(events)).toBe(9_500) }) diff --git a/packages/core/session/tests/session.spec.ts b/packages/core/session/tests/session.spec.ts index 0c5ccb7130..71524c7354 100644 --- a/packages/core/session/tests/session.spec.ts +++ b/packages/core/session/tests/session.spec.ts @@ -48,7 +48,7 @@ describe('Session', () => { isError: false, }), }, { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const messages = session.deriveMessages() expect(messages.map(m => m.role)).toEqual(['user', 'assistant', 'user']) @@ -62,7 +62,7 @@ describe('Session', () => { // append and persist like any other reason (JSON-serializable, no fields). const session = new Session(SessionId('s1')) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'max-tokens' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'max-tokens' } }) const turnEnd = session.events.findLast(e => e.type === 'turn/end')! expect(turnEnd.data.reason).toEqual({ kind: 'max-tokens' }) @@ -73,7 +73,7 @@ describe('Session', () => { it('round-trips an aborted turn with its cancellation cause', () => { const session = new Session(SessionId('aborted')) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'aborted', reason: { kind: 'user' } } }) + session.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } } }) const replayed = new Session(SessionId('aborted-replay'), structuredClone(session.events)) expect(replayed.events.slice(0, -1)).toEqual(session.events) const turnEnd = replayed.events.findLast(event => event.type === 'turn/end') @@ -129,7 +129,7 @@ describe('Session', () => { }, }), }, { surfaceOp: 'append' }) - original.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + original.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const replayed = new Session(SessionId('s3-replay'), [...original.events]) expect(replayed.deriveMessages()).toEqual(original.deriveMessages()) @@ -487,7 +487,7 @@ describe('Session', () => { it('validates seed events: rejects a non-contiguous seq', () => { const gapSeed = [ { type: 'turn/start' as const, seq: 0, time: 1, data: { turn: 1 } }, - { type: 'turn/end' as const, seq: 5, time: 2, data: { turn: 1, step: 0, reason: { kind: 'completed' as const } } }, // gap: expected seq 1 + { type: 'turn/end' as const, seq: 5, time: 2, data: { turn: 1, reason: { kind: 'completed' as const } } }, // gap: expected seq 1 ] as SessionEvent[] expect(() => new Session(SessionId('seed-gap'), gapSeed)).toThrow(/contiguous|seq/) }) @@ -502,7 +502,7 @@ describe('Session', () => { { type: 'user/message' as const, seq: 1, time: 2, data: createUserMessage({ content: [{ type: 'text' as const, text: 'hi' }], source: { kind: 'user' as const }, }) }, - { type: 'turn/end' as const, seq: 2, time: 3, data: { turn: 1, step: 0, reason: { kind: 'completed' as const } } }, + { type: 'turn/end' as const, seq: 2, time: 3, data: { turn: 1, reason: { kind: 'completed' as const } } }, ] as SessionEvent[] expect(() => new Session(SessionId('seed-no-marker'), markerlessSeed)).toThrow(/requires a surfaceOp marker/) }) @@ -513,7 +513,7 @@ describe('Session', () => { { type: 'user/message' as const, seq: 1, time: 2, data: createUserMessage({ content: [{ type: 'text' as const, text: 'hi' }], source: { kind: 'user' as const }, }), surfaceOp: 'append' as const }, - { type: 'turn/end' as const, seq: 2, time: 3, data: { turn: 1, step: 0, reason: { kind: 'completed' as const } } }, + { type: 'turn/end' as const, seq: 2, time: 3, data: { turn: 1, reason: { kind: 'completed' as const } } }, ] as SessionEvent[] const session = new Session(SessionId('seed-ok'), goodSeed) expect(session.events.slice(0, 3)).toEqual(goodSeed) @@ -702,7 +702,7 @@ describe('Session', () => { role: 'user' as const, content: [{ type: 'text' as const, text: 'original' }], source: { kind: 'user' as const }, }, surfaceOp: 'append' as const }, - { type: 'turn/end' as const, seq: 2, time: 3, data: { turn: 1, step: 0, reason: { kind: 'completed' as const } } }, + { type: 'turn/end' as const, seq: 2, time: 3, data: { turn: 1, reason: { kind: 'completed' as const } } }, ] as SessionEvent[] const session = new Session(SessionId('seed-snapshot'), seed) // Mutate the ORIGINAL seed objects after construction: a shared reference @@ -896,7 +896,7 @@ describe('Session', () => { expect(() => { (before as SessionEvent[]).push(beforeEvent) }).toThrow(TypeError) expect(() => { beforeEvent.data.turn = 99 }).toThrow(TypeError) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const after = session.events expect(before).toHaveLength(1) expect(after).toHaveLength(2) @@ -1632,7 +1632,7 @@ describe('todo/write event', () => { const original = new Session(SessionId('t4')) original.append('turn/start', { turn: 1 }) original.append('todo/write', { todos: [{ content: 'only', status: 'completed' }] }) - original.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + original.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Seeding a non-surface event with no surfaceOp must not throw. const replayed = new Session(SessionId('t4-replay'), [...original.events]) expect(replayed.events.findLast(e => e.type === 'todo/write')!.data.todos) diff --git a/packages/core/session/tests/surface.spec.ts b/packages/core/session/tests/surface.spec.ts index 00bfae4503..6ce7313f4a 100644 --- a/packages/core/session/tests/surface.spec.ts +++ b/packages/core/session/tests/surface.spec.ts @@ -36,7 +36,7 @@ function surfaceSession(): Session { }, }), }, { surfaceOp: 'append' }) - s.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + s.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) return s } @@ -401,7 +401,7 @@ describe('SurfaceManager', () => { s.append('turn/start', { turn: 1 }) s.append('step/start', { turn: 1, step: 1 }) s.append('step/end', { turn: 1, step: 1 }) - s.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + s.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) expect(s.surface.nodes.length).toBe(0) expect(s.deriveMessages()).toEqual([]) }) @@ -684,7 +684,7 @@ describe('deriveMessages with surface', () => { }, }), }, { surfaceOp: 'append' }) - s.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + s.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Chunks and boundaries are NOT in the surface, so only 2 messages. expect(s.deriveMessages()).toHaveLength(2) }) @@ -772,7 +772,7 @@ describe('Session.append surface opts', () => { }), }, surfaceOp: 'append' }, { type: 'step/end', seq: 3, time: 4, data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 4, time: 5, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 4, time: 5, data: { turn: 1, reason: { kind: 'completed' } } }, ] const s = new Session(SessionId('nomessage'), seed) // The empty assistant/message is on the surface but _deriveOneMessage returns null for it. diff --git a/packages/core/tools/tests/invariant.spec.ts b/packages/core/tools/tests/invariant.spec.ts index 38eb6943b9..80ae299da7 100644 --- a/packages/core/tools/tests/invariant.spec.ts +++ b/packages/core/tools/tests/invariant.spec.ts @@ -100,7 +100,7 @@ describe('tool-pipeline invariants', () => { expect(() => session.append('tool/code-dispatch-start', data)).toThrow(/outside any open turn/) session.append('turn/start', { turn: 1 }) expect(() => session.append('tool/code-dispatch-start', data)).not.toThrow() - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }) it('replays enclosed code-dispatch records on late registration', async () => { @@ -116,7 +116,7 @@ describe('tool-pipeline invariants', () => { isError: false, content: [{ type: 'text', text: 'ok' }], }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.plugin(InvariantService) await expect(ctx.plugin(ToolsInvariant).then(() => undefined)).resolves.toBeUndefined() }) diff --git a/packages/examples/cli-demo/tests/cli.spec.ts b/packages/examples/cli-demo/tests/cli.spec.ts index 4cecb7cd12..39afe0a24e 100644 --- a/packages/examples/cli-demo/tests/cli.spec.ts +++ b/packages/examples/cli-demo/tests/cli.spec.ts @@ -440,7 +440,7 @@ describe('runOneShot and executeCli', () => { source: { kind: 'plugin', plugin: 'test' }, })) other.append('turn/start', { turn: 1 }) - other.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + other.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }) const streamed: { sessionId: string; event: SessionEvent }[] = [] const result = runOneShot(ctx, { diff --git a/packages/goal/goal-session/tests/goal-session.spec.ts b/packages/goal/goal-session/tests/goal-session.spec.ts index dbe1b59902..5bdc1e0026 100644 --- a/packages/goal/goal-session/tests/goal-session.spec.ts +++ b/packages/goal/goal-session/tests/goal-session.spec.ts @@ -260,7 +260,7 @@ describe('same-session goal driving', () => { message: 'Goal round was rejected before entering its step.', }) expect(test.adapter.requests).toHaveLength(0) - expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(false) + expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(true) }) it('does not reserve again when a stopped-goal observer queues cancel-scoped work', async () => { @@ -744,7 +744,7 @@ describe('same-session goal driving', () => { await test.agent.whenIdle() expect(test.adapter.requests).toHaveLength(0) - expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(false) + expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(true) }) it('leaves round-zero goal context to the ordinary pre-step chain', async () => { @@ -1023,7 +1023,7 @@ describe('same-session goal driving', () => { expect(test.ctx.goals.get(test.agent)).toMatchObject({ phase: 'active', roundsStarted: 0 }) expect(test.adapter.requests).toHaveLength(0) - expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(false) + expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(true) }) it('ignores session events without an exact owning agent and retires disposed agent state', async () => { @@ -1032,7 +1032,7 @@ describe('same-session goal driving', () => { orphan.append('turn/start', { turn: 1, }) - orphan.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + orphan.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const handle = await test.ctx.agents.create({ sessionId: SessionId('goal-session-disposed'), diff --git a/packages/goal/goal-session/tests/invariant.spec.ts b/packages/goal/goal-session/tests/invariant.spec.ts index 68a90dfa8b..6baaa724da 100644 --- a/packages/goal/goal-session/tests/invariant.spec.ts +++ b/packages/goal/goal-session/tests/invariant.spec.ts @@ -41,7 +41,7 @@ function appendRound(session: Session, turn: number, content = renderGoalRoundPr session.append('user/message', createUserMessage({ content, source, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } async function mount(sessionFirst = false): Promise<{ ctx: Context; session: Session }> { @@ -73,7 +73,7 @@ describe('goal-session prompt invariants', () => { content: [{ type: 'text', text: 'ordinary human message' }], source: userSource, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 4, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 4, reason: { kind: 'completed' } }) const stateSource = { kind: 'goal', goalId: change.goal.id, revision: change.goal.revision, round: 0, diff --git a/packages/goal/goal/tests/goal.spec.ts b/packages/goal/goal/tests/goal.spec.ts index 4a77336f36..ca2db709f5 100644 --- a/packages/goal/goal/tests/goal.spec.ts +++ b/packages/goal/goal/tests/goal.spec.ts @@ -74,7 +74,7 @@ function appendRound(session: Session, ref: GoalRef, round: number): void { session.append('user/message', createUserMessage({ content: [{ type: 'text', text: `round ${round}` }], source, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } describe('GoalService creation and replay', () => { @@ -582,7 +582,7 @@ describe('goal replay validation', () => { session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'ordinary' }], source, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) expect(foldGoal(session.events)).toEqual({ roundsStarted: 0 }) }) @@ -724,7 +724,7 @@ describe('goal replay validation', () => { session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'missing' }], source, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) expect(() => foldGoal(session.events)).toThrow('goal message source is invalid') }) diff --git a/packages/goal/tool-goal/tests/tool-goal.spec.ts b/packages/goal/tool-goal/tests/tool-goal.spec.ts index 6774783987..d0583092e0 100644 --- a/packages/goal/tool-goal/tests/tool-goal.spec.ts +++ b/packages/goal/tool-goal/tests/tool-goal.spec.ts @@ -66,7 +66,7 @@ function openTurn(stub: StubAgent, source: MessageSource, text = 'prompt'): numb /** Close the currently open test turn. */ function closeTurn(stub: StubAgent, turn: number): void { - stub.session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + stub.session.append('turn/end', { turn, reason: { kind: 'completed' } }) } async function harness(config: toolGoal.Config = {}) { diff --git a/packages/hooks/hook-protocol/tests/invariant.spec.ts b/packages/hooks/hook-protocol/tests/invariant.spec.ts index 09984f220c..4efaae36c6 100644 --- a/packages/hooks/hook-protocol/tests/invariant.spec.ts +++ b/packages/hooks/hook-protocol/tests/invariant.spec.ts @@ -54,7 +54,7 @@ describe('hook-protocol invariants', () => { await ctx.plugin(InvariantService) await ctx.plugin(HookInvariant) expect(() => session.append('hook/result', result())).not.toThrow() - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }) it('adopts a bare session first observed through publication', async () => { @@ -87,7 +87,7 @@ describe('hook-protocol invariants', () => { await ctx.plugin(SessionStore) const session = ctx.sessions.create() startTurn(session) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) session.append('hook/invoked', invoked()) await ctx.plugin(InvariantService) await expect(ctx.plugin(HookInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/) diff --git a/packages/host/apiproxy/tests/api-proxy-cold.spec.ts b/packages/host/apiproxy/tests/api-proxy-cold.spec.ts index 5ec2ed80d1..b8d867e1fa 100644 --- a/packages/host/apiproxy/tests/api-proxy-cold.spec.ts +++ b/packages/host/apiproxy/tests/api-proxy-cold.spec.ts @@ -91,7 +91,7 @@ describe('attached updatedAt excludes end-seed', () => { const resumed = ctx.sessions.create(sid('resumed-untouched'), { seed: [ { type: 'turn/start', seq: 0, time: worked, data: { turn: 1 } }, - { type: 'turn/end', seq: 1, time: worked, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: worked, data: { turn: 1, reason: { kind: 'completed' } } }, ], meta: { cwd: '/proj', createdAt: 500 }, }) diff --git a/packages/host/apiproxy/tests/api-proxy-fork.spec.ts b/packages/host/apiproxy/tests/api-proxy-fork.spec.ts index d7f3d33d9b..9f6ef65e2f 100644 --- a/packages/host/apiproxy/tests/api-proxy-fork.spec.ts +++ b/packages/host/apiproxy/tests/api-proxy-fork.spec.ts @@ -64,7 +64,7 @@ function liveAgent( content: [{ type: 'text', text: `prompt ${String(turn)}` }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } if (tail !== 'none') { session.append('turn/start', { turn: turns + 1 }) @@ -74,7 +74,6 @@ function liveAgent( }), { surfaceOp: 'append' }) if (tail === 'aborted') session.append('turn/end', { turn: turns + 1, - step: 0, reason: { kind: 'aborted', reason: { kind: 'user' } }, }) } diff --git a/packages/host/apiproxy/tests/api-proxy-rename.spec.ts b/packages/host/apiproxy/tests/api-proxy-rename.spec.ts index db34d135e4..15c7361024 100644 --- a/packages/host/apiproxy/tests/api-proxy-rename.spec.ts +++ b/packages/host/apiproxy/tests/api-proxy-rename.spec.ts @@ -62,7 +62,7 @@ function liveAgent(ctx: Context, id: string, turns: number): Session { content: [{ type: 'text', text: `prompt ${String(turn)}` }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } ctx.agents.register({ id: session.id, session, status: 'idle', ctx } as Agent) return session diff --git a/packages/host/apiproxy/tests/api-proxy-view.spec.ts b/packages/host/apiproxy/tests/api-proxy-view.spec.ts index 4c14ea2c81..43083545db 100644 --- a/packages/host/apiproxy/tests/api-proxy-view.spec.ts +++ b/packages/host/apiproxy/tests/api-proxy-view.spec.ts @@ -316,7 +316,7 @@ describe('mux live view computation', () => { const session = ctx.sessions.create() session.append('turn/start', { turn: 1 }) session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-late'), name: 'term', arguments: '{"cmd":"tail"}' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // The turn/end above cleared the live table; pairing must fall back to // scanning the session's in-memory events. session.append('tool/result', { diff --git a/packages/llm/llm-retry/tests/invariant.spec.ts b/packages/llm/llm-retry/tests/invariant.spec.ts index da47434ec9..1c478b226b 100644 --- a/packages/llm/llm-retry/tests/invariant.spec.ts +++ b/packages/llm/llm-retry/tests/invariant.spec.ts @@ -181,7 +181,7 @@ describe('llm-retry invariants', () => { const closedTurn = openStep(ctx, 'retry-invariant-closed-turn') closedTurn.append('step/end', { turn: 1, step: 1 }) - closedTurn.append('turn/end', { turn: 1, step: 1, reason: { kind: 'aborted', reason: { kind: 'user' } }, + closedTurn.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } }, }) expect(() => { closedTurn.append('llm/retry', { turn: 1, step: 1, ...normal }) @@ -228,7 +228,7 @@ describe('llm-retry invariants', () => { appendRetryTurn(missingEnd, 2) const nonFailureEnd = ctx.sessions.create(SessionId('retry-invariant-non-failure-end')) - nonFailureEnd.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + nonFailureEnd.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) nonFailureEnd.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'idle context' }], source: { kind: 'user' }, @@ -236,7 +236,7 @@ describe('llm-retry invariants', () => { appendRetryTurn(nonFailureEnd, 2) const missingStart = ctx.sessions.create(SessionId('retry-invariant-missing-start')) - missingStart.append('turn/end', { turn: 1, step: 0, reason: { kind: 'error', error: failure }, + missingStart.append('turn/end', { turn: 1, reason: { kind: 'error', error: failure }, }) appendRetryTurn(missingStart, 2) diff --git a/packages/llm/llm-retry/tests/persistence.spec.ts b/packages/llm/llm-retry/tests/persistence.spec.ts index 8e4ab8bd41..36a1fb5dbe 100644 --- a/packages/llm/llm-retry/tests/persistence.spec.ts +++ b/packages/llm/llm-retry/tests/persistence.spec.ts @@ -49,7 +49,7 @@ describe.each(['jsonl', 'sqlite'] as const)('%s retry-event persistence', (kind) failure: { message: 'provider busy', code: 'RATE_LIMIT', status: 429 }, }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'error', error: { message: 'provider busy', code: 'RATE_LIMIT', status: 429 }, + session.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'provider busy', code: 'RATE_LIMIT', status: 429 }, }, }) diff --git a/packages/llm/llm-retry/tests/retry.spec.ts b/packages/llm/llm-retry/tests/retry.spec.ts index 6ff39aca71..d1500fa781 100644 --- a/packages/llm/llm-retry/tests/retry.spec.ts +++ b/packages/llm/llm-retry/tests/retry.spec.ts @@ -337,7 +337,7 @@ describe('provider-routed retry policy', () => { expect(agent.session.events.filter(event => event.type === 'llm/retry')).toHaveLength(2) expect(agent.session.events.at(-1)).toMatchObject({ type: 'turn/end', - data: { step: 1, reason: { kind: 'error', error: { message: 'busy three', code: 'SERVER' } } }, + data: { reason: { kind: 'error', error: { message: 'busy three', code: 'SERVER' } } }, }) }) @@ -451,7 +451,7 @@ describe('provider-routed retry policy', () => { const end = agent.session.events.at(-1) expect(end).toMatchObject({ type: 'turn/end', - data: { step: 1, reason: { kind: 'error', error: { code: 'NO_ADAPTER' } } }, + data: { reason: { kind: 'error', error: { code: 'NO_ADAPTER' } } }, }) if (end?.type === 'turn/end' && end.data.reason.kind === 'error') { expect(end.data.reason.error.message).toContain('no adapter registered for provider') diff --git a/packages/llm/llm-retry/tests/transport-recovery.spec.ts b/packages/llm/llm-retry/tests/transport-recovery.spec.ts index 7ce8224171..a5e7552496 100644 --- a/packages/llm/llm-retry/tests/transport-recovery.spec.ts +++ b/packages/llm/llm-retry/tests/transport-recovery.spec.ts @@ -193,7 +193,7 @@ describe('bounded retry through the real DeepSeek HTTP/SSE adapter', () => { expect(agent.session.events.some(event => event.type === 'llm/retry')).toBe(false) expect(agent.session.events.at(-1)).toMatchObject({ type: 'turn/end', - data: { step: 1, reason: { kind: 'error', error: { message: 'SSE stream ended without [DONE]', code: 'STREAM_CLOSED' } } }, + data: { reason: { kind: 'error', error: { message: 'SSE stream ended without [DONE]', code: 'STREAM_CLOSED' } } }, }) }) @@ -236,7 +236,7 @@ describe('bounded retry through the real DeepSeek HTTP/SSE adapter', () => { const end = agent.session.events.at(-1) expect(end).toMatchObject({ type: 'turn/end', - data: { step: 1, reason: { kind: 'error', error: { code: 'TRANSPORT' } } }, + data: { reason: { kind: 'error', error: { code: 'TRANSPORT' } } }, }) if (end?.type === 'turn/end' && end.data.reason.kind === 'error') { expect(end.data.reason.error.message).toContain('DeepSeek API request to') diff --git a/packages/plan/plan-mode/tests/invariant.spec.ts b/packages/plan/plan-mode/tests/invariant.spec.ts index e33954706d..cb2dc2eadd 100644 --- a/packages/plan/plan-mode/tests/invariant.spec.ts +++ b/packages/plan/plan-mode/tests/invariant.spec.ts @@ -31,7 +31,7 @@ describe('plan-mode stream invariants', () => { expect(() => { ctx.emit('session/event', session, event(true)) }).not.toThrow() expect(() => { ctx.emit('session/event', session, event(false)) }).not.toThrow() ctx.emit('session/event', session, { - type: 'turn/end', seq: 3, time: 3, data: { turn: 1, step: 0, reason: { kind: 'completed' } }, + type: 'turn/end', seq: 3, time: 3, data: { turn: 1, reason: { kind: 'completed' } }, }) }) @@ -66,7 +66,7 @@ describe('plan-mode stream invariants', () => { const session = ctx.sessions.create() session.append('turn/start', { turn: 1 }) session.append('plan/mode', { active: 'plan' as unknown as boolean }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.plugin(InvariantService, { enabled: true }) await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).rejects.toThrow(/expected a boolean/) @@ -78,7 +78,7 @@ describe('plan-mode stream invariants', () => { const session = ctx.sessions.create() session.append('turn/start', { turn: 1 }) session.append('plan/mode', { active: true }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.plugin(InvariantService, { enabled: true }) await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).resolves.toBeUndefined() diff --git a/packages/plan/plan-mode/tests/plan-mode.spec.ts b/packages/plan/plan-mode/tests/plan-mode.spec.ts index 48c139cd92..cef086a502 100644 --- a/packages/plan/plan-mode/tests/plan-mode.spec.ts +++ b/packages/plan/plan-mode/tests/plan-mode.spec.ts @@ -96,7 +96,7 @@ function openTurn(session: Session, turn = 0): void { /** Close the open turn (the between-turns shape: selections commit immediately). */ function closeTurn(session: Session, turn = 0): void { - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } /** Append a minimal `request/header` snapshot so the log has a "what the model was told" anchor. */ diff --git a/packages/plan/plan-mode/tests/projection.spec.ts b/packages/plan/plan-mode/tests/projection.spec.ts index 02c92243d3..c26112c8fd 100644 --- a/packages/plan/plan-mode/tests/projection.spec.ts +++ b/packages/plan/plan-mode/tests/projection.spec.ts @@ -60,7 +60,7 @@ function runPlanCommand(session: Session, args: string, index: number): void { function commitPlanMode(session: Session, active: boolean, turn: number): void { session.append('turn/start', { turn }) session.append('plan/mode', { active }) - session.append('turn/end', { turn, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn, reason: { kind: 'completed' } }) } describe('plan projection unit', () => { diff --git a/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts b/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts index 7d2957d641..c573948c2c 100644 --- a/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts +++ b/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts @@ -64,7 +64,7 @@ function appendClosedTurn(session: Session): void { content: [{ type: 'text', text: 'hello' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) } // Run the shared backend contract against the real JSONL backend. @@ -224,7 +224,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => { }), }, surfaceOp: 'append', sourceEventSeqs: [2, 3] }, { type: 'step/end', seq: 5, time: 6, data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 6, time: 7, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 6, time: 7, data: { turn: 1, reason: { kind: 'completed' } } }, ] await ctx.sessionPersistence.create(m) await ctx.sessionPersistence.append(m.id, log) @@ -344,7 +344,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => { JSON.stringify(toHeaderLine(m)), JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }), JSON.stringify({ type: 'request/header-delta', seq: 1, time: 2, data: { config: { model: 'legacy' } } }), - JSON.stringify({ type: 'turn/end', seq: 2, time: 3, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }), + JSON.stringify({ type: 'turn/end', seq: 2, time: 3, data: { turn: 1, reason: { kind: 'completed' } } }), '', ].join('\n')) @@ -419,7 +419,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => { // The next append continues at seq 10 (the balanced length). const turn3 = [ { type: 'turn/start', seq: 10, time: 11, data: { turn: 3 } }, - { type: 'turn/end', seq: 11, time: 12, data: { turn: 3, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 11, time: 12, data: { turn: 3, reason: { kind: 'completed' } } }, ] as SessionEvent[] await ctx.sessionPersistence.append(m.id, turn3) const reloaded = await ctx.sessionPersistence.load(m.id) @@ -438,7 +438,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => { await ctx.sessionPersistence.load(m.id) await ctx.sessionPersistence.append(m.id, [ { type: 'turn/start', seq: 6, time: 9, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, reason: { kind: 'completed' } } }, ] as SessionEvent[]) const after = await readFile(rawLogPath(root, undefined, m.id), 'utf8') // the committed prefix is byte-for-byte intact at the head of the file @@ -466,7 +466,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => { const turn2 = [ { type: 'turn/start', seq: 6, time: 9, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, reason: { kind: 'completed' } } }, ] as SessionEvent[] // The append rejects, but the partial bytes are truncated back: the file is // its pre-append size and the cursor is unchanged. @@ -529,7 +529,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => { mutableHeader(loaded.meta).cwd = '/evil' await ctx.sessionPersistence.append(m.id, [ { type: 'turn/start', seq: 6, time: 9, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, reason: { kind: 'completed' } } }, ] as SessionEvent[]) // The append landed in the ORIGINAL /proj log, not beside an /evil path. const reloaded = await ctx.sessionPersistence.load(m.id) @@ -606,8 +606,8 @@ describe('SessionPersistenceJsonl: write path (session/event → flush)', () => b.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'B' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - a.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) - b.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + a.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) + b.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(a) await ctx.sessions.flush(b) @@ -694,7 +694,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => { JSON.stringify({ type: 'session', version: 0, id: 'g2', createdAt: 1, delegationDepth: 0 }), JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }), JSON.stringify({ type: 'step/start', seq: 2, time: 2, data: { turn: 1, step: 1 } }), // gap: missing seq 1 - JSON.stringify({ type: 'turn/end', seq: 3, time: 3, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }), + JSON.stringify({ type: 'turn/end', seq: 3, time: 3, data: { turn: 1, reason: { kind: 'completed' } } }), ].join('\n') + '\n' // A turn/end exists, so the prefix up to it is committed — but it has a hole. // Truncating it would silently drop committed data → unloadable. @@ -705,7 +705,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => { const log = [ JSON.stringify({ type: 'session', version: 0, id: 'c', createdAt: 1, delegationDepth: 0 }), '{not json', // corrupt, sits in the committed region (a turn/end follows) - JSON.stringify({ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }), + JSON.stringify({ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } }), ].join('\n') + '\n' expect(() => scanLog(Buffer.from(log))).toThrow(/unparsable committed event/) }) @@ -733,7 +733,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => { const log = [ JSON.stringify({ type: 'session', version: 0, id: 't', createdAt: 1, delegationDepth: 0 }), JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }), - JSON.stringify({ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }), + JSON.stringify({ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } }), JSON.stringify({ type: 'step/start', seq: 9, time: 3, data: { turn: 2, step: 1 } }), // gap in uncommitted tail ].join('\n') + '\n' const { events } = scanLog(Buffer.from(log)) @@ -777,7 +777,7 @@ describe('SessionPersistenceJsonl: default packed chunk rows', () => { }), }, surfaceOp: 'append', sourceEventSeqs: [2, 3, 4, 5, 6] }, { type: 'step/end', seq: 8, time: 9, data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 9, time: 10, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 9, time: 10, data: { turn: 1, reason: { kind: 'completed' } } }, ] } @@ -855,7 +855,7 @@ describe('SessionPersistenceJsonl: default packed chunk rows', () => { JSON.stringify({ type: 'session', version: 0, id: 'rows', createdAt: 1, delegationDepth: 0 }), JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }), JSON.stringify({ type: 'text-chunks', seq0: 1, time0: 2, data: { turn: 1, step: 1, index: 0, dt: [1, 1], texts: ['a', 'b', 'c'] } }), - JSON.stringify({ type: 'turn/end', seq: 4, time: 5, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }), + JSON.stringify({ type: 'turn/end', seq: 4, time: 5, data: { turn: 1, reason: { kind: 'completed' } } }), ].join('\n') + '\n' const { events } = scanLog(Buffer.from(logText)) expect(events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4]) @@ -867,7 +867,7 @@ describe('SessionPersistenceJsonl: default packed chunk rows', () => { JSON.stringify({ type: 'session', version: 0, id: 'bad-row', createdAt: 1, delegationDepth: 0 }), // dt arity mismatch — row validation throws, so the line is a committed hole. JSON.stringify({ type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [], texts: ['a', 'b'] } }), - JSON.stringify({ type: 'turn/end', seq: 2, time: 3, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }), + JSON.stringify({ type: 'turn/end', seq: 2, time: 3, data: { turn: 1, reason: { kind: 'completed' } } }), ].join('\n') + '\n' expect(() => scanLog(Buffer.from(logText))).toThrow(/unparsable committed event/) }) @@ -1154,7 +1154,7 @@ describe('SessionPersistenceJsonl: edge cases', () => { const firstFiber = await ctx.plugin(Object.assign((inner: Context) => { const a = inner.sessions.create(SessionId('bound'), { meta: { cwd: '/a' } }) a.append('turn/start', { turn: 1 }) - a.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + a.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }, { inject: ['sessions'] })) for (const s of ctx.sessions.list()) await ctx.sessions.flush(s) await firstFiber.dispose() @@ -1232,7 +1232,7 @@ describe('SessionPersistenceJsonl: edge cases', () => { await ctx2.plugin(SessionPersistenceJsonl, { root, compression: 'none' }) await ctx2.sessionPersistence.append(m.id, [ { type: 'turn/start', seq: 6, time: 9, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 10, data: { turn: 2, reason: { kind: 'completed' } } }, ] as SessionEvent[]) const loaded = await ctx2.sessionPersistence.load(m.id) expect(loaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7]) @@ -1282,7 +1282,7 @@ describe('SessionPersistenceJsonl: edge cases', () => { session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Make the durable materialize fail on the next flush. const backend = ctx2.sessionPersistence as unknown as { materialize: (...args: unknown[]) => Promise } const origMat = backend.materialize.bind(backend) diff --git a/packages/session-persistence/session-persistence-jsonl/tests/zstd.spec.ts b/packages/session-persistence/session-persistence-jsonl/tests/zstd.spec.ts index bce455f81f..566d649a79 100644 --- a/packages/session-persistence/session-persistence-jsonl/tests/zstd.spec.ts +++ b/packages/session-persistence/session-persistence-jsonl/tests/zstd.spec.ts @@ -286,7 +286,7 @@ describe('SessionPersistenceJsonl: default Zstandard encoding', () => { const before = await readFile(path) const secondTurn = [ { type: 'turn/start', seq: 6, time: 7, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, reason: { kind: 'completed' } } }, ] as SessionEvent[] await ctx.sessionPersistence.append(header.id, secondTurn) @@ -428,7 +428,7 @@ describe('SessionPersistenceJsonl: default Zstandard encoding', () => { const path = logPath(root, header.cwd, header.id, 'zstd') const secondTurn = [ { type: 'turn/start', seq: 6, time: 7, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, reason: { kind: 'completed' } } }, ] as SessionEvent[] const frame = await compressZstdFrame(secondTurn.map(e => JSON.stringify(e)).join('\n') + '\n') await appendFile(path, frame.subarray(0, -1)) @@ -476,7 +476,7 @@ describe('SessionPersistenceJsonl: default Zstandard encoding', () => { }) const secondTurn = [ { type: 'turn/start', seq: 6, time: 7, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, reason: { kind: 'completed' } } }, ] as SessionEvent[] await expect(ctx.sessionPersistence.append(header.id, secondTurn)).rejects.toThrow(/simulated Zstandard fsync failure/) expect(await readFile(path)).toEqual(before) diff --git a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts index 6e0c41e510..a619309fd8 100644 --- a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts +++ b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts @@ -135,7 +135,7 @@ describe('scanRows', () => { const gapped: SessionEvent[] = [ { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }, { type: 'step/start', seq: 2, time: 2, data: { turn: 1, step: 1 } }, // seq 1 missing - { type: 'turn/end', seq: 3, time: 3, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 3, time: 3, data: { turn: 1, reason: { kind: 'completed' } } }, ] expect(() => scanRows(rows(gapped))).toThrow(/seq gap in committed region/) }) @@ -143,7 +143,7 @@ describe('scanRows', () => { it('throws on an unparsable row inside the committed region', () => { const withCorruptCommitted: EventRow[] = [ { seq: 0, type: 'turn/start', time: 1, data: '{not json', source_event_seqs: null, surface_op: null }, // corrupt, sits before a turn/end - { seq: 1, type: 'turn/end', time: 2, data: JSON.stringify({ turn: 1, step: 0, reason: { kind: 'completed' } }), source_event_seqs: null, surface_op: null }, + { seq: 1, type: 'turn/end', time: 2, data: JSON.stringify({ turn: 1, reason: { kind: 'completed' } }), source_event_seqs: null, surface_op: null }, ] expect(() => scanRows(withCorruptCommitted)).toThrow(/unparsable committed event/) }) @@ -201,7 +201,7 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => { const insert = db.prepare('INSERT INTO events (session_id, seq, type, time, data) VALUES (?, ?, ?, ?, ?)') insert.run(m.id, 0, 'turn/start', 1, JSON.stringify({ turn: 1 })) insert.run(m.id, 1, 'request/header-delta', 2, JSON.stringify({ config: { model: 'legacy' } })) - insert.run(m.id, 2, 'turn/end', 3, JSON.stringify({ turn: 1, step: 0, reason: { kind: 'completed' } })) + insert.run(m.id, 2, 'turn/end', 3, JSON.stringify({ turn: 1, reason: { kind: 'completed' } })) db.close() const mounted = await backend(path) @@ -268,7 +268,7 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => { // length (seq 10) and a reload round-trips identically. await ctx2.sessionPersistence.append(m.id, [ { type: 'turn/start', seq: 10, time: 9, data: { turn: 3 } }, - { type: 'turn/end', seq: 11, time: 10, data: { turn: 3, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 11, time: 10, data: { turn: 3, reason: { kind: 'completed' } } }, ]) const reloaded = await ctx2.sessionPersistence.load(m.id) expect(reloaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) @@ -494,7 +494,7 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => { // load physically deleted the corrupt tail row, so a fresh append continues. await b2.ctx.sessionPersistence.append(m.id, [ { type: 'turn/start', seq: 6, time: 8, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 9, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 9, data: { turn: 2, reason: { kind: 'completed' } } }, ]) const reloaded = await b2.ctx.sessionPersistence.load(m.id) expect(reloaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7]) @@ -720,7 +720,7 @@ describe('SessionPersistenceSqlite: edge cases', () => { await b2.ctx.sessionPersistence.load(m.id) // cursor 6 in b2 const turn2: SessionEvent[] = [ { type: 'turn/start', seq: 6, time: 7, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, reason: { kind: 'completed' } } }, ] // b1 commits seq 6..7 first. await b1.ctx.sessionPersistence.append(m.id, turn2) @@ -815,7 +815,7 @@ describe('surface field round-trip', () => { data: JSON.stringify({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }), source_event_seqs: null, surface_op: '{"op":"replace","start":0,"end":0}' }, { seq: 1, type: 'turn/end', time: 2, - data: JSON.stringify({ turn: 1, step: 0, reason: { kind: 'completed' } }), + data: JSON.stringify({ turn: 1, reason: { kind: 'completed' } }), source_event_seqs: null, surface_op: null }, ] const { preserved } = scanRows(rows) @@ -847,7 +847,7 @@ describe('surface field round-trip', () => { }), }, { surfaceOp: 'append', sourceEventSeqs: [2] }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('roundtrip-surface')) expect(loaded.events).toHaveLength(6) @@ -870,7 +870,7 @@ describe('surface field round-trip', () => { content: [], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('surface-noseq')) expect((loaded.events[1]! as SurfaceEvent).surfaceOp).toBe('append') diff --git a/packages/session-persistence/session-persistence/README.i18n.yaml b/packages/session-persistence/session-persistence/README.i18n.yaml index eb22a3ab69..28e9cc6872 100644 --- a/packages/session-persistence/session-persistence/README.i18n.yaml +++ b/packages/session-persistence/session-persistence/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/session-persistence/session-persistence/README.md -README.md: 7554d8e6804f880712f38f44efc2ed2f60a7dbfc -README.zh.md: 0368e9d60c0217e5f030f4cd7880583d0f67aa94 +README.md: 1de7b237d7ca812a55b080e8d91161551cea2129 +README.zh.md: 9f5281a07a0ba2fa7caa761a02f99fbfc4a20582 diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session-persistence/session-persistence/README.md index 7554d8e680..1de7b237d7 100644 --- a/packages/session-persistence/session-persistence/README.md +++ b/packages/session-persistence/session-persistence/README.md @@ -34,7 +34,7 @@ Each `session/event` copies its event into the session controller and starts an Crash repair is cold-only. For a live id, `load(id)` snapshots the authoritative in-memory log, waits for that snapshot to become durable, and returns it with the coordinator's stored header only when balanced; an open live turn rejects instead of receiving synthetic interruption closers. A cold load reserves its id across backend reads and repair writes, so concurrent publication of a same-id live `Session` rejects and rolls back. HMR adoption reads through `loadStored`, applies the coordinator's cwd check, and never closes the active turn. -Backend reads normalize the exact supported same-version shapes before current-shape validation. Pre-identity messages receive the deterministic id `legacy-message::`; a tool-result content replacement inherits its target's imported id. A pre-react-loop `turn/start` loses its obsolete trigger, a removed `steering/message` becomes the same identified `user/message`, and an older `turn/end` gains its last entered step while its terminal reason maps without inventing unavailable cancellation provenance. The coordinator uses the same normalized view for `load`, `inspect`, `readFrom`, ownerless-state claims, and HMR prefix adoption. Storage remains append-only: reads do not rewrite old records, and later appends use the current shape. These are narrow import exceptions from the [pre-identity message](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md) and [pre-react-loop session](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md) decisions, not a general v0 migration promise. +Backend reads normalize the exact supported same-version shapes before current-shape validation. Pre-identity messages receive the deterministic id `legacy-message::`; a tool-result content replacement inherits its target's imported id. A pre-react-loop `turn/start` loses its obsolete trigger, a removed `steering/message` becomes the same identified `user/message`, and an older `turn/end` maps its terminal reason without inventing unavailable cancellation provenance. The coordinator uses the same normalized view for `load`, `inspect`, `readFrom`, ownerless-state claims, and HMR prefix adoption. Storage remains append-only: reads do not rewrite old records, and later appends use the current shape. These are narrow import exceptions from the [pre-identity message](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md) and [pre-react-loop session](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md) decisions, not a general v0 migration promise. When a live session emits `session/disposed`, the coordinator waits for its controller, serializes a final drain, then releases state owned by that exact `Session` object. Failed retirement leaves the controller in the live-session map, so backend teardown can retry it. Backend teardown stops event admission first, flushes every remaining controller, awaits per-id operations, and only then closes the storage handle. diff --git a/packages/session-persistence/session-persistence/README.zh.md b/packages/session-persistence/session-persistence/README.zh.md index 0368e9d60c..9f5281a07a 100644 --- a/packages/session-persistence/session-persistence/README.zh.md +++ b/packages/session-persistence/session-persistence/README.zh.md @@ -34,7 +34,7 @@ 崩溃修复只适用于冷状态。对于实时 id,`load(id)` 为权威内存日志制作快照,等待该快照持久,并只在平衡时将其与协调器已存储 header 一起返回;开放实时轮次会被拒绝,而不会收到合成中断 closer。冷 load 在后端读取和修复写入期间保留 id,因此同 id 实时 `Session` 的并发发布会拒绝并回滚。HMR 接管通过 `loadStored` 读取,应用协调器 cwd 检查,并绝不关闭活动轮次。 -后端读取会在当前形状验证前,规范化明确受支持的同版本形状。消息标识机制引入前的消息会获得确定性的 id `legacy-message::`;工具结果的内容替换会继承其目标导入后的 id。react-loop 重构前的 `turn/start` 会移除过时的 trigger,已移除的 steering(中途引导)事件 `steering/message` 会转换为同一条带标识的 `user/message`;旧版 `turn/end` 会补上最后进入的步骤,并在不虚构无法获得的取消来源的前提下映射终止原因。协调器对 `load`、`inspect`、`readFrom`、无 owner 状态的认领和 HMR 前缀接管使用同一份规范化视图。存储仍然仅追加:读取不会重写旧记录,此后追加的事件使用当前形状。这些是[消息标识机制引入前的消息](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md)与 [react-loop 重构前会话](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md)决策所规定的范围受限的导入例外,并不构成通用的 v0 迁移承诺。 +后端读取会在当前形状验证前,规范化明确受支持的同版本形状。消息标识机制引入前的消息会获得确定性的 id `legacy-message::`;工具结果的内容替换会继承其目标导入后的 id。react-loop 重构前的 `turn/start` 会移除过时的 trigger,已移除的 steering(中途引导)事件 `steering/message` 会转换为同一条带标识的 `user/message`;旧版 `turn/end` 会在不虚构无法获得的取消来源的前提下映射终止原因。协调器对 `load`、`inspect`、`readFrom`、无 owner 状态的认领和 HMR 前缀接管使用同一份规范化视图。存储仍然仅追加:读取不会重写旧记录,此后追加的事件使用当前形状。这些是[消息标识机制引入前的消息](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md)与 [react-loop 重构前会话](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md)决策所规定的范围受限的导入例外,并不构成通用的 v0 迁移承诺。 实时会话发出 `session/disposed` 时,协调器等待其 controller,串行化最终 drain,然后释放该精确 `Session` 对象拥有的状态。失败退役会将 controller 保留在实时会话 map 中,使后端拆卸可重试。后端拆卸先停止事件接纳,flush 每个剩余 controller,等待每 id 操作,最后才关闭存储句柄。 diff --git a/packages/session-persistence/session-persistence/tests/contract.ts b/packages/session-persistence/session-persistence/tests/contract.ts index b98a643a0e..ca9384b616 100644 --- a/packages/session-persistence/session-persistence/tests/contract.ts +++ b/packages/session-persistence/session-persistence/tests/contract.ts @@ -53,7 +53,7 @@ export function oneTurnLog(): SessionEvent[] { }), }, surfaceOp: 'append' }, { type: 'step/end', seq: 4, time: 5, data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 5, time: 6, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 5, time: 6, data: { turn: 1, reason: { kind: 'completed' } } }, ] } @@ -158,7 +158,7 @@ export function runPersistenceContract(name: string, make: () => Promise e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) diff --git a/packages/session-persistence/session-persistence/tests/coordinator-contract.ts b/packages/session-persistence/session-persistence/tests/coordinator-contract.ts index d21bb9e692..a5e799130e 100644 --- a/packages/session-persistence/session-persistence/tests/coordinator-contract.ts +++ b/packages/session-persistence/session-persistence/tests/coordinator-contract.ts @@ -104,7 +104,7 @@ function legacyMessageLog(): SessionEvent[] { surfaceOp: { op: 'replace', start: 5, end: 5 }, }, { type: 'step/end', seq: 7, time: 9, data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 8, time: 10, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 8, time: 10, data: { turn: 1, reason: { kind: 'completed' } } }, ] as unknown as SessionEvent[] } @@ -294,7 +294,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< const live = ctx.sessions.create(id, { seed: [start], meta: header }) await expect(loading).rejects.toThrow(/live turn is open/) - live.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + live.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(live) const loaded = await ctx.sessionPersistence.load(id) // The constructor's end-seed event persisted between the stored @@ -382,7 +382,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< expect(() => { ;(ev.data as { content: { type: 'text'; text: string }[] }).content[0]!.text = 'HACKED' }).toThrow(TypeError) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('mutate')) @@ -492,22 +492,19 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< { turn: 1 }, { turn: 2 }, { turn: 3 }, { turn: 4 }, { turn: 5 }, { turn: 6 }, { turn: 7 }, ]) expect(snapshot.events.filter(event => event.type === 'turn/end').map(event => event.data)).toEqual([ - { turn: 1, step: 1, reason: { kind: 'completed' } }, + { turn: 1, reason: { kind: 'completed' } }, { turn: 2, - step: 1, reason: { kind: 'error', error: { message: 'old provider failure', code: 'SERVER' } }, }, - { turn: 3, step: 0, reason: { kind: 'aborted', reason: { kind: 'legacy' } } }, - { turn: 4, step: 0, reason: { kind: 'aborted', reason: { kind: 'disposed' } } }, + { turn: 3, reason: { kind: 'aborted', reason: { kind: 'legacy' } } }, + { turn: 4, reason: { kind: 'aborted', reason: { kind: 'disposed' } } }, { turn: 5, - step: 1, reason: { kind: 'error', error: { message: 'old thrown value', code: 'UNKNOWN' } }, }, { turn: 6, - step: 0, reason: { kind: 'error', error: { @@ -521,7 +518,6 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< }, { turn: 7, - step: 0, reason: { kind: 'error', error: { message: 'old coded error', code: 'CODED' } }, }, ]) @@ -539,8 +535,8 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< seq: 3, data: { id: legacySteering.data.message.id }, }) - expect(suffix.events.filter(event => event.type === 'turn/end').map(event => event.data.step)) - .toEqual([1, 1, 0, 0, 1, 0, 0]) + expect(suffix.events.filter(event => event.type === 'turn/end') + .every(event => !Object.hasOwn(event.data, 'step'))).toBe(true) const flatId = SessionId('pre-react-loop-flat-steering') await ctx.sessionPersistence.create(meta(flatId, WORK)) @@ -633,6 +629,14 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< } as unknown as SessionEvent, message: 'malformed pre-react-loop turn/end', }, + { + id: 'unsupported-intermediate-turn-end-step', + event: { + type: 'turn/end', seq: 0, time: 1, + data: { turn: 1, step: 1, reason: { kind: 'completed' } }, + } as unknown as SessionEvent, + message: 'malformed pre-react-loop turn/end', + }, { id: 'invalid-old-turn-end-aborted', event: { @@ -665,14 +669,6 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< } as unknown as SessionEvent, message: 'malformed pre-react-loop turn/end', }, - { - id: 'invalid-old-turn-end-kind', - event: { - type: 'turn/end', seq: 0, time: 1, - data: { turn: 1, reason: { kind: 'unknown' } }, - } as unknown as SessionEvent, - message: 'malformed pre-react-loop turn/end', - }, ] for (const malformed of malformedLegacy) { const malformedId = SessionId(malformed.id) @@ -798,7 +794,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< const s2 = second.ctx.sessions.create(SessionId('resumed'), { seed: loaded.events, meta: { cwd: WORK } }) await second.ctx.sessions.flush(s2) // let onCreated adopt s2.append('turn/start', { turn: 2 }) - s2.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + s2.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) await second.ctx.sessions.flush(s2) const reloaded = await second.ctx.sessionPersistence.load(SessionId('resumed')) @@ -823,7 +819,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const fiber = await fix.mount(ctx) try { @@ -847,7 +843,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'buffered' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // No explicit flush — dispose must drain. await fiber.dispose() @@ -875,7 +871,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) // Hot-reload: dispose instance 1, mount instance 2 over the same storage while the @@ -887,7 +883,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'again' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) await expect(ctx.sessions.flush(session)).resolves.not.toThrow() const loaded = await ctx.sessionPersistence.load(SessionId('hmr-adopt')) @@ -907,7 +903,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< // Instance 1 flushes turn 1. const backend1 = await fix.mount(ctx) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) // Append turn 2 to the LIVE session, then dispose instance 1 WITHOUT @@ -915,7 +911,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< // backend never buffered it via session/event. await backend1.dispose() session.append('turn/start', { turn: 2 }) - session.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) // Instance 2 adopts the stored prefix (turn 1) and MUST also persist the // live suffix (turn 2) carried in the session's events. @@ -948,7 +944,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< // The live session is still the authority: it appends the REAL step/turn // end. Adoption must truncate the torn tail but NOT synthesize closers. session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('hmr-open')) @@ -1008,7 +1004,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< }, { inject: ['sessions'] })) await expect(ctx.sessions.flush(reuse)).resolves.toBe(true) reuse.append('turn/start', { turn: 1 }) - reuse.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + reuse.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(reuse) const loaded = await ctx.sessionPersistence.load(SessionId('abandoned')) expect(loaded.events.map(e => e.seq)).toEqual([0, 1]) @@ -1029,7 +1025,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< await ctx.sessions.flush(first) // Append a turn but do NOT flush — events sit in the write-behind buffer. first.append('turn/start', { turn: 1 }) - first.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + first.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await firstFiber.dispose() // Disposal is an observe-only notification. Poll storage rather than @@ -1059,7 +1055,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'x' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) // Re-emit session/created for the SAME live session (idempotent initFor). ctx.emit(scopeTarget(session, undefined), 'session/created', session) @@ -1157,7 +1153,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< cont = inner.sessions.create(SessionId('claim'), { seed: [ ...events, { type: 'turn/start', seq: 6, time: 7, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, reason: { kind: 'completed' } } }, ], meta: { cwd: WORK, createdAt: 2000 } }) }, { inject: ['sessions'] })) await ctx.sessions.flush(cont) @@ -1249,7 +1245,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< try { await second.ctx.sessionPersistence.append(SessionId('adopt-append'), [ { type: 'turn/start', seq: 6, time: 7, data: { turn: 2 } }, - { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 7, time: 8, data: { turn: 2, reason: { kind: 'completed' } } }, ]) const loaded = await second.ctx.sessionPersistence.load(SessionId('adopt-append')) expect(loaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7]) @@ -1352,7 +1348,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'q' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('flush-nostate')) expect(loaded.events).toHaveLength(3) @@ -1409,7 +1405,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< // (seq 10) and a reload round-trips identically. await second.ctx.sessionPersistence.append(SessionId('torn'), [ { type: 'turn/start', seq: 10, time: 9, data: { turn: 3 } }, - { type: 'turn/end', seq: 11, time: 10, data: { turn: 3, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 11, time: 10, data: { turn: 3, reason: { kind: 'completed' } } }, ]) const reloaded = await second.ctx.sessionPersistence.load(SessionId('torn')) expect(reloaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) diff --git a/packages/session-persistence/session-persistence/tests/persistence.spec.ts b/packages/session-persistence/session-persistence/tests/persistence.spec.ts index e75050c8c6..15a37277c7 100644 --- a/packages/session-persistence/session-persistence/tests/persistence.spec.ts +++ b/packages/session-persistence/session-persistence/tests/persistence.spec.ts @@ -240,7 +240,7 @@ describe('PersistenceCoordinator eager writes', () => { session.append('turn/start', { turn: 1 }) await vi.waitFor(() => { expect(backend.appendAttempts).toBe(1) }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) appendGate.resolve(true) await vi.waitFor(() => { @@ -273,7 +273,7 @@ describe('PersistenceCoordinator eager writes', () => { const session = ctx.sessions.create(SessionId('eager-flush-retry')) await ctx.sessions.flush(session) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await vi.waitFor(() => { expect(backend.appendAttempts).toBe(1) }) const barriers = [ctx.sessions.flush(session), ctx.sessions.flush(session)] @@ -535,7 +535,7 @@ describe('PersistenceCoordinator observation cancellation', () => { session = inner.sessions.create(id) }, { inject: ['sessions'] })) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Dispose the session so retirement starts; its append is gated, so the // retirement promise stays pending in the coordinator. await sessionFiber.dispose() @@ -678,7 +678,7 @@ describe('PersistenceCoordinator retirement', () => { await ctx.sessions.flush(first) backend.beforeAppend = async () => { await appendGate.promise } first.append('turn/start', { turn: 1 }) - first.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + first.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await vi.waitFor(() => { expect(backend.appendAttempts).toBe(1) }) await firstFiber.dispose() @@ -718,7 +718,7 @@ describe('PersistenceCoordinator retirement', () => { await ctx.sessions.flush(first) backend.beforeAppend = async () => { await appendGate.promise } first.append('turn/start', { turn: 1 }) - first.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + first.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await vi.waitFor(() => { expect(backend.appendAttempts).toBe(1) }) await firstFiber.dispose() const baselineLoads = backend.loadAttempts @@ -782,7 +782,7 @@ describe('PersistenceCoordinator retirement', () => { type: 'turn/end', seq: 1, time: 2, - data: { turn: 1, step: 0, reason: { kind: 'completed' } }, + data: { turn: 1, reason: { kind: 'completed' } }, }]) await vi.waitFor(() => { expect(backend.appendAttempts).toBe(1) }) @@ -825,7 +825,7 @@ describe('PersistenceCoordinator retirement', () => { session = inner.sessions.create(SessionId('retry-retirement')) }, { inject: ['sessions'] })) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await sessionFiber.dispose() await vi.waitFor(() => { @@ -869,7 +869,7 @@ describe('PersistenceCoordinator retirement', () => { session = inner.sessions.create(SessionId('inflight-retirement')) }, { inject: ['sessions'] })) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await sessionFiber.dispose() await vi.waitFor(() => { expect(backend.appendAttempts).toBe(1) @@ -1054,7 +1054,7 @@ describe('SessionPersistence service registration', () => { session = inner.sessions.create(SessionId(`disposed-${index}`)) }, { inject: ['sessions'] })) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessions.flush(session) await sessionFiber.dispose() } diff --git a/packages/session-projection/session-projection-cache/tests/cache.spec.ts b/packages/session-projection/session-projection-cache/tests/cache.spec.ts index 8ccddd6e1e..8474e21594 100644 --- a/packages/session-projection/session-projection-cache/tests/cache.spec.ts +++ b/packages/session-projection/session-projection-cache/tests/cache.spec.ts @@ -93,7 +93,7 @@ const mark = (session: Session, marks: string[]): SessionEvent => session.append('cache-test/mark', { marks }) const endTurn = (session: Session): SessionEvent => - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) /** The stored medium record for one session id (undefined = never written). */ function storedRecord(pool: MemoryMediaPool, id: Session['id']) { @@ -226,7 +226,7 @@ describe('SessionProjectionCache cold read', () => { for (const m of marks) { events.push({ type: 'cache-test/mark', seq: events.length, time: events.length, data: { marks: m } }) } - events.push({ type: 'turn/end', seq: events.length, time: events.length, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }) + events.push({ type: 'turn/end', seq: events.length, time: events.length, data: { turn: 1, reason: { kind: 'completed' } } }) return events } diff --git a/packages/session-projection/session-projection/tests/registry.spec.ts b/packages/session-projection/session-projection/tests/registry.spec.ts index 518722f2c7..bd33914b2d 100644 --- a/packages/session-projection/session-projection/tests/registry.spec.ts +++ b/packages/session-projection/session-projection/tests/registry.spec.ts @@ -225,7 +225,7 @@ describe('SessionProjectionRegistry drive', () => { ctx.sessionProjections.register(countUnit()) const tail: SessionEvent[] = [ { type: 'test/mark', seq: 3, time: 3, data: { marks: ['new'] } }, - { type: 'turn/end', seq: 4, time: 4, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 4, time: 4, data: { turn: 1, reason: { kind: 'completed' } } }, ] // marks row usable (watermark 2, tail starts at 3); count row mismatched — but // a mismatch with baseSeq > 0 cannot silently refold: it throws for a re-read. @@ -262,7 +262,7 @@ describe('SessionProjectionRegistry drive', () => { } const tail: SessionEvent[] = [ { type: 'turn/start', seq: 3, time: 3, data: { turn: 2 } }, - { type: 'turn/end', seq: 4, time: 4, data: { turn: 2, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 4, time: 4, data: { turn: 2, reason: { kind: 'completed' } } }, ] const { snapshot } = ctx.sessionProjections.restore(rows, tail, 3) expect(snapshot.asOfSeq).toBe(4) @@ -302,7 +302,7 @@ describe('SessionProjectionRegistry drive', () => { const floor = ctx.sessionProjections.restoreFloor(rows) expect(floor).toBe(9) // …an intact log serves the anchor event and the checkpoint stands as-is. - const anchor: SessionEvent = { type: 'turn/end', seq: 9, time: 9, data: { turn: 2, step: 0, reason: { kind: 'completed' } } } + const anchor: SessionEvent = { type: 'turn/end', seq: 9, time: 9, data: { turn: 2, reason: { kind: 'completed' } } } expect(ctx.sessionProjections.restore(rows, [anchor], 9).snapshot.values['test/count']).toBe(10) // …while a log crash-repaired down to fewer events returns an empty tail: // the row overreaches the proven end and a tail read cannot fix this key. @@ -310,7 +310,7 @@ describe('SessionProjectionRegistry drive', () => { // The full re-read discards the overreaching row and refolds from init. const events: SessionEvent[] = [ { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }, - { type: 'turn/end', seq: 1, time: 1, data: { turn: 1, step: 0, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 1, time: 1, data: { turn: 1, reason: { kind: 'completed' } } }, ] const { snapshot } = ctx.sessionProjections.restore(rows, events, 0) expect(snapshot.asOfSeq).toBe(1) diff --git a/packages/session-query/session-query-sqlite/tests/sqlite.spec.ts b/packages/session-query/session-query-sqlite/tests/sqlite.spec.ts index 7d74c8b0b0..36be5cb9e5 100644 --- a/packages/session-query/session-query-sqlite/tests/sqlite.spec.ts +++ b/packages/session-query/session-query-sqlite/tests/sqlite.spec.ts @@ -340,7 +340,7 @@ describe('SQLite session search', () => { { type: 'user/message', seq: 2, time: 12, data: createUserMessage({ content: [{ type: 'text', text: 'needle summary' }], source: { kind: 'plugin', plugin: 'test' }, }), surfaceOp: { op: 'replace', start: 0, end: 0 }, sourceEventSeqs: [0] }, - { type: 'turn/end', seq: 3, time: 13, data: { turn: 1, step: 1, reason: { kind: 'error', error: { message: 'needle failure', code: 'UNKNOWN' } } } }, + { type: 'turn/end', seq: 3, time: 13, data: { turn: 1, reason: { kind: 'error', error: { message: 'needle failure', code: 'UNKNOWN' } } } }, ] ctx.sessions.create(SessionId('a'), { seed: events, meta: { cwd: '/a', parentSession: parent, createdAt: 20 } }) ctx.sessions.create(SessionId('b'), { seed: messageEvents('needle peer', 12), meta: { createdAt: 20 } }) diff --git a/packages/session-query/session-query/tests/search-helpers.spec.ts b/packages/session-query/session-query/tests/search-helpers.spec.ts index 03748aeabb..1b139feacd 100644 --- a/packages/session-query/session-query/tests/search-helpers.spec.ts +++ b/packages/session-query/session-query/tests/search-helpers.spec.ts @@ -129,7 +129,7 @@ describe('session-query semantic extraction', () => { [{ kind: 'future-status' } as never, ''], ] for (const [reason, text] of reasons) { - expect(extractSessionEventText({ type: 'turn/end', seq: 0, time: 1, data: { turn: 1, step: 1, reason } })).toBe(text) + expect(extractSessionEventText({ type: 'turn/end', seq: 0, time: 1, data: { turn: 1, reason } })).toBe(text) } const structural: SessionEvent[] = [ { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }, @@ -160,7 +160,7 @@ describe('session-query document and filter helpers', () => { }, }), }, surfaceOp: { op: 'replace', start: 0, end: 0 }, sourceEventSeqs: [0] }, - { type: 'turn/end', seq: 3, time: 13, data: { turn: 1, step: 1, reason: { kind: 'interrupted' } } }, + { type: 'turn/end', seq: 3, time: 13, data: { turn: 1, reason: { kind: 'interrupted' } } }, ] it('classifies every event and omits non-semantic documents', () => { diff --git a/packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts b/packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts index be0470a59d..5390607d23 100644 --- a/packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts +++ b/packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts @@ -39,7 +39,7 @@ describe('all-messages LLM title provider', () => { seeded.append('session/title', { title: 'Inherited fallback', messageSeqs: [inherited.seq], source: { kind: 'fallback' }, }) - seeded.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + seeded.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const ctx = new Context() await ctx.plugin(LlmService) diff --git a/packages/session-title/session-title-llm/tests/llm.spec.ts b/packages/session-title/session-title-llm/tests/llm.spec.ts index 4996bf3586..6572eb89c5 100644 --- a/packages/session-title/session-title-llm/tests/llm.spec.ts +++ b/packages/session-title/session-title-llm/tests/llm.spec.ts @@ -89,7 +89,7 @@ function request(ctx: Context, signal = new AbortController().signal): SessionTi content: [{ type: 'text', text: '第二个问题' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) return { session, messages: [ diff --git a/packages/session-title/session-title/tests/persistence.spec.ts b/packages/session-title/session-title/tests/persistence.spec.ts index 00449374de..7d5428983f 100644 --- a/packages/session-title/session-title/tests/persistence.spec.ts +++ b/packages/session-title/session-title/tests/persistence.spec.ts @@ -30,7 +30,7 @@ async function appendPersistedTitle(ctx: Context, id: ReturnType { }) const inheritedMessage = appendHumanPrompt(parent, 'Inherited title prompt') await settle() - parent.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + parent.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const child = ctx.sessions.fork(parent, undefined, SessionId('title-child')) expect(ctx.sessionTitle.get(child)).toEqual(ctx.sessionTitle.get(parent)) @@ -81,7 +81,7 @@ describe('SessionTitleService provider lifecycle', () => { await settle() appendRoute(child) await settle() - child.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + child.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) expect(firstGenerate).not.toHaveBeenCalled() await disposeFirst() @@ -101,7 +101,7 @@ describe('SessionTitleService provider lifecycle', () => { await settle() appendRoute(child, 'change') await settle() - child.append('turn/end', { turn: 3, step: 0, reason: { kind: 'completed' } }) + child.append('turn/end', { turn: 3, reason: { kind: 'completed' } }) expect(allGenerate).toHaveBeenCalledOnce() expect(ctx.sessionTitle.get(child)).toMatchObject({ @@ -287,7 +287,7 @@ describe('SessionTitleService provider lifecycle', () => { appendRoute(session) await settle() session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) session.append('turn/start', { turn: 2, diff --git a/packages/session-title/session-title/tests/service-contracts.spec.ts b/packages/session-title/session-title/tests/service-contracts.spec.ts index e724f2d768..632b7ec749 100644 --- a/packages/session-title/session-title/tests/service-contracts.spec.ts +++ b/packages/session-title/session-title/tests/service-contracts.spec.ts @@ -168,7 +168,7 @@ describe('SessionTitleService configuration and refresh boundaries', () => { turn: 1, }) const source = appendPrompt(seed, 'Create exactly one fallback title') - seed.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + seed.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const session = ctx.sessions.create(SessionId('fallback-concurrency'), { seed: seed.events }) const results = await Promise.all([ @@ -210,7 +210,7 @@ describe('SessionTitleService configuration and refresh boundaries', () => { const session = startSession(ctx, 'refresh-order') const source = appendPrompt(session, 'Keep the newest explicit refresh') await settle() - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const requests: SessionTitleProviderRequest[] = [] const results: Array>> = [] ctx.sessionTitle.register({ diff --git a/packages/support/acp-snapshot/tests/harness.spec.ts b/packages/support/acp-snapshot/tests/harness.spec.ts index 693797a5aa..a868bd46cf 100644 --- a/packages/support/acp-snapshot/tests/harness.spec.ts +++ b/packages/support/acp-snapshot/tests/harness.spec.ts @@ -573,7 +573,7 @@ describe('runScenario', () => { file: 'project/main/session.jsonl', lines: [ { type: 'session', version: 0, id: '{{SID}}', createdAt: 1, delegationDepth: 0 }, - { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'aborted' } } }, + { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'aborted' } } }, ], }], }) @@ -641,7 +641,7 @@ describe('runScenario', () => { file: 'project/main/session.jsonl', lines: [ { type: 'session', version: 0, id: '{{SID}}', createdAt: 1, delegationDepth: 0 }, - { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'aborted' } } }, + { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'aborted' } } }, { type: 'session/title', seq: 2, time: 3, data: { title: 'Late title' } }, ], }], @@ -721,7 +721,7 @@ describe('runScenario', () => { lines: [ { type: 'session', version: 0, id: '{{SID}}', createdAt: 1, delegationDepth: 0 }, { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }, - { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, step: 0, reason: { kind: 'stop' } } }, + { type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'stop' } } }, ], }], }) @@ -919,7 +919,7 @@ describe('runScenario', () => { lines: [ { type: 'session', version: 0, id: '{{SID}}', createdAt: 1, delegationDepth: 0 }, { type: 'session/title', seq: 1, time: 1, data: { title: 'Early title' } }, - { type: 'turn/end', seq: 2, time: 2, data: { turn: 1, step: 0, reason: { kind: 'aborted' } } }, + { type: 'turn/end', seq: 2, time: 2, data: { turn: 1, reason: { kind: 'aborted' } } }, ], }], }) diff --git a/packages/support/llm-replay/tests/llm-replay.spec.ts b/packages/support/llm-replay/tests/llm-replay.spec.ts index 2f65f53e52..0b7d87ad13 100644 --- a/packages/support/llm-replay/tests/llm-replay.spec.ts +++ b/packages/support/llm-replay/tests/llm-replay.spec.ts @@ -160,7 +160,7 @@ describe('deriveReplayScript', () => { const events: SessionEvent[] = [ { type: 'turn/start', seq: seq++, time: 0, data: { turn: 1 } }, ...TEXT_CHUNKS.map(c => chunkEvent(seq++, 1, 1, c)), - { type: 'turn/end', seq: seq++, time: 0, data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: seq++, time: 0, data: { turn: 1, reason: { kind: 'completed' } } }, ] expect(deriveReplayScript(events)).toEqual([{ kind: 'chunks', chunks: TEXT_CHUNKS }]) }) @@ -183,7 +183,7 @@ describe('deriveReplayScript', () => { const events: SessionEvent[] = [ chunkEvent(1, 1, 1, { type: 'block-start', index: 0, blockType: 'text' }), chunkEvent(2, 1, 1, { type: 'text-delta', index: 0, text: 'par' }), - { type: 'turn/end', seq: 3, time: 0, data: { turn: 1, step: 1, reason: { kind: 'error', error: { message: 'x', code: 'UNKNOWN' } } } }, + { type: 'turn/end', seq: 3, time: 0, data: { turn: 1, reason: { kind: 'error', error: { message: 'x', code: 'UNKNOWN' } } } }, ] expect(() => deriveReplayScript(events)).toThrow(/without a finish chunk.*replay\.override\.json/s) }) diff --git a/packages/telemetry/session-telemetry-otel/tests/otel.spec.ts b/packages/telemetry/session-telemetry-otel/tests/otel.spec.ts index 8313a486f2..56502766d1 100644 --- a/packages/telemetry/session-telemetry-otel/tests/otel.spec.ts +++ b/packages/telemetry/session-telemetry-otel/tests/otel.spec.ts @@ -113,7 +113,7 @@ describe('TelemetryOtel wire', () => { const { ctx, fiber } = await boot(url) const session = ctx.sessions.create(SessionId('wire'), { meta: { cwd: '/tmp/w' } }) session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } }) + session.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } }) await fiber.dispose() expect(captures.length).toBeGreaterThan(0) diff --git a/packages/telemetry/session-telemetry/tests/telemetry.spec.ts b/packages/telemetry/session-telemetry/tests/telemetry.spec.ts index 6fe4bbf207..02ca434c0d 100644 --- a/packages/telemetry/session-telemetry/tests/telemetry.spec.ts +++ b/packages/telemetry/session-telemetry/tests/telemetry.spec.ts @@ -126,7 +126,7 @@ describe('TelemetryCoordinator capture', () => { }), }, { surfaceOp: 'append' }) session.append('telemetry-test/opaque', { payload: { nested: [] } }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } }) + session.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } }) const severities = backend.ledger().map(r => [r.attributes['event.type'], r.severity]) expect(severities).toEqual([ ['turn/start', 'info'], @@ -180,7 +180,7 @@ describe('TelemetryCoordinator adoption', () => { apply: (inner: Context) => void new TelemetryCoordinator(inner, backend), }) const child = ctx.sessions.prepare(SessionId('seeded'), { seed: [...parent.events], meta: {} }) - child.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + child.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) ctx.sessions.enter(child) ctx.sessions.announce(child) @@ -234,7 +234,7 @@ describe('TelemetryCoordinator adoption', () => { inject: ['sessions'], apply: (inner: Context) => void new TelemetryCoordinator(inner, backend), }) - child.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + child.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const record = backend.ledger().find(r => r.attributes['session.id'] === 'stitch-child')! expect(record.attributes['session.parent_id']).toBe('stitch-parent') expect(record.attributes['session.seed_length']).toBe(2) @@ -272,7 +272,7 @@ describe('TelemetryCoordinator adoption', () => { await fiber.dispose() // The reload window: appends while no telemetry listener is registered. session.append('assistant/chunk', { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'mid-step continuation' } }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const second = new FakeBackend() await ctx.plugin({ @@ -292,7 +292,7 @@ describe('TelemetryCoordinator adoption', () => { const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {}) const session = liveSession(ctx, 'partial') appendTurn(session) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // The backend rejects exactly the middle historical event: fail-closed // must withhold THAT record only — an adoption replay that dies on the // first contained failure would silently skip the rest of the log while @@ -415,7 +415,7 @@ describe('TelemetryCoordinator lifecycle and containment', () => { expect(() => session.append('turn/start', { turn: 1 })).not.toThrow() expect(warn).toHaveBeenCalled() backend.emitError = undefined - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) expect(backend.ledger().map(r => r.attributes['event.type'])).toEqual(['turn/end']) }) diff --git a/packages/todo/tool-todo/tests/projection.spec.ts b/packages/todo/tool-todo/tests/projection.spec.ts index c605ed0878..d751d2a949 100644 --- a/packages/todo/tool-todo/tests/projection.spec.ts +++ b/packages/todo/tool-todo/tests/projection.spec.ts @@ -97,7 +97,7 @@ describe('todos projection provider', () => { seedMessage(session) const list: TodoItem[] = [{ content: 'done', status: 'completed' }] session.append('todo/write', { todos: list }) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) expect((await bench.tailProjections())?.values.todos).toEqual(list) session.append('turn/start', { turn: 1 }) const cleared = await bench.tailProjections() diff --git a/packages/ui/permission/tests/permission.spec.ts b/packages/ui/permission/tests/permission.spec.ts index 51f4619aa4..d161157389 100644 --- a/packages/ui/permission/tests/permission.spec.ts +++ b/packages/ui/permission/tests/permission.spec.ts @@ -221,7 +221,7 @@ describe('new-session default', () => { }) const legacy = freshSession('legacy-source') legacy.append('turn/start', { turn: 1 }) - legacy.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + legacy.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const resumed = ctx.sessions.create(SessionId('legacy-resumed'), { seed: legacy.events }) expect(ctx.permission.current(resumed.events)).toBe('workspace-write') expect(resumed.events.slice(-3).map(event => event.type)).toEqual([ diff --git a/packages/ui/tui/tests/tui.snapshot.ts b/packages/ui/tui/tests/tui.snapshot.ts index 864a33d01d..d6d8884e52 100644 --- a/packages/ui/tui/tests/tui.snapshot.ts +++ b/packages/ui/tui/tests/tui.snapshot.ts @@ -407,7 +407,7 @@ describe('TUI terminal-state snapshots', () => { ], }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) session.append('turn/start', { turn: 2, }) @@ -454,7 +454,7 @@ describe('TUI terminal-state snapshots', () => { }, }), }, { surfaceOp: 'append' }) - harness.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + harness.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await checkpoint('retry-recovered', harness.terminal, { includeScrollback: true }) await disposeSnapshot(harness) }) @@ -473,7 +473,7 @@ describe('TUI terminal-state snapshots', () => { delayMs: 1_000, failure: { message: 'temporary transport failure', code: 'TRANSPORT' }, }) - harness.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'aborted', reason: { kind: 'user' } }, + harness.session.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } }, }) }) await checkpoint('retry-cancelled', harness.terminal, { includeScrollback: true }) @@ -489,7 +489,7 @@ describe('TUI terminal-state snapshots', () => { step: 3, chunk: { type: 'text-delta', index: 0, text: 'discarded terminal partial output' }, }) - harness.session.append('turn/end', { turn: 1, step: 3, reason: { kind: 'error', error: { message: 'provider still unavailable', code: 'SERVER', status: 503 }, + harness.session.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'provider still unavailable', code: 'SERVER', status: 503 }, }, }) }) @@ -645,7 +645,7 @@ describe('TUI terminal-state snapshots', () => { harness.session.append('step/start', { turn: 1, step: 2 }) appendAssistant(harness.session, [{ type: 'text', text: 'The renderer is sound; no refactor needed.' }], undefined, { turn: 1, step: 2 }) harness.session.append('step/end', { turn: 1, step: 2 }) - harness.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + harness.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }) // collapsed -> expanded -> hidden: one Assistant header, no tool card. await renderAfter(harness, () => { harness.terminal.send('\x0f') }) @@ -672,7 +672,7 @@ describe('TUI terminal-state snapshots', () => { ]) appendToolResult(harness.session, 'details-1', [{ type: 'text', text: 'all tests pass' }]) harness.session.append('step/end', { turn: 1, step: 1 }) - harness.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + harness.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }) await renderAfter(harness, () => { harness.terminal.send('/details hidden reasoning off') @@ -733,7 +733,7 @@ describe('TUI terminal-state snapshots', () => { todos: [{ content: `Unsafe todo ${CONTROL_PROBE}`, status: 'in_progress' }], }) session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'error', error: { message: `Unsafe turn error ${CONTROL_PROBE}`, code: 'UNKNOWN' } }, + session.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: `Unsafe turn error ${CONTROL_PROBE}`, code: 'UNKNOWN' } }, }) }, }, { columns: 100, rows: 34 }) @@ -905,18 +905,18 @@ describe('TUI terminal-state snapshots', () => { harness.terminal.send('\r') agentEvents(harness.ctx, harness.agent).emit('agent/error', 1, 1, new Error('provider stream failed after partial output')) harness.session.append('step/end', { turn: 1, step: 1 }) - harness.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'error', error: { message: 'provider stream failed after partial output', code: 'UNKNOWN' } }, + harness.session.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'provider stream failed after partial output', code: 'UNKNOWN' } }, }) harness.session.append('turn/start', { turn: 2 }) - harness.session.append('turn/end', { turn: 2, step: 0, reason: { kind: 'interrupted' }, + harness.session.append('turn/end', { turn: 2, reason: { kind: 'interrupted' }, }) harness.session.append('turn/start', { turn: 3 }) - harness.session.append('turn/end', { turn: 3, step: 0, reason: { kind: 'aborted', reason: { kind: 'disposed' } }, + harness.session.append('turn/end', { turn: 3, reason: { kind: 'aborted', reason: { kind: 'disposed' } }, }) harness.session.append('turn/start', { turn: 4 }) // A merge-extensible turn-end kind unknown to the TUI still surfaces its // name so the agent never stops without a visible reason. - harness.session.append('turn/end', { turn: 4, step: 0, reason: { kind: 'plugin-policy' } as never }) + harness.session.append('turn/end', { turn: 4, reason: { kind: 'plugin-policy' } as never }) }) await checkpoint('errors-and-help', harness.terminal, { includeScrollback: true }) @@ -967,7 +967,7 @@ describe('TUI terminal-state snapshots', () => { }), }, surfaceOp: 'append' }, { type: 'step/end', seq: 5, time: Date.parse(`${day}T00:00:06Z`), data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 6, time: Date.parse(`${day}T00:00:07Z`), data: { turn: 1, step: 1, reason: { kind: 'completed' } } }, + { type: 'turn/end', seq: 6, time: Date.parse(`${day}T00:00:07Z`), data: { turn: 1, reason: { kind: 'completed' } } }, { type: 'session/title', seq: 7, time: Date.parse(`${day}T00:00:08Z`), data: { title, messageSeqs: [1], source: { kind: 'fallback' } } }, ], }) diff --git a/packages/ui/tui/tests/tui.spec.ts b/packages/ui/tui/tests/tui.spec.ts index 6b6b6fb2e3..1b4c2c6a5c 100644 --- a/packages/ui/tui/tests/tui.spec.ts +++ b/packages/ui/tui/tests/tui.spec.ts @@ -281,7 +281,7 @@ describe('goodbye message and /resume', () => { }), }, surfaceOp: 'append' }, { type: 'step/end', seq: 5, time: time + 5, data: { turn: 1, step: 1 } }, - { type: 'turn/end', seq: 6, time: time + 6, data: { turn: 1, step: 1, reason } }, + { type: 'turn/end', seq: 6, time: time + 6, data: { turn: 1, reason } }, { type: 'session/title', seq: 7, time: time + 7, data: { title, messageSeqs: [1], source: { kind: 'fallback' } } }, ] /** Derive the selector's batch title read from a fake per-session readSession. */ @@ -1681,10 +1681,10 @@ describe('pi-tui chat lifecycle and transcript', () => { }), { surfaceOp: 'append' }) appendAssistant(result.session, []) result.session.append('step/end', { turn: 1, step: 1 }) - result.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'aborted', reason: { kind: 'user' } }, + result.session.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } }, }) result.session.append('turn/start', { turn: 2 }) - result.session.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } }) + result.session.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) result.session.append('turn/start', { turn: 3 }) result.session.append('step/start', { turn: 3, step: 1 }) result.session.append('assistant/chunk', { @@ -2172,7 +2172,7 @@ describe('pi-tui chat lifecycle and transcript', () => { session.append('assistant/chunk', { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'done' } }) clock += 1_000 session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }, }) result.agent.status = 'running' @@ -4384,25 +4384,25 @@ describe('pi-tui chat lifecycle and transcript', () => { agentEvents(events.ctx, unrelatedAgent).emit('agent/disposed') agentEvents(events.ctx, events.agent).emit('agent/error', 1, 1, new Error('live failure')) events.session.append('step/end', { turn: 1, step: 1 }) - events.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'error', error: { message: 'live failure', code: 'UNKNOWN' } } }) + events.session.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'live failure', code: 'UNKNOWN' } } }) events.session.append('turn/start', { turn: 2 }) - events.session.append('turn/end', { turn: 2, step: 0, reason: { kind: 'error', error: { message: 'durable failure', code: 'UNKNOWN' } } }) + events.session.append('turn/end', { turn: 2, reason: { kind: 'error', error: { message: 'durable failure', code: 'UNKNOWN' } } }) events.session.append('turn/start', { turn: 3 }) - events.session.append('turn/end', { turn: 3, step: 0, reason: { kind: 'aborted', reason: { kind: 'user' } }, + events.session.append('turn/end', { turn: 3, reason: { kind: 'aborted', reason: { kind: 'user' } }, }) events.session.append('turn/start', { turn: 4 }) - events.session.append('turn/end', { turn: 4, step: 0, reason: { kind: 'max-tokens' } }) + events.session.append('turn/end', { turn: 4, reason: { kind: 'max-tokens' } }) events.session.append('turn/start', { turn: 5 }) - events.session.append('turn/end', { turn: 5, step: 0, reason: { kind: 'interrupted' } }) + events.session.append('turn/end', { turn: 5, reason: { kind: 'interrupted' } }) events.session.append('turn/start', { turn: 6 }) - events.session.append('turn/end', { turn: 6, step: 0, reason: { kind: 'error', error: { message: 'structured provider failure', code: 'SERVER' } }, + events.session.append('turn/end', { turn: 6, reason: { kind: 'error', error: { message: 'structured provider failure', code: 'SERVER' } }, }) events.session.append('turn/start', { turn: 8 }) - events.session.append('turn/end', { turn: 8, step: 0, reason: { kind: 'aborted', reason: { kind: 'disposed' } }, + events.session.append('turn/end', { turn: 8, reason: { kind: 'aborted', reason: { kind: 'disposed' } }, }) events.session.append('turn/start', { turn: 9 }) // Merge-extensible reason kind unknown to the TUI still names the stop. - events.session.append('turn/end', { turn: 9, step: 0, reason: { kind: 'plugin-policy' } as never }) + events.session.append('turn/end', { turn: 9, reason: { kind: 'plugin-policy' } as never }) agentEvents(events.ctx, events.agent).emit('agent/disposed') await tick() expect(events.terminal.output).toContain('live failure') @@ -5612,7 +5612,7 @@ describe('tool cards and surface replay', () => { session.append('step/start', { turn: 1, step: 2 }) appendAssistant(session, [{ type: 'text', text: 'second step text' }], undefined, { turn: 1, step: 2 }) session.append('step/end', { turn: 1, step: 2 }) - session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) } it('folds a turn to one Assistant header in hidden mode and restores headers on cycle', async () => { @@ -5664,14 +5664,14 @@ describe('tool cards and surface replay', () => { result.session.append('step/start', { turn: 1, step: 2 }) appendAssistant(result.session, [{ type: 'text', text: 'late turn-one text' }], undefined, { turn: 1, step: 2 }) result.session.append('step/end', { turn: 1, step: 2 }) - result.session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } }) + result.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Turn 2 keeps its own header. result.session.append('turn/start', { turn: 2 }) appendUser(result.session, 'next turn') result.session.append('step/start', { turn: 2, step: 1 }) appendAssistant(result.session, [{ type: 'text', text: 'turn-two text' }], undefined, { turn: 2, step: 1 }) result.session.append('step/end', { turn: 2, step: 1 }) - result.session.append('turn/end', { turn: 2, step: 1, reason: { kind: 'completed' } }) + result.session.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) await tick() result.terminal.send('\x0f') diff --git a/packages/ui/user-approval/tests/invariant.spec.ts b/packages/ui/user-approval/tests/invariant.spec.ts index 7266c76927..6be086df4a 100644 --- a/packages/ui/user-approval/tests/invariant.spec.ts +++ b/packages/ui/user-approval/tests/invariant.spec.ts @@ -38,7 +38,7 @@ describe('approval invariants', () => { await ctx.plugin(InvariantService) await ctx.plugin(ApprovalInvariant) expect(() => session.append('approval/decided', { id, outcome: 'cancelled' })).not.toThrow() - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }) it('adopts a bare session first observed through publication', async () => { @@ -77,7 +77,7 @@ describe('approval invariants', () => { await ctx.plugin(SessionStore) const session = ctx.sessions.create() startTurn(session) - session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) session.append('approval/asked', { id: ApprovalRequestId('ask-replay'), toolName: 'bash', })