@zhushanwen/pi-session-reader 0.8.0 → 0.9.0
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.
- package/package.json +4 -4
- package/src/__tests__/cross-package-subagent-core.test.ts +32 -9
- package/src/__tests__/entry-anchor.test.ts +21 -9
- package/src/__tests__/execution-tree.test.ts +38 -40
- package/src/__tests__/rebuild-manifest-dual-write.test.ts +10 -6
- package/src/__tests__/subagents.test.ts +36 -185
- package/src/__tests__/tool-handler.test.ts +49 -135
- package/src/__tests__/workflow.test.ts +69 -163
- package/src/__tests__/zcode-manifest.test.ts +4 -1
- package/src/__tests__/zcode-routing.test.ts +12 -15
- package/src/core/workflow.ts +1 -1
- package/src/discovery/entry-anchor.ts +10 -13
- package/src/discovery/workflows.ts +73 -221
- package/src/tool-handler.ts +1 -1
- package/src/zcode-anchor-classify.ts +4 -8
package/src/core/workflow.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ============================================================
|
|
4
4
|
//
|
|
5
5
|
// 本文件消费 discovery/workflows.ts 的 readRunSnapshot(v1/旧指针档——返 unknown
|
|
6
|
-
// 原始快照对象,NEW/OLD 双格式分支)与 record 流文本([D16③] v2 档——
|
|
6
|
+
// 原始快照对象,NEW/OLD 双格式分支)与 record 流文本([D16③] v2 档——recordPath
|
|
7
7
|
// 锚点语义重定义为 record 流路径后的新数据源),把 unknown/事件流行类型化为
|
|
8
8
|
// WorkflowOverview,再渲染为人类可读文本。零 IO:parseRunSnapshot /
|
|
9
9
|
// parseRunRecordStream / renderWorkflowOverview 喂 mock 即可单测(w5
|
|
@@ -46,12 +46,12 @@ const SUBAGENT_RECORD_ENTRY_V2 = 2
|
|
|
46
46
|
* 从单条 entry 提取 zcode 锚。形状校验窄而严,任一不满足 = 该条不算命中(返回
|
|
47
47
|
* undefined,调用方继续扫——缺键条目不遮蔽更早的完整条目):
|
|
48
48
|
* - `type === 'custom'` ∧ `customType === 'subagent-record'`
|
|
49
|
-
* -
|
|
50
|
-
* record-entry.ts classifySubagentRecordEntryData 同口径):
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
49
|
+
* - 版本/判别键(schema 版本守卫,不认识的版本跳过而非猜测——写侧
|
|
50
|
+
* record-entry.ts classifySubagentRecordEntryData v2-only 同口径):
|
|
51
|
+
* `data.v === 2` ∧ `data.kind === 'settled'`(W1 注册/终态两条小条目——
|
|
52
|
+
* **锚只取自终态条**:registered 条目不携带 engineHandle,且设计 D1 裁决
|
|
53
|
+
* 「sessionRef 双键取自终态条」;kind 非法 = unknown-kind 跳过。v1 全量快照形态
|
|
54
|
+
* 已随兼容层删除,不再命中,2026-09-30 登记 §3.3);
|
|
55
55
|
* - `data.id === saId`(record id 即 sa-id,record-access.ts `sa-${uuid}`)
|
|
56
56
|
* - `engineHandle.sessionRef.sessionId` / `.dbPath` 均为非空 string(F8 双键齐判据的
|
|
57
57
|
* 锚字段部分;pi record 的 sessionRef 无 dbPath → 天然不命中,无需 engine 判别)
|
|
@@ -66,14 +66,11 @@ function zcodeAnchorOfEntry(entry: Entry, saId: string): ZcodeAnchor | undefined
|
|
|
66
66
|
return extractZcodeSessionRefAnchor(d.engineHandle)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** [zcodeAnchorOfEntry 拆分]
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
/** [zcodeAnchorOfEntry 拆分] 条目形态判别:当前版本终态条(registered 条目不携带
|
|
70
|
+
* engineHandle,且设计 D1 裁决「sessionRef 双键取自终态条」;kind 非法 = 跳过。
|
|
71
|
+
* v1 全量快照形态已随兼容层删除,不再命中)。 */
|
|
72
72
|
function isAnchorEligibleRecordEntryVersion(d: Record<string, unknown>): boolean {
|
|
73
|
-
|
|
74
|
-
return d.kind === 'settled'
|
|
75
|
-
}
|
|
76
|
-
return d.v === 1
|
|
73
|
+
return d.v === SUBAGENT_RECORD_ENTRY_V2 && d.kind === 'settled'
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
/** [zcodeAnchorOfEntry 拆分] engineHandle.sessionRef 双键提取(sessionId/dbPath
|
|
@@ -5,109 +5,45 @@ import type { SessionRef, WorkflowRef } from '../core/family.js'
|
|
|
5
5
|
import { extractSessionIdFromFilename } from './subagents.js'
|
|
6
6
|
|
|
7
7
|
// ============================================================
|
|
8
|
-
// workflow-
|
|
8
|
+
// workflow-record 发现链路(单档:v2 注册条目 recordPath 主源)
|
|
9
9
|
// ============================================================
|
|
10
10
|
//
|
|
11
11
|
// 本文件持有 workflow run 的发现与 sessionFile 提取逻辑(IO 适配层):
|
|
12
|
-
// - resolveWorkflows
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// - readRunSnapshot:读 wf-state 文件尾向找首个可解析行,返回原始对象(unknown,格式收窄交 core 层)。
|
|
25
|
-
// - extractCallSessionFiles:从快照对象提 calls 的 sessionFile 绝对路径数组(NEW/OLD 双格式)。
|
|
12
|
+
// - resolveWorkflows:workflow-record v2 注册条目(`v === 2` ∧ `kind === 'registered'`
|
|
13
|
+
// ∧ runId/recordPath 均非空 string)是唯一发现通道——recordPath 锚点语义 = record
|
|
14
|
+
// 流路径([D16③]),指向 <runId>.record.jsonl([D1] record 单源事件流),发现链读
|
|
15
|
+
// record 流行自提 calls——sessionFile 取 agent-settled 帧 result 全文携带的
|
|
16
|
+
// sessionFile([D1] 载荷表:result 全文入事件,AgentResult 自带 sessionFile 字段
|
|
17
|
+
// ——家族链数据源同 record 流,无第二承载位);旧后缀锚点(.events.jsonl 形态)=
|
|
18
|
+
// 历史实体([D1] 历史数据处置:旧两件不读)→ calls=[] 仅 run 存在性兜底。
|
|
19
|
+
// 历史格式条目(v1 全量快照 / workflow-state-link 指针)不识别、静默从发现链消失
|
|
20
|
+
// (不拒读、不报错)——run 侧 v1 读面已删除(ADR-0095,同 record 侧裁决:项目未
|
|
21
|
+
// 上线无历史数据,不迁移不兼容)。
|
|
22
|
+
// - readRunSnapshot:读 wf-state 文件尾向找首个可解析行,返回原始对象(unknown,格式
|
|
23
|
+
// 收窄交 core 层)。唯一消费方 = tool-handler workflow 概览的快照链分支。
|
|
26
24
|
// - extractRecordStreamSessionFiles:[D16③] 从 record 事件流提 calls 的 sessionFile
|
|
27
25
|
// (agent-settled 帧 result.sessionFile——活跃 run 与已收编 run 同源可用)。
|
|
28
|
-
// - sessionRefFromPath:sessionFile 路径 → SessionRef(命中 pathToRef
|
|
26
|
+
// - sessionRefFromPath:sessionFile 路径 → SessionRef(命中 pathToRef 取完整,否则
|
|
27
|
+
// 文件名提取最小 ref)。
|
|
29
28
|
//
|
|
30
29
|
// 分层约定(w5 TC-wf-core-pure-logic):IO 全在 discovery/,core/workflow.ts 的
|
|
31
|
-
// parseRunSnapshot/renderWorkflowOverview 是纯逻辑零 IO(喂 mock 可单测)。
|
|
32
|
-
// unknown
|
|
30
|
+
// parseRunSnapshot/renderWorkflowOverview 是纯逻辑零 IO(喂 mock 可单测)。
|
|
31
|
+
// readRunSnapshot 返 unknown 不收窄——类型化是 core 层 parseRunSnapshot 的职责
|
|
32
|
+
// (TC-wf-snapshot-version-union)。
|
|
33
33
|
//
|
|
34
|
-
// extractSessionIdFromFilename 留在 subagents.ts(find.ts +
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 从 wf-state 快照对象提取 calls[].sessionFile(绝对路径数组)。
|
|
38
|
-
*
|
|
39
|
-
* 两种格式(探查确认,本机 371 个 wf 文件):
|
|
40
|
-
* - NEW (v="wf-run-v1" 或 "wf-run-v2",读取面形状一致):state.calls[],每项顶层
|
|
41
|
-
* .sessionFile(258 文件 / 1590 sessionFile)。v2 由 pi-subagent-workflow 8.x 一次性
|
|
42
|
-
* 生命周期收敛引入(status 两态、无 pausedAt),calls[].sessionFile/result 保留
|
|
43
|
-
* - OLD (无 v):callCache[]=[{key,value}],value.sessionFile(112 文件 / 0 sessionFile,旧 pi 不持久化)
|
|
44
|
-
*/
|
|
45
|
-
export function extractCallSessionFiles(snap: unknown): string[] {
|
|
46
|
-
const out: string[] = []
|
|
47
|
-
if (typeof snap !== 'object' || snap === null) return out
|
|
48
|
-
const s = snap as Record<string, unknown>
|
|
49
|
-
const isNew = isNewSnapshotFormat(s)
|
|
50
|
-
const callsRaw = extractCallsRaw(s, isNew)
|
|
51
|
-
if (!Array.isArray(callsRaw)) return out
|
|
52
|
-
for (const c of callsRaw) {
|
|
53
|
-
if (typeof c !== 'object' || c === null) continue
|
|
54
|
-
const item = resolveCallItem(c as Record<string, unknown>, isNew)
|
|
55
|
-
const sf = extractSessionFileFromItem(item)
|
|
56
|
-
if (sf !== undefined) out.push(sf)
|
|
57
|
-
}
|
|
58
|
-
return out
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** 快照是否为 NEW 格式(v="wf-run-v1"/"wf-run-v2",读取面形状一致) */
|
|
62
|
-
function isNewSnapshotFormat(s: Record<string, unknown>): boolean {
|
|
63
|
-
return s.v === 'wf-run-v1' || s.v === 'wf-run-v2'
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** 按格式取 calls 原始集合:NEW 读 state.calls(state 守卫后取),OLD 读顶层 callCache */
|
|
67
|
-
function extractCallsRaw(s: Record<string, unknown>, isNew: boolean): unknown {
|
|
68
|
-
if (isNew) {
|
|
69
|
-
const state = s.state
|
|
70
|
-
return typeof state === 'object' && state !== null
|
|
71
|
-
? (state as Record<string, unknown>).calls
|
|
72
|
-
: undefined
|
|
73
|
-
}
|
|
74
|
-
return s.callCache
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 单条 call 归一为可取 sessionFile 的对象:
|
|
79
|
-
* NEW: call 本身;OLD: {key, value},取 value(value 非对象时回退 call 本身,容错脏数据)
|
|
80
|
-
*/
|
|
81
|
-
function resolveCallItem(co: Record<string, unknown>, isNew: boolean): Record<string, unknown> {
|
|
82
|
-
if (isNew) return co
|
|
83
|
-
const value = co.value
|
|
84
|
-
return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : co
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** 从归一后的 call 对象提 sessionFile:顶层优先,回退 result.sessionFile;均缺 → undefined */
|
|
88
|
-
function extractSessionFileFromItem(item: Record<string, unknown>): string | undefined {
|
|
89
|
-
const sf = item.sessionFile
|
|
90
|
-
if (typeof sf === 'string') return sf
|
|
91
|
-
const result = item.result
|
|
92
|
-
const sf2 =
|
|
93
|
-
typeof result === 'object' && result !== null
|
|
94
|
-
? (result as Record<string, unknown>).sessionFile
|
|
95
|
-
: undefined
|
|
96
|
-
if (typeof sf2 === 'string') return sf2
|
|
97
|
-
return undefined
|
|
98
|
-
}
|
|
34
|
+
// extractSessionIdFromFilename 留在 subagents.ts(find.ts + 导出契约测试直接消费),
|
|
35
|
+
// 此处反向 import。
|
|
99
36
|
|
|
100
37
|
/**
|
|
101
38
|
* 读 wf-state 文件,从尾向头找首个 trim 非空且 JSON.parse 成功的行,返回解析后的原始对象。
|
|
102
39
|
*
|
|
103
|
-
* 拆解自原 readWorkflowCallSessionFiles 的读行职责(w5 TC-wf-core-pure-logic
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* 的 parseRunSnapshot(C-readrunsnapshot-unknown,TC-wf-snapshot-version-union)。
|
|
40
|
+
* 拆解自原 readWorkflowCallSessionFiles 的读行职责(w5 TC-wf-core-pure-logic)——
|
|
41
|
+
* 读行(本函数,返 unknown)+ 类型化(core/workflow.ts 的 parseRunSnapshot,
|
|
42
|
+
* C-readrunsnapshot-unknown)分层。
|
|
107
43
|
*
|
|
108
44
|
* 尾向回退策略(沿用原实现,ES-wf-snapshot-partial):wf 文件是 rewrite 覆盖模式,读撞 rewrite
|
|
109
45
|
* 中点时末行是半截 JSON(parse 失败)→ 试上一完整行;单行文件半行 → 全失败 → undefined。
|
|
110
|
-
* 文件不存在/读失败/全行不可解析 → undefined
|
|
46
|
+
* 文件不存在/读失败/全行不可解析 → undefined(不抛错,调用方据此跳过该 run)。
|
|
111
47
|
*/
|
|
112
48
|
export async function readRunSnapshot(wfPath: string): Promise<unknown | undefined> {
|
|
113
49
|
let content: string
|
|
@@ -146,7 +82,7 @@ function sessionRefFromPath(path: string, pathToRef: Map<string, SessionRef>): S
|
|
|
146
82
|
}
|
|
147
83
|
|
|
148
84
|
// ============================================================
|
|
149
|
-
// workflow-record
|
|
85
|
+
// workflow-record 条目契约(v2 注册条目判别——本地字面量 + 测试守卫漂移)
|
|
150
86
|
// ============================================================
|
|
151
87
|
|
|
152
88
|
/**
|
|
@@ -159,111 +95,57 @@ function sessionRefFromPath(path: string, pathToRef: Map<string, SessionRef>): S
|
|
|
159
95
|
*/
|
|
160
96
|
const WORKFLOW_RECORD_CUSTOM_TYPE = 'workflow-record'
|
|
161
97
|
|
|
162
|
-
/** v2 条目 data
|
|
98
|
+
/** v2 条目 data 的认识版本(写侧 WORKFLOW_RECORD_ENTRY_VERSION 同值,W1 起 = 2)。 */
|
|
163
99
|
const WORKFLOW_RECORD_ENTRY_V2 = 2
|
|
164
100
|
|
|
165
101
|
/**
|
|
166
102
|
* run record 流文件名尾段(`<runId>.record.jsonl`)。写侧 subagent-core
|
|
167
|
-
*
|
|
168
|
-
*
|
|
103
|
+
* RUN_EVENTS_SUFFIX 同值([D1] record 单源后缀)——v2 注册条目的
|
|
104
|
+
* recordPath 锚点后缀判定用(新锚点直读 record 流;旧 `.events.jsonl` 锚点 =
|
|
169
105
|
* 历史实体分流),本地持有 + 测试守卫漂移。
|
|
170
106
|
*/
|
|
171
107
|
export const RUN_RECORD_STREAM_SUFFIX = '.record.jsonl'
|
|
172
108
|
|
|
173
|
-
/**
|
|
174
|
-
interface
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
/** ② v1 快照条目:runId → 全量快照对象(W17~W1 期间创建的 run)。 */
|
|
178
|
-
v1ByRunId: Map<string, unknown>
|
|
179
|
-
/** ③ 旧指针条目:runId → link path(W17 前的 run)。 */
|
|
180
|
-
linkByRunId: Map<string, { runId: string; path: string }>
|
|
181
|
-
/** runId 首见序(输出稳定序——条目在 session 文件中的出现顺序)。 */
|
|
182
|
-
runIdOrder: string[]
|
|
109
|
+
/** v2 注册条目收集结果(同 runId 后写覆盖前写;order = 条目在 session 文件中的首见序)。 */
|
|
110
|
+
interface RegisteredRecordPaths {
|
|
111
|
+
byRunId: Map<string, string>
|
|
112
|
+
order: string[]
|
|
183
113
|
}
|
|
184
114
|
|
|
185
115
|
/**
|
|
186
|
-
* 单遍扫描 entries
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* - workflow-record v1 快照条目:`v === 1` ∧ snapshot 为对象 ∧ `snapshot.runId` 为
|
|
190
|
-
* string(runId 在快照内——entry data 顶层无 runId 字段);
|
|
191
|
-
* - workflow-state-link 指针条目:现状判别。
|
|
116
|
+
* 单遍扫描 entries 收集 workflow-record v2 注册条目(纯收集,无 IO):
|
|
117
|
+
* `v === 2` ∧ `kind === 'registered'` ∧ runId/recordPath 均非空 string。终态条目
|
|
118
|
+
* (kind === 'settled')不携带 recordPath、不参与发现链,不收集。
|
|
192
119
|
*/
|
|
193
|
-
function
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
v1ByRunId: new Map(),
|
|
197
|
-
linkByRunId: new Map(),
|
|
198
|
-
runIdOrder: [],
|
|
199
|
-
}
|
|
120
|
+
function collectRegisteredRecordPaths(entries: readonly Entry[]): RegisteredRecordPaths {
|
|
121
|
+
const byRunId = new Map<string, string>()
|
|
122
|
+
const order: string[] = []
|
|
200
123
|
const seen = new Set<string>()
|
|
201
|
-
const noteRunId = (runId: string): void => {
|
|
202
|
-
if (!seen.has(runId)) {
|
|
203
|
-
seen.add(runId)
|
|
204
|
-
tiers.runIdOrder.push(runId)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
124
|
for (const e of entries) {
|
|
208
125
|
const data = e.data as Record<string, unknown> | undefined
|
|
209
|
-
if (e.customType
|
|
210
|
-
|
|
211
|
-
continue
|
|
212
|
-
}
|
|
213
|
-
// legacy 指针档字面量:常量 SSOT 在 subagent-core(WORKFLOW_STATE_LINK_CUSTOM_TYPE,
|
|
214
|
-
// orchestration/workflow-record-entry.ts)。reader 生产依赖面不引 subagent-core
|
|
215
|
-
// (包架构裁决,同 entry-anchor.ts 的协议字面量模式),保持本地镜像,值等价由
|
|
216
|
-
// __tests__/workflow.test.ts 跨包契约守卫断言锚定。
|
|
217
|
-
if (e.customType === 'workflow-state-link') {
|
|
218
|
-
collectWorkflowStateLinkTier(tiers, noteRunId, data)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return tiers
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** [collectWorkflowEntryTiers 拆分] workflow-record 条目两代分流(v2 注册 / v1 快照)。 */
|
|
225
|
-
function collectWorkflowRecordEntryTier(
|
|
226
|
-
tiers: WorkflowEntryTiers,
|
|
227
|
-
noteRunId: (runId: string) => void,
|
|
228
|
-
data: Record<string, unknown>,
|
|
229
|
-
): void {
|
|
230
|
-
if (data.v === WORKFLOW_RECORD_ENTRY_V2 && data.kind === 'registered') {
|
|
126
|
+
if (e.customType !== WORKFLOW_RECORD_CUSTOM_TYPE || data === undefined) continue
|
|
127
|
+
if (data.v !== WORKFLOW_RECORD_ENTRY_V2 || data.kind !== 'registered') continue
|
|
231
128
|
const runId = data.runId
|
|
232
|
-
const
|
|
233
|
-
if (typeof runId
|
|
234
|
-
|
|
235
|
-
noteRunId(runId)
|
|
129
|
+
const recordPath = data.recordPath
|
|
130
|
+
if (typeof runId !== 'string' || runId === '' || typeof recordPath !== 'string' || recordPath === '') {
|
|
131
|
+
continue
|
|
236
132
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
tiers.v1ByRunId.set(snapshotRunId, data.snapshot)
|
|
241
|
-
noteRunId(snapshotRunId)
|
|
133
|
+
if (!seen.has(runId)) {
|
|
134
|
+
seen.add(runId)
|
|
135
|
+
order.push(runId)
|
|
242
136
|
}
|
|
137
|
+
byRunId.set(runId, recordPath) // 后写覆盖前写(取最新锚点)
|
|
243
138
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** [collectWorkflowEntryTiers 拆分] workflow-state-link 指针条目(后写覆盖前写——取最新 link)。 */
|
|
247
|
-
function collectWorkflowStateLinkTier(
|
|
248
|
-
tiers: WorkflowEntryTiers,
|
|
249
|
-
noteRunId: (runId: string) => void,
|
|
250
|
-
data: Record<string, unknown> | undefined,
|
|
251
|
-
): void {
|
|
252
|
-
const runId = data?.runId
|
|
253
|
-
const path = data?.path
|
|
254
|
-
if (typeof runId === 'string' && typeof path === 'string') {
|
|
255
|
-
tiers.linkByRunId.set(runId, { runId, path }) // 后写覆盖前写(取最新 link)
|
|
256
|
-
noteRunId(runId)
|
|
257
|
-
}
|
|
139
|
+
return { byRunId, order }
|
|
258
140
|
}
|
|
259
141
|
|
|
260
142
|
/**
|
|
261
|
-
* [D16③]
|
|
143
|
+
* [D16③] recordPath 后缀分流:`.record.jsonl` 锚点 = 新形态(record 流直读);
|
|
262
144
|
* 其余(含旧 `.events.jsonl` 锚点)= 历史实体([D1] 历史数据处置——旧两件不读
|
|
263
145
|
* 不写,历史 run 从发现链数据面退空即预期行为)。
|
|
264
146
|
*/
|
|
265
|
-
function isNewRecordStreamAnchor(
|
|
266
|
-
return
|
|
147
|
+
function isNewRecordStreamAnchor(recordPath: string): boolean {
|
|
148
|
+
return recordPath.endsWith(RUN_RECORD_STREAM_SUFFIX)
|
|
267
149
|
}
|
|
268
150
|
|
|
269
151
|
/** agent-settled 帧的最小消费视图(record 流宽容解析只消费 taskIndex 与
|
|
@@ -310,20 +192,16 @@ export function extractRecordStreamSessionFiles(content: string): string[] {
|
|
|
310
192
|
}
|
|
311
193
|
|
|
312
194
|
/**
|
|
313
|
-
* 读目标 session
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
195
|
+
* 读目标 session 文件全文,从 workflow-record v2 注册条目构造 WorkflowRef[]
|
|
196
|
+
* (单档发现链——v1 快照条目与 workflow-state-link 指针条目不再识别,历史 run
|
|
197
|
+
* 从发现链数据面退空即预期行为,见 ADR-0095)。同一 runId 的多条目后写覆盖
|
|
198
|
+
* (条目顺序 = 时间顺序,取最新锚点)。
|
|
317
199
|
*
|
|
318
|
-
* **签名与返回值结构(WorkflowRef[]{runId,stateFile,calls:SessionRef[]}
|
|
319
|
-
* (C-resolveworkflows-signature,保 m1
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* 兜底);
|
|
324
|
-
* - v1 档:空串(v1 快照条目不携带 state 路径,快照数据直接从条目提 calls;概览
|
|
325
|
-
* 消费面 readRunSnapshot('') 自然落「快照不可读」跳过,家族链 calls 不受影响);
|
|
326
|
-
* - 旧指针档:link 的 path(现状)。
|
|
200
|
+
* **签名与返回值结构(WorkflowRef[]{runId,stateFile,calls:SessionRef[]})不变**
|
|
201
|
+
* (C-resolveworkflows-signature,保 m1 已冻结交付的消费者)。stateFile = recordPath
|
|
202
|
+
* 本身(record 流路径——[D16③] 锚点语义,D1 后 v2 条目携带的锚点即 record 事件流
|
|
203
|
+
* 文件);record 流直读提 calls(agent-settled.result.sessionFile),流被清理/
|
|
204
|
+
* 不可读 → calls=[](run 存在性兜底)。
|
|
327
205
|
*/
|
|
328
206
|
export async function resolveWorkflows(
|
|
329
207
|
sessionId: string,
|
|
@@ -339,49 +217,23 @@ export async function resolveWorkflows(
|
|
|
339
217
|
return []
|
|
340
218
|
}
|
|
341
219
|
const { entries } = parseSessionContent(content)
|
|
342
|
-
const
|
|
220
|
+
const recordPaths = collectRegisteredRecordPaths(entries)
|
|
343
221
|
const workflows: WorkflowRef[] = []
|
|
344
|
-
for (const runId of
|
|
345
|
-
const
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
content = await readFile(journalPath, 'utf8')
|
|
355
|
-
} catch {
|
|
356
|
-
content = undefined // 流被保留期清理/不可读 → calls=[](run 存在性兜底)
|
|
357
|
-
}
|
|
358
|
-
sessionFiles = content === undefined ? [] : extractRecordStreamSessionFiles(content)
|
|
222
|
+
for (const runId of recordPaths.order) {
|
|
223
|
+
const recordPath = recordPaths.byRunId.get(runId)
|
|
224
|
+
if (recordPath === undefined) continue
|
|
225
|
+
let streamContent: string | undefined
|
|
226
|
+
if (isNewRecordStreamAnchor(recordPath)) {
|
|
227
|
+
try {
|
|
228
|
+
streamContent = await readFile(recordPath, 'utf8')
|
|
229
|
+
} catch {
|
|
230
|
+
streamContent = undefined // 流被保留期清理/不可读 → calls=[](run 存在性兜底)
|
|
359
231
|
}
|
|
360
|
-
workflows.push({
|
|
361
|
-
runId,
|
|
362
|
-
stateFile: journalPath,
|
|
363
|
-
calls: sessionFiles.map((sf) => sessionRefFromPath(sf, pathToRef)),
|
|
364
|
-
})
|
|
365
|
-
continue
|
|
366
|
-
}
|
|
367
|
-
const snapshot = tiers.v1ByRunId.get(runId)
|
|
368
|
-
if (snapshot !== undefined) {
|
|
369
|
-
// ② v1 快照档:快照全量在场,直接提 calls(W17~W1 中间档恢复)
|
|
370
|
-
workflows.push({
|
|
371
|
-
runId,
|
|
372
|
-
stateFile: '',
|
|
373
|
-
calls: extractCallSessionFiles(snapshot).map((sf) => sessionRefFromPath(sf, pathToRef)),
|
|
374
|
-
})
|
|
375
|
-
continue
|
|
376
232
|
}
|
|
377
|
-
|
|
378
|
-
const link = tiers.linkByRunId.get(runId)
|
|
379
|
-
if (link === undefined) continue
|
|
380
|
-
const snap = await readRunSnapshot(link.path)
|
|
381
|
-
const sessionFiles = snap === undefined ? [] : extractCallSessionFiles(snap)
|
|
233
|
+
const sessionFiles = streamContent === undefined ? [] : extractRecordStreamSessionFiles(streamContent)
|
|
382
234
|
workflows.push({
|
|
383
|
-
runId
|
|
384
|
-
stateFile:
|
|
235
|
+
runId,
|
|
236
|
+
stateFile: recordPath,
|
|
385
237
|
calls: sessionFiles.map((sf) => sessionRefFromPath(sf, pathToRef)),
|
|
386
238
|
})
|
|
387
239
|
}
|
package/src/tool-handler.ts
CHANGED
|
@@ -1129,7 +1129,7 @@ async function renderSingleWorkflowRun(
|
|
|
1129
1129
|
contentParts: string[]
|
|
1130
1130
|
},
|
|
1131
1131
|
): Promise<void> {
|
|
1132
|
-
// [D16③] 概览链换源:v2 档的 stateFile = record 流路径(
|
|
1132
|
+
// [D16③] 概览链换源:v2 档的 stateFile = record 流路径(recordPath 锚点语义
|
|
1133
1133
|
// 重定义)——直读流经 parseRunRecordStream 概览([D2] 三态 status;空流/读失败
|
|
1134
1134
|
// 投影 running,活跃 run 概览不退化为 skipped——红线)。record 后缀以外的
|
|
1135
1135
|
// stateFile(v1 快照档空串 / 旧指针档 link path)走原快照链。
|
|
@@ -19,9 +19,9 @@ const SUBAGENT_RECORD_ENTRY_V2 = 2
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* 目标 sa-id 的 zcode 形态 subagent-record data:type/customType/版本关/id/engine
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* 全过才返回。版本关(与 entry-anchor.ts zcodeAnchorOfEntry 同口径):
|
|
23
|
+
* v2 终态条(`d.v === 2` ∧ `d.kind === 'settled'`——engineHandle 只在终态条,
|
|
24
|
+
* registered 不携带不归因;v1 全量快照形态已随兼容层删除,2026-09-30 登记 §3.3)。
|
|
25
25
|
* engine 判别(D5)在此——`d.engine !== 'zcode'` 的记录(pi 形态:
|
|
26
26
|
* engine 缺省、sessionRef 无 dbPath)不是「zcode 锚不完整」,返回 undefined 跳过
|
|
27
27
|
* 不归因:误归因 missing-dbPath 会把「pi record 先于 manifest settle 落盘」的正常
|
|
@@ -34,11 +34,7 @@ function zcodeRecordDataOf(entry: Entry, saId: string): Record<string, unknown>
|
|
|
34
34
|
if (typeof data !== 'object' || data === null) return undefined
|
|
35
35
|
const d = data as Record<string, unknown>
|
|
36
36
|
if (d.id !== saId) return undefined
|
|
37
|
-
if (d.v
|
|
38
|
-
if (d.kind !== 'settled') return undefined
|
|
39
|
-
} else if (d.v !== 1) {
|
|
40
|
-
return undefined
|
|
41
|
-
}
|
|
37
|
+
if (d.v !== SUBAGENT_RECORD_ENTRY_V2 || d.kind !== 'settled') return undefined
|
|
42
38
|
if (d.engine !== 'zcode') return undefined
|
|
43
39
|
return d
|
|
44
40
|
}
|