@zhushanwen/pi-session-reader 0.11.0 → 0.13.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.
- package/package.json +5 -5
- package/src/__tests__/archive-ledger.test.ts +447 -0
- package/src/__tests__/cross-package-subagent-core.test.ts +34 -25
- package/src/__tests__/entry-anchor.test.ts +3 -3
- package/src/__tests__/execution-tree.test.ts +85 -86
- package/src/__tests__/family-zcode.test.ts +24 -14
- package/src/__tests__/family.test.ts +4 -4
- package/src/__tests__/find.test.ts +50 -5
- package/src/__tests__/index.test.ts +3 -3
- package/src/__tests__/rebuild-manifest-dual-write.test.ts +29 -22
- package/src/__tests__/render.test.ts +2 -2
- package/src/__tests__/result.test.ts +20 -11
- package/src/__tests__/roots.test.ts +64 -26
- package/src/__tests__/subagents.test.ts +108 -52
- package/src/__tests__/tool-handler.test.ts +133 -83
- package/src/__tests__/workflow.test.ts +5 -5
- package/src/__tests__/zcode-manifest.test.ts +18 -10
- package/src/__tests__/zcode-routing.test.ts +21 -19
- package/src/__tests__/zcode-whitelist.test.ts +1 -1
- package/src/core/family.ts +6 -6
- package/src/core/render.ts +2 -2
- package/src/core/toolcall.ts +1 -1
- package/src/core/workflow.ts +9 -9
- package/src/discovery/archive-ledger.ts +221 -0
- package/src/discovery/archive-match.ts +84 -0
- package/src/discovery/entry-anchor.ts +5 -5
- package/src/discovery/find.ts +12 -1
- package/src/discovery/roots.ts +149 -14
- package/src/discovery/subagents.ts +24 -17
- package/src/discovery/whitelist.ts +4 -4
- package/src/discovery/workflows.ts +8 -8
- package/src/discovery/zcode-manifest.ts +22 -27
- package/src/doctor.ts +3 -0
- package/src/extract.ts +1 -1
- package/src/index.ts +5 -2
- package/src/no-match.ts +29 -4
- package/src/result-action.ts +7 -1
- package/src/search-across.ts +3 -3
- package/src/tool-format.ts +24 -13
- package/src/tool-handler.ts +68 -29
- package/src/tui/session-command.ts +1 -1
- package/src/zcode-anchor-classify.ts +2 -2
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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 =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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 //
|
|
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 =
|
|
240
|
+
const root = resolveSessionStoreRoot(agentDir)
|
|
240
241
|
const out: ZcodeManifestRecord[] = []
|
|
241
|
-
|
|
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
|
-
|
|
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/doctor.ts
CHANGED
|
@@ -93,6 +93,9 @@ function renderDoctor(
|
|
|
93
93
|
// 仅 subagents:'stat' 的 subagent 根(doctor 默认形态)——其余根恒有计数(实扫);
|
|
94
94
|
// 被去重根已在上方提前返回
|
|
95
95
|
facts.push('未扫描(subagent 根默认不扫,includeSubagents:true 开启)')
|
|
96
|
+
} else if (r.kind === 'archive') {
|
|
97
|
+
// [session-archive D7] 归档根计数 = 簿记数(非 .jsonl 文件数,措辞按实名)
|
|
98
|
+
facts.push(`${r.fileCount} 归档单元`, `扫 ${Math.round(r.scanMs ?? 0)}ms`)
|
|
96
99
|
} else {
|
|
97
100
|
facts.push(`${r.fileCount} 文件`, `扫 ${Math.round(r.scanMs ?? 0)}ms`)
|
|
98
101
|
}
|
package/src/extract.ts
CHANGED
|
@@ -347,7 +347,7 @@ function collectCommitCandidates(
|
|
|
347
347
|
return { high, low }
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
/**
|
|
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,9 +156,12 @@ 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
|
|
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
|
+
// [session-archive D7] 归档态硬契约的 LLM 侧预告:引用已归档 session 只得摘要
|
|
163
|
+
//(返回头部显式标注「已归档」),恢复完整历史是用户操作。
|
|
164
|
+
'Archived sessions return summary-only views (marked 已归档/archived)—for full history, ask the user to restore the session.',
|
|
162
165
|
'Errors carry a 👉 recovery hint—follow it to retry.',
|
|
163
166
|
'Unsure about host environment or session roots? Run action:"doctor".',
|
|
164
167
|
]
|
|
@@ -250,7 +253,7 @@ export default function sessionReaderExtension(pi: ExtensionAPI): void {
|
|
|
250
253
|
//
|
|
251
254
|
// addAutocompleteProvider 挂在 ctx.ui(非 ExtensionAPI),setup 入口无 ctx,
|
|
252
255
|
// 只能在 event handler 里拿——session_start 是最早且每 session 触发的 event。
|
|
253
|
-
|
|
256
|
+
// once-guard + ctx.mode 检查 + typeof 运行时检查三重防护。
|
|
254
257
|
//
|
|
255
258
|
// 2026-08-10 重构:数据源从全盘 findSessions(agentDir) 换为 SessionManager.listAll(ctx.sessionManager.getSessionDir())。
|
|
256
259
|
// getSessionDir() 返回当前 session 的目录(encoded cwd),listAll 只扫该目录 →
|
package/src/no-match.ts
CHANGED
|
@@ -62,6 +62,13 @@ function buildSelfCheckLines(roots: SessionRoot[]): string[] {
|
|
|
62
62
|
continue
|
|
63
63
|
}
|
|
64
64
|
const count = r.fileCount ?? r.files.length
|
|
65
|
+
// [session-archive D2 U2] 归档根不计入 find 候选集合计(find 只迭代在场 .jsonl,
|
|
66
|
+
// 归档根 files 恒空、fileCount = 簿记数——计入则「候选集非空」陈述失真);
|
|
67
|
+
// 措辞对齐 doctor 的「N 归档单元」实名(区别于 .jsonl 文件计数)。
|
|
68
|
+
if (r.kind === 'archive') {
|
|
69
|
+
lines.push(` [${r.kind}]`.padEnd(F1_ROOT_LABEL_PAD) + `${r.path}:${count} 归档单元`)
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
65
72
|
totalFiles += count
|
|
66
73
|
lines.push(` [${r.kind}]`.padEnd(F1_ROOT_LABEL_PAD) + `${r.path}:${count} 文件`)
|
|
67
74
|
}
|
|
@@ -117,8 +124,15 @@ function formatSuggestionLines(suggestions: NoMatchSuggestion[], query: string):
|
|
|
117
124
|
return lines
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
/**
|
|
121
|
-
|
|
127
|
+
/**
|
|
128
|
+
* ③ 正确做法四条 + ④ 封死 shell 绕行(纯静态文案)。
|
|
129
|
+
*
|
|
130
|
+
* [session-archive D7] archiveHint:归档根存在且有簿记时由 formatNoMatch 追加第
|
|
131
|
+
* 五条(目标可能已归档的引用指引)——find 结果不含归档条目,归档引用通道是深读
|
|
132
|
+
* action 的 session 参数(resolveByFragment 的归档回退层)。无归档目录 → 空数组,
|
|
133
|
+
* 文案零变化。
|
|
134
|
+
*/
|
|
135
|
+
function guidanceLines(archiveHint: string[] = []): string[] {
|
|
122
136
|
return [
|
|
123
137
|
'',
|
|
124
138
|
'正确做法:',
|
|
@@ -126,6 +140,7 @@ function guidanceLines(): string[] {
|
|
|
126
140
|
` - 已知文件路径时直接传绝对路径:session_read { action:"outline", session:"<绝对路径>.jsonl" }`,
|
|
127
141
|
` - 截断/过期 id 用更短前缀:session_read { action:"find", query:"<更短前缀>" }`,
|
|
128
142
|
` - 想先看环境与根目录状态:session_read { action:"doctor" }`,
|
|
143
|
+
...archiveHint,
|
|
129
144
|
'',
|
|
130
145
|
'不要用 shell find/ls/rg 搜 session 目录,不要 cat/read 原始 .jsonl —— session_read 是唯一入口。',
|
|
131
146
|
]
|
|
@@ -147,15 +162,25 @@ function guidanceLines(): string[] {
|
|
|
147
162
|
* ③「正确做法」四条:标题/keyword、绝对路径 outline、更短前缀、doctor。
|
|
148
163
|
* ④ 最后一行封死 shell 绕行:find/ls/rg 搜 session 目录、cat/read 原始 .jsonl 均禁止。
|
|
149
164
|
*
|
|
150
|
-
* 无「👉 用 recent 看全量」误导指引(recent 候选对写错的 uuid 无自纠价值,§6.7
|
|
165
|
+
* 无「👉 用 recent 看全量」误导指引(recent 候选对写错的 uuid 无自纠价值,§6.7 不采用项)。
|
|
151
166
|
*/
|
|
152
167
|
export function formatNoMatch(query: string, roots: SessionRoot[]): string {
|
|
168
|
+
// [session-archive D7] 归档指引(条件行):归档根存在且簿记非空才出现——自检行
|
|
169
|
+
// 已列 [archive] 根计数,此处补「怎么引用」的动作指引,find→深读链路对归档
|
|
170
|
+
// session 不断链。无归档目录的 roots 不含 archive 根,guidanceLines 零变化。
|
|
171
|
+
const archiveRoot = roots.find((r) => r.kind === 'archive' && r.dedupedInto === undefined)
|
|
172
|
+
const archiveHint =
|
|
173
|
+
archiveRoot !== undefined && (archiveRoot.fileCount ?? 0) > 0
|
|
174
|
+
? [
|
|
175
|
+
` - 目标可能已归档:直接用 session 参数引用(返回归档摘要),如 session_read { action:"outline", session:"<名称片段或id>" }`,
|
|
176
|
+
]
|
|
177
|
+
: []
|
|
153
178
|
return [
|
|
154
179
|
`无匹配 session:"${query}"`,
|
|
155
180
|
'',
|
|
156
181
|
...buildSelfCheckLines(roots),
|
|
157
182
|
'',
|
|
158
183
|
...formatSuggestionLines(collectSuggestions(query, roots), query),
|
|
159
|
-
...guidanceLines(),
|
|
184
|
+
...guidanceLines(archiveHint),
|
|
160
185
|
].join('\n')
|
|
161
186
|
}
|
package/src/result-action.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { Entry, ParseResult } from '@zhushanwen/session-core'
|
|
19
19
|
import { textBlockParts } from './core/render.js'
|
|
20
|
+
import { archivedResult } from './discovery/archive-match.js'
|
|
20
21
|
import type { MatchedSession } from './discovery/find.js'
|
|
21
22
|
import { listRecordManifests, type RecordManifest } from './discovery/subagents.js'
|
|
22
23
|
import { err, requireStr, SESSION_ID_PREFIX_LEN, stripHash } from './handler-utils.js'
|
|
@@ -31,7 +32,7 @@ const RESULT_DEFAULT_LIMIT = 8000
|
|
|
31
32
|
/** result 批量条目分隔符(与 notifier 批通知 "\n\n---\n\n" join 同款)。 */
|
|
32
33
|
const RESULT_BATCH_SEPARATOR = '\n\n---\n\n'
|
|
33
34
|
|
|
34
|
-
/** result action 对 tool-handler 文件私有 helper
|
|
35
|
+
/** result action 对 tool-handler 文件私有 helper 的注入点(入口分发处 per-call 构造)。 */
|
|
35
36
|
export interface ResultActionDeps {
|
|
36
37
|
resolveSessionId(
|
|
37
38
|
rawSession: string | undefined,
|
|
@@ -208,6 +209,11 @@ export async function doResult(
|
|
|
208
209
|
for (const id of ids) {
|
|
209
210
|
const resolved = await deps.resolveSessionId(id, 'result', agentDir, params.source, prefetchedManifests)
|
|
210
211
|
if (resolved.kind === 'multi') return deps.disambiguate(resolved.query, resolved.candidates)
|
|
212
|
+
// [session-archive D7] 片段命中归档 session:完整正文不可读,统一返回归档态摘要
|
|
213
|
+
//(构造与渲染收敛在 archive-match 的 archivedResult,与 tool-handler 同一出口)
|
|
214
|
+
if (resolved.kind === 'archived' || resolved.kind === 'archived-multi') {
|
|
215
|
+
return archivedResult(resolved)
|
|
216
|
+
}
|
|
211
217
|
const { entries } = await deps.safeParse(resolved.fileName)
|
|
212
218
|
const text = extractFinalAssistantText(entries)
|
|
213
219
|
if (text.length === 0) {
|
package/src/search-across.ts
CHANGED
|
@@ -137,7 +137,7 @@ export function collectSearchHits(
|
|
|
137
137
|
return hits
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
/** search
|
|
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
|
-
/**
|
|
358
|
+
/** 缺省值收尾后的检索参数(各缺省值与旧内联 `opts?.** ?? 常量` 逐一对应)。 */
|
|
359
359
|
interface CrossSearchResolvedOptions {
|
|
360
360
|
scope: NonNullable<SessionReadParams['scope']>
|
|
361
361
|
limit: number
|
package/src/tool-format.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* session_read 各 action 的输出文本渲染(max-lines 拆分轮从 tool-handler.ts 机械
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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 等公共类型留
|
|
@@ -33,7 +33,8 @@ import {
|
|
|
33
33
|
import type { Family } from './core/family.js'
|
|
34
34
|
import { SESSION_ID_PREFIX_LEN, rangeLabel } from './handler-utils.js'
|
|
35
35
|
import { formatNoMatch } from './no-match.js'
|
|
36
|
-
import
|
|
36
|
+
import { archivedResult } from './discovery/archive-match.js'
|
|
37
|
+
import type { ResolveResult, ToolResult } from './tool-handler.js'
|
|
37
38
|
|
|
38
39
|
// 结构化日志通道(zcodeReadErrorMessage 的降级/TOCTOU warn):getLogger 按名缓存
|
|
39
40
|
// 单例,与 tool-handler 侧调用取同一实例,appendEntry/文件日志行为不变。
|
|
@@ -84,7 +85,7 @@ export function formatSaIdNotFound(saId: string): string {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
// ---------------------------------------------------------------------------
|
|
87
|
-
// zcode
|
|
88
|
+
// zcode 错误范围(U9,design §3.4 七码 + 👉 逐行;错误码标识入 message 供机器识别,
|
|
88
89
|
// 形态对齐 §3.1 失败路径示例 `[zcode_db_path_forbidden] …`)
|
|
89
90
|
// ---------------------------------------------------------------------------
|
|
90
91
|
|
|
@@ -131,7 +132,7 @@ function formatZcodeSchemaDrift(observed: string | undefined): string {
|
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
134
|
* bun 驱动探测失败(§3.4 表第 8 行理论态)的 message:宿主既无 bun:sqlite 也无
|
|
134
|
-
* node:sqlite——zcode action
|
|
135
|
+
* node:sqlite——zcode action 整体降级为本错误范围(fail-fast + 日志),pi 链路不受影响。
|
|
135
136
|
* 错误码字面量为实施期命名(设计该行未定码名,形态对齐七码 `[zcode_*]` 惯例)。
|
|
136
137
|
*/
|
|
137
138
|
function formatZcodeHostUnsupported(detail: string): string {
|
|
@@ -145,8 +146,8 @@ function formatZcodeHostUnsupported(detail: string): string {
|
|
|
145
146
|
/**
|
|
146
147
|
* sqlite 驱动探测失败的消息特征(zcode-session-source sqlite-driver.ts 探测失败的
|
|
147
148
|
* 错误消息契约;该包不导出专用错误子类,reader 侧按消息特征单列识别——跨包漂移
|
|
148
|
-
* 由 zcode-routing.test.ts
|
|
149
|
-
*
|
|
149
|
+
* 由 zcode-routing.test.ts 的源文本契约测试检查:sqlite-driver 源文不再含本子串即红)。
|
|
150
|
+
* 导出范围仅测试消费(契约锚 + 映射测试引用同值,禁测试内硬编码副本)。
|
|
150
151
|
*/
|
|
151
152
|
export const SQLITE_DRIVER_UNSUPPORTED_MARK = '不支持 node:sqlite'
|
|
152
153
|
|
|
@@ -154,17 +155,17 @@ export const SQLITE_DRIVER_UNSUPPORTED_MARK = '不支持 node:sqlite'
|
|
|
154
155
|
* 库文件缺失的消息特征(zcode-session-source sqlite-access.ts `openZcodeSessionDb`
|
|
155
156
|
* 开头 existsSync 失败抛普通 Error「db 文件不存在:<dbPath>」的消息契约锚点)。该包
|
|
156
157
|
* 不导出专用错误子类,reader 侧按消息特征单列识别——落 §3.4 第 4 行
|
|
157
|
-
* `zcode_db_unreadable`(不误标 schema_drift
|
|
158
|
+
* `zcode_db_unreadable`(不误标 schema_drift)。生产路径上白名单门禁
|
|
158
159
|
* (assertZcodeDbPathAllowed 第 1 段)已先行拦掉绝大多数缺失,此处命中的是两段
|
|
159
|
-
* existsSync 之间的 TOCTOU
|
|
160
|
-
* zcode-routing.test.ts
|
|
161
|
-
*
|
|
160
|
+
* existsSync 之间的 TOCTOU 理论窗口(门禁通过后开库前文件被删)。跨包漂移由
|
|
161
|
+
* zcode-routing.test.ts 的源文本契约测试检查(SQLITE_DRIVER_UNSUPPORTED_MARK 同范式)。
|
|
162
|
+
* 导出范围仅测试消费(契约锚 + 映射测试引用同值,禁测试内硬编码副本)。
|
|
162
163
|
*/
|
|
163
164
|
export const ZCODE_DB_MISSING_MARK = 'db 文件不存在'
|
|
164
165
|
|
|
165
166
|
/**
|
|
166
|
-
* 开库/查询期错误 → §3.4
|
|
167
|
-
*
|
|
167
|
+
* 开库/查询期错误 → §3.4 错误范围映射(SqliteUnreadableError / schema drift / 驱动探测失败 / 库缺失 / 其余)。
|
|
168
|
+
* 导出范围仅测试消费(L4 包装形态的映射契约直测,zcode-routing.test.ts)。
|
|
168
169
|
*/
|
|
169
170
|
export function zcodeReadErrorMessage(e: unknown, agentDir: string): string {
|
|
170
171
|
if (e instanceof SqliteUnreadableError) {
|
|
@@ -229,6 +230,16 @@ export function disambiguate(query: string, candidates: MatchedSession[]): ToolR
|
|
|
229
230
|
return { content: [{ type: 'text', text }], details: { ambiguous: true, candidates } }
|
|
230
231
|
}
|
|
231
232
|
|
|
233
|
+
/**
|
|
234
|
+
* ResolveResult 非 ok 支的统一分发([session-archive D7] 归并点):multi → 既有
|
|
235
|
+
* F2 消歧;archived / archived-multi → 归档态返回(archive-match 两支渲染,不读
|
|
236
|
+
* 压缩体)。8 个 do* 的「multi 消歧 + 归档短路」重复分支收敛到本函数单一出口。
|
|
237
|
+
*/
|
|
238
|
+
export function resolveNonOkResult(resolved: Exclude<ResolveResult, { kind: 'ok' }>): ToolResult {
|
|
239
|
+
if (resolved.kind === 'multi') return disambiguate(resolved.query, resolved.candidates)
|
|
240
|
+
return archivedResult(resolved)
|
|
241
|
+
}
|
|
242
|
+
|
|
232
243
|
// ---------------------------------------------------------------------------
|
|
233
244
|
// 文本渲染(content)
|
|
234
245
|
// ---------------------------------------------------------------------------
|