@zhushanwen/pi-session-reader 0.6.0 → 0.7.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 (48) hide show
  1. package/README.md +100 -0
  2. package/package.json +7 -4
  3. package/src/__tests__/cross-package-subagent-core.test.ts +36 -44
  4. package/src/__tests__/entry-anchor.test.ts +226 -0
  5. package/src/__tests__/env.test.ts +54 -54
  6. package/src/__tests__/execution-tree.test.ts +6 -0
  7. package/src/__tests__/family-zcode.test.ts +255 -0
  8. package/src/__tests__/family.test.ts +1 -1
  9. package/src/__tests__/find.test.ts +24 -1
  10. package/src/__tests__/index.test.ts +1 -1
  11. package/src/__tests__/parser.test.ts +1 -2
  12. package/src/__tests__/real-data.ts +22 -9
  13. package/src/__tests__/render.test.ts +1 -2
  14. package/src/__tests__/result.test.ts +2 -2
  15. package/src/__tests__/roots.test.ts +1 -1
  16. package/src/__tests__/subagents.test.ts +1 -4
  17. package/src/__tests__/tool-handler.test.ts +76 -85
  18. package/src/__tests__/toolcall.test.ts +1 -1
  19. package/src/__tests__/tree.test.ts +1 -1
  20. package/src/__tests__/turns.test.ts +1 -2
  21. package/src/__tests__/zcode-manifest.test.ts +361 -0
  22. package/src/__tests__/zcode-routing.test.ts +751 -0
  23. package/src/__tests__/zcode-whitelist.test.ts +119 -0
  24. package/src/core/execution-tree.ts +3 -1
  25. package/src/core/family.ts +80 -1
  26. package/src/core/render.ts +2 -3
  27. package/src/core/toolcall.ts +3 -4
  28. package/src/core/tree.ts +1 -1
  29. package/src/core/turns.ts +1 -1
  30. package/src/core/units.ts +14 -0
  31. package/src/discovery/entry-anchor.ts +108 -0
  32. package/src/discovery/env.ts +41 -41
  33. package/src/discovery/find.ts +1 -1
  34. package/src/discovery/roots.ts +2 -2
  35. package/src/discovery/session-header.ts +30 -69
  36. package/src/discovery/subagents.ts +9 -2
  37. package/src/discovery/whitelist.ts +87 -0
  38. package/src/discovery/workflows.ts +1 -1
  39. package/src/discovery/zcode-manifest.ts +277 -0
  40. package/src/doctor.ts +5 -70
  41. package/src/index.ts +9 -2
  42. package/src/result-action.ts +15 -6
  43. package/src/search-across.ts +2 -5
  44. package/src/tool-format.ts +455 -0
  45. package/src/tool-handler.ts +321 -325
  46. package/src/zcode-anchor-classify.ts +60 -0
  47. package/vitest.config.ts +2 -4
  48. package/src/core/parser.ts +0 -167
@@ -267,10 +267,10 @@ describe('result action(U6)', () => {
267
267
  }
268
268
  })
269
269
 
