@zhushanwen/pi-session-reader 0.11.0 → 0.12.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.
Files changed (38) hide show
  1. package/package.json +5 -5
  2. package/src/__tests__/cross-package-subagent-core.test.ts +34 -25
  3. package/src/__tests__/entry-anchor.test.ts +3 -3
  4. package/src/__tests__/execution-tree.test.ts +85 -86
  5. package/src/__tests__/family-zcode.test.ts +24 -14
  6. package/src/__tests__/family.test.ts +4 -4
  7. package/src/__tests__/find.test.ts +50 -5
  8. package/src/__tests__/index.test.ts +3 -3
  9. package/src/__tests__/rebuild-manifest-dual-write.test.ts +29 -22
  10. package/src/__tests__/render.test.ts +2 -2
  11. package/src/__tests__/result.test.ts +20 -11
  12. package/src/__tests__/roots.test.ts +64 -26
  13. package/src/__tests__/subagents.test.ts +108 -52
  14. package/src/__tests__/tool-handler.test.ts +133 -83
  15. package/src/__tests__/workflow.test.ts +5 -5
  16. package/src/__tests__/zcode-manifest.test.ts +18 -10
  17. package/src/__tests__/zcode-routing.test.ts +21 -19
  18. package/src/__tests__/zcode-whitelist.test.ts +1 -1
  19. package/src/core/family.ts +6 -6
  20. package/src/core/render.ts +2 -2
  21. package/src/core/toolcall.ts +1 -1
  22. package/src/core/workflow.ts +9 -9
  23. package/src/discovery/entry-anchor.ts +5 -5
  24. package/src/discovery/find.ts +9 -1
  25. package/src/discovery/roots.ts +95 -7
  26. package/src/discovery/subagents.ts +24 -17
  27. package/src/discovery/whitelist.ts +4 -4
  28. package/src/discovery/workflows.ts +8 -8
  29. package/src/discovery/zcode-manifest.ts +22 -27
  30. package/src/extract.ts +1 -1
  31. package/src/index.ts +2 -2
  32. package/src/no-match.ts +1 -1
  33. package/src/result-action.ts +1 -1
  34. package/src/search-across.ts +3 -3
  35. package/src/tool-format.ts +12 -12
  36. package/src/tool-handler.ts +27 -19
  37. package/src/tui/session-command.ts +1 -1
  38. package/src/zcode-anchor-classify.ts +2 -2
@@ -8,7 +8,11 @@ import {
8
8
  extractSessionIdFromFilename,
9
9
  type RecordManifest,
10
10
  } from '../discovery/subagents.js'
11
- import { resolveSessionRoots } from '../discovery/roots.js'
11
+ import {
12
+ resolveSessionRoots,
13
+ resolveStoreSubagentSessionsDir,
14
+ resolveStoreSubagentRecordsDir,
15
+ } from '../discovery/roots.js'
12
16
  import { parseSessionHeader, readSessionHeaderFirstLine } from '../discovery/session-header.js'
13
17
 
14
18
  // ---- fixture 常量(uuid 特征,满足 extractSessionIdFromFilename + 互不为子串)----
@@ -18,10 +22,20 @@ const SUB_REAL = '0aaaaaaa-bbbb-7ccc-dddd-000000000003'
18
22
 
19
23
  // ---- fixture helpers ----
20
24
 
25
+ /**
26
+ * 建 mkdtemp dataDir 根(生产拓扑复刻,[session-store-layout-inversion] 布局 B):
27
+ * agentDir = `<dataDir>/agent`(调用方经 agentDirOf 推导并传入被测函数),存储树 =
28
+ * `<dataDir>/ext-sessions`(fixture 经 roots.ts 生产同源推导函数落盘,禁手拼树根段字面量)。
29
+ */
21
30
  async function makeAgentDir(): Promise<string> {
22
31
  return mkdtemp(join(tmpdir(), 'subagents-test-'))
23
32
  }
24
33
 
