@zhushanwen/pi-session-reader 0.6.0 → 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.
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-session-reader",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
- "xyz-agent": {
6
+ "taiji": {
7
7
  "role": "universal"
8
8
  },
9
9
  "pi": {
@@ -16,12 +16,12 @@
16
16
  "pi-package"
17
17
  ],
18
18
  "dependencies": {
19
- "@zhushanwen/pi-ext-guards": "0.4.0"
19
+ "@zhushanwen/pi-ext-guards": "0.4.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@vitest/coverage-v8": "^4.1.9",
23
23
  "vitest": "^4.1.8",
24
- "@zhushanwen/subagent-core": "0.10.1"
24
+ "@zhushanwen/subagent-core": "0.10.5"
25
25
  },
26
26
  "files": [
27
27
  "index.ts",
@@ -20,20 +20,21 @@ import {
20
20
  } from '@zhushanwen/subagent-core/execution/assembly/path-encoding.ts'
21
21
 
22
22
  /**
23
- * W4 跨包集成测试(subagent-sync-collect v2 impl-plan W4 验收条款①,设计 §5 W4:
24
- * 「subagent-core 真实 tmpdir 产出磁盘状态 → session-reader result action sa- id 反查,
25
- * 不 mock manifest 写入」)。
23
+ * W4 跨包集成测试([collect 退役] 改造保留:原「E1 落标出口产出 manifest」驱动面
24
+ * 随 sync 批机制删除,现行真实投影出口 = store 重建通道 rebuildIndexes——manifest
25
+ * 是可丢缓存,boot 全量腿按磁盘子文件锚幂等补缺)。「subagent-core 真实 tmpdir
26
+ * 产出磁盘状态 → session-reader result action sa- id 反查,不 mock manifest 写入」
27
+ * 的跨包契约保持不变。
26
28
  *
27
- * 与 result.test.ts 的分工:result.test.ts 的 fixture **手工预写** manifest(U6 时期
28
- * W3 投影代码尚不存在);本文件 manifest 内容必须来自 v2 W3 的投影代码路径——
29
- * subagent-core 侧真实驱动 E1 崩溃恢复(kill -9 同构链路:真实 RecordStore 落盘
30
- * register/轮终 entry → initSession 内 orphan 覆写真实跑 → recoverSyncCollectBatch
31
- * 落标出口 appendBatchFinalizedEntry fire-and-forget 真写 records/<sa-id>.json),
32
- * session-reader 侧零 mock 读同一 tmpdir 断言反查闭环。
29
+ * 与 result.test.ts 的分工:result.test.ts 的 fixture **手工预写** manifest;本文件
30
+ * manifest 内容必须来自投影代码路径——subagent-core 侧真实驱动(真实 RecordStore
31
+ * 落盘 register/轮终 entry → initSession 内 orphan 覆写真实跑 → rebuildIndexes
32
+ * derivedManifestRecord 投影真写 records/<sa-id>.json),session-reader 侧零 mock
33
+ * 读同一 tmpdir 断言反查闭环。
33
34
  *
34
35
  * 构造形态取自 subagent-core sync-collect-recovery.test.ts 的 kill -9 同构主用例
35
- * (W1 领地,形态同构是 v1 教训「种子绕过真实链路 = 假绿」的执行):子 session 文件
36
- * 在其基础上补 message entries(result action 的正文提取源)。
36
+ * (形态同构是 v1 教训「种子绕过真实链路 = 假绿」的执行):子 session 文件在其
37
+ * 基础上补 message entries(result action 的正文提取源)。
37
38
  *
38
39
  * mock 面最小:仅构造 SubagentService 所需的最小 pi 形状(appendEntry 真写主文件,
39
40
  * 其余 no-op stub)——零 vi.mock、notifier/store/config 全真实实现,agentDir 全部
@@ -48,9 +49,9 @@ const ENV_KEYS_TO_STRIP = [
48
49
  'PI_SUBAGENT_DEPTH',
49
50
  'PI_SUBAGENT_ROOT_CWD',
50
51
  'PI_SUBAGENT_FORK_DEPTH',
51
- 'XYZ_SUBAGENT_RELAY_SOCKET',
52
- 'XYZ_SUBAGENT_RELAY_NODE',
53
- 'XYZ_SUBAGENT_RELAY_SCRIPT',
52
+ 'TAIJI_SUBAGENT_RELAY_SOCKET',
53
+ 'TAIJI_SUBAGENT_RELAY_NODE',
54
+ 'TAIJI_SUBAGENT_RELAY_SCRIPT',
54
55
  ] as const
55
56
 
56
57
  const ROOT_SESSION = 'root-cross-pkg'
@@ -102,8 +103,6 @@ function memberRecord(overrides: Partial<SubagentRecord> & { id: string }): Suba
102
103
  result: undefined,
103
104
  error: undefined,
104
105
  sessionFile: undefined,
105
- chatMode: false,
106
- collectMode: 'sync',
107
106
  ...overrides,
108
107
  }
109
108
  }
@@ -118,7 +117,7 @@ interface MemberSeed {
118
117
  model: string
119
118
  }
120
119
 
121
- describe('跨包集成:subagent-core E1 落标 manifest → session-reader result 反查(W4)', () => {
120
+ describe('跨包集成:subagent-core 重建 manifest → session-reader result 反查(W4)', () => {
122
121
  let agentDir: string
123
122
  let mainFile: string
124
123
  let services: SubagentService[]
@@ -224,21 +223,11 @@ describe('跨包集成:subagent-core E1 落标 manifest → session-reader res
224
223
  )
225
224
  }
226
225
 
227
- /** 手写短轮询(真实 timers,vitest 4.1.8 vi.waitFor 对 falsy callback 不轮询——
228
- * subagent-core 同款纪律):manifest 为 fire-and-forget 异步写,需轮询等落盘。 */
229
- async function until(cond: () => boolean, timeoutMs = 3000): Promise<void> {
230
- const start = Date.now()
231
- while (!cond()) {
232
- if (Date.now() - start > timeoutMs) {
233
- throw new Error(`cross-pkg: condition not met within ${timeoutMs}ms`)
234
- }
235
- await new Promise((resolve) => setTimeout(resolve, 10))
236
- }
237
- }
238
-
239
- /** 全链路驱动:种子 → 恢复 service(orphan 覆写)→ E1(落标出口真写 manifest)。
240
- * 返回子 session 文件路径表。 */
241
- async function driveSyncBatchRecovery(members: MemberSeed[]): Promise<Map<string, string>> {
226
+ /** 全链路驱动:种子 → 恢复 service(orphan 覆写)→ rebuildIndexes(现行 boot
227
+ * 全量重建通道——derivedManifestRecord 投影真写 manifest)。返回子 session 文件
228
+ * 路径表。[collect 退役] 原 E1 落标出口(recoverSyncCollectBatch →
229
+ * appendBatchFinalizedEntry)随 sync 批机制删除。 */
230
+ async function driveManifestRebuild(members: MemberSeed[]): Promise<Map<string, string>> {
242
231
  const childFiles = new Map<string, string>()
243
232
  for (const m of members) {
244
233
  const childFile = writeChildSessionFile(m)
@@ -246,12 +235,15 @@ describe('跨包集成:subagent-core E1 落标 manifest → session-reader res
246
235
  seedRoundTerminalEntries(m, childFile)
247
236
  }
248
237
  const service = makeRecoveryService()
249
- service.recoverSyncCollectBatch()
250
- // E1 落标出口 appendBatchFinalizedEntry 的 manifest 是 fire-and-forget best-effort
251
- // 写——轮询等全部成员 manifest 真实落盘(W3 投影代码路径的产物)。
252
- await until(() =>
253
- members.every((m) => fs.existsSync(path.join(getSubagentRecordsDir(agentDir, agentDir), `${m.id}.json`))),
254
- )
238
+ service.rebuildIndexes()
239
+ // derivedManifestRecord 投影为同步落盘——全成员 manifest 即刻可见(W3 投影
240
+ // 代码路径的产物,非手工预写)。
241
+ for (const m of members) {
242
+ expect(
243
+ fs.existsSync(path.join(getSubagentRecordsDir(agentDir, agentDir), `${m.id}.json`)),
244
+ `manifest of ${m.id}`,
245
+ ).toBe(true)
246
+ }
255
247
  return childFiles
256
248
  }
257
249
 
@@ -263,12 +255,12 @@ describe('跨包集成:subagent-core E1 落标 manifest → session-reader res
263
255
  assistantParts: ['part one', 'part two'],
264
256
  model: 'prov/round-m',
265
257
  }
266
- const childFiles = await driveSyncBatchRecovery([m])
258
+ const childFiles = await driveManifestRebuild([m])
267
259
 
268
- // manifest 内容断言(W3 appendBatchFinalizedEntry 投影路径的产物,非手工预写):
269
- // sessionFile 来自 W1 投影扩展。[U2/U3 两态桥接] E1 落标成员经恢复链覆写后处于
270
- // idle(重建单规则)/running 形态、无旧终态遗留位 → legacy status 如实投影
271
- // "running"(batchManifestRecord 单点派生,session-reader 视角的活跃成员)。
260
+ // manifest 内容断言(rebuildIndexes → derivedManifestRecord 投影路径的产物,
261
+ // 非手工预写):sessionFile 来自 W1 投影扩展。[U2/U3 两态桥接] 恢复链覆写后成员
262
+ // 处于 idle(重建单规则)、无旧终态遗留位 → legacy status 如实投影 "running"
263
+ //(legacyManifestStatusFields 单点派生,session-reader 视角的活跃成员)。
272
264
  const manifest = JSON.parse(
273
265
  fs.readFileSync(path.join(getSubagentRecordsDir(agentDir, agentDir), 'sa-cross-1.json'), 'utf-8'),
274
266
  ) as Record<string, unknown>
@@ -303,7 +295,7 @@ describe('跨包集成:subagent-core E1 落标 manifest → session-reader res
303
295
  { id: 'sa-cross-a', resultText: 'alpha final answer', assistantParts: ['alpha final answer'], model: 'prov/ma' },
304
296
  { id: 'sa-cross-b', resultText: 'beta final answer', assistantParts: ['beta final answer'], model: 'prov/mb' },
305
297
  ]
306
- await driveSyncBatchRecovery(members)
298
+ await driveManifestRebuild(members)
307
299
 
308
300
  const r = await handleSessionRead({ action: 'result', session: 'sa-cross-a,sa-cross-b' }, { agentDir })
309
301
  const text = r.content[0]?.text ?? ''
@@ -1,9 +1,9 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import {
3
3
  detectEnvironment,
4
- XYZ_AGENT_EXT_LOG_ENV,
4
+ TAIJI_AGENT_EXT_LOG_ENV,
5
5
  PI_CODING_AGENT_DIR_ENV,
6
- XYZ_AGENT_DATA_DIR_ENV,
6
+ TAIJI_AGENT_DATA_DIR_ENV,
7
7
  type EnvironmentSignals,
8
8
  } from '../discovery/env.js'
9
9
 
@@ -22,54 +22,54 @@ function signals(overrides: Partial<EnvironmentSignals> = {}): EnvironmentSignal
22
22
  }
23
23
 
24
24
  describe('detectEnvironment 托管合取判定', () => {
25
- it('双信号齐备 + B 后新布局 <dataDir>/agent → xyz-agent(packaged)', () => {
25
+ it('双信号齐备 + B 后新布局 <dataDir>/agent → taiji(packaged)', () => {
26
26
  const r = detectEnvironment(
27
27
  signals({
28
28
  env: {
29
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
30
- [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.xyz-agent/agent',
29
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
30
+ [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.taiji/agent',
31
31
  },
32
32
  bundleUrl:
33
33
  'file:///Applications/TaiJi.app/Contents/Resources/extensions/@zhushanwen/pi-session-reader/index.mjs',
34
34
  }),
35
35
  )
36
- expect(r.kind).toBe('xyz-agent')
36
+ expect(r.kind).toBe('taiji')
37
37
  expect(r.distribution).toBe('packaged')
38
- expect(r.dataDir).toBe('/Users/u/.xyz-agent')
38
+ expect(r.dataDir).toBe('/Users/u/.taiji')
39
39
  })
40
40
 
41
- it('双信号齐备 + B 前旧布局 <dataDir>/pi/agent → xyz-agent(迁移窗口期双形态兼容)', () => {
41
+ it('双信号齐备 + B 前旧布局 <dataDir>/pi/agent → taiji(迁移窗口期双形态兼容)', () => {
42
42
  const r = detectEnvironment(
43
43
  signals({
44
44
  env: {
45
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
46
- [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.xyz-agent-dev/pi/agent',
45
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
46
+ [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.taiji-dev/pi/agent',
47
47
  },
48
- bundleUrl: 'file:///Users/u/Code/xyz-agent/extensions/universal/session-reader/src/index.ts',
48
+ bundleUrl: 'file:///Users/u/Code/taiji/extensions/universal/session-reader/src/index.ts',
49
49
  }),
50
50
  )
51
- expect(r.kind).toBe('xyz-agent')
51
+ expect(r.kind).toBe('taiji')
52
52
  expect(r.distribution).toBe('dev')
53
- expect(r.dataDir).toBe('/Users/u/.xyz-agent-dev')
53
+ expect(r.dataDir).toBe('/Users/u/.taiji-dev')
54
54
  })
55
55
 
56
- it('透传污染反例 1:仅 XYZ_AGENT_EXT_LOG=1(PI_CODING_AGENT_DIR 未设)→ standalone-pi,evidence 点名孤置信号', () => {
56
+ it('透传污染反例 1:仅 TAIJI_AGENT_EXT_LOG=1(PI_CODING_AGENT_DIR 未设)→ standalone-pi,evidence 点名孤置信号', () => {
57
57
  const r = detectEnvironment(
58
- signals({ env: { [XYZ_AGENT_EXT_LOG_ENV]: '1' } }),
58
+ signals({ env: { [TAIJI_AGENT_EXT_LOG_ENV]: '1' } }),
59
59
  )
60
60
  expect(r.kind).toBe('standalone-pi')
61
61
  expect(r.dataDir).toBeUndefined()
62
62
  const joined = r.evidence.join('\n')
63
- expect(joined).toContain(`${XYZ_AGENT_EXT_LOG_ENV}='1' → 命中`)
63
+ expect(joined).toContain(`${TAIJI_AGENT_EXT_LOG_ENV}='1' → 命中`)
64
64
  expect(joined).toContain('单独成立不合取')
65
- expect(joined).toContain(XYZ_AGENT_EXT_LOG_ENV)
65
+ expect(joined).toContain(TAIJI_AGENT_EXT_LOG_ENV)
66
66
  })
67
67
 
68
- it('透传污染反例 2:仅 XYZ_AGENT_EXT_LOG=1 且 PI_CODING_AGENT_DIR 为裸 pi 形态 → standalone-pi', () => {
68
+ it('透传污染反例 2:仅 TAIJI_AGENT_EXT_LOG=1 且 PI_CODING_AGENT_DIR 为裸 pi 形态 → standalone-pi', () => {
69
69
  const r = detectEnvironment(
70
70
  signals({
71
71
  env: {
72
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
72
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
73
73
  [PI_CODING_AGENT_DIR_ENV]: '/home/u/.pi/agent',
74
74
  },
75
75
  }),
@@ -81,7 +81,7 @@ describe('detectEnvironment 托管合取判定', () => {
81
81
  it('透传污染反例 3:仅 PI_CODING_AGENT_DIR 形态匹配(EXT_LOG 未设)→ standalone-pi,evidence 点名孤置信号', () => {
82
82
  const r = detectEnvironment(
83
83
  signals({
84
- env: { [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.xyz-agent/agent' },
84
+ env: { [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.taiji/agent' },
85
85
  }),
86
86
  )
87
87
  expect(r.kind).toBe('standalone-pi')
@@ -92,13 +92,13 @@ describe('detectEnvironment 托管合取判定', () => {
92
92
  expect(joined).toContain(PI_CODING_AGENT_DIR_ENV)
93
93
  })
94
94
 
95
- it('XYZ_AGENT_EXT_LOG 非 1 值(如 0 / true)不构成命中', () => {
95
+ it('TAIJI_AGENT_EXT_LOG 非 1 值(如 0 / true)不构成命中', () => {
96
96
  for (const value of ['0', 'true', '']) {
97
97
  const r = detectEnvironment(
98
98
  signals({
99
99
  env: {
100
- [XYZ_AGENT_EXT_LOG_ENV]: value,
101
- [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.xyz-agent/agent',
100
+ [TAIJI_AGENT_EXT_LOG_ENV]: value,
101
+ [PI_CODING_AGENT_DIR_ENV]: '/Users/u/.taiji/agent',
102
102
  },
103
103
  }),
104
104
  )
@@ -106,12 +106,12 @@ describe('detectEnvironment 托管合取判定', () => {
106
106
  }
107
107
  })
108
108
 
109
- it('形态防误报:无点前缀 xyz-agent、嵌套浅段不匹配', () => {
110
- for (const dir of ['/home/u/xyz-agent/agent', '/home/u/.xyz/agent', '/.pi/agent']) {
109
+ it('形态防误报:无点前缀 taiji、嵌套浅段不匹配', () => {
110
+ for (const dir of ['/home/u/taiji/agent', '/home/u/.foo/agent', '/.pi/agent']) {
111
111
  const r = detectEnvironment(
112
112
  signals({
113
113
  env: {
114
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
114
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
115
115
  [PI_CODING_AGENT_DIR_ENV]: dir,
116
116
  },
117
117
  }),
@@ -144,8 +144,8 @@ describe('detectEnvironment distribution(bundle 路径三态)', () => {
144
144
 
145
145
  it('仓库路径(源码与 dev staged 资源根两种)→ dev,staged 根不被误判 packaged', () => {
146
146
  for (const url of [
147
- 'file:///Users/u/Code/xyz-agent/extensions/universal/session-reader/src/index.ts',
148
- 'file:///Users/u/Code/xyz-agent/apps/electron/resources/extensions/@zhushanwen/pi-session-reader/index.mjs',
147
+ 'file:///Users/u/Code/taiji/extensions/universal/session-reader/src/index.ts',
148
+ 'file:///Users/u/Code/taiji/apps/electron/resources/extensions/@zhushanwen/pi-session-reader/index.mjs',
149
149
  ]) {
150
150
  const r = detectEnvironment(signals({ bundleUrl: url }))
151
151
  expect(r.distribution, `${url} 应判 dev`).toBe('dev')
@@ -160,48 +160,48 @@ describe('detectEnvironment distribution(bundle 路径三态)', () => {
160
160
  })
161
161
 
162
162
  describe('detectEnvironment dataDir 推导', () => {
163
- it('托管:XYZ_AGENT_DATA_DIR 优先于 PI_CODING_AGENT_DIR 剥层', () => {
163
+ it('托管:TAIJI_AGENT_DATA_DIR 优先于 PI_CODING_AGENT_DIR 剥层', () => {
164
164
  const r = detectEnvironment(
165
165
  signals({
166
166
  env: {
167
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
168
- [PI_CODING_AGENT_DIR_ENV]: '/data/.xyz-agent/pi/agent',
169
- [XYZ_AGENT_DATA_DIR_ENV]: '/data/.xyz-agent',
167
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
168
+ [PI_CODING_AGENT_DIR_ENV]: '/data/.taiji/pi/agent',
169
+ [TAIJI_AGENT_DATA_DIR_ENV]: '/data/.taiji',
170
170
  },
171
171
  }),
172
172
  )
173
- expect(r.kind).toBe('xyz-agent')
174
- expect(r.dataDir).toBe('/data/.xyz-agent')
175
- expect(r.evidence.join('\n')).toContain(`来源:${XYZ_AGENT_DATA_DIR_ENV}`)
173
+ expect(r.kind).toBe('taiji')
174
+ expect(r.dataDir).toBe('/data/.taiji')
175
+ expect(r.evidence.join('\n')).toContain(`来源:${TAIJI_AGENT_DATA_DIR_ENV}`)
176
176
  })
177
177
 
178
178
  it('托管:无 DATA_DIR 时按布局剥层——新布局剥一层、旧布局剥两层', () => {
179
179
  const newLayout = detectEnvironment(
180
180
  signals({
181
181
  env: {
182
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
183
- [PI_CODING_AGENT_DIR_ENV]: '/data/.xyz-agent/agent',
182
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
183
+ [PI_CODING_AGENT_DIR_ENV]: '/data/.taiji/agent',
184
184
  },
185
185
  }),
186
186
  )
187
- expect(newLayout.dataDir).toBe('/data/.xyz-agent')
187
+ expect(newLayout.dataDir).toBe('/data/.taiji')
188
188
 
189
189
  const oldLayout = detectEnvironment(
190
190
  signals({
191
191
  env: {
192
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
193
- [PI_CODING_AGENT_DIR_ENV]: '/data/.xyz-agent/pi/agent',
192
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
193
+ [PI_CODING_AGENT_DIR_ENV]: '/data/.taiji/pi/agent',
194
194
  },
195
195
  }),
196
196
  )
197
- expect(oldLayout.dataDir).toBe('/data/.xyz-agent')
197
+ expect(oldLayout.dataDir).toBe('/data/.taiji')
198
198
  })
199
199
 
200
- it('standalone:即使 XYZ_AGENT_DATA_DIR 被设置也恒 undefined', () => {
200
+ it('standalone:即使 TAIJI_AGENT_DATA_DIR 被设置也恒 undefined', () => {
201
201
  const r = detectEnvironment(
202
202
  signals({
203
203
  env: {
204
- [XYZ_AGENT_DATA_DIR_ENV]: '/data/.xyz-agent',
204
+ [TAIJI_AGENT_DATA_DIR_ENV]: '/data/.taiji',
205
205
  },
206
206
  }),
207
207
  )
@@ -215,15 +215,15 @@ describe('detectEnvironment evidence', () => {
215
215
  ['全信号缺失', signals({})],
216
216
  ['托管全信号', signals({
217
217
  env: {
218
- [XYZ_AGENT_EXT_LOG_ENV]: '1',
219
- [PI_CODING_AGENT_DIR_ENV]: '/data/.xyz-agent/agent',
220
- [XYZ_AGENT_DATA_DIR_ENV]: '/data/.xyz-agent',
218
+ [TAIJI_AGENT_EXT_LOG_ENV]: '1',
219
+ [PI_CODING_AGENT_DIR_ENV]: '/data/.taiji/agent',
220
+ [TAIJI_AGENT_DATA_DIR_ENV]: '/data/.taiji',
221
221
  },
222
222
  bundleUrl:
223
223
  'file:///Applications/TaiJi.app/Contents/Resources/extensions/@zhushanwen/pi-session-reader/index.mjs',
224
224
  })],
225
- ['EXT_LOG 孤置', signals({ env: { [XYZ_AGENT_EXT_LOG_ENV]: '1' } })],
226
- ['agentDir 孤置', signals({ env: { [PI_CODING_AGENT_DIR_ENV]: '/data/.xyz-agent/agent' } })],
225
+ ['EXT_LOG 孤置', signals({ env: { [TAIJI_AGENT_EXT_LOG_ENV]: '1' } })],
226
+ ['agentDir 孤置', signals({ env: { [PI_CODING_AGENT_DIR_ENV]: '/data/.taiji/agent' } })],
227
227
  ['bundleUrl 缺失', signals({})],
228
228
  ]
229
229
 
@@ -238,20 +238,20 @@ describe('detectEnvironment evidence', () => {
238
238
  const r = detectEnvironment(
239
239
  signals({
240
240
  env: {
241
- [XYZ_AGENT_EXT_LOG_ENV]: '0',
242
- [PI_CODING_AGENT_DIR_ENV]: '/data/.xyz-agent/pi/agent',
241
+ [TAIJI_AGENT_EXT_LOG_ENV]: '0',
242
+ [PI_CODING_AGENT_DIR_ENV]: '/data/.taiji/pi/agent',
243
243
  },
244
- agentDir: '/data/.xyz-agent/pi/agent',
244
+ agentDir: '/data/.taiji/pi/agent',
245
245
  }),
246
246
  )
247
247
  const joined = r.evidence.join('\n')
248
248
  // 未命中信号:原文 + 未命中注记
249
- expect(joined).toContain(`${XYZ_AGENT_EXT_LOG_ENV}='0' → 未命中`)
249
+ expect(joined).toContain(`${TAIJI_AGENT_EXT_LOG_ENV}='0' → 未命中`)
250
250
  // 命中信号:原文 + 匹配注记
251
- expect(joined).toContain(`${PI_CODING_AGENT_DIR_ENV}='/data/.xyz-agent/pi/agent'`)
251
+ expect(joined).toContain(`${PI_CODING_AGENT_DIR_ENV}='/data/.taiji/pi/agent'`)
252
252
  expect(joined).toContain('形态匹配')
253
253
  // 第三条输入:agentDir 透传记录
254
- expect(joined).toContain(`agentDir='/data/.xyz-agent/pi/agent'`)
254
+ expect(joined).toContain(`agentDir='/data/.taiji/pi/agent'`)
255
255
  // 判定结论行
256
256
  expect(joined).toContain('standalone-pi')
257
257
  })
@@ -578,7 +578,7 @@ describe('findSessions', () => {
578
578
  describe('u11 标题元数据(metadataProvider 注入,§6.6)', () => {
579
579
  let root: string
580
580
  let agentDir: string
581
- /** 平铺 main 根(xyz-agent 形态:session 直在 sessions/ 下,[default] 根无子目录) */
581
+ /** 平铺 main 根(taiji 形态:session 直在 sessions/ 下,[default] 根无子目录) */
582
582
  let flatMain: string
583
583
  /** legacy 根(dirname(agentDir)/sessions) */
584
584
  let legacyDir: string
@@ -169,7 +169,7 @@ describe('sessionReaderExtension - execute 信号包采集(U3:ctx 全形态
169
169
  })
170
170
 
171
171
  it('u11:execute 构造 metadataProvider 接 SessionManager.listAll——keyword 标题命中端到端', async () => {
172
- // fixture:平铺 main 根(xyz-agent 形态),session 首消息不含 query,标题经 mock listAll 注入
172
+ // fixture:平铺 main 根(taiji 形态),session 首消息不含 query,标题经 mock listAll 注入
173
173
  const tmp = await mkdtemp(join(tmpdir(), 'index-u11-'))
174
174
  try {
175
175
  const agentDir = join(tmp, 'agent')
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { execSync } from 'node:child_process'
3
+ import { homedir } from 'node:os'
3
4
  import { join } from 'node:path'
4
5
 
5
6
  /**
@@ -13,8 +14,8 @@ import { join } from 'node:path'
13
14
  * 本文件无 .test.ts 后缀,vitest include 只收集 `src/__tests__/` 下以 .test.ts 结尾的文件,不收集本文件。
14
15
  */
15
16
 
16
- /** 真实 pi agent 目录(本机),用于集成测试。 */
17
- export const REAL_AGENT_DIR = '/Users/zhushanwen/.pi/agent'
17
+ /** 真实 pi agent 目录(pi 官方家目录布局,随宿主 homedir 推导),用于集成测试。 */
18
+ export const REAL_AGENT_DIR = join(homedir(), '.pi', 'agent')
18
19
 
19
20
  /**
20
21
  * 全量扫描类真实数据用例的统一超时标定(90s)。
@@ -31,13 +32,25 @@ export const E6 = '019e6c96-0a0c-74b8-a73f-d1854d88e2a7'
31
32
  /** 真实 fork 家族根(fork 子代 019fe632,隔代 subagent 019fe635 挂在 019fe632 下)。 */
32
33
  export const FAM = '019fe620-8ae1-78a7-b76a-43a1ba4cc3c7'
33
34
 
34
- /** E6 的完整文件路径(parser/turns/render 直接 parseSessionFile 用)。 */
35
- export const REAL_SESSION = join(
36
- REAL_AGENT_DIR,
37
- 'sessions',
38
- '--Users-zhushanwen-Code-xyz-agent-workspace-feat-plugin-arch-3--',
39
- `2026-05-28T03-17-12-844Z_${E6}.jsonl`,
40
- )
35
+ /**
36
+ * E6 的完整文件路径(parser/turns/render 直接 parseSessionFile 用)。
37
+ * 按 sid 运行时定位而非硬编码宿主 workspace 目录名:真实文件挂在哪个 slug 目录随采集机器而异,
38
+ * 硬编码会绑死本机路径形态;定位不到时回落相对布局,由下方 HAS_REAL_SESSION 守卫 skip。
39
+ */
40
+ export const REAL_SESSION = (() => {
41
+ const fallback = join(REAL_AGENT_DIR, 'sessions', `2026-05-28T03-17-12-844Z_${E6}.jsonl`)
42
+ if (!existsSync(REAL_AGENT_DIR)) return fallback
43
+ try {
44
+ return (
45
+ execSync(
46
+ `find ${REAL_AGENT_DIR}/sessions -name '*${E6}*' -name '*.jsonl' ! -name '*.finalized' 2>/dev/null | head -1`,
47
+ { encoding: 'utf8' },
48
+ ).trim() || fallback
49
+ )
50
+ } catch {
51
+ return fallback
52
+ }
53
+ })()
41
54
 
42
55
  /** 同步探测真实 session 文件是否存在(不存在则 skip,避免在无该数据的机器上硬失败)。 */
43
56
  export function hasRealSession(sid: string): boolean {
@@ -214,7 +214,7 @@ const HOST_CASES: HostCase[] = [
214
214
  },
215
215
  },
216
216
  {
217
- name: 'xyz-agent:live=根本身(RPC 覆盖态),default 去重,legacy 残留与 subagent 独立列出',
217
+ name: 'taiji:live=根本身(RPC 覆盖态),default 去重,legacy 残留与 subagent 独立列出',
218
218
  async build(tmp) {
219
219
  const dataDir = join(tmp, 'data')
220
220
  const agentDir = join(dataDir, 'agent')
@@ -10,16 +10,13 @@ import {
10
10
  extractSessionIdFromFilename,
11
11
  type RecordManifest,
12
12
  } from '../discovery/subagents.js'
13
- import { REAL_DATA_TIMEOUT_MS } from './real-data.js'
13
+ import { REAL_AGENT_DIR, REAL_DATA_TIMEOUT_MS } from './real-data.js'
14
14
 
15
15
  // ---- fixture 常量(uuid 特征,满足 extractSessionIdFromFilename + 互不为子串)----
16
16
  const ROOT = '0aaaaaaa-bbbb-7ccc-dddd-000000000001'
17
17
  const FORK = '0aaaaaaa-bbbb-7ccc-dddd-000000000002'
18
18
  const SUB_REAL = '0aaaaaaa-bbbb-7ccc-dddd-000000000003'
19
19
 
20
- /** 真实 pi agent 目录(本机),用于集成测试。 */
21
- const REAL_AGENT_DIR = '/Users/zhushanwen/.pi/agent'
22
-
23
20
  // 同步探测真实 session 是否存在(不存在则 skip,避免在无该数据的机器上硬失败)
24
21
  function hasRealSession(sid: string): boolean {
25
22
  try {
@@ -30,11 +30,20 @@ import {
30
30
  hasRealSession,
31
31
  } from './real-data.js'
32
32
 
33
+ // fs-guard:TC6 的「~ 前缀展开」语义需要 homedir,但真实 homedir 根在白名单外(禁止向用户
34
+ // 主目录散落文件)。vi.mock 把 homedir 指到用例内 mkdtemp 的 tmp;fakeHome 未注入时回落真实值,
35
+ // real-data.ts 模块初始化时推导 REAL_AGENT_DIR 不受影响(模块初始化先于任何用例执行)。
36
+ const homeState = vi.hoisted(() => ({ fakeHome: undefined as string | undefined }))
37
+ vi.mock('node:os', async (importOriginal) => {
38
+ const actual = await importOriginal<typeof import('node:os')>()
39
+ return { ...actual, homedir: () => homeState.fakeHome ?? actual.homedir() }
40
+ })
41
+
33
42
  /**
34
43
  * M3 tool-handler 集成测试。
35
44
  *
36
45
  * 测试框架 vitest(禁止 node:test/tsx)。直接调 handleSessionRead(纯逻辑,agentDir 注入),
37
- * 传真实 `/Users/zhushanwen/.pi/agent` 作 agentDir——用本机真实历史 session 数据,无需 mock。
46
+ * 传真实 `~/.pi/agent` 作 agentDir——用本机真实历史 session 数据,无需 mock。
38
47
  *
39
48
  * 覆盖全 11 action 主路径 + F1(find 零匹配)/F4(turn 越界)/F5(缺参)/resolveSessionId 片段等价,
40
49
  * 以及 u8 doctor / u9 归一化 / u10 find 分组 / u11 metadataProvider 缓存 / u12 跨会话检索(各见对应 describe)。
@@ -95,14 +104,21 @@ describe.skipIf(!HAS_REAL)('handleSessionRead', () => {
95
104
  })
96
105
 
97
106
  it('6. export outline materializes a .md file', async () => {
98
- const r = await handleSessionRead(
99
- { action: 'export', session: E6, format: 'outline' },
100
- { agentDir: REAL },
101
- )
102
- const d = r.details as { path: string; sizeBytes: number }
103
- expect(d.path).toMatch(/\.md$/)
104
- expect(existsSync(d.path)).toBe(true)
105
- expect(d.sizeBytes).toBeGreaterThan(0)
107
+ // fs-guard:export 写目标 = <agentDir>/tmp/,agentDir 传 REAL 会 mkdir 真实 ~/.pi/agent/tmp
108
+ // (白名单外)。读侧经 liveSessionDir=[live] 根仍扫真实数据,写侧落 mkdtemp tmp 自建自删。
109
+ const dir = await mkdtemp(join(tmpdir(), 'tool-handler-export-'))
110
+ try {
111
+ const r = await handleSessionRead(
112
+ { action: 'export', session: E6, format: 'outline' },
113
+ { agentDir: dir, liveSessionDir: join(REAL, 'sessions') },
114
+ )
115
+ const d = r.details as { path: string; sizeBytes: number }
116
+ expect(d.path).toMatch(/\.md$/)
117
+ expect(existsSync(d.path)).toBe(true)
118
+ expect(d.sizeBytes).toBeGreaterThan(0)
119
+ } finally {
120
+ await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
121
+ }
106
122
  })
107
123
 
108
124
  // w1 合并 subagent 候选后,本机 subagent task 文本(含本用例 query 字符串本身,因当前
@@ -643,7 +659,7 @@ describe('extract commits 双路径(fixture:git-cmd 主路径 + commit-conte
643
659
  message: {
644
660
  role: 'assistant',
645
661
  content: [
646
- { type: 'toolCall', id: 'tc-read-1', name: 'read', arguments: { path: '/tmp/log.txt' } },
662
+ { type: 'toolCall', id: 'tc-read-1', name: 'read', arguments: { path: join(tmpdir(), 'log.txt') } },
647
663
  ],
648
664
  },
649
665
  }),
@@ -667,7 +683,7 @@ describe('extract commits 双路径(fixture:git-cmd 主路径 + commit-conte
667
683
  message: {
668
684
  role: 'assistant',
669
685
  content: [
670
- { type: 'toolCall', id: 'tc-read-2', name: 'read', arguments: { path: '/tmp/out.txt' } },
686
+ { type: 'toolCall', id: 'tc-read-2', name: 'read', arguments: { path: join(tmpdir(), 'out.txt') } },
671
687
  ],
672
688
  },
673
689
  }),
@@ -724,7 +740,7 @@ describe('extract commits 双路径(fixture:git-cmd 主路径 + commit-conte
724
740
  message: {
725
741
  role: 'assistant',
726
742
  content: [
727
- { type: 'toolCall', id: 'tc-read-1', name: 'read', arguments: { path: '/tmp/deploy.txt' } },
743
+ { type: 'toolCall', id: 'tc-read-1', name: 'read', arguments: { path: join(tmpdir(), 'deploy.txt') } },
728
744
  ],
729
745
  },
730
746
  }),
@@ -933,24 +949,33 @@ describe('resolveSessionId ① 绝对路径形态(w2 TC2-TC6)', () => {
933
949
  })
934
950
 
935
951
  it('TC6: ~ 前缀展开到 homedir(文件实际在 homedir 下)', async () => {
936
- const home = homedir()
937
- const tmpUnderHome = await mkdtemp(join(home, '.sr-w2-test-'))
952
+ // fs-guard:真实 homedir 根在白名单外(禁止在用户主目录根散落 .sr-w2-test-*)——
953
+ // homedir 经文件头 vi.mock 指到 mkdtemp tmp(白名单内),「~ 前缀展开」断言语义不变
954
+ const fakeHome = await mkdtemp(join(tmpdir(), 'tool-handler-tc6-home-'))
955
+ homeState.fakeHome = fakeHome
938
956
  try {
939
- const fileId = '019e6c96-dddd-eeee-ffff-0000000006c1'
940
- const sessionFile = join(tmpUnderHome, 's.jsonl')
941
- await writeFile(
942
- sessionFile,
943
- JSON.stringify({ type: 'session', id: fileId, cwd: '/demo' }) + '\n',
944
- )
945
- // ~/开头的相对 homedir 路径
946
- const tildePath = '~/' + sessionFile.slice(home.length + 1)
947
- const r = await handleSessionRead(
948
- { action: 'export', session: tildePath, format: 'outline' },
949
- { agentDir: dir },
950
- )
951
- expect((r.details as { path: string }).path).toContain(fileId)
957
+ const home = homedir()
958
+ const tmpUnderHome = await mkdtemp(join(home, '.sr-w2-test-'))
959
+ try {
960
+ const fileId = '019e6c96-dddd-eeee-ffff-0000000006c1'
961
+ const sessionFile = join(tmpUnderHome, 's.jsonl')
962
+ await writeFile(
963
+ sessionFile,
964
+ JSON.stringify({ type: 'session', id: fileId, cwd: '/demo' }) + '\n',
965
+ )
966
+ // ~/开头的相对 homedir 路径
967
+ const tildePath = '~/' + sessionFile.slice(home.length + 1)
968
+ const r = await handleSessionRead(
969
+ { action: 'export', session: tildePath, format: 'outline' },
970
+ { agentDir: dir },
971
+ )
972
+ expect((r.details as { path: string }).path).toContain(fileId)
973
+ } finally {
974
+ await rm(tmpUnderHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
975
+ }
952
976
  } finally {
953
- await rm(tmpUnderHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
977
+ homeState.fakeHome = undefined
978
+ await rm(fakeHome, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
954
979
  }
955
980
  })
956
981
  })
@@ -1801,8 +1826,8 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1801
1826
  }
1802
1827
 
1803
1828
  /**
1804
- * xyz-agent 形态四根 fixture:dataDir 目录名带 .xyz-agent 前缀(detectEnvironment 的
1805
- * PI_CODING_AGENT_DIR 形态判据 `<*>/.xyz-agent*\/agent` 可命中,托管判定走正态)。
1829
+ * taiji 形态四根 fixture:dataDir 目录名带 .taiji 前缀(detectEnvironment 的
1830
+ * PI_CODING_AGENT_DIR 形态判据 `<*>/.taiji*\/agent` 可命中,托管判定走正态)。
1806
1831
  * live=agent/sessions 与 default 同字面路径(default 去重)、legacy 独立非空、subagent 独立。
1807
1832
  */
1808
1833
  async function buildFourRootFixture(): Promise<{
@@ -1810,7 +1835,7 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1810
1835
  agentDir: string
1811
1836
  signals: SessionReadSignals
1812
1837
  }> {
1813
- const dataDir = join(tmp, '.xyz-agent-fixture')
1838
+ const dataDir = join(tmp, '.taiji-fixture')
1814
1839
  const agentDir = join(dataDir, 'agent')
1815
1840
  await writeJsonl(join(agentDir, 'sessions', SLUG, 'a.jsonl'), 'aaaaaaaa')
1816
1841
  await writeJsonl(join(agentDir, 'sessions', SLUG, 'b.jsonl'), 'bbbbbbbb')
@@ -1823,9 +1848,9 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1823
1848
  agentDir,
1824
1849
  liveSessionDir: join(agentDir, 'sessions'),
1825
1850
  env: {
1826
- XYZ_AGENT_EXT_LOG: '1',
1851
+ TAIJI_AGENT_EXT_LOG: '1',
1827
1852
  PI_CODING_AGENT_DIR: agentDir,
1828
- XYZ_AGENT_DATA_DIR: dataDir,
1853
+ TAIJI_AGENT_DATA_DIR: dataDir,
1829
1854
  },
1830
1855
  },
1831
1856
  }
@@ -1843,8 +1868,8 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1843
1868
  const r = await handleSessionRead({ action: 'doctor' }, signals)
1844
1869
  const text = r.content[0].text
1845
1870
 
1846
- // 环境判定行:双信号合取 → xyz-agent(托管)+ 数据目录 + evidence 正态
1847
- expect(text).toContain('环境判定:xyz-agent(托管)')
1871
+ // 环境判定行:双信号合取 → taiji(托管)+ 数据目录 + evidence 正态
1872
+ expect(text).toContain('环境判定:taiji(托管)')
1848
1873
  expect(text).toContain(`数据目录:${dataDir}`)
1849
1874
  expect(text).toContain('依据:')
1850
1875
  expect(text).toContain(`PI_CODING_AGENT_DIR='${agentDir}' → 形态匹配`)
@@ -1887,7 +1912,7 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1887
1912
  const { signals } = await buildFourRootFixture()
1888
1913
  const positive = await handleSessionRead({ action: 'doctor' }, signals)
1889
1914
  expect(positive.content[0].text).toContain(
1890
- `告警:[legacy] 根 ${join(tmp, '.xyz-agent-fixture', 'sessions')} 非空(1 文件)`,
1915
+ `告警:[legacy] 根 ${join(tmp, '.taiji-fixture', 'sessions')} 非空(1 文件)`,
1891
1916
  )
1892
1917
 
1893
1918
  // 反态:legacy 目录不存在
@@ -1908,43 +1933,6 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1908
1933
  expect(deduped.content[0].text).toContain('已去重')
1909
1934
  })
1910
1935
 
1911
- it('独立 glob 残留探测:pi/ 含 agent|sessions 形态列出 + 迁移指引;pi.backup-v2-* 备份列出;基点 = dirname(agentDir)', async () => {
1912
- const dataDir = join(tmp, 'data')
1913
- const agentDir = join(dataDir, 'agent')
1914
- await writeJsonl(join(agentDir, 'sessions', SLUG, 'a.jsonl'))
1915
- // 未迁移旧布局 pi/(含 agent/ 形态)
1916
- await mkdir(join(dataDir, 'pi', 'agent'), { recursive: true })
1917
- await writeJsonl(join(dataDir, 'pi', 'agent', 'sessions', 'old.jsonl'), 'old-id')
1918
- // 迁移备份(含 sessions/ 形态——备份即原 pi/ 改名)
1919
- const backup = join(dataDir, 'pi.backup-v2-1725900000000')
1920
- await mkdir(join(backup, 'sessions'), { recursive: true })
1921
- await writeJsonl(join(backup, 'sessions', 'bak.jsonl'), 'bak-id')
1922
-
1923
- const r = await handleSessionRead({ action: 'doctor' }, { agentDir })
1924
- const text = r.content[0].text
1925
- expect(text).toContain(`旧布局残留探测(基点 ${dataDir})`)
1926
- expect(text).toContain(` - ${join(dataDir, 'pi')}(未迁移旧布局 pi/)`)
1927
- expect(text).toContain(` - ${backup}(迁移备份 pi.backup-v2-*)`)
1928
- // 同一迁移指引(§6.11:doctor 与 u14b 启动探测同指引)
1929
- expect(text).toContain('👉 关闭应用后运行 scripts/migrate-pi-layout-v2.mjs 完成迁移。')
1930
-
1931
- const d = r.details as DoctorDetails
1932
- expect(d.globBase).toBe(dataDir)
1933
- expect(d.leftovers.map((l) => l.kind).sort()).toEqual(['backup', 'unmigrated'])
1934
- })
1935
-
1936
- it('残留探测反态:pi/ 空壳(无 agent|sessions 子目录)不报——防纯 pi 宿主 ~/.pi/pi/ 误报(§6.11 v9.1)', async () => {
1937
- const dataDir = join(tmp, 'data')
1938
- const agentDir = join(dataDir, 'agent')
1939
- await writeJsonl(join(agentDir, 'sessions', SLUG, 'a.jsonl'))
1940
- // 纯 pi 宿主下任意来源的 pi/ 目录:只有无关子目录,无 agent/ 与 sessions/ 形态
1941
- await mkdir(join(dataDir, 'pi', 'extensions'), { recursive: true })
1942
-
1943
- const r = await handleSessionRead({ action: 'doctor' }, { agentDir })
1944
- expect(r.content[0].text).not.toContain('旧布局残留')
1945
- expect((r.details as DoctorDetails).leftovers).toEqual([])
1946
- })
1947
-
1948
1936
  it('subagent 根默认不扫(只列路径与可扫性);includeSubagents:true 才扫出文件数(§6.3)', async () => {
1949
1937
  const { agentDir, signals } = await buildFourRootFixture()
1950
1938
 
@@ -1995,7 +1983,7 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1995
1983
  const r = await handleSessionRead({ action: 'doctor' }, { agentDir })
1996
1984
  const text = r.content[0].text
1997
1985
  expect(text).toContain('环境判定:standalone-pi · 发行形态:未知(不猜)')
1998
- expect(text).toContain('XYZ_AGENT_EXT_LOG=<未设置> → 未命中')
1986
+ expect(text).toContain('TAIJI_AGENT_EXT_LOG=<未设置> → 未命中')
1999
1987
  expect(text).toContain('会话根(按优先级)')
2000
1988
  expect(text).toContain('[default]')
2001
1989
  expect(text).toContain(
@@ -2003,13 +1991,12 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
2003
1991
  )
2004
1992
  })
2005
1993
 
2006
- it('空 agentDir 防御:无根表、诊断「无候选根」,残留探测不触发(不扫 cwd 相对路径)', async () => {
1994
+ it('空 agentDir 防御:无根表、诊断「无候选根」(不扫 cwd 相对路径)', async () => {
2007
1995
  const r = await handleSessionRead({ action: 'doctor' }, { agentDir: '' })
2008
1996
  const text = r.content[0].text
2009
1997
  expect(text).toContain('诊断:无候选根')
2010
1998
  expect(text).not.toContain('[default]')
2011
1999
  expect((r.details as DoctorDetails).roots).toEqual([])
2012
- expect((r.details as DoctorDetails).leftovers).toEqual([])
2013
2000
  })
2014
2001
  })
2015
2002
 
@@ -2641,7 +2628,7 @@ describe('E1 find 单次根解析(探针 P1:resolveSessionRoots 恰 1 次)
2641
2628
  // ============================================================
2642
2629
  describe('u11 metadataProvider 注入 + TTL 缓存(fixture,§6.6)', () => {
2643
2630
  let tmp: string
2644
- /** 平铺 main 根(xyz-agent 形态),同时作 liveSessionDir(与 [default] 根同字面路径) */
2631
+ /** 平铺 main 根(taiji 形态),同时作 liveSessionDir(与 [default] 根同字面路径) */
2645
2632
  let flatMain: string
2646
2633
 
2647
2634
  async function writeSession(
@@ -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 字符」)。 */
@@ -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
@@ -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)
@@ -327,7 +327,7 @@ function isFlatRoot(root: SessionRoot): boolean {
327
327
  * 加载标题元数据索引(u11)。仅 keyword 路径调用(惰性触发:uuid 精确/归一化两级零命中
328
328
  * 且 query 非纯 hex 才到达此处,§6.6 调用策略 ①)。
329
329
  *
330
- * 窄化目标(策略 ②,按序):未剥层 liveSessionDir 本身(xyz-agent 下即平铺主根;纯 pi 下
330
+ * 窄化目标(策略 ②,按序):未剥层 liveSessionDir 本身(taiji 下即平铺主根;纯 pi 下
331
331
  * 即当前 cwd 目录,小)+ 实扫结果无子目录的候选根。两条 guard:
332
332
  * - 仅对存在的根/目录调用(root.exists / stat liveSessionDir),且永传非空串——pi 的
333
333
  * listAll 对空串 falsy 走默认全盘分支(数千项 / 秒级,hash-provider.ts 空参灾难先例);
@@ -24,7 +24,7 @@ import { performance } from 'node:perf_hooks'
24
24
 
25
25
  /** 发现层信号包(index.ts 采集,全部可降级;本模块只消费不采集) */
26
26
  export interface SessionRootSignals {
27
- /** pi agent 数据目录(两宿主均成立;B 后 xyz-agent = `<dataDir>/agent`) */
27
+ /** pi agent 数据目录(两宿主均成立;B 后 taiji = `<dataDir>/agent`) */
28
28
  agentDir: string
29
29
  /**
30
30
  * 宿主已解析的 live session 目录(`ctx.sessionManager.getSessionDir()`,§6.1 信号 1)。
@@ -66,7 +66,7 @@ const SUBAGENTS_DIRNAME = 'subagents'
66
66
 
67
67
  /**
68
68
  * `[live]` 规范化(§6.1/§7B 要点 3):`getSessionDir()` 在纯 pi 返回按 cwd 编码的子目录
69
- *(`<agentDir>/sessions/--Users-x--`),在 xyz-agent 覆盖态返回根本身。判据:basename
69
+ *(`<agentDir>/sessions/--Users-x--`),在 taiji 覆盖态返回根本身。判据:basename
70
70
  * 匹配 encodeCwd 形态(`--` 开头 `--` 结尾,实证见 `~/.pi/agent/sessions/` 全部子目录
71
71
  * 与 real-data.ts)则取 dirname,否则取自身。
72
72
  */
package/src/doctor.ts CHANGED
@@ -2,13 +2,10 @@
2
2
  * doctor action(u8:design 2026-09-10 §6.2/§6.3/§6.4/§7B 要点 2/4/5/8 + §6.11 U14b 段)。
3
3
  *
4
4
  * 从 tool-handler.ts 机械提取(max-lines 拆分轮,零行为变更):环境判定 + 根表渲染 +
5
- * 旧布局残留 glob 探测。SessionReadSignals(u3 信号包超集)在域内定义,tool-handler
5
+ * SessionReadSignals(u3 信号包超集)在域内定义,tool-handler
6
6
  * re-export 供 index.ts 生产消费。原 u8 的扫描缓存机已删除(ext-simplify-04 U3):
7
7
  * 每次调用实扫,无缓存命中语义;TTL/mtime 判定随 metadata 缓存留在 tool-handler 侧。
8
8
  */
9
- import { existsSync } from 'node:fs'
10
- import { readdir } from 'node:fs/promises'
11
- import { join, dirname } from 'node:path'
12
9
  import { detectEnvironment, type DetectedEnvironment } from './discovery/env.js'
13
10
  import { resolveSessionRoots, type SessionRoot, type SessionRootSignals } from './discovery/roots.js'
14
11
  import type { SessionReadParams, ToolResult } from './tool-handler.js'
@@ -29,52 +26,8 @@ export interface SessionReadSignals extends SessionRootSignals {
29
26
  /** doctor 根表来源标签列宽(最长 `[subagent]` 10 字符 + 1 对齐间距,§5.1 表格形态)。 */
30
27
  const DOCTOR_ROOT_LABEL_PAD = 11
31
28
 
32
- /** 旧布局残留条目(§6.11 U14b doctor 侧独立 glob 探测)。 */
33
- /** 旧布局残留探测单条命中(doctor 与 u14b 启动探测同源判据,§6.11)。 */
34
- export interface PiLayoutLeftover {
35
- path: string
36
- kind: 'backup' | 'unmigrated'
37
- }
38
-
39
29
  /**
40
- * 旧布局残留独立 glob 探测(§6.11 v9.1)。基点 = dirname(agentDir)(xyz-agent 下 =
41
- * `<dataDir>`,纯 pi 下 = `~/.pi`);`pi/`(未迁移)与 `pi.backup-v2-` 前缀目录(迁移备份)
42
- * 都不在任何候选根推导式内([legacy] 够不到带时间戳的备份名与 pi/ 层),故须独立探测。
43
- *
44
- * 形态判据(与 u14b 启动探测 / u14a 脚本 0a 同源):目录下含 `agent/` 或 `sessions/`
45
- * 子目录才算残留——防纯 pi 宿主下任意来源的 `~/.pi/pi/` 目录误报。读探测,每次现查
46
- * 不入缓存(两次 readdir 成本可忽略,缓存只服务扫盘贵的根)。
47
- *
48
- * 迁移期支持(A4 登记):本探测服务于 v2 布局迁移窗口,全量迁移确认后可整段删除。
49
- */
50
- async function detectPiLayoutLeftovers(
51
- agentDir: string,
52
- ): Promise<{ base: string; hits: PiLayoutLeftover[] }> {
53
- const base = dirname(agentDir)
54
- if (agentDir.length === 0) return { base, hits: [] }
55
- let names: string[]
56
- try {
57
- names = (await readdir(base, { withFileTypes: true }))
58
- .filter((e) => e.isDirectory())
59
- .map((e) => e.name)
60
- } catch {
61
- return { base, hits: [] }
62
- }
63
- const hits: PiLayoutLeftover[] = []
64
- for (const name of names) {
65
- const isUnmigrated = name === 'pi'
66
- const isBackup = name.startsWith('pi.backup-v2-')
67
- if (!isUnmigrated && !isBackup) continue
68
- const dir = join(base, name)
69
- // 形态判据:含 agent/ 或 sessions/ 子目录(迁移备份必含其一——备份即原 pi/ 改名)
70
- if (!existsSync(join(dir, 'agent')) && !existsSync(join(dir, 'sessions'))) continue
71
- hits.push({ path: dir, kind: isBackup ? 'backup' : 'unmigrated' })
72
- }
73
- return { base, hits }
74
- }
75
-
76
- /**
77
- * doctor:环境判定 + 根表 + 事实型诊断 + legacy 非空告警 + 旧布局残留探测。
30
+ * doctor:环境判定 + 根表 + 事实型诊断 + legacy 非空告警。
78
31
  *
79
32
  * 诊断结论只陈述事实(「最高优先级 main 根 N 文件」),禁止「真的没有 session」类
80
33
  * 归因断言(§7B 要点 5 / §3.3 教训);env/bundleUrl 信号缺失时环境判定按降级输出,
@@ -94,8 +47,7 @@ export async function doDoctor(
94
47
  // 才扫。无缓存——每次实扫(原缓存机已删,ext-simplify-04 U3,§7B 要点 8 新态)。
95
48
  subagents: params.includeSubagents === true ? 'scan' : 'stat',
96
49
  })
97
- const leftovers = await detectPiLayoutLeftovers(signals.agentDir)
98
- return renderDoctor(roots, environment, leftovers)
50
+ return renderDoctor(roots, environment)
99
51
  }
100
52
 
101
53
  /**
@@ -105,24 +57,21 @@ export async function doDoctor(
105
57
  export interface DoctorDetails {
106
58
  environment: DetectedEnvironment
107
59
  roots: SessionRoot[]
108
- leftovers: PiLayoutLeftover[]
109
- globBase: string
110
60
  }
111
61
 
112
62
  /**
113
63
  * 渲染 doctor 输出(§5.1 形态):环境判定 + evidence + 会话根表(来源标签/路径/存在/
114
- * 文件数/扫描耗时/去重注记)+ 事实型诊断 + legacy 非空告警 + 旧布局残留(命中才出现)。
64
+ * 文件数/扫描耗时/去重注记)+ 事实型诊断 + legacy 非空告警。
115
65
  */
116
66
  function renderDoctor(
117
67
  roots: SessionRoot[],
118
68
  environment: DetectedEnvironment,
119
- leftovers: { base: string; hits: PiLayoutLeftover[] },
120
69
  ): ToolResult {
121
70
  const lines: string[] = []
122
71
 
123
72
  // 环境判定行 + evidence(§6.2:判定可被证据推翻,非黑盒断言;evidence 恒非空)
124
73
  lines.push(
125
- `环境判定:${environment.kind === 'xyz-agent' ? 'xyz-agent(托管)' : 'standalone-pi'}` +
74
+ `环境判定:${environment.kind === 'taiji' ? 'taiji(托管)' : 'standalone-pi'}` +
126
75
  ` · 发行形态:${environment.distribution ?? '未知(不猜)'}`,
127
76
  )
128
77
  if (environment.dataDir !== undefined) lines.push(`数据目录:${environment.dataDir}`)
@@ -172,25 +121,11 @@ function renderDoctor(
172
121
  )
173
122
  }
174
123
 
175
- // 旧布局残留(§6.11:命中才列,附同一迁移指引)
176
- if (leftovers.hits.length > 0) {
177
- lines.push('')
178
- lines.push(`旧布局残留探测(基点 ${leftovers.base}):`)
179
- for (const h of leftovers.hits) {
180
- lines.push(
181
- ` - ${h.path}(${h.kind === 'backup' ? '迁移备份 pi.backup-v2-*' : '未迁移旧布局 pi/'})`,
182
- )
183
- }
184
- lines.push(' 👉 关闭应用后运行 scripts/migrate-pi-layout-v2.mjs 完成迁移。')
185
- }
186
-
187
124
  return {
188
125
  content: [{ type: 'text', text: lines.join('\n') }],
189
126
  details: {
190
127
  environment,
191
128
  roots,
192
- leftovers: leftovers.hits,
193
- globBase: leftovers.base,
194
129
  } satisfies DoctorDetails,
195
130
  }
196
131
  }
package/src/index.ts CHANGED
@@ -157,7 +157,7 @@ const guidelines = [
157
157
  "workflow action to see workflow run overviews (status/budget/steps). Each step's call sessionId can jump to outline/detail for deep reading.",
158
158
  "result action to fetch a subagent's final result text (same content as its completion notice): session takes a single sa-id/uuid/path or a comma-separated batch of at most 10; optional limit caps chars per item (default 8000, truncated items carry a pointer to the full file).",
159
159
  'Errors carry a 👉 recovery hint—follow it to retry in one step.',
160
- 'Unsure about the host environment (pure pi vs xyz-agent) or where session roots live? Run action:"doctor" first—it prints the detected environment and every candidate session root.',
160
+ 'Unsure about the host environment (pure pi vs taiji) or where session roots live? Run action:"doctor" first—it prints the detected environment and every candidate session root.',
161
161
  ]
162
162
 
163
163
  // ---- 工具 description(design §3.4,照搬措辞)----
@@ -231,7 +231,7 @@ export default function sessionReaderExtension(pi: ExtensionAPI): void {
231
231
 
232
232
  // ── M4 TUI 层(design §1 + §3.3 D-3/D-4 + 附录 P-hash-trigger)──────────
233
233
  // # 引用补全 provider + /session-pick 命令(命令名避开 pi 内置 /session 冲突)。仅 ctx.mode === 'tui' 注册:RPC 模式
234
- // (xyz-agent 子进程)不用 pi TUI editor / slash 命令,加载即跳过。
234
+ // (taiji 子进程)不用 pi TUI editor / slash 命令,加载即跳过。
235
235
  //
236
236
  // addAutocompleteProvider 挂在 ctx.ui(非 ExtensionAPI),setup 入口无 ctx,
237
237
  // 只能在 event handler 里拿——session_start 是最早且每 session 触发的 event。
@@ -1,5 +1,5 @@
1
1
  /**
2
- * result action(subagent-sync-collect U6:design subagent-sync-collect.md §3.1.3)。
2
+ * result action。
3
3
  *
4
4
  * 从 tool-handler.ts 机械提取(max-lines 拆分轮):纯移动零行为变更。纯函数 helper
5
5
  *(err/stripHash/requireStr/SESSION_ID_PREFIX_LEN)直接 import 自 handler-utils
@@ -16,6 +16,7 @@ import {
16
16
  import { parseSessionFile } from './core/parser.js'
17
17
  import { segmentTurns, type Turn } from './core/turns.js'
18
18
  import { buildTreeView } from './core/tree.js'
19
+ import { BYTES_PER_KB, BYTES_PER_MB } from './core/units.js'
19
20
  import { err, pad } from './handler-utils.js'
20
21
  import type { SessionReadSignals } from './doctor.js'
21
22
  import type { SessionReadParams, ToolResult } from './tool-handler.js'
@@ -163,10 +164,6 @@ export function formatSearchText(
163
164
  */
164
165
  export const MULTI_SEARCH_MAX_SESSIONS = 10
165
166
 
166
- /** 字节展示/预算换算基数(人话格式化与预算常量共用同一量纲)。 */
167
- const BYTES_PER_KB = 1024
168
- const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB
169
-
170
167
  /** 跨会话单次检索的预算 MB 数(§8.2 V8 字节上限的量纲;换算见 SEARCH_SCAN_BYTE_BUDGET)。 */
171
168
  const SEARCH_SCAN_BUDGET_MB = 64
172
169
 
package/vitest.config.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import path from 'node:path'
2
2
 
3
- import { defineConfig } from 'vitest/config'
3
+ import { taijiTestConfig } from '../../../test-guard/factory.ts'
4
4
 
5
- export default defineConfig({
5
+ export default taijiTestConfig({
6
6
  test: {
7
- reporters: ['default', 'junit'],
8
- outputFile: { junit: './test-results/vitest-junit.xml' },
9
7
  include: ['src/__tests__/**/*.test.ts'],
10
8
  },
11
9
  resolve: {