270
- it('6. 不存在 id:sa-id 无 manifest → 无匹配 record;uuid 片段零匹配 → 无匹配 session', async () => {
270
+ it('6. 不存在 id:sa-id 无 manifest → zcode_record_not_found(U9 统一错误面);uuid 片段零匹配 → 无匹配 session', async () => {
271
271
  await expect(
272
272
  handleSessionRead({ action: 'result', session: 'sa-nonexist-9999' }, { agentDir: dir }),
273
- ).rejects.toThrow(/无匹配 record/)
273
+ ).rejects.toThrow(/zcode_record_not_found/)
274
274
  try {
275
275
  await handleSessionRead({ action: 'result', session: 'sa-nonexist-9999' }, { agentDir: dir })
276
276
  } catch (e) {
@@ -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
  })
@@ -1001,28 +1026,32 @@ describe('resolveSessionId ② sa-id 形态(w2 TC7-TC10 + CQ3)', () => {
1001
1026
  }
1002
1027
  })
1003
1028
 
1004
- it('TC9: sa-id 0 命中(可能 running)→ ES2(含 family 指引 + 完整 id 提示 + 👉)', async () => {
1029
+ it('TC9: sa-id 0 命中(可能 running)→ zcode_record_not_found(U9:sa-id 语境统一错误面,含 family 指引 + 👉)', async () => {
1005
1030
  await expect(
1006
1031
  handleSessionRead({ action: 'outline', session: 'sa-nonexist-9999' }, { agentDir: dir }),
1007
- ).rejects.toThrow('无匹配 record')
1032
+ ).rejects.toThrow('zcode_record_not_found')
1008
1033
  try {
1009
1034
  await handleSessionRead({ action: 'outline', session: 'sa-nonexist-9999' }, { agentDir: dir })
1010
1035
  } catch (e) {
1011
1036
  const msg = (e as Error).message
1012
- expect(msg).toContain('可能尚未落盘')
1037
+ expect(msg).toContain('未落盘')
1013
1038
  expect(msg).toContain('action:"family"')
1014
1039
  expect(msg).toContain('👉')
1015
1040
  }
1016
1041
  })
1017
1042
 
1018
- it('TC10: sa-id 片段输入(精确相等不命中)→ ES2', async () => {
1043
+ it('TC10: sa-id 片段输入(精确相等不命中)→ zcode_record_not_found + 完整 sa- id 重试指引(U9 统一错误面)', async () => {
1019
1044
  await makeFixtureSubagent(dir, 'sa-c8c8dfa8', {
1020
1045
  realSessionId: '019e6c96-dddd-eeee-ffff-0000000010a3',
1021
1046
  })
1022
- // 片段 sa-c8c8 不等于完整 sa-c8c8dfa8 → 精确相等不命中 → ES2
1047
+ // 片段 sa-c8c8 不等于完整 sa-c8c8dfa8 → 精确相等不命中(兜底亦未命中)
1023
1048
  await expect(
1024
1049
  handleSessionRead({ action: 'outline', session: 'sa-c8c8' }, { agentDir: dir }),
1025
- ).rejects.toThrow('无匹配 record')
1050
+ ).rejects.toThrow('zcode_record_not_found')
1051
+ // 片段输入恢复路径在指引内可见(A4:ES2 原文案「用完整 sa- id 重试」的覆盖补全)
1052
+ await expect(
1053
+ handleSessionRead({ action: 'outline', session: 'sa-c8c8' }, { agentDir: dir }),
1054
+ ).rejects.toThrow(/完整 sa- id(形如 `sa-xxxx`)后重试/)
1026
1055
  })
1027
1056
 
1028
1057
  it('sa-id 多 manifest 命中(数据异常)→ ES2 ambiguous(C4)', async () => {
@@ -1195,11 +1224,11 @@ describe.skipIf(!HAS_REAL_SUBAGENTS_DIR)('真实数据:subagent sa-id(w2 TC1
1195
1224
  ).rejects.toThrow('session 文件不存在')
1196
1225
  })
1197
1226
 
1198
- it('TC18: 不存在 sa-id → ES2(场景 4,可能 running 指引)', async () => {
1227
+ it('TC18: 不存在 sa-id → zcode_record_not_found(场景 4,U9 统一错误面)', async () => {
1199
1228
  const fakeId = `sa-nonexist-${Date.now().toString(16)}-${Math.random().toString(16).slice(2, 8)}`
1200
1229
  await expect(
1201
1230
  handleSessionRead({ action: 'outline', session: fakeId }, { agentDir: REAL }),
1202
- ).rejects.toThrow('无匹配 record')
1231
+ ).rejects.toThrow('zcode_record_not_found')
1203
1232
  })
1204
1233
  }, 60000)
1205
1234
 
@@ -1801,8 +1830,8 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1801
1830
  }
1802
1831
 
1803
1832
  /**
1804
- * xyz-agent 形态四根 fixture:dataDir 目录名带 .xyz-agent 前缀(detectEnvironment 的
1805
- * PI_CODING_AGENT_DIR 形态判据 `<*>/.xyz-agent*\/agent` 可命中,托管判定走正态)。
1833
+ * taiji 形态四根 fixture:dataDir 目录名带 .taiji 前缀(detectEnvironment 的
1834
+ * PI_CODING_AGENT_DIR 形态判据 `<*>/.taiji*\/agent` 可命中,托管判定走正态)。
1806
1835
  * live=agent/sessions 与 default 同字面路径(default 去重)、legacy 独立非空、subagent 独立。
1807
1836
  */
1808
1837
  async function buildFourRootFixture(): Promise<{
@@ -1810,7 +1839,7 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1810
1839
  agentDir: string
1811
1840
  signals: SessionReadSignals
1812
1841
  }> {
1813
- const dataDir = join(tmp, '.xyz-agent-fixture')
1842
+ const dataDir = join(tmp, '.taiji-fixture')
1814
1843
  const agentDir = join(dataDir, 'agent')
1815
1844
  await writeJsonl(join(agentDir, 'sessions', SLUG, 'a.jsonl'), 'aaaaaaaa')
1816
1845
  await writeJsonl(join(agentDir, 'sessions', SLUG, 'b.jsonl'), 'bbbbbbbb')
@@ -1823,9 +1852,9 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1823
1852
  agentDir,
1824
1853
  liveSessionDir: join(agentDir, 'sessions'),
1825
1854
  env: {
1826
- XYZ_AGENT_EXT_LOG: '1',
1855
+ TAIJI_AGENT_EXT_LOG: '1',
1827
1856
  PI_CODING_AGENT_DIR: agentDir,
1828
- XYZ_AGENT_DATA_DIR: dataDir,
1857
+ TAIJI_AGENT_DATA_DIR: dataDir,
1829
1858
  },
1830
1859
  },
1831
1860
  }
@@ -1843,8 +1872,8 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1843
1872
  const r = await handleSessionRead({ action: 'doctor' }, signals)
1844
1873
  const text = r.content[0].text
1845
1874
 