34
+ /** agentDir 推导(生产拓扑:agentDir = <dataDir>/agent,session-reader 的 agentDir 语义)。 */
35
+ function agentDirOf(dataDir: string): string {
36
+ return join(dataDir, 'agent')
37
+ }
38
+
25
39
  /** 写主 session 文件(首行 header)。返回绝对路径(供 fork 的 parentSession 指向)。 */
26
40
  async function writeMainSession(
27
41
  dir: string,
@@ -39,21 +53,22 @@ async function writeMainSession(
39
53
  }
40
54
 
41
55
  /**
42
- * 写 subagent session 文件:首行 header(真实 id)+ 占位 message + 尾行 identity。
56
+ * 写 subagent session 文件(新布局存储树:`<root>/<rootSessionId>/subagents/sessions/`,
57
+ * 分区键 = identity.rootSessionId,生产同源 resolveStoreSubagentSessionsDir 推导):
58
+ * 首行 header(真实 id)+ 占位 message + 尾行 identity。
43
59
  * identity 在尾行(实测 pi 行为;subagent-identity 由 session-runner 在 session 创建后写)。
44
60
  * U4 扩展:identity.task/agent 可定制(P-fallback 测试用,默认 't'/'explorer')。
45
61
  */
46
62
  async function writeSubagentSession(
47
63
  dir: string,
48
- slug: string,
49
- realId: string,
64
+ realId: string,
50
65
  identity: { rootSessionId: string; slug: string; dataId?: string; task?: string; agent?: string },
51
66
  ): Promise<string> {
52
- const sessionDir = join(dir, 'subagents', slug, 'sessions')
67
+ const sessionDir = resolveStoreSubagentSessionsDir(dir, identity.rootSessionId)
53
68
  await mkdir(sessionDir, { recursive: true })
54
69
  const path = join(sessionDir, `${realId}.jsonl`)
55
70
  const lines = [
56
- JSON.stringify({ type: 'session', id: realId, cwd: `/proj/${slug}` }),
71
+ JSON.stringify({ type: 'session', id: realId, cwd: `/proj/${identity.slug}` }),
57
72
  JSON.stringify({
58
73
  type: 'message',
59
74
  id: 'm1',
@@ -84,14 +99,14 @@ async function writeSubagentSession(
84
99
  */
85
100
  async function writeAliveSubagentNoIdentity(
86
101
  dir: string,
87
- slug: string,
102
+ rootSessionId: string,
88
103
  realId: string,
89
104
  ): Promise<string> {
90
- const sessionDir = join(dir, 'subagents', slug, 'sessions')
105
+ const sessionDir = resolveStoreSubagentSessionsDir(dir, rootSessionId)
91
106
  await mkdir(sessionDir, { recursive: true })
92
107
  const path = join(sessionDir, `${realId}.jsonl`)
93
108
  const lines = [
94
- JSON.stringify({ type: 'session', id: realId, cwd: `/proj/${slug}` }),
109
+ JSON.stringify({ type: 'session', id: realId, cwd: '/proj/root' }),
95
110
  JSON.stringify({
96
111
  type: 'message',
97
112
  id: 'm1',
@@ -103,10 +118,13 @@ async function writeAliveSubagentNoIdentity(
103
118
  return path
104
119
  }
105
120
 
106
- /** 写 records manifest(孤儿源)。U4 扩展:fields 支持富字段 task/slug/model/status。 */
121
+ /**
122
+ * 写 records manifest(孤儿源;新布局 `<root>/<rootSessionId>/subagents/records/`,
123
+ * 分区键 = fields.rootSessionId,生产同源 resolveStoreSubagentRecordsDir 推导)。
124
+ * U4 扩展:fields 支持富字段 task/slug/model/status。
125
+ */
107
126
  async function writeRecordManifest(
108
127
  dir: string,
109
- slug: string,
110
128
  id: string,
111
129
  fields: {
112
130
  rootSessionId: string
@@ -118,7 +136,7 @@ async function writeRecordManifest(
118
136
  status?: string
119
137
  },
120
138
  ): Promise<void> {
121
- const recordsDir = join(dir, 'subagents', slug, 'records')
139
+ const recordsDir = resolveStoreSubagentRecordsDir(dir, fields.rootSessionId)
122
140
  await mkdir(recordsDir, { recursive: true })
123
141
  await writeFile(join(recordsDir, `${id}.json`), JSON.stringify({ id, ...fields }))
124
142
  }
@@ -128,13 +146,15 @@ async function writeRecordManifest(
128
146
  // ============================================================
129
147
 
130
148
  describe('buildFamilyFromFs - fixture', () => {
131
- let dir: string
149
+ let dataDir: string
150
+ let dir: string // agentDir = <dataDir>/agent(生产拓扑复刻,见 makeAgentDir 注释)
132
151
 
133
152
  beforeEach(async () => {
134
- dir = await makeAgentDir()
153
+ dataDir = await makeAgentDir()
154
+ dir = agentDirOf(dataDir)
135
155
  })
136
156
  afterEach(async () => {
137
- await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
157
+ await rm(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
138
158
  })
139
159
 
140
160
  it('基础 family:root + fork + 隔代 subagent,SubagentRef.sessionId 是真实 id(非 sa-xxx)', async () => {
@@ -145,7 +165,7 @@ describe('buildFamilyFromFs - fixture', () => {
145
165
  parentSession: rootPath,
146
166
  })
147
167
  // subagent 挂在 fork 子代下(rootSessionId=FORK,非 ROOT)→ 隔代
148
- await writeSubagentSession(dir, '--fork-cwd--', SUB_REAL, {
168
+ await writeSubagentSession(dir, SUB_REAL, {
149
169
  rootSessionId: FORK,
150
170
  slug: 'test-sub',
151
171
  dataId: 'sa-placeholder-1',
@@ -174,7 +194,7 @@ describe('buildFamilyFromFs - fixture', () => {
174
194
  it('从 fork 子代 resolve 也能关联到挂在其下的 subagent', async () => {
175
195
  const rootPath = await writeMainSession(dir, '--root-cwd--', ROOT)
176
196
  await writeMainSession(dir, '--fork-cwd--', FORK, { parentSession: rootPath })
177
- await writeSubagentSession(dir, '--fork-cwd--', SUB_REAL, { rootSessionId: FORK, slug: 's' })
197
+ await writeSubagentSession(dir, SUB_REAL, { rootSessionId: FORK, slug: 's' })
178
198
 
179
199
  const family = await buildFamilyFromFs(FORK, dir)
180
200
  expect(family.subagents.some((s) => s.sessionId === SUB_REAL)).toBe(true)
@@ -183,7 +203,7 @@ describe('buildFamilyFromFs - fixture', () => {
183
203
  it('cleanedUp:manifest 孤儿(.jsonl 不存在)→ cleanedUp=true', async () => {
184
204
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
185
205
  // 孤儿 manifest:rootSessionId=ROOT,sessionFile 指向不存在路径(模拟 .jsonl 被 GC)
186
- await writeRecordManifest(dir, '--root-cwd--', 'sa-ghost-id', {
206
+ await writeRecordManifest(dir, 'sa-ghost-id', {
187
207
  rootSessionId: ROOT,
188
208
  agentName: 'explorer',
189
209
  sessionFile: '/nonexistent/ghost.jsonl',
@@ -203,12 +223,12 @@ describe('buildFamilyFromFs - fixture', () => {
203
223
  it('alive subagent 同时有 manifest → 不重复计数(manifest 跳过 alive)', async () => {
204
224
  const rootPath = await writeMainSession(dir, '--root-cwd--', ROOT)
205
225
  await writeMainSession(dir, '--fork-cwd--', FORK, { parentSession: rootPath })
206
- const subPath = await writeSubagentSession(dir, '--fork-cwd--', SUB_REAL, {
226
+ const subPath = await writeSubagentSession(dir, SUB_REAL, {
207
227
  rootSessionId: FORK,
208
228
  slug: 'dup-test',
209
229
  })
210
230
  // manifest 指向真实文件路径(alive)→ 应被跳过,不产生孤儿副本
211
- await writeRecordManifest(dir, '--fork-cwd--', `sa-${SUB_REAL}`, {
231
+ await writeRecordManifest(dir, `sa-${SUB_REAL}`, {
212
232
  rootSessionId: FORK,
213
233
  agentName: 'explorer',
214
234
  sessionFile: subPath,
@@ -231,16 +251,14 @@ describe('buildFamilyFromFs - fixture', () => {
231
251
  it('workflows:v2 注册条目 record 流直读提 calls;命中 pathToRef 取完整 ref,GC\'d 路径回退最小 ref', async () => {
232
252
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
233
253
  // 真实存在的 subagent(步骤 2 扫到 → pathToRef 命中 → 完整 SessionRef)
234
- const subPath = await writeSubagentSession(dir, '--root-cwd--', SUB_REAL, {
254
+ const subPath = await writeSubagentSession(dir, SUB_REAL, {
235
255
  rootSessionId: ROOT,
236
256
  slug: 'wf-sub',
237
257
  })
238
- // GC'd 路径(文件不存在 → pathToRef 未命中 → sessionRefFromPath 最小 ref)
258
+ // GC'd 路径(文件不存在 → pathToRef 未命中 → sessionRefFromPath 最小 ref);
259
+ // 新布局 subagent 会话路径形态(存储树分区,生产同源推导)
239
260
  const gced = join(
240
- dir,
241
- 'subagents',
242
- '--root-cwd--',
243
- 'sessions',
261
+ resolveStoreSubagentSessionsDir(dir, ROOT),
244
262
  '2026-08-07T16-49-48-393Z_019fdd21-8169-7a02-8f11-eef6c9ca11cc.jsonl',
245
263
  )
246
264
  // record 流:agent-settled 帧携带 result.sessionFile(两 call:命中 + GC 各一)
@@ -286,7 +304,7 @@ describe('buildFamilyFromFs - fixture', () => {
286
304
  expect(wf.calls[0].sessionId).toBe(SUB_REAL)
287
305
  expect(wf.calls[0].mtime).toBeGreaterThan(0)
288
306
  expect(wf.calls[0].sizeBytes).toBeGreaterThan(0)
289
- expect(wf.calls[0].cwd).toBe('/proj/--root-cwd--')
307
+ expect(wf.calls[0].cwd).toBe('/proj/wf-sub') // cwd 从 subagent 文件首行 header 透传(fixture header.cwd = /proj/<identity.slug>)
290
308
  // GC'd 未命中:fileName-only 最小 ref(sessionId 从文件名提取,mtime/size/cwd 占位)
291
309
  expect(wf.calls[1].fileName).toBe(gced)
292
310
  expect(wf.calls[1].sessionId).toBe('019fdd21-8169-7a02-8f11-eef6c9ca11cc')
@@ -298,14 +316,14 @@ describe('buildFamilyFromFs - fixture', () => {
298
316
  it('MF-3 回归:alive 但无 identity 的 subagent(运行中)不被收编为 cleanedUp——U4 后 manifest 主路径建族', async () => {
299
317
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
300
318
  // 运行中 subagent:有效 header、无 identity 尾行(identity 完成时才写入),manifest 已存在
301
- const subDir = join(dir, 'subagents', '--root-cwd--', 'sessions')
319
+ const subDir = resolveStoreSubagentSessionsDir(dir, ROOT)
302
320
  await mkdir(subDir, { recursive: true })
303
321
  const subPath = join(subDir, SUB_REAL + '.jsonl')
304
322
  await writeFile(
305
323
  subPath,
306
324
  JSON.stringify({ type: 'session', id: SUB_REAL, cwd: '/proj/root' }) + '\n',
307
325
  )
308
- await writeRecordManifest(dir, '--root-cwd--', `sa-${SUB_REAL}`, {
326
+ await writeRecordManifest(dir, `sa-${SUB_REAL}`, {
309
327
  rootSessionId: ROOT,
310
328
  agentName: 'explorer',
311
329
  sessionFile: subPath,
@@ -332,17 +350,19 @@ describe('buildFamilyFromFs - fixture', () => {
332
350
  // ============================================================
333
351
 
334
352
  describe('listRecordManifests 导出(w2 TC1)', () => {
335
- let dir: string
353
+ let dataDir: string
354
+ let dir: string // agentDir = <dataDir>/agent(生产拓扑复刻,见 makeAgentDir 注释)
336
355
 
337
356
  beforeEach(async () => {
338
- dir = await makeAgentDir()
357
+ dataDir = await makeAgentDir()
358
+ dir = agentDirOf(dataDir)
339
359
  })
340
360
  afterEach(async () => {
341
- await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
361
+ await rm(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
342
362
  })
343
363
 
344
364
  it('导出生效:import + 调用返回 RecordManifest[],字段完整', async () => {
345
- await writeRecordManifest(dir, '--demo-cwd--', 'sa-tc1', {
365
+ await writeRecordManifest(dir, 'sa-tc1', {
346
366
  rootSessionId: 'root-1',
347
367
  agentName: 'explorer',
348
368
  sessionFile: '/tmp/sa-tc1.jsonl',
@@ -355,6 +375,38 @@ describe('listRecordManifests 导出(w2 TC1)', () => {
355
375
  expect(m.agentName).toBe('explorer')
356
376
  expect(m.sessionFile).toBe('/tmp/sa-tc1.jsonl')
357
377
  })
378
+
379
+ it('D6 单新根锚定:旧布局 <agentDir>/subagents 造 record + 新根不造 → 不返回(存量不可见,防回归双根扫描)', async () => {
380
+ // 旧布局存量原位保留(D6 裁决:不迁移、不再读写)——旧根造一份合法 manifest
381
+ const legacyRecordsDir = join(dir, 'subagents', '--legacy-cwd--', 'records')
382
+ await mkdir(legacyRecordsDir, { recursive: true })
383
+ await writeFile(
384
+ join(legacyRecordsDir, 'sa-legacy.json'),
385
+ JSON.stringify({ id: 'sa-legacy', rootSessionId: 'root-legacy', sessionFile: '/nonexistent/legacy.jsonl' }),
386
+ )
387
+ // 单新根语义:旧根 manifest 不进结果集(result/find 面旧记录不可见是 D6 取舍清单明示项)
388
+ expect(await listRecordManifests(dir)).toHaveLength(0)
389
+ })
390
+
391
+ it('D6 单新根锚定:非会话分区目录(isPiSessionId 不过)剪枝跳过,合法分区正常读', async () => {
392
+ const { resolveSessionStoreRoot } = await import('../discovery/roots.js')
393
+ const storeRoot = resolveSessionStoreRoot(dir)
394
+ // 非法分区名(前导 - 违反 isPiSessionId 首字符规则)下的 records 不扫
395
+ const bogus = join(storeRoot, '-bogus-partition', 'subagents', 'records')
396
+ await mkdir(bogus, { recursive: true })
397
+ await writeFile(
398
+ join(bogus, 'sa-bogus.json'),
399
+ JSON.stringify({ id: 'sa-bogus', rootSessionId: '-bogus-partition', sessionFile: '/nonexistent/b.jsonl' }),
400
+ )
401
+ // 合法分区在场
402
+ await writeRecordManifest(dir, 'sa-ok', {
403
+ rootSessionId: 'root-ok',
404
+ agentName: 'explorer',
405
+ sessionFile: '/tmp/sa-ok.jsonl',
406
+ })
407
+ const manifests = await listRecordManifests(dir)
408
+ expect(manifests.map((m) => m.id)).toEqual(['sa-ok'])
409
+ })
358
410
  })
359
411
 
360
412
  // ============================================================
@@ -363,25 +415,27 @@ describe('listRecordManifests 导出(w2 TC1)', () => {
363
415
  // ============================================================
364
416
 
365
417
  describe('U4 buildFamilyFromFs 富化(manifest 主 / P-fallback)', () => {
366
- let dir: string
418
+ let dataDir: string
419
+ let dir: string // agentDir = <dataDir>/agent(生产拓扑复刻,见 makeAgentDir 注释)
367
420
 
368
421
  beforeEach(async () => {
369
- dir = await makeAgentDir()
422
+ dataDir = await makeAgentDir()
423
+ dir = agentDirOf(dataDir)
370
424
  })
371
425
  afterEach(async () => {
372
- await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
426
+ await rm(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
373
427
  })
374
428
 
375
429
  it('TC-u4-manifest-enrich: alive + manifest 全字段 → SubagentRef 富字段全透传', async () => {
376
430
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
377
- const subPath = await writeSubagentSession(dir, '--root-cwd--', SUB_REAL, {
431
+ const subPath = await writeSubagentSession(dir, SUB_REAL, {
378
432
  rootSessionId: ROOT,
379
433
  slug: 'identity-slug', // identity.slug,manifest 主时被 manifest.slug 覆盖
380
434
  task: 'identity-task', // identity.task,manifest 主时被 manifest.task 覆盖
381
435
  agent: 'worker', // identity.agent,manifest 主时被 manifest.agentName 覆盖
382
436
  })
383
437
  // manifest 全字段(命中 meta.path 索引 → 走 manifest 主路径,覆盖 identity 值)
384
- await writeRecordManifest(dir, '--root-cwd--', `sa-${SUB_REAL}`, {
438
+ await writeRecordManifest(dir, `sa-${SUB_REAL}`, {
385
439
  rootSessionId: ROOT,
386
440
  agentName: 'explorer',
387
441
  sessionFile: subPath,
@@ -409,7 +463,7 @@ describe('U4 buildFamilyFromFs 富化(manifest 主 / P-fallback)', () => {
409
463
  it('TC-u4-pfallback-identity: alive 无 manifest + identity 含 task/agent → 回退 identity,model/status undefined', async () => {
410
464
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
411
465
  // 只写 alive session(含 identity 尾行),不写 manifest → P-fallback 路径
412
- const subPath = await writeSubagentSession(dir, '--root-cwd--', SUB_REAL, {
466
+ const subPath = await writeSubagentSession(dir, SUB_REAL, {
413
467
  rootSessionId: ROOT,
414
468
  slug: 'fix',
415
469
  task: 'fix bug',
@@ -434,7 +488,7 @@ describe('U4 buildFamilyFromFs 富化(manifest 主 / P-fallback)', () => {
434
488
  it('TC-u4-pfallback-no-identity: alive 无 manifest 无 identity(运行中)→ 不入 family.subagents,不抛错', async () => {
435
489
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
436
490
  // alive 文件无 identity 尾行(运行中/异常),无 manifest
437
- await writeAliveSubagentNoIdentity(dir, '--root-cwd--', SUB_REAL)
491
+ await writeAliveSubagentNoIdentity(dir, ROOT, SUB_REAL)
438
492
 
439
493
  // 核心断言:buildFamilyFromFs 不抛错、不崩溃
440
494
  const family = await buildFamilyFromFs(ROOT, dir)
@@ -447,7 +501,7 @@ describe('U4 buildFamilyFromFs 富化(manifest 主 / P-fallback)', () => {
447
501
  it('TC-u4-orphan-manifest: manifest 全字段 + sessionFile 指向不存在路径 → 孤儿 cleanedUp=true,富字段透传', async () => {
448
502
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
449
503
  const ghostPath = '/nonexistent/gc-ghost.jsonl' // .jsonl 已被 GC(不写该文件)
450
- await writeRecordManifest(dir, '--root-cwd--', 'sa-ghost-id', {
504
+ await writeRecordManifest(dir, 'sa-ghost-id', {
451
505
  rootSessionId: ROOT,
452
506
  agentName: 'worker',
453
507
  sessionFile: ghostPath,
@@ -475,7 +529,7 @@ describe('U4 buildFamilyFromFs 富化(manifest 主 / P-fallback)', () => {
475
529
  it('TC-u4-recordmanifest-compat: 旧 manifest(仅 id/rootSessionId/sessionFile)→ 富字段 undefined,不抛错', async () => {
476
530
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
477
531
  // 旧格式 manifest:无 task/slug/model/status/agentName
478
- await writeRecordManifest(dir, '--root-cwd--', 'sa-old', {
532
+ await writeRecordManifest(dir, 'sa-old', {
479
533
  rootSessionId: ROOT,
480
534
  sessionFile: '/nonexistent/old.jsonl',
481
535
  })
@@ -518,18 +572,20 @@ describe('U4 buildFamilyFromFs 富化(manifest 主 / P-fallback)', () => {
518
572
  // 扫描器即可与该文件族共存)。
519
573
 
520
574
  describe('.events 文件族结构性忽略(W1 D3 / 检查点④)', () => {
521
- let dir: string
575
+ let dataDir: string
576
+ let dir: string // agentDir = <dataDir>/agent(生产拓扑复刻,见 makeAgentDir 注释)
522
577
 
523
578
  beforeEach(async () => {
524
- dir = await makeAgentDir()
579
+ dataDir = await makeAgentDir()
580
+ dir = agentDirOf(dataDir)
525
581
  })
526
582
  afterEach(async () => {
527
- await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
583
+ await rm(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
528
584
  })
529
585
 
530
586
  /** 写 record 事件文件(首行头行 + 事件行集,形态对齐写侧 record-events.ts)。 */
531
- async function writeEventsFile(slug: string, saId: string): Promise<string> {
532
- const recordsDir = join(dir, 'subagents', slug, 'records')
587
+ async function writeEventsFile(saId: string): Promise<string> {
588
+ const recordsDir = resolveStoreSubagentRecordsDir(dir, ROOT)
533
589
  await mkdir(recordsDir, { recursive: true })
534
590
  const path = join(recordsDir, `${saId}.events`)
535
591
  const lines = [
@@ -543,11 +599,11 @@ describe('.events 文件族结构性忽略(W1 D3 / 检查点④)', () => {
543
599
 
544
600
  it('subagent 树扫描不收 .events:resolveSessionRoots 的 subagent 根 files 含 .jsonl 不含 .events', async () => {
545
601
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
546
- const subPath = await writeSubagentSession(dir, '--root-cwd--', SUB_REAL, {
602
+ const subPath = await writeSubagentSession(dir, SUB_REAL, {
547
603
  rootSessionId: ROOT,
548
604
  slug: 'ev-sibling',
549
605
  })
550
- await writeEventsFile('--root-cwd--', 'sa-ev-1')
606
+ await writeEventsFile('sa-ev-1')
551
607
 
552
608
  const roots = await resolveSessionRoots({ agentDir: dir })
553
609
  const subRoot = roots.find((r) => r.kind === 'subagent')
@@ -558,7 +614,7 @@ describe('.events 文件族结构性忽略(W1 D3 / 检查点④)', () => {
558
614
  })
559
615
 
560
616
  it('首行头行命中非 session header 即忽略:record-journal 头行 → parseSessionHeader 返回 null', async () => {
561
- const eventsPath = await writeEventsFile('--root-cwd--', 'sa-ev-1')
617
+ const eventsPath = await writeEventsFile('sa-ev-1')
562
618
  // 检查点④形态:session-reader 对未知文件读首行判 header,非 session 行 → null
563
619
  //(即使未来通配规则把 .events 收进候选集,该文件也不会被误认成 session)
564
620
  const firstLine = await readSessionHeaderFirstLine(eventsPath)
@@ -568,11 +624,11 @@ describe('.events 文件族结构性忽略(W1 D3 / 检查点④)', () => {
568
624
 
569
625
  it('家族扫描:.events 与正常 subagent 同目录共存 → 家族正常、不产幻影 subagent 节点', async () => {
570
626
  await writeMainSession(dir, '--root-cwd--', ROOT, { cwd: '/proj/root' })
571
- await writeSubagentSession(dir, '--root-cwd--', SUB_REAL, {
627
+ await writeSubagentSession(dir, SUB_REAL, {
572
628
  rootSessionId: ROOT,
573
629
  slug: 'ev-coexist',
574
630
  })
575
- await writeEventsFile('--root-cwd--', 'sa-ev-1')
631
+ await writeEventsFile('sa-ev-1')
576
632
 
577
633
  const family = await buildFamilyFromFs(ROOT, dir)
578
634