@zhushanwen/pi-session-reader 0.5.1 → 0.6.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.
@@ -30,7 +30,6 @@ const NEW_SNAPSHOT_FIXTURE = {
30
30
  opts: {
31
31
  prompt: 'Reply with exactly: PROBE-OK',
32
32
  model: 'deepseek-router/ds-pro',
33
- thinkingLevel: 'high',
34
33
  description: 'step-0',
35
34
  },
36
35
  status: 'done',
@@ -64,7 +63,7 @@ const V2_SNAPSHOT_FIXTURE = {
64
63
  calls: [
65
64
  {
66
65
  id: 0,
67
- opts: { prompt: 'task 0', model: 'default', thinkingLevel: 'high', description: 'v2-step-0' },
66
+ opts: { prompt: 'task 0', model: 'default', description: 'v2-step-0' },
68
67
  status: 'done',
69
68
  attempts: 1,
70
69
  sessionId: '019v2a',
@@ -126,12 +125,10 @@ describe('parseRunSnapshot', () => {
126
125
  expect(step.status).toBe('done')
127
126
  expect(step.description).toBe('step-0')
128
127
  expect(step.model).toBe('deepseek-router/ds-pro')
129
- expect(step.thinkingLevel).toBe('high')
130
128
  expect(step.attempts).toBe(1)
131
129
  expect(step.durationMs).toBe(1234)
132
130
  expect(step.sessionId).toBe('019xxx')
133
131
  expect(step.sessionFile).toBe('/abs/session.jsonl')
134
- expect(step.contentPreview).toBe('PROBE-OK')
135
132
  })
136
133
 
137
134
  it('TC-w5-parse-new-v2:v2 快照(pi-subagent-workflow 8.x 写入)解析非 null,calls sessionFile 读出', () => {
@@ -148,7 +145,6 @@ describe('parseRunSnapshot', () => {
148
145
  expect(overview!.steps[0].description).toBe('v2-step-0')
149
146
  expect(overview!.steps[1].sessionFile).toBe('/abs/v2-call1.jsonl')
150
147
  expect(overview!.steps[1].sessionId).toBe('019v2b')
151
- expect(overview!.steps[1].contentPreview).toBe('OK')
152
148
  })
153
149
 
154
150
  it('TC-w5-parse-new-status-unknown:NEW call.status 非 done/running/pending(如 failed)→ step.status=pending', () => {
@@ -174,7 +170,6 @@ describe('parseRunSnapshot', () => {
174
170
  expect(step.index).toBe(3)
175
171
  expect(step.status).toBe('pending')
176
172
  expect(step.description).toBe('step-3')
177
- expect(step.contentPreview).toBe('partial')
178
173
  expect(step.durationMs).toBe(9)
179
174
  })
180
175
 
@@ -193,7 +188,6 @@ describe('parseRunSnapshot', () => {
193
188
  expect(step.index).toBe(0) // callCache 顺序索引
194
189
  expect(step.status).toBe('pending') // content='' 空串不算完成标志 → pending
195
190
  expect(step.sessionFile).toBeUndefined() // OLD 未持久化
196
- expect(step.contentPreview).toBe('') // value.content='' 仍提取
197
191
  })
198
192
 
199
193
  it('TC-w5-parse-corrupt-nonobject:非对象输入(null/undefined/string/number/array)返回 null', () => {
@@ -59,6 +59,7 @@ export interface ExecutionTreeNode {
59
59
  agentName?: string
60
60
  slug?: string
61
61
  task?: string
62
+ /** 仅 details 可见,不进文本渲染(formatExecutionTreeText 渲染 status/slug/agentName/task,与 SubagentRef.model 同口径) */
62
63
  model?: string
63
64
  /** subagent 终态 completed/failed/running(manifest.status) */
64
65
  status?: string
@@ -6,11 +6,14 @@ import type { Entry } from './parser.js'
6
6
  * M1 范围:纯逻辑层。buildFamilyIndex 接收已读入的 entry 数组(不做文件 IO),
7
7
  * 文件扫描/首行读取归 M2 discovery 层(roots.ts/find.ts/subagents.ts)。
8
8
  *
9
- * M1 占位约定(M2 discovery 层补全真实值,逻辑不变):
10
- * - SessionRef.fileName:session header 推不出文件路径 → 占位空串
9
+ * 占位约定(原「M2 回填补全」双阶段约定已随 enrichRefs 删除退役,ext-simplify-04 U4/E2
10
+ * ——回填的 fileName/cwd 零文本读者;SessionRef 类型与 find 路径共享,find 侧两字段有
11
+ * 真实消费者,字段本身保留):
12
+ * - SessionRef.fileName / subagent cwd:family 路径恒为占位空串(session header 推不出
13
+ * 路径 / identity entry 无 cwd),不再回填
11
14
  * - SessionRef.mtime/sizeBytes:从 fileStats 取(M1 key=sessionId),取不到为 0
12
15
  * - SubagentRef.sessionId:identity entry 不含 subagent session 的 id → 占位用 entry.id
13
- * - SubagentRef.cwd:identity entry(custom 类型)无 cwd → 占位空串
16
+ * (M2 buildFamilyFromFs 以 header.id 替换为真实 id)
14
17
  * - fileStats 的 key:sessionId(M2 实现沿用此 key,cleanedUp 按 sessionId 命中),逻辑通用
15
18
  *
16
19
  * 隔代关联规则(design §3.3 D-7 Q1)见 resolveFamily 注释。
@@ -35,11 +38,11 @@ export interface SubagentRef extends SessionRef {
35
38
  task?: string
36
39
  /** agent 类型名(manifest.agentName,P-fallback 时 identity.data.agent;同语义异名) */
37
40
  agentName?: string
38
- /** 模型 id(仅 manifest 有;P-fallback 时 undefined) */
41
+ /** 模型 id(仅 manifest 有;P-fallback 时 undefined)。仅 details 可见,不进文本渲染(D2②:展示面只取 status/agentName/task) */
39
42
  model?: string
40
43
  /** 终态 completed/failed/running(仅 manifest 有;P-fallback 时 undefined) */
41
44
  status?: string
42
- /** subagent session.jsonl 绝对路径(manifest.sessionFile 或 alive 文件 meta.path) */
45
+ /** subagent session.jsonl 绝对路径(manifest.sessionFile 或 alive 文件 meta.path)。仅 details 可见,不进文本渲染 */
43
46
  sessionFile?: string
44
47
  }
45
48
 
@@ -118,16 +121,16 @@ function isSubagentIdentityData(v: unknown): v is SubagentIdentityData {
118
121
  * 文件名格式 `<timestamp>_<sessionId>.jsonl`,故路径字符串含父 sessionId。
119
122
  * 遍历已知 sessionId 做子串匹配反查。
120
123
  *
121
- * 兼容 parentSession 直接就是 sessionId 的简化场景(测试 fixture 常用)。
122
124
  * 假设 sessionId 互不为子串(pi 用 UUID,满足);M2 可优化为 fileName→sessionId
123
125
  * 索引反查(O(1)),当前遍历 O(N),家族索引文件数通常几十到几百,可接受。
126
+ *(原「parentSession 直接是 sessionId」快路径已删除,ext-simplify-04 A4——生产数据
127
+ * 恒为文件路径,该分支只服务测试 fixture 简化形态。)
124
128
  */
125
129
  function resolveParentSessionId(
126
130
  parentSession: string | undefined,
127
131
  byId: Map<string, SessionRef>,
128
132
  ): string | null {
129
133
  if (!parentSession) return null
130
- if (byId.has(parentSession)) return parentSession // 直接是 sessionId(简化场景)
131
134
  for (const sid of byId.keys()) {
132
135
  if (parentSession.includes(sid)) return sid
133
136
  }
@@ -144,7 +147,7 @@ function buildById(
144
147
  const stat = fileStats.get(h.id)
145
148
  const ref: SessionRef = {
146
149
  sessionId: h.id,
147
- fileName: '', // M1 占位:M2 discovery 补真实文件路径
150
+ fileName: '', // 占位空串:family 路径不回填(enrichRefs 已删除,ext-simplify-04 U4)
148
151
  mtime: stat?.mtime ?? 0,
149
152
  sizeBytes: stat?.size ?? 0,
150
153
  cwd: h.cwd ?? '',
@@ -198,7 +201,7 @@ function buildSubagentsByRoot(
198
201
  fileName: '', // M1 占位
199
202
  mtime: stat?.mtime ?? 0,
200
203
  sizeBytes: stat?.size ?? 0,
201
- cwd: '', // identity entry 无 cwd;M2 从 subagent 文件 header 补
204
+ cwd: '', // identity entry 无 cwd;family 路径不回填(enrichRefs 已删除,ext-simplify-04 U4)
202
205
  // fileStats key=sessionId(buildFamilyFromFs 以 header.id 写入),cleanedUp 按 sessionId 命中
203
206
  cleanedUp: !fileStats.has(ident.id),
204
207
  // U4 富字段:从 identity data 读(manifest 主/P-fallback 由 buildFamilyFromFs 组装时决定)
@@ -1,4 +1,5 @@
1
1
  import type { Entry } from './parser.js'
2
+ import { BYTES_PER_KB } from './units.js'
2
3
  import { extractToolCalls, formatToolCallSummary, type ToolCallInfo } from './toolcall.js'
3
4
  import type { Turn } from './turns.js'
4
5
  import type { TreeView } from './tree.js'
@@ -7,8 +8,6 @@ import type { TreeView } from './tree.js'
7
8
  // 模块常量(渲染口径:截断宽度 / 换算基数 / 降级档位)
8
9
  // ---------------------------------------------------------------------------
9
10
 
10
- /** bytes→KB 换算基数(omitted 字节 / read 结果规模的 KB 显示)。 */
11
- const BYTES_PER_KB = 1024
12
11
  /** userBrief 截断字符数(TurnBrief.userBrief 文档口径「截 60 字符」)。 */
13
12
  const USER_BRIEF_MAX_CHARS = 60
14
13
  /** assistantBrief 截断字符数(TurnBrief.assistantBrief 文档口径「截 80 字符」)。 */
@@ -17,7 +16,8 @@ const ASSISTANT_BRIEF_MAX_CHARS = 80
17
16
  const ENTRY_BRIEF_MAX_CHARS = 100
18
17
  /** turn 索引显示宽度(T013 三位补零)。 */
19
18
  const TURN_INDEX_WIDTH = 3
20
- /** outline 默认 token 预算(OutlineOptions.budget 缺省值)。 */
19
+ /** outline 默认 token 预算(OutlineOptions.budget 缺省值)。生产路径(doOutline/doExport)
20
+ * 唯一取值:outline 是设计定档的 ~1500 token 全貌 TOC,无按调用方调节的需求。 */
21
21
  const OUTLINE_DEFAULT_BUDGET_TOKENS = 2000
22
22
  /** token 估算换算基数(chars/4,与 tokenEstimate 口径一致)。 */
23
23
  const CHARS_PER_TOKEN = 4
@@ -55,6 +55,11 @@ export interface TurnBrief {
55
55
 
56
56
  export interface OutlineResult {
57
57
  turns: TurnBrief[]
58
+ /**
59
+ * 渲染行(E7/D3 行渲染统一):formatLine 产出、降级与总预算截断后的最终展示行——
60
+ * 预算度量与工具层展示共用同一份(度量 = 展示 by construction),展示侧不得重建行格式。
61
+ */
62
+ lines: string[]
58
63
  /** skippedLines:JSON 解析失败/缺结构字段的行数(parser 检测,工具层覆盖精确值;render 层无 ParseResult 恒 0) */
59
64
  stats: { totalTurns: number; totalEntries: number; totalBytes: number; parsedBytes: number; skippedLines: number }
60
65
  /** chars / 4 近似(与 design P-outline 口径一致) */
@@ -64,7 +69,9 @@ export interface OutlineResult {
64
69
  }
65
70
 
66
71
  export interface OutlineOptions {
67
- /** 默认 2000(token) */
72
+ /** 测试注入缝(E10 定性):非模型可见参数——session_read schema 无 budget 入参,模型
73
+ * 无法也无需调节;存在动机 = 测试注入小预算触发降级档用例(砍 assistantBrief / 骨架档)。
74
+ * 生产路径不传,走 OUTLINE_DEFAULT_BUDGET_TOKENS 默认。 */
68
75
  budget?: number
69
76
  allBranches?: boolean
70
77
  /** 默认 turn;entry = 每 entry 一行不聚合(D-1 兜底,坏 session 调试) */
@@ -101,6 +108,26 @@ function blockThinking(block: ContentBlock): string {
101
108
  return ''
102
109
  }
103
110
 
111
+ /**
112
+ * E11 共享核:白名单提取 message.content 中 `type==='text'` 块的 text 字段(块 → string[])。
113
+ * string content 直取为单元素数组;非 string 非数组返回空数组。join 语义(''/'\n'/' ')
114
+ * 与空态语义(空串 vs undefined)留在各调用点组合——S7 警告适用:join 差异是行为敏感点,
115
+ * 不可在共享核合并。消费方:result-action('' join)/ extract('\n' join)/ find(' ' join
116
+ * + 空返 undefined)。
117
+ */
118
+ export function textBlockParts(content: unknown): string[] {
119
+ if (typeof content === 'string') return [content]
120
+ if (!Array.isArray(content)) return []
121
+ const parts: string[] = []
122
+ for (const b of content) {
123
+ if (b !== null && typeof b === 'object') {
124
+ const o = b as Record<string, unknown>
125
+ if (o.type === 'text' && typeof o.text === 'string') parts.push(o.text)
126
+ }
127
+ }
128
+ return parts
129
+ }
130
+
104
131
  /** 拼接 text 块文本(排除 thinking / toolCall / tool_use / tool_result 块)。 */
105
132
  function extractText(content: unknown): string {
106
133
  if (isStringContent(content)) return content
@@ -276,7 +303,7 @@ function findBranchForkPoint(turn: Turn, tree: TreeView): string | undefined {
276
303
  }
277
304
 
278
305
  // ---------------------------------------------------------------------------
279
- // 行渲染(预算度量依据)
306
+ // 行渲染(预算度量与展示同一份渲染行,E7/D3 行渲染统一)
280
307
  // ---------------------------------------------------------------------------
281
308
 
282
309
  function formatHHMM(timestamp?: string): string {
@@ -292,10 +319,11 @@ export function formatBytesMarker(bytes: number): string {
292
319
  }
293
320
 
294
321
  /**
295
- * 渲染单行为字符串(用于预算度量与降级判断)。
322
+ * 渲染单行为字符串(E7 行渲染统一:预算度量与展示同一份渲染行,OutlineResult.lines 的行工厂)。
296
323
  * v2 O1:L1 行含 assistantBrief(补 assistant 结论行让 outline 单独可决策,不再逼反复 expand)。
297
324
  * level: 0=全有(toolSummary + assistantBrief)/ 1=砍 assistantBrief / 2=再砍 toolSummary(骨架)。
298
325
  * userBrief + omittedBytes 骨架永保。assistantBrief 格式 `→ <结论>`,在 toolSummary 后、omitted 前。
326
+ * 旁支标记 `[旁支 N entries]`(branchSize = 该 forkPoint 下旁支子树 entry 数)。
299
327
  */
300
328
  function formatLine(b: TurnBrief, level: LineLevel, branchSize?: number): string {
301
329
  const parts: string[] = []
@@ -341,7 +369,8 @@ function applyBranchLabels(briefs: TurnBrief[], turns: Turn[], tree: TreeView):
341
369
  /**
342
370
  * 降级序逐行渲染(design §3.5 算法 1 step3):level 0 全有;超预算降到 level 1
343
371
  * 砍 assistantBrief;仍超降到 level 2 砍 toolSummary(骨架)。
344
- * 直接改写 brief 的 assistantBrief/toolSummary 字段,返回行缓存。
372
+ * 直接改写 brief 的 assistantBrief/toolSummary 字段,返回行缓存(E7 后随结果
373
+ * 返回为 OutlineResult.lines,预算度量与展示同一份)。
345
374
  */
346
375
  function renderDegradingLines(
347
376
  briefs: TurnBrief[],
@@ -408,7 +437,7 @@ export function renderOutline(
408
437
  }
409
438
 
410
439
  if (turns.length === 0) {
411
- return { turns: [], stats, tokenEstimate: 0 }
440
+ return { turns: [], lines: [], stats, tokenEstimate: 0 }
412
441
  }
413
442
 
414
443
  // granularity:entry —— 每 entry 一行,不聚合 turn(D-1 兜底)
@@ -428,11 +457,11 @@ export function renderOutline(
428
457
  // 3. 降级序渲染:level 0 全有 → level 1 砍 assistantBrief → level 2 骨架
429
458
  const lineCache = renderDegradingLines(briefs, tree, perTurnCharBudget)
430
459
 
431
- // 4. 总预算截断(从尾部丢弃)
460
+ // 4. 总预算截断(从尾部丢弃,lines 与 briefs 同步截短)
432
461
  const { totalChars, truncated } = truncateToTotalBudget(briefs, lineCache, budget)
433
462
 
434
463
  const tokenEstimate = Math.ceil(totalChars / CHARS_PER_TOKEN)
435
- return { turns: briefs, stats, tokenEstimate, truncated }
464
+ return { turns: briefs, lines: lineCache, stats, tokenEstimate, truncated }
436
465
  }
437
466
 
438
467
  /** granularity:entry 模式:每 entry 一行 TurnBrief,不聚合,仅总预算截断。 */
@@ -486,6 +515,7 @@ function renderEntryGranularity(
486
515
  }
487
516
  return {
488
517
  turns: briefs,
518
+ lines,
489
519
  stats,
490
520
  tokenEstimate: Math.ceil(totalChars / CHARS_PER_TOKEN),
491
521
  truncated,
@@ -568,7 +598,7 @@ function stripThinking(content: unknown): unknown {
568
598
 
569
599
  /**
570
600
  * L3 detail 默认摘要态的 toolResult entry(v2 O3:toolResult 不再整条消失,给中间态)。
571
- * includeToolResult:true 时 renderDetail 返回原 Entry(全文),否则返回此摘要。
601
+ * includeToolResult:true 时 renderDetail 直接 push 原 Entry(全文态真实路径),否则返回此摘要。
572
602
  */
573
603
  export interface ToolResultSummaryEntry {
574
604
  type: 'toolResultSummary'
@@ -580,8 +610,6 @@ export interface ToolResultSummaryEntry {
580
610
  headLines: string
581
611
  /** 结果文本总行数 */
582
612
  totalLines: number
583
- /** 原 toolResult entry(includeToolResult:true 时 renderDetail 改用此返回全文) */
584
- fullEntry: Entry
585
613
  }
586
614
 
587
615
  export function renderDetail(
@@ -620,7 +648,6 @@ export function renderDetail(
620
648
  summary: formatToolResultSummary(msg.toolName, tc, text),
621
649
  headLines,
622
650
  totalLines: lines.length,
623
- fullEntry: e,
624
651
  })
625
652
  }
626
653
  continue
@@ -9,6 +9,7 @@
9
9
  * parser.ts 已 additive 透出这两个字段,O2/O3 据此精确关联取参数。
10
10
  */
11
11
  import type { Entry } from './parser.js'
12
+ import { BYTES_PER_KB } from './units.js'
12
13
 
13
14
  // ---------------------------------------------------------------------------
14
15
  // 模块常量(参数摘要的截断宽度 / 换算基数)
@@ -20,8 +21,6 @@ const BASH_CMD_MAX_CHARS = 60
20
21
  const SUBAGENT_TASK_MAX_CHARS = 40
21
22
  /** 未知工具 arguments JSON 摘要截断字符数。 */
22
23
  const ARGS_JSON_MAX_CHARS = 50
23
- /** bytes→KB 换算基数(write content 的 KB 显示)。 */
24
- const BYTES_PER_KB = 1024
25
24
 
26
25
  /** 单次工具调用信息(从 assistant content 的 toolCall block 提取)。 */
27
26
  export interface ToolCallInfo {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 字节量纲换算基数(本包内 KB/MB 换算的唯一来源)。
3
+ *
4
+ * 本包是独立 npm 发布的 pi extension(仅依赖 pi-ext-guards),不 import taiji 内部
5
+ * workspace 包(@taiji/shared 的 BYTES_PER_MB 单点不含本包)——量纲基数本地定义,
6
+ * 数值对齐由注释锚定(1MB = 1024×1024)。三个消费域(render 的 omitted 标记 /
7
+ * toolcall 的 write 摘要 / search-across 的预算与人性化格式化)共用本单点,
8
+ * 避免同值常量在本包内多份分裂。
9
+ */
10
+
11
+ /** bytes→KB 换算基数(omitted 字节 / read 结果规模 / write content 的 KB 显示)。 */
12
+ export const BYTES_PER_KB = 1024
13
+ /** bytes→MB 换算基数(跨会话检索预算与人性化格式化)。 */
14
+ export const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB
@@ -11,25 +11,20 @@
11
11
  // 升版连带编译期影响本扩展;且上游类型只描述 NEW,OLD 仍需自处理(TC-wf-snapshot-version-union)。
12
12
  // session-reader 作为纯读取者,按字段存在性 + v 标记分支做「结构化快照」式解析,与上游解耦。
13
13
 
14
- // ---- 类型守卫 helpers ----
14
+ import { isRecord } from '@zhushanwen/pi-ext-guards'
15
15
 
16
- /** unknown → Record<string, unknown> 守卫(非对象或 null → false)。 */
17
- function isRecord(v: unknown): v is Record<string, unknown> {
18
- return typeof v === 'object' && v !== null
19
- }
16
+ // ---- 类型守卫 helpers ----
20
17
 
21
18
  /** unknown → string 收窄(非 string → undefined)。快照可选字符串字段的统一入口。 */
22
19
  function strOr(v: unknown): string | undefined {
23
20
  return typeof v === 'string' ? v : undefined
24
21
  }
25
22
 
26
- /** 字符串截断(超 max 加省略号)。概览预览用,全文走 detail。 */
23
+ /** 字符串截断(超 max 加省略号)。step 行渲染用。 */
27
24
  function truncate(s: string, max: number): string {
28
25
  return s.length <= max ? s : s.slice(0, max) + '…'
29
26
  }
30
27
 
31
- /** contentPreview 截断长度(概览预览,全文走 detail)。 */
32
- const CONTENT_PREVIEW_MAX = 120
33
28
  /** step 行 call sessionId 显示截断长度(uuid 前缀段,LLM 可读)。 */
34
29
  const SESSION_ID_PREVIEW_MAX = 12
35
30
 
@@ -61,8 +56,6 @@ export interface WorkflowStep {
61
56
  description?: string
62
57
  /** NEW call.opts.model */
63
58
  model?: string
64
- /** NEW call.opts.thinkingLevel */
65
- thinkingLevel?: string
66
59
  /** NEW call.attempts / OLD 无 → undefined */
67
60
  attempts?: number
68
61
  /** NEW call.result.durationMs / OLD value.result.durationMs */
@@ -71,8 +64,6 @@ export interface WorkflowStep {
71
64
  sessionId?: string
72
65
  /** call.sessionFile 或 result.sessionFile(LLM 跳 outline/detail 的绝对路径入口,OLD 多数为 undefined) */
73
66
  sessionFile?: string
74
- /** result.content 截断前 120 字(概览预览,全文走 detail) */
75
- contentPreview?: string
76
67
  }
77
68
 
78
69
  /** workflow run 概览(parseRunSnapshot 输出 / renderWorkflowOverview 输入)。 */
@@ -83,7 +74,7 @@ export interface WorkflowOverview {
83
74
  stateFile: string
84
75
  /** NEW state.status / OLD 顶层 status */
85
76
  status: string
86
- /** 格式标记(渲染/调试用)。v2 读取面形状与 v1 一致(pi-subagent-workflow 8.x 一次性生命周期) */
77
+ /** details 格式标记(随 details.runs 透传给程序化消费方,文本渲染不读)。v2 读取面形状与 v1 一致(pi-subagent-workflow 8.x 一次性生命周期) */
87
78
  version: 'wf-run-v1' | 'wf-run-v2' | 'legacy'
88
79
  /** NEW spec.scriptName / spec.name / OLD name */
89
80
  script?: string
@@ -138,19 +129,16 @@ function mapCallToStep(call: unknown, fallbackIndex: number): WorkflowStep {
138
129
  const index = typeof call.id === 'number' ? call.id : fallbackIndex
139
130
  const status = normalizeCallStatus(strOr(call.status) ?? '')
140
131
  const refs = pickSessionRefs(call, result)
141
- const content = strOr(result.content)
142
132
 
143
133
  return {
144
134
  index,
145
135
  status,
146
136
  description: strOr(opts.description),
147
137
  model: strOr(opts.model),
148
- thinkingLevel: strOr(opts.thinkingLevel),
149
138
  attempts: typeof call.attempts === 'number' ? call.attempts : undefined,
150
139
  durationMs: typeof result.durationMs === 'number' ? result.durationMs : undefined,
151
140
  sessionId: refs.sessionId,
152
141
  sessionFile: refs.sessionFile,
153
- contentPreview: content !== undefined ? truncate(content, CONTENT_PREVIEW_MAX) : undefined,
154
142
  }
155
143
  }
156
144
 
@@ -162,11 +150,11 @@ function mapCacheEntryToStep(entry: unknown, index: number): WorkflowStep {
162
150
 
163
151
  // sessionFile/sessionId: value.sessionFile 或 value.result.sessionFile(OLD 多数缺失,探针 112 文件 0)
164
152
  const refs = pickSessionRefs(value, result)
165
- // content:真实 OLD 数据 value.content(wf-skip-ok)与测试 fixture value.result.content 并存
153
+ // content:真实 OLD 数据 value.content(wf-skip-ok)与测试 fixture value.result.content 并存,
154
+ // 仅用于 status 推测(A4 死字段删除:contentPreview 已删,不再随 step 输出)
166
155
  const content = strOr(result.content) ?? strOr(value.content)
167
156
  // OLD 无 status 字段:有 sessionFile 或非空 content → done,否则 pending
168
- //(空 content 如 wf-skip-ok 的 '' 不算完成标志,对齐 TC-w5-parse-old expected status='pending';
169
- // content 仍提取为 contentPreview='')
157
+ //(空 content 如 wf-skip-ok 的 '' 不算完成标志,对齐 TC-w5-parse-old expected status='pending')
170
158
  const hasContent = content !== undefined && content.length > 0
171
159
  const status: WorkflowStep['status'] =
172
160
  refs.sessionFile !== undefined || hasContent ? 'done' : 'pending'
@@ -177,7 +165,6 @@ function mapCacheEntryToStep(entry: unknown, index: number): WorkflowStep {
177
165
  durationMs: typeof result.durationMs === 'number' ? result.durationMs : undefined,
178
166
  sessionId: refs.sessionId,
179
167
  sessionFile: refs.sessionFile,
180
- contentPreview: content !== undefined ? truncate(content, CONTENT_PREVIEW_MAX) : undefined,
181
168
  }
182
169
  }
183
170
 
@@ -12,35 +12,35 @@ import { dirname } from 'node:path'
12
12
  * 齐备),不 import roots.ts 的信号类型,避免并行单元耦合;接线层分别组装两种信号包。
13
13
  *
14
14
  * 信号可靠性依据(design §4.4 活体实测表,非推断):
15
- * - XYZ_AGENT_EXT_LOG 单独使用可靠性「中」——runtime 无条件注入,但
16
- * ENV_WHITELIST_PREFIXES 含裸 `XYZ_` 前缀,用户 shell 里的同名变量会透传进任何进程;
15
+ * - TAIJI_AGENT_EXT_LOG 单独使用可靠性「中」——runtime 无条件注入,但
16
+ * ENV_WHITELIST_PREFIXES 含裸 `TAIJI_` 前缀,用户 shell 里的同名变量会透传进任何进程;
17
17
  * - PI_CODING_AGENT_DIR 单独使用可靠性「中」——pi 原生变量,裸 pi 用户也可能自设,
18
- * 但其值形态是 xyz-agent 专属约定:`<*>/.xyz-agent*` 目录后接 `/agent`;
19
- * - XYZ_AGENT_PACKAGED **不可用**——在 SPAWN_ENV_OUTBOUND_DENY_LIST 被剥除
18
+ * 但其值形态是 taiji 专属约定:`<*>/.taiji*` 目录后接 `/agent`;
19
+ * - TAIJI_AGENT_PACKAGED **不可用**——在 SPAWN_ENV_OUTBOUND_DENY_LIST 被剥除
20
20
  * (§4.4 在案结论),故不引入该信号,防后人重蹈。
21
21
  * 因此托管判定必须是双信号合取:任一信号单独成立只判 standalone-pi,
22
22
  * 透传/自设污染提示写入 evidence。
23
23
  */
24
24
 
25
- /** xyz-agent 托管信号 1:runtime 无条件注入的 extension 日志开关(恒 '1') */
26
- export const XYZ_AGENT_EXT_LOG_ENV = 'XYZ_AGENT_EXT_LOG'
27
- /** xyz-agent 托管信号 2:pi agentDir 注入变量;其**值形态**是 xyz-agent 专属约定 */
25
+ /** taiji 托管信号 1:runtime 无条件注入的 extension 日志开关(恒 '1') */
26
+ export const TAIJI_AGENT_EXT_LOG_ENV = 'TAIJI_AGENT_EXT_LOG'
27
+ /** taiji 托管信号 2:pi agentDir 注入变量;其**值形态**是 taiji 专属约定 */
28
28
  export const PI_CODING_AGENT_DIR_ENV = 'PI_CODING_AGENT_DIR'
29
29
  /** 数据目录推导首选:runtime getDataDir() 的权威注入值 */
30
- export const XYZ_AGENT_DATA_DIR_ENV = 'XYZ_AGENT_DATA_DIR'
30
+ export const TAIJI_AGENT_DATA_DIR_ENV = 'TAIJI_AGENT_DATA_DIR'
31
31
 
32
32
  /** 托管信号 1 的唯一合法值(注释「托管语义恒为 '1'」) */
33
33
  const EXT_LOG_MANAGED_VALUE = '1'
34
34
 
35
- // xyz-agent agentDir 值形态(§6.2 双形态兼容——迁移窗口期 B 前后两种布局并存):
36
- // - B 后新布局:`<*>/.xyz-agent*` 目录后接 `/agent`(一层)
37
- // - B 前旧布局:`<*>/.xyz-agent*` 目录后接 `/pi/agent`(两层)
38
- // `.xyz-agent*` = 以 `.xyz-agent` 开头的目录段(prod `.xyz-agent` / dev `.xyz-agent-dev`)。
39
- // 三条正则都要求匹配到路径末尾(`$`),调用前输入先经 normalizeXyzPath 剥尾斜杠。
35
+ // taiji agentDir 值形态(§6.2 双形态兼容——迁移窗口期 B 前后两种布局并存):
36
+ // - B 后新布局:`<*>/.taiji*` 目录后接 `/agent`(一层)
37
+ // - B 前旧布局:`<*>/.taiji*` 目录后接 `/pi/agent`(两层)
38
+ // `.taiji*` = 以 `.taiji` 开头的目录段(prod `.taiji` / dev `.taiji-dev`)。
39
+ // 三条正则都要求匹配到路径末尾(`$`),调用前输入先经 normalizeTaijiPath 剥尾斜杠。
40
40
  // (用行注释:形态文本含星号斜杠序列,写进块注释会提前终止注释。)
41
- const AGENT_DIR_SHAPE_ANY = /(^|\/)\.xyz-agent[^/]*\/(pi\/)?agent$/
42
- const AGENT_DIR_SHAPE_OLD = /(^|\/)\.xyz-agent[^/]*\/pi\/agent$/
43
- const AGENT_DIR_SHAPE_NEW = /(^|\/)\.xyz-agent[^/]*\/agent$/
41
+ const AGENT_DIR_SHAPE_ANY = /(^|\/)\.taiji[^/]*\/(pi\/)?agent$/
42
+ const AGENT_DIR_SHAPE_OLD = /(^|\/)\.taiji[^/]*\/pi\/agent$/
43
+ const AGENT_DIR_SHAPE_NEW = /(^|\/)\.taiji[^/]*\/agent$/
44
44
 
45
45
  /**
46
46
  * 打包形态判据(§6.2:bundle 路径判发行形态;无法判定 → null 不猜)。
@@ -56,7 +56,7 @@ const MACOS_PACKAGED_PATTERN = /\.app\/Contents\/Resources\/extensions\//
56
56
  const REPO_STAGED_PATTERN = /(^|\/)apps\/electron\/resources\/extensions\//
57
57
  const WIN_LINUX_PACKAGED_PATTERN = /(^|\/)resources\/extensions\//
58
58
 
59
- export type EnvironmentKind = 'xyz-agent' | 'standalone-pi'
59
+ export type EnvironmentKind = 'taiji' | 'standalone-pi'
60
60
  export type Distribution = 'packaged' | 'dev' | null
61
61
 
62
62
  export interface EnvironmentSignals {
@@ -71,19 +71,19 @@ export interface EnvironmentSignals {
71
71
  export interface DetectedEnvironment {
72
72
  kind: EnvironmentKind
73
73
  distribution: Distribution
74
- /** 托管时的 xyz-agent 数据目录;standalone-pi 恒 undefined */
74
+ /** 托管时的 taiji 数据目录;standalone-pi 恒 undefined */
75
75
  dataDir?: string
76
76
  /** 每条命中/未命中信号的原文 + 判定依据,恒非空(§6.2:判定可被证据推翻,非黑盒断言) */
77
77
  evidence: string[]
78
78
  }
79
79
 
80
80
  /** 形态匹配前的归一化:Windows 分隔符 → `/`,剥尾部斜杠 */
81
- function normalizeXyzPath(p: string): string {
81
+ function normalizeTaijiPath(p: string): string {
82
82
  return p.replace(/\\/g, '/').replace(/\/+$/, '')
83
83
  }
84
84
 
85
- function matchesXyzAgentAgentDir(value: string): boolean {
86
- return AGENT_DIR_SHAPE_ANY.test(normalizeXyzPath(value))
85
+ function matchesTaijiAgentDir(value: string): boolean {
86
+ return AGENT_DIR_SHAPE_ANY.test(normalizeTaijiPath(value))
87
87
  }
88
88
 
89
89
  /** distribution 判定,判定行写入 evidence */
@@ -110,19 +110,19 @@ function detectDistribution(bundleUrl: string | undefined, evidence: string[]):
110
110
  }
111
111
 
112
112
  /**
113
- * 托管时的数据目录推导:XYZ_AGENT_DATA_DIR 优先(runtime 注入的权威值,与
113
+ * 托管时的数据目录推导:TAIJI_AGENT_DATA_DIR 优先(runtime 注入的权威值,与
114
114
  * getDataDir() 同源),缺失时按布局形态从 PI_CODING_AGENT_DIR 剥层。来源写入 evidence。
115
115
  */
116
116
  function deriveDataDir(env: Record<string, string | undefined>, evidence: string[]): string | undefined {
117
- const dataDirEnv = env[XYZ_AGENT_DATA_DIR_ENV]
117
+ const dataDirEnv = env[TAIJI_AGENT_DATA_DIR_ENV]
118
118
  if (dataDirEnv) {
119
- evidence.push(`数据目录:'${dataDirEnv}'(来源:${XYZ_AGENT_DATA_DIR_ENV})`)
119
+ evidence.push(`数据目录:'${dataDirEnv}'(来源:${TAIJI_AGENT_DATA_DIR_ENV})`)
120
120
  return dataDirEnv
121
121
  }
122
122
  const agentDirEnv = env[PI_CODING_AGENT_DIR_ENV]
123
123
  if (agentDirEnv) {
124
- const normalized = normalizeXyzPath(agentDirEnv)
125
- // 旧形态更长,先判:`.xyz-agent*/pi/agent` → 剥 pi/agent 两层
124
+ const normalized = normalizeTaijiPath(agentDirEnv)
125
+ // 旧形态更长,先判:`.taiji*/pi/agent` → 剥 pi/agent 两层
126
126
  if (AGENT_DIR_SHAPE_OLD.test(normalized)) {
127
127
  const dataDir = dirname(dirname(normalized))
128
128
  evidence.push(`数据目录:'${dataDir}'(来源:${PI_CODING_AGENT_DIR_ENV} 剥 pi/agent 两层,B 前旧布局)`)
@@ -134,7 +134,7 @@ function deriveDataDir(env: Record<string, string | undefined>, evidence: string
134
134
  return dataDir
135
135
  }
136
136
  }
137
- evidence.push(`数据目录:无法推导(${XYZ_AGENT_DATA_DIR_ENV} 未设置且 ${PI_CODING_AGENT_DIR_ENV} 形态不可剥层)`)
137
+ evidence.push(`数据目录:无法推导(${TAIJI_AGENT_DATA_DIR_ENV} 未设置且 ${PI_CODING_AGENT_DIR_ENV} 形态不可剥层)`)
138
138
  return undefined
139
139
  }
140
140
 
@@ -146,16 +146,16 @@ function pushExtLogEvidence(
146
146
  ): void {
147
147
  evidence.push(
148
148
  extLogHit
149
- ? `${XYZ_AGENT_EXT_LOG_ENV}='${extLog}' → 命中`
150
- : `${XYZ_AGENT_EXT_LOG_ENV}=${extLog === undefined ? '<未设置>' : `'${extLog}'`} → 未命中(要求值为 '1')`,
149
+ ? `${TAIJI_AGENT_EXT_LOG_ENV}='${extLog}' → 命中`
150
+ : `${TAIJI_AGENT_EXT_LOG_ENV}=${extLog === undefined ? '<未设置>' : `'${extLog}'`} → 未命中(要求值为 '1')`,
151
151
  )
152
152
  }
153
153
 
154
- /** 信号 2 的形态标注(§6.2 双形态兼容:B 前旧布局 / B 后新布局 / 非 xyz-agent 形态)。 */
154
+ /** 信号 2 的形态标注(§6.2 双形态兼容:B 前旧布局 / B 后新布局 / 非 taiji 形态)。 */
155
155
  function describeAgentDirShape(normalized: string): string {
156
156
  if (AGENT_DIR_SHAPE_OLD.test(normalized)) return 'B 前旧布局 pi/agent'
157
157
  if (AGENT_DIR_SHAPE_NEW.test(normalized)) return 'B 后新布局 agent'
158
- return '非 xyz-agent 形态'
158
+ return '非 taiji 形态'
159
159
  }
160
160
 
161
161
  /** 信号 2 evidence 行(未设置 → 未命中;有值 → 形态匹配与否 + 形态标注)。 */
@@ -168,19 +168,19 @@ function pushAgentDirEvidence(
168
168
  evidence.push(`${PI_CODING_AGENT_DIR_ENV}=<未设置> → 未命中`)
169
169
  return
170
170
  }
171
- const shape = describeAgentDirShape(normalizeXyzPath(agentDirEnv))
171
+ const shape = describeAgentDirShape(normalizeTaijiPath(agentDirEnv))
172
172
  evidence.push(
173
- `${PI_CODING_AGENT_DIR_ENV}='${agentDirEnv}' → ${agentDirHit ? `形态匹配(${shape})` : `形态不匹配(${shape},要求 <*>/.xyz-agent*/agent)`}`,
173
+ `${PI_CODING_AGENT_DIR_ENV}='${agentDirEnv}' → ${agentDirHit ? `形态匹配(${shape})` : `形态不匹配(${shape},要求 <*>/.taiji*/agent)`}`,
174
174
  )
175
175
  }
176
176
 
177
177
  /** 判定行:托管(双信号合取成立)/ standalone-pi(孤置信号点名,提示透传/自设污染)。 */
178
178
  function judgedEvidenceLine(extLogHit: boolean, agentDirHit: boolean): string {
179
179
  if (extLogHit && agentDirHit) {
180
- return `判定:xyz-agent 托管(${XYZ_AGENT_EXT_LOG_ENV}='1' 且 ${PI_CODING_AGENT_DIR_ENV} 形态匹配,双信号合取成立)`
180
+ return `判定:taiji 托管(${TAIJI_AGENT_EXT_LOG_ENV}='1' 且 ${PI_CODING_AGENT_DIR_ENV} 形态匹配,双信号合取成立)`
181
181
  }
182
182
  const orphaned: string[] = []
183
- if (extLogHit) orphaned.push(XYZ_AGENT_EXT_LOG_ENV)
183
+ if (extLogHit) orphaned.push(TAIJI_AGENT_EXT_LOG_ENV)
184
184
  if (agentDirHit) orphaned.push(PI_CODING_AGENT_DIR_ENV)
185
185
  const orphanNote =
186
186
  orphaned.length > 0
@@ -192,8 +192,8 @@ function judgedEvidenceLine(extLogHit: boolean, agentDirHit: boolean): string {
192
192
  /**
193
193
  * 判定当前宿主环境(design §6.2)。
194
194
  *
195
- * 托管(xyz-agent)= 合取:`XYZ_AGENT_EXT_LOG === '1'` **且** `PI_CODING_AGENT_DIR`
196
- * 值匹配 xyz-agent 形态(`<*>/.xyz-agent*` 目录后接 `/agent`;B 前旧布局接
195
+ * 托管(taiji)= 合取:`TAIJI_AGENT_EXT_LOG === '1'` **且** `PI_CODING_AGENT_DIR`
196
+ * 值匹配 taiji 形态(`<*>/.taiji*` 目录后接 `/agent`;B 前旧布局接
197
197
  * `/pi/agent` 同样接受)。任一信号单独成立 → standalone-pi,evidence 点名孤置
198
198
  * 信号(透传污染提示)。
199
199
  * evidence 恒非空:每条命中/未命中信号的原文都在列,让 agent 能核对并推翻判定。
@@ -205,19 +205,19 @@ export function detectEnvironment(signals: EnvironmentSignals): DetectedEnvironm
205
205
  const evidence: string[] = []
206
206
  const { env } = signals
207
207
 
208
- // 信号 1:XYZ_AGENT_EXT_LOG === '1'
209
- const extLog = env[XYZ_AGENT_EXT_LOG_ENV]
208
+ // 信号 1:TAIJI_AGENT_EXT_LOG === '1'
209
+ const extLog = env[TAIJI_AGENT_EXT_LOG_ENV]
210
210
  const extLogHit = extLog === EXT_LOG_MANAGED_VALUE
211
211
  pushExtLogEvidence(evidence, extLog, extLogHit)
212
212
 
213
213
  // 信号 2:PI_CODING_AGENT_DIR 值形态
214
214
  const agentDirEnv = env[PI_CODING_AGENT_DIR_ENV]
215
- const agentDirHit = agentDirEnv !== undefined && matchesXyzAgentAgentDir(agentDirEnv)
215
+ const agentDirHit = agentDirEnv !== undefined && matchesTaijiAgentDir(agentDirEnv)
216
216
  pushAgentDirEvidence(evidence, agentDirEnv, agentDirHit)
217
217
  evidence.push(`agentDir='${signals.agentDir}' → pi getAgentDir() 透传记录`)
218
218
 
219
219
  const managed = extLogHit && agentDirHit
220
- const kind: EnvironmentKind = managed ? 'xyz-agent' : 'standalone-pi'
220
+ const kind: EnvironmentKind = managed ? 'taiji' : 'standalone-pi'
221
221
  evidence.push(judgedEvidenceLine(extLogHit, agentDirHit))
222
222
 
223
223
  const distribution = detectDistribution(signals.bundleUrl, evidence)