1846
- // 环境判定行:双信号合取 → xyz-agent(托管)+ 数据目录 + evidence 正态
1847
- expect(text).toContain('环境判定:xyz-agent(托管)')
1875
+ // 环境判定行:双信号合取 → taiji(托管)+ 数据目录 + evidence 正态
1876
+ expect(text).toContain('环境判定:taiji(托管)')
1848
1877
  expect(text).toContain(`数据目录:${dataDir}`)
1849
1878
  expect(text).toContain('依据:')
1850
1879
  expect(text).toContain(`PI_CODING_AGENT_DIR='${agentDir}' → 形态匹配`)
@@ -1887,7 +1916,7 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1887
1916
  const { signals } = await buildFourRootFixture()
1888
1917
  const positive = await handleSessionRead({ action: 'doctor' }, signals)
1889
1918
  expect(positive.content[0].text).toContain(
1890
- `告警:[legacy] 根 ${join(tmp, '.xyz-agent-fixture', 'sessions')} 非空(1 文件)`,
1919
+ `告警:[legacy] 根 ${join(tmp, '.taiji-fixture', 'sessions')} 非空(1 文件)`,
1891
1920
  )
1892
1921
 
1893
1922
  // 反态:legacy 目录不存在
@@ -1908,43 +1937,6 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1908
1937
  expect(deduped.content[0].text).toContain('已去重')
1909
1938
  })
1910
1939
 
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
1940
  it('subagent 根默认不扫(只列路径与可扫性);includeSubagents:true 才扫出文件数(§6.3)', async () => {
1949
1941
  const { agentDir, signals } = await buildFourRootFixture()
1950
1942
 
@@ -1995,7 +1987,7 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
1995
1987
  const r = await handleSessionRead({ action: 'doctor' }, { agentDir })
1996
1988
  const text = r.content[0].text
1997
1989
  expect(text).toContain('环境判定:standalone-pi · 发行形态:未知(不猜)')
1998
- expect(text).toContain('XYZ_AGENT_EXT_LOG=<未设置> → 未命中')
1990
+ expect(text).toContain('TAIJI_AGENT_EXT_LOG=<未设置> → 未命中')
1999
1991
  expect(text).toContain('会话根(按优先级)')
2000
1992
  expect(text).toContain('[default]')
2001
1993
  expect(text).toContain(
@@ -2003,13 +1995,12 @@ describe('doctor action(u8:环境判定 + 根表 + 告警 + 残留 glob)',
2003
1995
  )
2004
1996
  })
2005
1997
 
2006
- it('空 agentDir 防御:无根表、诊断「无候选根」,残留探测不触发(不扫 cwd 相对路径)', async () => {
1998
+ it('空 agentDir 防御:无根表、诊断「无候选根」(不扫 cwd 相对路径)', async () => {
2007
1999
  const r = await handleSessionRead({ action: 'doctor' }, { agentDir: '' })
2008
2000
  const text = r.content[0].text
2009
2001
  expect(text).toContain('诊断:无候选根')
2010
2002
  expect(text).not.toContain('[default]')
2011
2003
  expect((r.details as DoctorDetails).roots).toEqual([])
2012
- expect((r.details as DoctorDetails).leftovers).toEqual([])
2013
2004
  })
2014
2005
  })
2015
2006
 
@@ -2641,7 +2632,7 @@ describe('E1 find 单次根解析(探针 P1:resolveSessionRoots 恰 1 次)
2641
2632
  // ============================================================
2642
2633
  describe('u11 metadataProvider 注入 + TTL 缓存(fixture,§6.6)', () => {
2643
2634
  let tmp: string
2644
- /** 平铺 main 根(xyz-agent 形态),同时作 liveSessionDir(与 [default] 根同字面路径) */
2635
+ /** 平铺 main 根(taiji 形态),同时作 liveSessionDir(与 [default] 根同字面路径) */
2645
2636
  let flatMain: string
2646
2637
 
2647
2638
  async function writeSession(
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { extractToolCalls, formatToolCallSummary } from '../core/toolcall.js'
3
- import type { Entry } from '../core/parser.js'
3
+ import type { Entry } from '@zhushanwen/session-core'
4
4
 
5
5
  /**
6
6
  * D1 映射表(design §3.3)确定性测试:formatToolCallSummary 12 个 switch 分支
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { buildTreeView } from '../core/tree.js'
3
- import type { Entry } from '../core/parser.js'
3
+ import type { Entry } from '@zhushanwen/session-core'
4
4
 
5
5
  /** 构造最小 Entry(tree.ts 只消费 type/id/parentId,其余字段省略) */
6
6
  function e(id: string, parentId: string | null): Entry {
@@ -1,7 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { segmentTurns } from '../core/turns.js'
3
- import type { Entry } from '../core/parser.js'
4
- import { parseSessionFile } from '../core/parser.js'
3
+ import { parseSessionFile, type Entry } from '@zhushanwen/session-core'
5
4
  import { buildTreeView } from '../core/tree.js'
6
5
  import { REAL_SESSION, HAS_REAL_SESSION } from './real-data.js'
7
6