@zhushanwen/subagent-engine-sdk 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/{chunk-7I4XGL5J.js → chunk-BBWUC2EE.js} +2 -2
  2. package/dist/{chunk-3K2P2CM2.js → chunk-CNQTJJF5.js} +1 -1
  3. package/dist/{chunk-DSQ7JQKM.js → chunk-GSW3DMN6.js} +5 -5
  4. package/dist/{chunk-BBVZK5DL.js → chunk-M3SZTDNQ.js} +20 -20
  5. package/dist/{chunk-HYES77BR.js → chunk-MSAJXIP4.js} +2 -2
  6. package/dist/{chunk-FJT23P7T.js → chunk-QOEXD2FT.js} +1 -1
  7. package/dist/{chunk-LKPVN3N6.js → chunk-W3BKI2BP.js} +3 -3
  8. package/dist/{chunk-WS4WAZVR.js → chunk-WMDMXP2F.js} +2 -2
  9. package/dist/{chunk-GT6YLLN4.js → chunk-X24IVQKJ.js} +5 -5
  10. package/dist/{chunk-365AUV6N.js → chunk-YE472JOO.js} +2 -2
  11. package/dist/{contract-types-C71Sqq8d.d.cts → contract-types-Dsvy79r3.d.cts} +9 -10
  12. package/dist/{contract-types-C71Sqq8d.d.ts → contract-types-Dsvy79r3.d.ts} +9 -10
  13. package/dist/data-dir.cjs +6 -6
  14. package/dist/data-dir.d.cts +4 -4
  15. package/dist/data-dir.d.ts +4 -4
  16. package/dist/data-dir.js +3 -3
  17. package/dist/env.cjs +20 -20
  18. package/dist/env.d.cts +12 -5
  19. package/dist/env.d.ts +12 -5
  20. package/dist/env.js +1 -1
  21. package/dist/{error-codes-D5YlWaBx.d.ts → error-codes-DMGeUUUm.d.ts} +12 -12
  22. package/dist/{error-codes-CiOO3PMg.d.cts → error-codes-pT7RYTqQ.d.cts} +12 -12
  23. package/dist/index.cjs +42 -42
  24. package/dist/index.d.cts +3 -3
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +12 -12
  27. package/dist/journal-io.d.cts +1 -1
  28. package/dist/journal-io.d.ts +1 -1
  29. package/dist/journal-replay.d.cts +1 -1
  30. package/dist/journal-replay.d.ts +1 -1
  31. package/dist/kill-chain.d.cts +2 -2
  32. package/dist/kill-chain.d.ts +2 -2
  33. package/dist/kill-chain.js +2 -2
  34. package/dist/logs/stderr-rotation.cjs +2 -2
  35. package/dist/logs/stderr-rotation.d.cts +1 -1
  36. package/dist/logs/stderr-rotation.d.ts +1 -1
  37. package/dist/logs/stderr-rotation.js +1 -1
  38. package/dist/nesting-guard.cjs +2 -2
  39. package/dist/nesting-guard.d.cts +2 -2
  40. package/dist/nesting-guard.d.ts +2 -2
  41. package/dist/nesting-guard.js +1 -1
  42. package/dist/node-executor.cjs +2 -2
  43. package/dist/node-executor.d.cts +5 -5
  44. package/dist/node-executor.d.ts +5 -5
  45. package/dist/node-executor.js +2 -2
  46. package/dist/port-contract.d.cts +1 -1
  47. package/dist/port-contract.d.ts +1 -1
  48. package/dist/protocol/index.cjs +2 -2
  49. package/dist/protocol/index.d.cts +5 -5
  50. package/dist/protocol/index.d.ts +5 -5
  51. package/dist/protocol/index.js +1 -1
  52. package/dist/relay-env.cjs +5 -5
  53. package/dist/relay-env.d.cts +5 -5
  54. package/dist/relay-env.d.ts +5 -5
  55. package/dist/relay-env.js +1 -1
  56. package/dist/spawn.cjs +1 -1
  57. package/dist/spawn.d.cts +2 -2
  58. package/dist/spawn.d.ts +2 -2
  59. package/dist/spawn.js +1 -1
  60. package/dist/ui-channels.cjs +2 -2
  61. package/dist/ui-channels.js +1 -1
  62. package/package.json +2 -4
  63. package/src/best-effort.ts +0 -37
  64. package/src/cli-entry.ts +0 -82
  65. package/src/data-dir.ts +0 -89
  66. package/src/env.ts +0 -265
  67. package/src/error-message.ts +0 -22
  68. package/src/index.ts +0 -63
  69. package/src/journal-io.ts +0 -82
  70. package/src/journal-replay.ts +0 -436
  71. package/src/kill-chain.ts +0 -265
  72. package/src/logger.ts +0 -105
  73. package/src/logs/stderr-rotation.ts +0 -166
  74. package/src/nesting-guard.ts +0 -140
  75. package/src/node-executor.ts +0 -272
  76. package/src/paths.ts +0 -48
  77. package/src/port-contract.ts +0 -150
  78. package/src/protocol/contract-types.ts +0 -379
  79. package/src/protocol/engine-protocol.ts +0 -91
  80. package/src/protocol/error-codes.ts +0 -180
  81. package/src/protocol/frames.ts +0 -143
  82. package/src/protocol/index.ts +0 -12
  83. package/src/protocol/methods.ts +0 -237
  84. package/src/protocol/reverse-channels.ts +0 -123
  85. package/src/protocol/schema.ts +0 -185
  86. package/src/relay-env.ts +0 -60
  87. package/src/schema-emulation.ts +0 -192
  88. package/src/spawn.ts +0 -246
  89. package/src/ui-channels.ts +0 -219
  90. package/src/ui-types.ts +0 -101
