@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
@@ -1,11 +1,11 @@
1
1
  import { readFile, readdir, open, stat } from 'node:fs/promises'
2
2
  import type { FileHandle } from 'node:fs/promises'
3
- import { join, basename } from 'node:path'
3
+ import { join } from 'node:path'
4
4
  import type { Entry } from '@zhushanwen/session-core'
5
5
  import type { Family, SessionRef } from '../core/family.js'
6
6
  import { buildFamilyIndex, resolveFamily } from '../core/family.js'
7
7
  import { listZcodeManifests } from './zcode-manifest.js'
8
- import { resolveSessionRoots, type SessionFileMeta } from './roots.js'
8
+ import { resolveSessionRoots, resolveSessionStoreRoot, resolveStoreSubagentRecordsDir, listSessionStorePartitions, type SessionFileMeta } from './roots.js'
9
9
  import { parseSessionHeader, readSessionHeaderFirstLine } from './session-header.js'
10
10
  import { resolveWorkflows } from './workflows.js'
11
11
 
@@ -186,7 +186,8 @@ async function collectSubagentIdentities(
186
186
  // U4 数据流:manifest 命中透全字段;未命中 P-fallback 读尾行 identity 取 task/slug/agent
187
187
  //(model/status 不可回退,留 undefined);无 manifest 无 identity(运行中/异常)跳过。
188
188
  // A3(ext-simplify-04):直调 resolveSessionRoots(原 listSubagentSessions 薄包装已删除)
189
- //——agentDir 信号包下 [subagent] 根 = <agentDir>/subagents(常量推导,恒唯一)
189
+ //——[subagent] 根 = session 存储树([session-store-layout-inversion] 单新根,常量
190
+ // 推导,恒唯一;扫描定向探各 sid 分区的 subagents/sessions/)。
190
191
  const subMetas =
191
192
  (await resolveSessionRoots({ agentDir })).find((r) => r.kind === 'subagent')?.files ?? []
192
193
  for (const meta of subMetas) {
@@ -418,31 +419,37 @@ async function tryReadManifest(path: string): Promise<RecordManifest | undefined
418
419
  }
419
420
 
420
421
  /**
421
- * 扫描 subagents/<cwdSlug>/records/*.json —— subagent 注册清单。
422
- * 每个 manifest 由终态 finalize 或 sync 批落标出口写入(创建时不写),持久存在即使
422
+ * 扫描 records/*.json —— subagent 注册清单(读侧单新根,[session-store-layout-inversion]
423
+ * D6 裁决:旧布局存量原位保留、不再读写——旧 record 从 agent 面 result/find 消失是该
424
+ * 裁决取舍清单明示项,不做旧根兼容扫描)。
425
+ * 每个 manifest 由终态 finalize 或 rebuildIndexes 投影写入(创建时不写),持久存在即使
423
426
  * .jsonl 被 GC。坏 manifest(缺必填字段/JSON 损坏)跳过,不中断扫描。
427
+ *
428
+ * 单新根:`<dataDir>/ext-sessions`(dataDir = dirname(agentDir),布局 B agentDir =
429
+ * `<dataDir>/agent`;树根段镜像 shared SESSION_STORE_DIR_SEGMENT,对拍机检锚点 =
430
+ * scripts/check-artifact-dir-formula-sync.mjs 域 9)。新布局 records 在
431
+ * `<root>/<sid>/subagents/records/`(分区键 = rootSessionId,嵌套派发全层同值——
432
+ * 嵌套 subagent 的 manifest 同落根会话分区,固定位置扫描完整覆盖)。扫描剪枝:
433
+ * `<root>/` 一级只进 isPiSessionId 形态目录(不进 logs/attachments 等大目录),每个
434
+ * sid 分区内只探 subagents/records/ 固定位置,不整树递归。根不存在时静默空读。
424
435
  */
425
436
  export async function listRecordManifests(agentDir: string): Promise<RecordManifest[]> {
426
- const root = join(agentDir, 'subagents')
437
+ const storeRoot = resolveSessionStoreRoot(agentDir)
427
438
  const out: RecordManifest[] = []
428
- async function walk(dir: string): Promise<void> {
439
+ for (const sid of await listSessionStorePartitions(storeRoot)) {
440
+ const recordsDir = resolveStoreSubagentRecordsDir(agentDir, sid)
429
441
  let entries
430
442
  try {
431
- entries = await readdir(dir, { withFileTypes: true })
443
+ entries = await readdir(recordsDir, { withFileTypes: true })
432
444
  } catch {
433
- return // 目录不存在/无权限 → 静默返回
445
+ continue // 该分区无 records/(无 subagent 的会话)→ 静默跳过
434
446
  }
435
447
  for (const e of entries) {
436
- const full = join(dir, e.name)
437
- if (e.isDirectory()) {
438
- await walk(full)
439
- } else if (e.isFile() && e.name.endsWith('.json') && basename(dir) === 'records') {
440
- const m = await tryReadManifest(full)
441
- if (m) out.push(m)
442
- }
448
+ if (!e.isFile() || !e.name.endsWith('.json')) continue
449
+ const m = await tryReadManifest(join(recordsDir, e.name))
450
+ if (m) out.push(m)
443
451
  }
444
452
  }
445
- await walk(root)
446
453
  return out
447
454
  }
448
455
 
@@ -16,8 +16,8 @@ import { zcodeImportDbAllowlist } from '@zhushanwen/zcode-session-source'
16
16
  *
17
17
  * **检查顺序铁律(§3.4 三段递进,实现与测试双锚定)**:存在性先于路径——
18
18
  * `existsSync` 为 false 报 `zcode_db_unreadable`(库文件不存在)而非
19
- * `zcode_db_path_forbidden`,避免把「只是还没落库」误报成安全事件(forbidden 是
20
- * 安全面错误,语义更重、更惊扰 agent)。存在才 realpath 后与派生允许集合精确比对。
19
+ * `zcode_db_path_forbidden`,避免把「只是还没写入仓库」误报成安全事件(forbidden 是
20
+ * 安全侧错误,语义更重、更惊扰 agent)。存在才 realpath 后与派生允许集合精确比对。
21
21
  * 开库(第 3 段)由调用方在本闸通过后进行。
22
22
  *
23
23
  * **TOCTOU 残留(设计 §3.4 显式登记,不在本梯队硬化)**:realpath 之后仍按原
@@ -72,10 +72,10 @@ export function formatZcodeDbPathForbidden(dbPath: string): string {
72
72
  * 白名单闸(第 1 段存在性 → 第 2 段路径;通过才允许开库)。
73
73
  *
74
74
  * @throws Error(消费侧映射为 zcode_db_unreadable / zcode_db_path_forbidden,
75
- * 消息即 §3.4 的 👉 指引文案——tool-handler 直接 throw 给 pi 错误面)
75
+ * 消息即 §3.4 的 👉 指引文案——tool-handler 直接 throw 给 pi 错误范围)
76
76
  */
77
77
  export function assertZcodeDbPathAllowed(dbPath: string, agentDir: string): void {
78
- // 第 1 段 存在性(先于路径——「还没落库」不是安全事件)
78
+ // 第 1 段 存在性(先于路径——「还没写入仓库」不是安全事件)
79
79
  if (!existsSync(dbPath)) {
80
80
  throw new Error(formatZcodeDbUnreadable(dirname(agentDir), `(库文件不存在:${dbPath})`))
81
81
  }
@@ -17,7 +17,7 @@ import { extractSessionIdFromFilename } from './subagents.js'
17
17
  // ——家族链数据源同 record 流,无第二承载位);旧后缀锚点(.events.jsonl 形态)=
18
18
  // 历史实体([D1] 历史数据处置:旧两件不读)→ calls=[] 仅 run 存在性兜底。
19
19
  // 历史格式条目(v1 全量快照 / workflow-state-link 指针)不识别、静默从发现链消失
20
- // (不拒读、不报错)——run 侧 v1 读面已删除(ADR-0095,同 record 侧裁决:项目未
20
+ // (不拒读、不报错)——run 侧 v1 读侧已删除(ADR-0095,同 record 侧裁决:项目未
21
21
  // 上线无历史数据,不迁移不兼容)。
22
22
  // - readRunSnapshot:读 wf-state 文件尾向找首个可解析行,返回原始对象(unknown,格式
23
23
  // 收窄交 core 层)。唯一消费方 = tool-handler workflow 概览的快照链分支。
@@ -82,16 +82,16 @@ function sessionRefFromPath(path: string, pathToRef: Map<string, SessionRef>): S
82
82
  }
83
83
 
84
84
  // ============================================================
85
- // workflow-record 条目契约(v2 注册条目判别——本地字面量 + 测试守卫漂移)
85
+ // workflow-record 条目契约(v2 注册条目判别——本地字面量 + 测试检查漂移)
86
86
  // ============================================================
87
87
 
88
88
  /**
89
89
  * `workflow-record` custom entry 的 customType 值。
90
90
  *
91
91
  * 磁盘 JSONL 协议字符串(跨侧契约):写侧单源 = subagent-core
92
- * workflow-record-entry.ts 的 WORKFLOW_RECORD_CUSTOM_TYPE。reader 生产依赖面不引
92
+ * workflow-record-entry.ts 的 WORKFLOW_RECORD_CUSTOM_TYPE。reader 生产依赖范围不引
93
93
  * subagent-core(同 entry-anchor.ts 的协议字面量模式),两侧漂移由 workflow.test.ts
94
- * 的跨包断言守卫。
94
+ * 的跨包断言检查。
95
95
  */
96
96
  const WORKFLOW_RECORD_CUSTOM_TYPE = 'workflow-record'
97
97
 
@@ -102,7 +102,7 @@ const WORKFLOW_RECORD_ENTRY_V2 = 2
102
102
  * run record 流文件名尾段(`<runId>.record.jsonl`)。写侧 subagent-core
103
103
  * RUN_EVENTS_SUFFIX 同值([D1] record 单源后缀)——v2 注册条目的
104
104
  * recordPath 锚点后缀判定用(新锚点直读 record 流;旧 `.events.jsonl` 锚点 =
105
- * 历史实体分流),本地持有 + 测试守卫漂移。
105
+ * 历史实体分流),本地持有 + 测试检查漂移。
106
106
  */
107
107
  export const RUN_RECORD_STREAM_SUFFIX = '.record.jsonl'
108
108
 
@@ -142,7 +142,7 @@ function collectRegisteredRecordPaths(entries: readonly Entry[]): RegisteredReco
142
142
  /**
143
143
  * [D16③] recordPath 后缀分流:`.record.jsonl` 锚点 = 新形态(record 流直读);
144
144
  * 其余(含旧 `.events.jsonl` 锚点)= 历史实体([D1] 历史数据处置——旧两件不读
145
- * 不写,历史 run 从发现链数据面退空即预期行为)。
145
+ * 不写,历史 run 从发现链的数据中退空即预期行为)。
146
146
  */
147
147
  function isNewRecordStreamAnchor(recordPath: string): boolean {
148
148
  return recordPath.endsWith(RUN_RECORD_STREAM_SUFFIX)
@@ -155,7 +155,7 @@ interface AgentSettledSessionFileView {
155
155
  result: { sessionFile: string }
156
156
  }
157
157
 
158
- /** [extractRecordStreamSessionFiles 守卫] 单帧是否为携带非空 result.sessionFile
158
+ /** [extractRecordStreamSessionFiles 检查] 单帧是否为携带非空 result.sessionFile
159
159
  * 的 agent-settled 帧:逐字段 typeof 校验(type → taskIndex → result →
160
160
  * sessionFile),不过关 = 宽容跳过(与 readRunSnapshot 同容忍度)。 */
161
161
  function isAgentSettledSessionFileFrame(v: unknown): v is AgentSettledSessionFileView {
@@ -194,7 +194,7 @@ export function extractRecordStreamSessionFiles(content: string): string[] {
194
194
  /**
195
195
  * 读目标 session 文件全文,从 workflow-record v2 注册条目构造 WorkflowRef[]
196
196
  * (单档发现链——v1 快照条目与 workflow-state-link 指针条目不再识别,历史 run
197
- * 从发现链数据面退空即预期行为,见 ADR-0095)。同一 runId 的多条目后写覆盖
197
+ * 从发现链的数据中退空即预期行为,见 ADR-0095)。同一 runId 的多条目后写覆盖
198
198
  * (条目顺序 = 时间顺序,取最新锚点)。
199
199
  *
200
200
  * **签名与返回值结构(WorkflowRef[]{runId,stateFile,calls:SessionRef[]})不变**
@@ -2,6 +2,7 @@ import { readFile, readdir } from 'node:fs/promises'
2
2
  import { statSync } from 'node:fs'
3
3
  import { join } from 'node:path'
4
4
 
5
+ import { resolveSessionStoreRoot, listSessionStorePartitions } from './roots.js'
5
6
  import type { ZcodeAnchor } from './entry-anchor.js'
6
7
  import type { ZcodeFamilyNode } from '../core/family.js'
7
8
 
@@ -9,9 +10,9 @@ import type { ZcodeFamilyNode } from '../core/family.js'
9
10
  * zcode manifest 直读与枚举(design session-reader-shared-core §3.1 第①步 / D5 / D5-1)。
10
11
  *
11
12
  * 背景(F21):pi 路径的 `isRecordManifest` 三必填含 `sessionFile`,而 zcode manifest
12
- * 落盘时该键被 JSON.stringify 丢弃(subagent-core record-store-rebuild.ts:665/725 投影
13
+ * 落盘时该键被 JSON.stringify 丢弃(subagent-core record-store-rebuild.ts:665/725 派生视图
13
14
  * 照写 `sessionFile: undefined`)→ `listRecordManifests` 对 zcode manifest 恒返回 []。
14
- * 本单元另开一条**不经过该守卫**的独立路径:
15
+ * 本单元另开一条**不经过该检查**的独立路径:
15
16
  * - **定点直读**(`readZcodeManifest`):manifest 文件名由 sa-id 完全确定(写侧
16
17
  * `path.join(recordsDir, \`${record.id}.json\`)`,manifest-store.ts:141/153),按确定性
17
18
  * 文件名 `<sa-id>.json` 在 `<agentDir>/subagents/<cwd>/records/` 下**窄 walk(命中即止,
@@ -24,7 +25,7 @@ import type { ZcodeFamilyNode } from '../core/family.js'
24
25
  * `listRecordManifests`(subagents.ts)一行不改——pi manifest 仍走原扫描路径,本单元
25
26
  * 的谓词**不走** isRecordManifest。判别规则与 runtime `transcriptAnchorOf` 同构
26
27
  * (F8:`engine === 'zcode'` 第一判据 + sessionRef 双键齐)。用 `engine` 判别而非
27
- * 「engineHandle 存在性」的理由见 D5 被否项 ②(pi record 提升后 engineHandle 仍持久化,
28
+ * 「engineHandle 存在性」的理由见 D5 不采用项 ②(pi record 提升后 engineHandle 仍持久化,
28
29
  * 存在性判别会误路由)。
29
30
  */
30
31
 
@@ -43,7 +44,7 @@ export interface ZcodeManifestRecord extends ZcodeFamilyNode {
43
44
  /**
44
45
  * anchor-missing 的缺失键归因(§3.4:缺失键名进结构化日志——engineHandle 整体缺席 /
45
46
  * 缺 sessionRef / 缺 sessionId / 缺 dbPath 归因不同:整体缺席多为旧版本产物,缺
46
- * sessionId 多为引擎未回调 onHandleReady,缺 dbPath 多为写入面截断)。entry 兜底侧
47
+ * sessionId 多为引擎未回调 onHandleReady,缺 dbPath 多为写侧截断)。entry 兜底侧
47
48
  * 的同构分类(tool-handler classifyIncompleteEntryAnchor)复用本类型单源。
48
49
  */
49
50
  export type ZcodeAnchorMissingReason =
@@ -67,7 +68,7 @@ export type ZcodeManifestLookup =
67
68
  | { kind: 'not-zcode' }
68
69
  | { kind: 'anchor-missing'; reason: ZcodeAnchorMissingReason }
69
70
 
70
- /** manifest 顶层 JSON 的读取视图(守卫输入) */
71
+ /** manifest 顶层 JSON 的读取视图(检查输入) */
71
72
  type ManifestJson = Record<string, unknown>
72
73
 
73
74
  function asObject(v: unknown): ManifestJson | undefined {
@@ -125,7 +126,7 @@ function richFieldsOf(m: ManifestJson): ManifestRichFields {
125
126
  return out
126
127
  }
127
128
 
128
- /** stat 存在性(任意路径无安全面——D5-1 只查「文件在不在」,不开库) */
129
+ /** stat 存在性(任意路径无安全侧——D5-1 只查「文件在不在」,不开库) */
129
130
  function fileExists(p: string): boolean {
130
131
  try {
131
132
  statSync(p)
@@ -161,7 +162,13 @@ function toZcodeManifestRecord(m: ManifestJson): ZcodeManifestRecord | undefined
161
162
  }
162
163
  }
163
164
 
164
- /** `<agentDir>/subagents` 布局常量(与写侧 path-encoding/manifest-store 布局对齐) */
165
+ /**
166
+ * [session-store-layout-inversion D6/单新根] zcode manifest 读侧 = session 存储树分区
167
+ * 固定位置 `<root>/<rootSessionId>/subagents/records/`(根推导与分区枚举复用 roots.ts
168
+ * 的 resolveSessionStoreRoot / listSessionStorePartitions——同包单源防漂移)。旧布局
169
+ * `<agentDir>/subagents/<cwdSlug>/records/` 不再读写(D6 存量不迁移不读取——旧 zcode
170
+ * record 从 agent 面消失是 D6 取舍清单明示项)。
171
+ */
165
172
  const SUBAGENTS_DIR = 'subagents'
166
173
  const RECORDS_DIR = 'records'
167
174
 
@@ -196,22 +203,16 @@ export async function readZcodeManifest(
196
203
  agentDir: string,
197
204
  saId: string,
198
205
  ): Promise<ZcodeManifestLookup> {
199
- const root = join(agentDir, SUBAGENTS_DIR)
200
- let slugs: DirentLike[]
201
- try {
202
- slugs = await readdir(root, { withFileTypes: true })
203
- } catch {
204
- return { kind: 'not-zcode' } // subagents 根不存在/不可读 → 无 manifest
205
- }
206
- for (const e of slugs) {
207
- if (!e.isDirectory()) continue
208
- const file = join(root, e.name, RECORDS_DIR, `${saId}.json`)
206
+ const root = resolveSessionStoreRoot(agentDir)
207
+ const partitions = await listSessionStorePartitions(root)
208
+ for (const part of partitions) {
209
+ const file = join(root, part, SUBAGENTS_DIR, RECORDS_DIR, `${saId}.json`)
209
210
  let raw: string
210
211
  try {
211
212
  // 命中即止:只读这一个文件(文件名由 sa-id 确定),坏内容同样终止(见 parseManifestLookup)
212
213
  raw = await readFile(file, 'utf8')
213
214
  } catch {
214
- continue // 该 cwd 目录下无此 manifest → 下一候选目录
215
+ continue // 该分区无此 manifest → 下一候选分区
215
216
  }
216
217
  return parseManifestLookup(raw)
217
218
  }
@@ -236,17 +237,11 @@ interface DirentLike {
236
237
  * family 是列表视图,单节点不可达不炸整屏(D5-1 GC 语义)。
237
238
  */
238
239
  export async function listZcodeManifests(agentDir: string): Promise<ZcodeManifestRecord[]> {
239
- const root = join(agentDir, SUBAGENTS_DIR)
240
+ const root = resolveSessionStoreRoot(agentDir)
240
241
  const out: ZcodeManifestRecord[] = []
241
- let slugs: DirentLike[]
242
- try {
243
- slugs = await readdir(root, { withFileTypes: true })
244
- } catch {
245
- return out // 根不存在/不可读 → 空(无 zcode 节点,不报错)
246
- }
242
+ const slugs = await listSessionStorePartitions(root)
247
243
  for (const slug of slugs) {
248
- if (!slug.isDirectory()) continue
249
- const recordsDir = join(root, slug.name, RECORDS_DIR)
244
+ const recordsDir = join(root, slug, SUBAGENTS_DIR, RECORDS_DIR)
250
245
  let files: DirentLike[]
251
246
  try {
252
247
  files = await readdir(recordsDir, { withFileTypes: true })
package/src/extract.ts CHANGED
@@ -347,7 +347,7 @@ function collectCommitCandidates(
347
347
  return { high, low }
348
348
  }
349
349
 
350
- /** 收口阶段:去重,高置信优先,同 hash 保留首次。 */
350
+ /** 收尾阶段:去重,高置信优先,同 hash 保留首次。 */
351
351
  function dedupeCommits(high: CommitItem[], low: CommitItem[]): CommitItem[] {
352
352
  const seen = new Set<string>()
353
353
  const items: CommitItem[] = []
package/src/index.ts CHANGED
@@ -156,7 +156,7 @@ const guidelines = [
156
156
  // 工具内指向本工具的交叉引用,非本清单的锚)。
157
157
  'Progressive reading: outline (~1500 tokens) → expand (one turn) → detail; defaults omit toolResult/thinking noise.',
158
158
  'find first (uuid fragment / name); TUI #references are full uuids. Never read raw .jsonl—use this tool.',
159
- // zcode 路由(U9)对 LLM 的唯一可见面:sa-id 输入自动路由,无需任何参数;首遇
159
+ // zcode 路由(U9)对 LLM 的唯一可见部分:sa-id 输入自动路由,无需任何参数;首遇
160
160
  // zcode_* 错误码前先有背景预告。
161
161
  'sa- ids route transparently to pi or zcode engine sessions; sess_ prefixed ids are not accepted—use the completion-notice sa- id.',
162
162
  'Errors carry a 👉 recovery hint—follow it to retry.',
@@ -250,7 +250,7 @@ export default function sessionReaderExtension(pi: ExtensionAPI): void {
250
250
  //
251
251
  // addAutocompleteProvider 挂在 ctx.ui(非 ExtensionAPI),setup 入口无 ctx,
252
252
  // 只能在 event handler 里拿——session_start 是最早且每 session 触发的 event。
253
- // once-guard + ctx.mode 守卫 + typeof 运行时守卫三重防护。
253
+ // once-guard + ctx.mode 检查 + typeof 运行时检查三重防护。
254
254
  //
255
255
  // 2026-08-10 重构:数据源从全盘 findSessions(agentDir) 换为 SessionManager.listAll(ctx.sessionManager.getSessionDir())。
256
256
  // getSessionDir() 返回当前 session 的目录(encoded cwd),listAll 只扫该目录 →
package/src/no-match.ts CHANGED
@@ -147,7 +147,7 @@ function guidanceLines(): string[] {
147
147
  * ③「正确做法」四条:标题/keyword、绝对路径 outline、更短前缀、doctor。
148
148
  * ④ 最后一行封死 shell 绕行:find/ls/rg 搜 session 目录、cat/read 原始 .jsonl 均禁止。
149
149
  *
150
- * 无「👉 用 recent 看全量」误导指引(recent 候选对写错的 uuid 无自纠价值,§6.7 被否项)。
150
+ * 无「👉 用 recent 看全量」误导指引(recent 候选对写错的 uuid 无自纠价值,§6.7 不采用项)。
151
151
  */
152
152
  export function formatNoMatch(query: string, roots: SessionRoot[]): string {
153
153
  return [
@@ -31,7 +31,7 @@ const RESULT_DEFAULT_LIMIT = 8000
31
31
  /** result 批量条目分隔符(与 notifier 批通知 "\n\n---\n\n" join 同款)。 */
32
32
  const RESULT_BATCH_SEPARATOR = '\n\n---\n\n'
33
33
 
34
- /** result action 对 tool-handler 文件私有 helper 的注入面(入口分发处 per-call 构造)。 */
34
+ /** result action 对 tool-handler 文件私有 helper 的注入点(入口分发处 per-call 构造)。 */
35
35
  export interface ResultActionDeps {
36
36
  resolveSessionId(
37
37
  rawSession: string | undefined,
@@ -137,7 +137,7 @@ export function collectSearchHits(
137
137
  return hits
138
138
  }
139
139
 
140
- /** search 收口阶段:结果文本组装(header 标注降级/scope/截断 + 逐行命中列表)。 */
140
+ /** search 收尾阶段:结果文本组装(header 标注降级/scope/截断 + 逐行命中列表)。 */
141
141
  export function formatSearchText(
142
142
  pattern: string,
143
143
  degraded: boolean,
@@ -196,7 +196,7 @@ interface CrossSearchSkipped {
196
196
  sizeBytes?: number
197
197
  }
198
198
 
199
- /** 跨会话检索 details(程序化消费/测试断言面)。 */
199
+ /** 跨会话检索 details(程序化消费/测试断言范围)。 */
200
200
  export interface CrossSearchDetails {
201
201
  pattern: string
202
202
  scope: NonNullable<SessionReadParams['scope']>
@@ -355,7 +355,7 @@ interface CrossSearchOptions {
355
355
  byteBudget?: number
356
356
  }
357
357
 
358
- /** 缺省值收口后的检索参数(各缺省值与旧内联 `opts?.** ?? 常量` 逐一对应)。 */
358
+ /** 缺省值收尾后的检索参数(各缺省值与旧内联 `opts?.** ?? 常量` 逐一对应)。 */
359
359
  interface CrossSearchResolvedOptions {
360
360
  scope: NonNullable<SessionReadParams['scope']>
361
361
  limit: number
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * session_read 各 action 的输出文本渲染(max-lines 拆分轮从 tool-handler.ts 机械
3
- * 提取,零行为变更):错误面 message(ES1/ES2 + zcode 七码 + 开库/查询期错误映射)+
3
+ * 提取,零行为变更):错误范围 message(ES1/ES2 + zcode 七码 + 开库/查询期错误映射)+
4
4
  * content 文本(find/outline/expand/detail/family)+ entry 可读文本 + F2 消歧与
5
5
  * find 零匹配的 ToolResult 包装。纯字符串/纯渲染函数,不触 fs(findNoMatch 的
6
6
  * roots 由调用方传入)。
7
7
  *
8
- * 导出面:SQLITE_DRIVER_UNSUPPORTED_MARK / ZCODE_DB_MISSING_MARK /
8
+ * 导出清单:SQLITE_DRIVER_UNSUPPORTED_MARK / ZCODE_DB_MISSING_MARK /
9
9
  * zcodeReadErrorMessage(测试契约锚消费方直接 import 本模块)。logger 用同名
10
10
  * getLogger('session-reader')——按名缓存单例,与 tool-handler 侧同一实例,
11
11
  * warn/error 落同一 appendEntry/文件日志通道。ToolResult 等公共类型留
@@ -84,7 +84,7 @@ export function formatSaIdNotFound(saId: string): string {
84
84
  }
85
85
 
86
86
  // ---------------------------------------------------------------------------
87
- // zcode 错误面(U9,design §3.4 七码 + 👉 逐行;错误码标识入 message 供机器识别,
87
+ // zcode 错误范围(U9,design §3.4 七码 + 👉 逐行;错误码标识入 message 供机器识别,
88
88
  // 形态对齐 §3.1 失败路径示例 `[zcode_db_path_forbidden] …`)
89
89
  // ---------------------------------------------------------------------------
90
90
 
@@ -131,7 +131,7 @@ function formatZcodeSchemaDrift(observed: string | undefined): string {
131
131
 
132
132
  /**
133
133
  * bun 驱动探测失败(§3.4 表第 8 行理论态)的 message:宿主既无 bun:sqlite 也无
134
- * node:sqlite——zcode action 整体降级为本错误面(fail-fast + 日志),pi 链路不受影响。
134
+ * node:sqlite——zcode action 整体降级为本错误范围(fail-fast + 日志),pi 链路不受影响。
135
135
  * 错误码字面量为实施期命名(设计该行未定码名,形态对齐七码 `[zcode_*]` 惯例)。
136
136
  */
137
137
  function formatZcodeHostUnsupported(detail: string): string {
@@ -145,8 +145,8 @@ function formatZcodeHostUnsupported(detail: string): string {
145
145
  /**
146
146
  * sqlite 驱动探测失败的消息特征(zcode-session-source sqlite-driver.ts 探测失败的
147
147
  * 错误消息契约;该包不导出专用错误子类,reader 侧按消息特征单列识别——跨包漂移
148
- * 由 zcode-routing.test.ts 的源文本契约测试守卫:sqlite-driver 源文不再含本子串即红)。
149
- * 导出面仅测试消费(契约锚 + 映射测试引用同值,禁测试内硬编码副本)。
148
+ * 由 zcode-routing.test.ts 的源文本契约测试检查:sqlite-driver 源文不再含本子串即红)。
149
+ * 导出范围仅测试消费(契约锚 + 映射测试引用同值,禁测试内硬编码副本)。
150
150
  */
151
151
  export const SQLITE_DRIVER_UNSUPPORTED_MARK = '不支持 node:sqlite'
152
152
 
@@ -154,17 +154,17 @@ export const SQLITE_DRIVER_UNSUPPORTED_MARK = '不支持 node:sqlite'
154
154
  * 库文件缺失的消息特征(zcode-session-source sqlite-access.ts `openZcodeSessionDb`
155
155
  * 开头 existsSync 失败抛普通 Error「db 文件不存在:<dbPath>」的消息契约锚点)。该包
156
156
  * 不导出专用错误子类,reader 侧按消息特征单列识别——落 §3.4 第 4 行
157
- * `zcode_db_unreadable`(不误标 schema_drift)。生产路径上白名单闸
157
+ * `zcode_db_unreadable`(不误标 schema_drift)。生产路径上白名单门禁
158
158
  * (assertZcodeDbPathAllowed 第 1 段)已先行拦掉绝大多数缺失,此处命中的是两段
159
- * existsSync 之间的 TOCTOU 理论窗口(闸通过后开库前文件被删)。跨包漂移由
160
- * zcode-routing.test.ts 的源文本契约测试守卫(SQLITE_DRIVER_UNSUPPORTED_MARK 同范式)。
161
- * 导出面仅测试消费(契约锚 + 映射测试引用同值,禁测试内硬编码副本)。
159
+ * existsSync 之间的 TOCTOU 理论窗口(门禁通过后开库前文件被删)。跨包漂移由
160
+ * zcode-routing.test.ts 的源文本契约测试检查(SQLITE_DRIVER_UNSUPPORTED_MARK 同范式)。
161
+ * 导出范围仅测试消费(契约锚 + 映射测试引用同值,禁测试内硬编码副本)。
162
162
  */
163
163
  export const ZCODE_DB_MISSING_MARK = 'db 文件不存在'
164
164
 
165
165
  /**
166
- * 开库/查询期错误 → §3.4 错误面映射(SqliteUnreadableError / schema drift / 驱动探测失败 / 库缺失 / 其余)。
167
- * 导出面仅测试消费(L4 包装形态的映射契约直测,zcode-routing.test.ts)。
166
+ * 开库/查询期错误 → §3.4 错误范围映射(SqliteUnreadableError / schema drift / 驱动探测失败 / 库缺失 / 其余)。
167
+ * 导出范围仅测试消费(L4 包装形态的映射契约直测,zcode-routing.test.ts)。
168
168
  */
169
169
  export function zcodeReadErrorMessage(e: unknown, agentDir: string): string {
170
170
  if (e instanceof SqliteUnreadableError) {
@@ -11,7 +11,7 @@
11
11
  * 域模块拆分(max-lines 拆分轮机械提取,零行为变更,result-action.ts 先例同型):
12
12
  * result-action.ts(result)/ doctor.ts(doctor + SessionReadSignals)/
13
13
  * search-across.ts(search 管线 + u12 跨会话)/ extract.ts(extract 预设)/
14
- * tool-format.ts(各 action 输出文本渲染 + 错误面 message + F2 消歧与
14
+ * tool-format.ts(各 action 输出文本渲染 + 错误范围 message + F2 消歧与
15
15
  * find 零匹配包装)/ no-match.ts(F1 自检行)/ zcode-anchor-classify.ts(entry
16
16
  * 兜底归因的 entry 级纯判定)/ handler-utils.ts(pad/err/
17
17
  * stripHash/requireStr/SESSION_ID_PREFIX_LEN/turn 索引解析低层小工具)。
@@ -26,7 +26,7 @@
26
26
  */
27
27
  import { existsSync } from 'node:fs'
28
28
  import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
29
- import { join, isAbsolute } from 'node:path'
29
+ import { join, dirname, isAbsolute } from 'node:path'
30
30
  import { homedir } from 'node:os'
31
31
  import { toErrorMessage } from '@zhushanwen/pi-ext-guards'
32
32
  import { getLogger } from '@zhushanwen/pi-extension-logger'
@@ -102,7 +102,7 @@ import {
102
102
  extractUserMessages,
103
103
  type ExtractWhat,
104
104
  } from './extract.js'
105
- // 同轮拆分的渲染域模块(format* 纯函数簇,含 zcode 错误面 message 与
105
+ // 同轮拆分的渲染域模块(format* 纯函数簇,含 zcode 错误范围 message 与
106
106
  // zcodeReadErrorMessage 错误映射):运行时依赖方向本模块 → tool-format,
107
107
  // tool-format 对本模块仅 type import(ToolResult),无循环。
108
108
  import {
@@ -132,7 +132,7 @@ import { firstIncompleteAnchorReason } from './zcode-anchor-classify.js'
132
132
  export type { SessionReadSignals }
133
133
 
134
134
  // zcode 读链的结构化日志(「事后排查」通道:degradations 留痕 / L2-L3 恢复成功 /
135
- // anchor-missing 归因——§3.4 可观测性 + P2 降级隔离契约 F13,均不进 LLM 可见面)。
135
+ // anchor-missing 归因——§3.4 可观测性 + P2 降级隔离契约 F13,均不进 LLM 可见部分)。
136
136
  // pi handle 由 index.ts 初始化时 setPiHandle 注入(appendEntry 通道);未注入时
137
137
  // warn/error 降级文件日志,双开关缺省 no-op(纯 pi 独立用户零行为影响)。
138
138
  const logger = getLogger('session-reader')
@@ -267,7 +267,7 @@ async function resolveSessionId(
267
267
 
268
268
  // §3.4 第 0 段:sess_ 形态 id 无第一梯队直读入口(agent 从不持有 sess id,F14;
269
269
  // 且无 zcode 候选发现,D6)→ zcode_param_invalid。pi 的 session id 是 uuid(无
270
- // sess_ 前缀),此检测只改变错误输入的错误面文案。
270
+ // sess_ 前缀),此检测只改变错误输入的错误范围文案。
271
271
  if (session.startsWith('sess_')) {
272
272
  throw err(formatZcodeParamInvalid(session))
273
273
  }
@@ -287,7 +287,7 @@ async function resolveSessionId(
287
287
  * `resolveByRecordId`(engine 缺省/'pi' 形态,行为与今天逐字节一致)。
288
288
  * ③ pi 无命中(今天此处直接 ES2)→ entry 兜底(§3.5 第②层,仅 liveSessionDir 内,
289
289
  * 越界显式失败):命中 → zcode 读链;未命中 → `zcode_record_not_found` + 👉
290
- * (§3.4:sa-id 语境的统一错误面——F14 agent 唯一持有的 id 就是 sa-id,指引动作
290
+ * (§3.4:sa-id 语境的统一错误范围——F14 agent 唯一持有的 id 就是 sa-id,指引动作
291
291
  * 覆盖除 find 指针外的全部恢复路径(find 按 §3.4 刻意不保留)。
292
292
  */
293
293
  async function resolveSaIdRoute(
@@ -299,7 +299,7 @@ async function resolveSaIdRoute(
299
299
  ): Promise<ResolveResult> {
300
300
  const zm = await readZcodeManifest(agentDir, session)
301
301
  if (zm.kind === 'anchor-missing') {
302
- // 归因进结构化日志不进 LLM 可见面(§3.4):reason 逐键归因(missing-engineHandle /
302
+ // 归因进结构化日志不进 LLM 可见部分(§3.4):reason 逐键归因(missing-engineHandle /
303
303
  // missing-sessionRef / missing-sessionId / missing-dbPath,见 zcode-manifest.ts)
304
304
  logger.warn('zcode manifest anchor-missing', { saId: session, reason: zm.reason })
305
305
  throw err(formatZcodeAnchorMissing(session))
@@ -391,7 +391,7 @@ async function resolveZcodeRoute(
391
391
  const zcodeNodes = await listZcodeManifests(agentDir)
392
392
  const rootSessionId = zcodeNodes.find((n) => n.id === saId)?.rootSessionId
393
393
  // 枚举未含该 id(直读命中与枚举之间 manifest 被迁移的极端窗口)→ 退回锚 sessionId,
394
- // 后续 byId 查不到走既有 not-found 错误面(不静默伪造家族)
394
+ // 后续 byId 查不到走既有 not-found 错误范围(不静默伪造家族)
395
395
  if (rootSessionId === undefined) {
396
396
  return { kind: 'ok', sessionId: anchor.sessionId, fileName: '' }
397
397
  }
@@ -501,8 +501,8 @@ async function resolveByFragment(
501
501
  /**
502
502
  * zcode 锚 → ParseResult(既有 turns/render 管线的统一内容入口)。
503
503
  *
504
- * 检查顺序三段递进(§3.4,实现与测试双锚定):第 1 段存在性 → 第 2 段路径闸
505
- * (whitelist.ts)→ 第 3 段开库与查询(四级恢复阶梯 + schema 已知集闸门在
504
+ * 检查顺序三段递进(§3.4,实现与测试双锚定):第 1 段存在性 → 第 2 段路径门禁
505
+ * (whitelist.ts)→ 第 3 段开库与查询(四级恢复阶梯 + schema 已知集门禁在
506
506
  * openZcodeSessionDb 单点)。读库发生在 ext 进程内(§3.5 关键论断),不经 taiji。
507
507
  */
508
508
  async function loadZcodeParsed(anchor: ZcodeAnchor, agentDir: string): Promise<ParseResult> {
@@ -515,7 +515,7 @@ async function loadZcodeParsed(anchor: ZcodeAnchor, agentDir: string): Promise<P
515
515
  }
516
516
  try {
517
517
  // 恢复成功不再是静默事件(§3.4 硬要求):结构化日志(恢复方式 + db 路径),不进
518
- // LLM 可见面。用 warn 而非 debug——L2/L3 是恢复降级路径,语义表 warn=内部降级与
518
+ // LLM 可见部分。用 warn 而非 debug——L2/L3 是恢复降级路径,语义表 warn=内部降级与
519
519
  // 失败(appendEntry 持久化);debug 缺省 no-op 会让恢复成功重新变回静默事件。
520
520
  if (handle.via !== 'L1-direct') {
521
521
  logger.warn('zcode session db recovered via recovery ladder', {
@@ -545,7 +545,7 @@ async function loadZcodeParsed(anchor: ZcodeAnchor, agentDir: string): Promise<P
545
545
  // converter 保证),totalBytes/skippedLines 恒 0
546
546
  return { entries: normalized.entries, totalBytes: 0, skippedLines: 0, lastLinePartial: false }
547
547
  } catch (e) {
548
- if (e instanceof Error && e.message.startsWith('[zcode_')) throw e // 本函数产出的错误面原样传播
548
+ if (e instanceof Error && e.message.startsWith('[zcode_')) throw e // 本函数产出的错误范围原样传播
549
549
  throw err(zcodeReadErrorMessage(e, agentDir))
550
550
  } finally {
551
551
  handle.dispose()
@@ -912,7 +912,7 @@ async function doSearch(
912
912
  /** export full 模式的 entry 分隔线('=' 重复)宽度。 */
913
913
  const EXPORT_SEPARATOR_LEN = 40
914
914
 
915
- /** export:物化摘要到 <agentDir>/tmp/session-view-<id>.md(design §3.4 export,D-8)。 */
915
+ /** export:物化摘要到 <dataDir>/ext-sessions/<id>/session-view.md(session-store-layout-inversion 域 6;文件名去 sid——sid 已在路径)。 */
916
916
  async function doExport(
917
917
  params: SessionReadParams,
918
918
  agentDir: string,
@@ -972,8 +972,16 @@ async function doExport(
972
972
  label = 'outline'
973
973
  }
974
974
 
975
- const outDir = join(agentDir, 'tmp')
976
- const outPath = join(outDir, `session-view-${resolved.sessionId}.md`)
975
+ // [session-store-layout-inversion 域 6] 导出落 session 存储树(<root>/<sid>/session-view.md,
976
+ // 文件名去 sid——sid 已在路径):基址 = agentDir 剥一层(taiji dataDir,托管态
977
+ // PI_CODING_AGENT_DIR = <dataDir>/agent)。session-reader 零 shared 依赖(extension 包
978
+ // 边界),公式与 shared getSessionViewPath 同字面镜像;sessionId 过 isPiSessionId 同域
979
+ // 校验(解析器产出的 sid 正常路径恒合法,此处是路径穿越纵深防线)。
980
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(resolved.sessionId)) {
981
+ throw err(`导出拒绝:sessionId 形态非法(${resolved.sessionId})👉 检查 session 参数或用 find 重新定位`)
982
+ }
983
+ const outDir = join(dirname(agentDir), 'ext-sessions', resolved.sessionId)
984
+ const outPath = join(outDir, 'session-view.md')
977
985
  await mkdir(outDir, { recursive: true })
978
986
  await writeFile(outPath, text, 'utf8')
979
987
  const sizeBytes = Buffer.byteLength(text, 'utf8')
@@ -996,7 +1004,7 @@ async function doExport(
996
1004
  //(max-lines 拆分轮机械提取);本段保留 F7 what 校验与 doExtract 编排(定位依赖
997
1005
  // tool-handler 私有的 resolveSessionId/safeParse/segmentTurns)。纯提取,不调 LLM。
998
1006
 
999
- /** what 类型守卫(直接比较,避开不安全断言;schema 已校验,此处防御 + 可单测绕过)。 */
1007
+ /** what 类型检查(直接比较,避开不安全断言;schema 已校验,此处防御 + 可单测绕过)。 */
1000
1008
  function isExtractWhat(v: unknown): v is ExtractWhat {
1001
1009
  return (
1002
1010
  v === 'user-messages' ||
@@ -1109,7 +1117,7 @@ interface SkippedRun {
1109
1117
  * session 会抛「session not found in family index」)——resolveSessionId 已把 session 解析到
1110
1118
  * 真实文件(kind==='ok' 保证文件存在,三形态:绝对路径/sa-id 均 existsSync 校验,片段匹配
1111
1119
  * 来自实际 fs 扫描),直接用 resolved.fileName 构造单条目 sessionIdToPath 调 resolveWorkflows
1112
- *(与 buildFamilyFromFs 步骤 6 的 workflow 腿同源)。pathToRef 传空 Map(单条目链路无其他
1120
+ *(与 buildFamilyFromFs 步骤 6 的 workflow 路径同源)。pathToRef 传空 Map(单条目链路无其他
1113
1121
  * 文件可反查),call 引用 100% 走 sessionRefFromPath 文件名最小回退(sessionId+fileName,
1114
1122
  * 足够 LLM 跳 outline/detail 深读)。
1115
1123
  *
@@ -1131,7 +1139,7 @@ async function renderSingleWorkflowRun(
1131
1139
  ): Promise<void> {
1132
1140
  // [D16③] 概览链换源:v2 档的 stateFile = record 流路径(recordPath 锚点语义
1133
1141
  // 重定义)——直读流经 parseRunRecordStream 概览([D2] 三态 status;空流/读失败
1134
- // 投影 running,活跃 run 概览不退化为 skipped——红线)。record 后缀以外的
1142
+ // 映射 running,活跃 run 概览不退化为 skipped——红线)。record 后缀以外的
1135
1143
  // stateFile(v1 快照档空串 / 旧指针档 link path)走原快照链。
1136
1144
  if (wf.stateFile.endsWith('.record.jsonl')) {
1137
1145
  await renderRunFromRecordStream(wf, ctx)
@@ -1391,7 +1399,7 @@ export async function handleSessionRead(
1391
1399
  case 'workflow':
1392
1400
  return doWorkflow(params, agentDir, signals.liveSessionDir)
1393
1401
  case 'result': {
1394
- // per-call 构造注入面(仅剩 tool-handler 文件私有 helper,纯函数经 handler-utils
1402
+ // per-call 构造注入点(仅剩 tool-handler 文件私有 helper,纯函数经 handler-utils
1395
1403
  // 直接 import——ext-simplify-04 E5):resolveSessionId 包装把信号包中的
1396
1404
  // liveSessionDir 闭包进解析调用(ResultActionDeps 接口签名固定 5 参,包装保持
1397
1405
  // 同形、末位补传),result 的片段形态与 find/outline 消费同一 roots
@@ -77,7 +77,7 @@ export function createSessionCommand(
77
77
  const chosen = await ctx.ui.select('选择一个 session 插入 # 引用', labels)
78
78
  if (chosen === undefined) return
79
79
  // chosen 必属 labels(select 契约返回列表成员或 undefined,后者上一行已拦截),
80
- // indexOf 恒命中——不再保留 idx<0 死防御分支
80
+ // indexOf 恒命中——不再保留 idx<0 永不命中的防御机制分支
81
81
  const idx = labels.indexOf(chosen)
82
82
  // 选中后插入 # + 完整 uuid(剥 # 算片段,再补 # 插入编辑器)
83
83
  const frag = toCandidate(top[idx]).insertText.slice(1)
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * 判定与 entry-anchor.ts 的 zcodeAnchorOfEntry 双键判据同构(v1 快照 / v2 终态条
7
7
  * 双版本门),协议字面量同值(写侧 subagent-core record-entry.ts
8
- * SUBAGENT_RECORD_CUSTOM_TYPE,漂移由 entry-anchor.test.ts 守卫)。
8
+ * SUBAGENT_RECORD_CUSTOM_TYPE,漂移由 entry-anchor.test.ts 检查)。
9
9
  */
10
10
  import type { Entry } from '@zhushanwen/session-core'
11
11
  import type { ZcodeAnchorMissingReason } from './discovery/zcode-manifest.js'
@@ -13,7 +13,7 @@ import type { ZcodeAnchorMissingReason } from './discovery/zcode-manifest.js'
13
13
  /**
14
14
  * v2 条目 data 的认识版本词表(W1 起 = 2)。与写侧 subagent-core
15
15
  * SUBAGENT_RECORD_ENTRY_VERSION 同值——本地持有 + entry-anchor.test.ts 跨包断言
16
- * 守卫漂移(生产依赖面不引 subagent-core,同 entry-anchor.ts 的协议字面量模式)。
16
+ * 检查漂移(生产依赖范围不引 subagent-core,同 entry-anchor.ts 的协议字面量模式)。
17
17
  */
18
18
  const SUBAGENT_RECORD_ENTRY_V2 = 2
19
19