@@ -1,436 +0,0 @@
1
- // src/journal-replay.ts
2
- //
3
- // journal → SessionView 的纯投影(引擎侧原语,自 core
4
- // execution/engine/common/journal-replay.ts + execution-record.ts reducer 核心 +
5
- // common/session-view-projection.ts 迁入 @zhushanwen/subagent-engine-sdk)。
6
- // 迁移处置(impl-plan §2.1 journal-replay 行):**纯投影部分(entry → record 的
7
- // reducer)下沉 SDK,journal I/O 与②级降级链留 core**——
8
- // - SDK 版 = eventsToSessionView(事件流 → SessionView,live/replay 共用 reducer
9
- // 语义)+ reducer 本体(updateFromEvent 及其私有 handler)+ Turn → ReplayedTurn
10
- // 投影 / usage 聚合(原 core session-view-projection.ts 同名函数逐字等价,
11
- // R6 收口后本模块为唯一实现,双活副本已删);
12
- // - 留 core = replayJournal(journal 文件 I/O)+ replayJournalToSessionView(read
13
- // 第②级降级编排)——core 侧引用切换已完成(core 依赖 SDK 方向合法)。
14
- //
15
- // record 形态:SDK 侧 reducer 操作 ReplayRecordView(turns/turnCount/totalTokens/
16
- // lastError 四字段视图)——core ExecutionRecord 留 core(类型闭包表裁决),其结构
17
- // 满足本视图(W2 双向可赋值断言验证)。重放场景不需要 identity 字段(agent/model/
18
- // task 等只服务投影与持久化,reducer 不触碰),不搬 createRecord 全量 identity。
19
- //
20
- // 设计权威源:docs/architecture/subagent-engine-abstraction.md D6 + §3.3.6「重放等价性」
21
- // ——journal 重放与 live 通路共用同一 reducer(updateFromEvent 范式),不引入第二套
22
- // 解析器;conformance C5 断言重放 turns 与 live 一致。
23
- //
24
- // CJS 多 entry 内联副本的实例分裂影响 = runningToolIndex WeakMap(按 record 实例
25
- // 隔离)各自为政,无跨实例语义。
26
-
27
- import type {
28
- AgentEvent,
29
- AgentUsage,
30
- AgentUsageTotal,
31
- InternalToolCall,
32
- ReplayedTurn,
33
- SessionView,
34
- ToolCall,
35
- Turn,
36
- EngineHandleData,
37
- } from "./protocol/contract-types.ts";
38
-
39
- /**
40
- * reducer 的 record 视图(core ExecutionRecord 的结构子集:reducer 只触碰这四个字段)。
41
- * core ExecutionRecord 满足本视图(W2 断言挂靠点);SDK 内自持 createReplayRecord 产出。
42
- */
43
- export interface ReplayRecordView {
44
- turns: Turn[];
45
- turnCount: number;
46
- totalTokens: number;
47
- lastError: string | undefined;
48
- }
49
-
50
- // ============================================================
51
- // usage 累积(core execution-record.ts 私有 helper 逐字等价)
52
- // ============================================================
53
-
54
- /** usage 单字段求和(undefined 视为 0——与旧 `(a ?? 0) + (b ?? 0)` 内联式逐字等价)。 */
55
- function sumUsageField(a: number | undefined, b: number | undefined): number {
56
- return (a ?? 0) + (b ?? 0);
57
- }
58
-
59
- /** prev 为空时 next 的规范化拷贝(cost 保留原值,可能 undefined——与旧首条分支逐字等价)。 */
60
- function usageFromNext(next: AgentUsage): AgentUsage {
61
- return {
62
- input: next.input ?? 0,
63
- output: next.output ?? 0,
64
- cacheRead: next.cacheRead ?? 0,
65
- cacheWrite: next.cacheWrite ?? 0,
66
- cost: next.cost,
67
- };
68
- }
69
-
70
- /**
71
- * 累加两个 AgentUsage(field-wise)。prev 为空时返回 next 的拷贝。
72
- * 供 message_end 把 usage 增量并入 turn.usageDelta。
73
- */
74
- function addUsage(prev: AgentUsage | undefined, next: AgentUsage): AgentUsage {
75
- if (prev === undefined) return usageFromNext(next);
76
- return {
77
- input: sumUsageField(prev.input, next.input),
78
- output: sumUsageField(prev.output, next.output),
79
- cacheRead: sumUsageField(prev.cacheRead, next.cacheRead),
80
- cacheWrite: sumUsageField(prev.cacheWrite, next.cacheWrite),
81
- cost: sumUsageField(prev.cost, next.cost),
82
- };
83
- }
84
-
85
- // ============================================================
86
- // 创建(重放场景的 record 构造)
87
- // ============================================================
88
-
89
- /** 创建一个空 turn(text/thinking 空,无 toolCalls,未闭合)。 */
90
- function emptyTurn(): Turn {
91
- return { text: "", thinking: "", toolCalls: [], usageDelta: undefined, closed: false };
92
- }
93
-
94
- /** 重放场景的 record 构造(对齐 core eventsToSessionView 内 createRecord 后的 reducer 视图)。 */
95
- export function createReplayRecord(): ReplayRecordView {
96
- return {
97
- // turns[] 初始化为 [空 turn]——第一个 turn 从创建即存在,
98
- // updateFromEvent 直接往 turns[last] 累积,无需「无 turn」分支判断。
99
- turns: [emptyTurn()],
100
- turnCount: 0,
101
- totalTokens: 0,
102
- lastError: undefined,
103
- };
104
- }
105
-
106
- // ============================================================
107
- // 事件更新(唯一更新点;core updateFromEvent 私有结构逐字等价)
108
- // ============================================================
109
-
110
- /**
111
- * 取当前正在进行(未 closed)的 turn;若全部 closed 则开新 turn。
112
- * 保证调用后返回的 turn 一定 closed===false,可安全累积内容。
113
- */
114
- function currentTurn(record: ReplayRecordView): Turn {
115
- const last = record.turns[record.turns.length - 1];
116
- if (last !== undefined && !last.closed) return last;
117
- const fresh = emptyTurn();
118
- record.turns.push(fresh);
119
- return fresh;
120
- }
121
-
122
- /**
123
- * 在 record.turns[] 范围内倒序找最后一个同名且仍 running 的 toolCall。
124
- *
125
- * 扫描所有 turn(非仅当前 turn)——SDK 在 turn_end 后仍可能补发滞后的 tool_end,
126
- * 仅扫当前 turn 会漏配对、误 push 幽灵 ToolCall。跨 turn 扫描兜底滞后事件。
127
- *
128
- * 返回 [turn, index];未找到返回 undefined。
129
- */
130
- function findRunningToolCall(
131
- record: ReplayRecordView,
132
- toolName: string,
133
- ): readonly [Turn, number] | undefined {
134
- for (let t = record.turns.length - 1; t >= 0; t--) {
135
- const turn = record.turns[t];
136
- if (turn === undefined) continue;
137
- for (let i = turn.toolCalls.length - 1; i >= 0; i--) {
138
- const tc = turn.toolCalls[i];
139
- if (tc?._status === "running" && tc.toolName === toolName) {
140
- return [turn, i] as const;
141
- }
142
- }
143
- }
144
- return undefined;
145
- }
146
-
147
- /**
148
- * [perf] running toolCall 倒序索引:tool_start push 位置入索引,tool_end 弹尾定位
149
- *(尾部 = 最后 push 的同名项,与 findRunningToolCall 倒序全扫的语义等价),把每次
150
- * tool_end 的 O(所有 turns × toolCalls) 扫描降为 O(1)。WeakMap 按 record 实例隔离
151
- *(createRecord 新实例从空索引开始,不影响旧实例)。
152
- * 索引 miss(重建 record 的历史 running toolCall / 外部注入工具无 tool_start)
153
- * 回退 findRunningToolCall 全扫兜底——正确性不依赖索引完整性。
154
- */
155
- const runningToolIndex = new WeakMap<ReplayRecordView, Map<string, Array<{ turn: Turn; idx: number }>>>();
156
-
157
- function indexToolStart(record: ReplayRecordView, turn: Turn, toolName: string): void {
158
- let byName = runningToolIndex.get(record);
159
- if (byName === undefined) {
160
- byName = new Map();
161
- runningToolIndex.set(record, byName);
162
- }
163
- const arr = byName.get(toolName);
164
- if (arr === undefined) {
165
- byName.set(toolName, [{ turn, idx: turn.toolCalls.length - 1 }]);
166
- } else {
167
- arr.push({ turn, idx: turn.toolCalls.length - 1 });
168
- }
169
- }
170
-
171
- // ── 各事件处理器(updateFromEvent 按 case 分发,每个处理器单一职责)──
172
-
173
- /** text_delta:流式累积进当前 turn 的 text(完整内容,非切片)。 */
174
- function applyTextDelta(
175
- record: ReplayRecordView,
176
- event: Extract<AgentEvent, { type: "text_delta" }>,
177
- ): void {
178
- currentTurn(record).text += event.delta;
179
- }
180
-
181
- /** thinking_delta:流式累积进当前 turn 的 thinking(完整内容)。 */
182
- function applyThinkingDelta(
183
- record: ReplayRecordView,
184
- event: Extract<AgentEvent, { type: "thinking_delta" }>,
185
- ): void {
186
- currentTurn(record).thinking += event.delta;
187
- }
188
-
189
- /** tool_start:push 一个 running 的 InternalToolCall(带 startedTs)+ 弹尾索引入册。 */
190
- function applyToolStart(
191
- record: ReplayRecordView,
192
- event: Extract<AgentEvent, { type: "tool_start" }>,
193
- ): void {
194
- const tc: InternalToolCall = {
195
- toolName: event.toolName,
196
- args: event.args,
197
- result: undefined,
198
- isError: false,
199
- _status: "running",
200
- startedTs: Date.now(),
201
- };
202
- const turn = currentTurn(record);
203
- turn.toolCalls.push(tc);
204
- indexToolStart(record, turn, event.toolName);
205
- }
206
-
207
- /**
208
- * tool_end 定位:索引弹尾 O(1) 命中 running 同名 toolCall;索引 miss(无记录 / 槽位
209
- * 已非 running)回退 findRunningToolCall 跨 turn 倒序全扫兜底。
210
- * 返回 [turn, index];两路都 miss 返回 undefined。
211
- */
212
- function matchRunningToolCall(
213
- record: ReplayRecordView,
214
- toolName: string,
215
- ): readonly [Turn, number] | undefined {
216
- const byName = runningToolIndex.get(record);
217
- const arr = byName?.get(toolName);
218
- if (arr !== undefined && arr.length > 0) {
219
- const item = arr[arr.length - 1];
220
- arr.pop();
221
- const tc = item.turn.toolCalls[item.idx];
222
- if (tc !== undefined && tc._status === "running") {
223
- return [item.turn, item.idx] as const;
224
- }
225
- }
226
- // 兜底:重建 record 的历史 running toolCall(索引未覆盖)、索引项被外部路径
227
- // 置非 running 等场景——保持与旧实现一致的跨 turn 倒序全扫。
228
- return findRunningToolCall(record, toolName);
229
- }
230
-
231
- /**
232
- * tool_end:命中则回填 result/isError/_status;未命中(SDK 发了 tool_end 但无对应
233
- * tool_start,如外部注入的工具)直接 push 一个已完成的 InternalToolCall,避免数据丢失。
234
- */
235
- function applyToolEnd(
236
- record: ReplayRecordView,
237
- event: Extract<AgentEvent, { type: "tool_end" }>,
238
- ): void {
239
- const matched = matchRunningToolCall(record, event.toolName);
240
- if (matched !== undefined) {
241
- const [turn, i] = matched;
242
- const tc = turn.toolCalls[i]!;
243
- tc.args = event.args ?? tc.args;
244
- tc.result = event.result;
245
- tc.isError = event.isError ?? false;
246
- tc._status = event.isError ? "failed" : "done";
247
- return;
248
- }
249
- currentTurn(record).toolCalls.push({
250
- toolName: event.toolName,
251
- args: event.args,
252
- result: event.result,
253
- isError: event.isError ?? false,
254
- _status: event.isError ? "failed" : "done",
255
- startedTs: Date.now(),
256
- });
257
- }
258
-
259
- /**
260
- * turn_end:闭合当前 turn,记 closedTs(真实墙钟),turnCount++,清 lastError。
261
- * 正常闭合清 lastError:瞬态 error 恢复后不应误判 success=false
262
- * (若 turn_end 后 message_end 报 error,会在 message_end 处理器重新写回)。
263
- */
264
- function applyTurnEnd(record: ReplayRecordView): void {
265
- const turn = currentTurn(record);
266
- turn.closed = true;
267
- turn.closedTs = Date.now();
268
- record.turnCount += 1;
269
- record.lastError = undefined;
270
- }
271
-
272
- /**
273
- * message_end:usage 增量存进末 turn.usageDelta(直接写末 turn,不开新 turn);
274
- * totalTokens 累加;error(stopReason=error)记进 lastError。
275
- *
276
- * usageDelta 按 message_end **累加**(非覆盖)——同一 turn 内若多次 message_end
277
- * 到达(或 turn_end 后的滞后 message_end 落到 currentTurn 开的新 turn),
278
- * 累加保证不丢 usage。getTotalUsage 扁平求和所有 turn,归属 turn 的精确性
279
- * 不影响最终 total(无消费方读单 turn usage)。
280
- */
281
- function applyMessageEnd(
282
- record: ReplayRecordView,
283
- event: Extract<AgentEvent, { type: "message_end" }>,
284
- ): void {
285
- if (event.usage) {
286
- const turn = currentTurn(record);
287
- turn.usageDelta = addUsage(turn.usageDelta, event.usage);
288
- // totalTokens 累加四项之和(保留旧语义,投影直接读)
289
- record.totalTokens +=
290
- (event.usage.input ?? 0) + (event.usage.output ?? 0) +
291
- (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
292
- }
293
- if (event.error) {
294
- record.lastError = event.error;
295
- }
296
- }
297
-
298
- /** error:存 record.lastError(getEventLog 派生 error 条目用)。 */
299
- function applyErrorEvent(
300
- record: ReplayRecordView,
301
- event: Extract<AgentEvent, { type: "error" }>,
302
- ): void {
303
- record.lastError = event.message;
304
- }
305
-
306
- /**
307
- * 从 AgentEvent 更新 record。所有数据收口进 record.turns[]。
308
- * - text/thinking:流式累积进 currentTurn()(完整内容,非切片)
309
- * - tool_start/end:push 进 currentTurn().toolCalls(含完整 result)
310
- * tool_end 跨 turn 扫描找 running 同名 toolCall(兜底滞后事件)
311
- * - turn_end:闭合当前 turn,记 closedTs(真实墙钟,供 getEventLog);
312
- * 正常闭合清 lastError(瞬态 error 恢复后不应误判 success=false)
313
- * - message_end:usage 增量存进末 turn.usageDelta(直接写末 turn,不开新 turn);
314
- * totalTokens 累加
315
- * - error:存 record.lastError(getEventLog 派生 error 条目用)
316
- *
317
- * 唯一写点——replay 与 live 共用(重放等价性的实现体)。
318
- *
319
- * 穷尽性:switch 覆盖 AgentEvent 全部 variant;default 的 `never` 断言保证
320
- * 新增 variant 时编译期报错(而非静默 no-op)。
321
- */
322
- export function updateFromEvent(record: ReplayRecordView, event: AgentEvent): void {
323
- switch (event.type) {
324
- // ── text / thinking:流式累积进当前 turn ──
325
- case "text_delta":
326
- return applyTextDelta(record, event);
327
- case "thinking_delta":
328
- return applyThinkingDelta(record, event);
329
-
330
- // ── tool_start/end:push 进 currentTurn().toolCalls(含完整 result)──
331
- case "tool_start":
332
- return applyToolStart(record, event);
333
- case "tool_end":
334
- return applyToolEnd(record, event);
335
-
336
- // ── turn_end:闭合当前 turn ──
337
- case "turn_end":
338
- return applyTurnEnd(record);
339
-
340
- // ── message_end:usage 增量累加 + totalTokens 累加 ──
341
- case "message_end":
342
- return applyMessageEnd(record, event);
343
-
344
- // ── error:存 record.lastError ──
345
- case "error":
346
- return applyErrorEvent(record, event);
347
-
348
- // ── compaction:不产生数据(不变)──
349
- case "compaction":
350
- return;
351
-
352
- // ── activity:纯活性信号,reducer no-op(协议语义见 contract-types)──
353
- case "activity":
354
- return;
355
-
356
- default: {
357
- // 穷尽性检查:新增 AgentEvent variant 时编译期报错
358
- const _exhaustive: never = event;
359
- return _exhaustive;
360
- }
361
- }
362
- }
363
-
364
- // ============================================================
365
- // Turn → ReplayedTurn 投影 + usage 聚合
366
- // (原 core session-view-projection.ts 逐字等价,R6 收口后本模块为唯一实现:
367
- // 投影语义唯一——strip 内部态 + closed 恒 true + usageDelta 聚合,实现也唯一)
368
- // ============================================================
369
-
370
- /** InternalToolCall → ToolCall(导出纯净形状,不泄漏 running/done/failed 内部状态机)。 */
371
- function toExportedToolCall(tc: InternalToolCall): ToolCall {
372
- return {
373
- toolName: tc.toolName,
374
- ...(tc.args !== undefined ? { args: tc.args } : {}),
375
- ...(tc.result !== undefined ? { result: tc.result } : {}),
376
- ...(tc.isError !== undefined ? { isError: tc.isError } : {}),
377
- };
378
- }
379
-
380
- /** Turn → ReplayedTurn:剥离内部态(closed 恒 true——重放物无进行时语义,§3.3.6)。 */
381
- export function toReplayedTurn(turn: Turn): ReplayedTurn {
382
- return {
383
- text: turn.text,
384
- thinking: turn.thinking,
385
- toolCalls: turn.toolCalls.map(toExportedToolCall),
386
- closed: true,
387
- };
388
- }
389
-
390
- /** 各 turn usageDelta 聚合为 AgentUsageTotal(无任何 usage 数据时 undefined)。 */
391
- export function aggregateUsage(turns: readonly Turn[]): AgentUsageTotal | undefined {
392
- let acc: AgentUsageTotal | undefined;
393
- for (const turn of turns) {
394
- const d = turn.usageDelta;
395
- if (!d) continue;
396
- if (!acc) acc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, total: 0 };
397
- acc.input += d.input;
398
- acc.output += d.output;
399
- acc.cacheRead += d.cacheRead;
400
- acc.cacheWrite += d.cacheWrite;
401
- acc.cost += d.cost ?? 0;
402
- }
403
- if (acc) acc.total = acc.input + acc.output + acc.cacheRead + acc.cacheWrite;
404
- return acc;
405
- }
406
-
407
- // ============================================================
408
- // 事件流 → SessionView(纯投影出口)
409
- // ============================================================
410
-
411
- /**
412
- * 事件流 → SessionView(live reducer 累积 turns——重放等价性的实现体)。
413
- * journal I/O(replayJournal)不在此层——read 第②级降级编排(core 版
414
- * replayJournalToSessionView)留 core,引擎侧按需自读 journal 后调本函数。
415
- */
416
- export function eventsToSessionView(
417
- events: readonly AgentEvent[],
418
- engineId: string,
419
- sessionId?: string,
420
- ): SessionView {
421
- const record = createReplayRecord();
422
- for (const ev of events) updateFromEvent(record, ev);
423
- return {
424
- engineId,
425
- ...(sessionId !== undefined ? { sessionId } : {}),
426
- turns: record.turns.map(toReplayedTurn),
427
- usage: aggregateUsage(record.turns),
428
- source: "journal",
429
- };
430
- }
431
-
432
- /** handle.sessionRef 的 sessionId 提取(引擎自定义键,运行时 guard)。 */
433
- export function sessionIdFromHandle(handle: EngineHandleData): string | undefined {
434
- const v = handle.sessionRef["sessionId"];
435
- return typeof v === "string" ? v : undefined;
436
- }