@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.
- package/README.md +100 -0
- package/package.json +7 -4
- package/src/__tests__/cross-package-subagent-core.test.ts +36 -44
- package/src/__tests__/entry-anchor.test.ts +226 -0
- package/src/__tests__/env.test.ts +54 -54
- package/src/__tests__/execution-tree.test.ts +6 -0
- package/src/__tests__/family-zcode.test.ts +255 -0
- package/src/__tests__/family.test.ts +1 -1
- package/src/__tests__/find.test.ts +24 -1
- package/src/__tests__/index.test.ts +1 -1
- package/src/__tests__/parser.test.ts +1 -2
- package/src/__tests__/real-data.ts +22 -9
- package/src/__tests__/render.test.ts +1 -2
- package/src/__tests__/result.test.ts +2 -2
- package/src/__tests__/roots.test.ts +1 -1
- package/src/__tests__/subagents.test.ts +1 -4
- package/src/__tests__/tool-handler.test.ts +76 -85
- package/src/__tests__/toolcall.test.ts +1 -1
- package/src/__tests__/tree.test.ts +1 -1
- package/src/__tests__/turns.test.ts +1 -2
- package/src/__tests__/zcode-manifest.test.ts +361 -0
- package/src/__tests__/zcode-routing.test.ts +751 -0
- package/src/__tests__/zcode-whitelist.test.ts +119 -0
- package/src/core/execution-tree.ts +3 -1
- package/src/core/family.ts +80 -1
- package/src/core/render.ts +2 -3
- package/src/core/toolcall.ts +3 -4
- package/src/core/tree.ts +1 -1
- package/src/core/turns.ts +1 -1
- package/src/core/units.ts +14 -0
- package/src/discovery/entry-anchor.ts +108 -0
- package/src/discovery/env.ts +41 -41
- package/src/discovery/find.ts +1 -1
- package/src/discovery/roots.ts +2 -2
- package/src/discovery/session-header.ts +30 -69
- package/src/discovery/subagents.ts +9 -2
- package/src/discovery/whitelist.ts +87 -0
- package/src/discovery/workflows.ts +1 -1
- package/src/discovery/zcode-manifest.ts +277 -0
- package/src/doctor.ts +5 -70
- package/src/index.ts +9 -2
- package/src/result-action.ts +15 -6
- package/src/search-across.ts +2 -5
- package/src/tool-format.ts +455 -0
- package/src/tool-handler.ts +321 -325
- package/src/zcode-anchor-classify.ts +60 -0
- package/vitest.config.ts +2 -4
- package/src/core/parser.ts +0 -167
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs'
|
|
4
|
+
import { homedir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
assertZcodeDbPathAllowed,
|
|
9
|
+
zcodeDbAllowlistFor,
|
|
10
|
+
} from '../discovery/whitelist.js'
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// P-whitelist(design session-reader-shared-core §3.3 D5 / §3.4 检查顺序 / 附录 A):
|
|
14
|
+
// 派生白名单在 taiji dataDir 布局下精确命中隔离库。
|
|
15
|
+
//
|
|
16
|
+
// 布局(mkdtemp 自建自删):root/agent = agentDir(dirname = dataDir root),
|
|
17
|
+
// root/engines/zcode/session-db/db.sqlite = 隔离库。测试内手拼路径段是**测试对
|
|
18
|
+
// 派生正确性的唯一验证手段**(生产 src 禁手拼,段单源 = SDK ZCODE_ISOLATED_DB_
|
|
19
|
+
// SEGMENTS 经 zcode-session-source zcodeImportDbAllowlist 引用)。
|
|
20
|
+
//
|
|
21
|
+
// 检查顺序铁律断言:存在性(不存在 → unreadable)先于路径(集合外 → forbidden)——
|
|
22
|
+
// 集合外且不存在的路径必须报 unreadable 而非 forbidden(场景 5 ④ 顺序断言的单元锚)。
|
|
23
|
+
// ============================================================
|
|
24
|
+
|
|
25
|
+
const ISOLATED_DB_SEGMENTS = ['engines', 'zcode', 'session-db', 'db.sqlite']
|
|
26
|
+
|
|
27
|
+
let root: string
|
|
28
|
+
let agentDir: string
|
|
29
|
+
let isolatedDbPath: string
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
root = mkdtempSync(join(tmpdir(), 'zcode-whitelist-test-'))
|
|
33
|
+
agentDir = join(root, 'agent')
|
|
34
|
+
mkdirSync(agentDir, { recursive: true })
|
|
35
|
+
isolatedDbPath = join(root, ...ISOLATED_DB_SEGMENTS)
|
|
36
|
+
})
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('zcodeDbAllowlistFor:tmp dataDir 布局精确命中隔离库(P-whitelist)', () => {
|
|
42
|
+
it('第一项 = zcodeIsolatedDbPath(dirname(agentDir)),集合共两项(隔离库 + 宿主库)', () => {
|
|
43
|
+
const allow = zcodeDbAllowlistFor(agentDir)
|
|
44
|
+
expect(allow).toHaveLength(2)
|
|
45
|
+
// 精确命中:手拼段与派生结果逐字节相等(同源常量 + 同一 join 派生式的行为锚)
|
|
46
|
+
expect(allow[0]).toBe(isolatedDbPath)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('realpath 对齐:agentDir 经 symlink 到达(macOS /var → /private/var 形态)仍命中', () => {
|
|
50
|
+
// tmpdir 本身在 macOS 即 symlink 链(/var → /private/var):mkdtemp 产物 realpath
|
|
51
|
+
// 后 ≠ 字面串。允许集合与 dbPath 同经 realpath 归一(whitelist.ts realOf),
|
|
52
|
+
// 两侧一致归一后比对 = 命中。isolatedDbPath 真实存在才有 realpath 归一输入。
|
|
53
|
+
mkdirSync(join(root, ...ISOLATED_DB_SEGMENTS.slice(0, -1)), { recursive: true })
|
|
54
|
+
writeFileSync(isolatedDbPath, '', 'utf8')
|
|
55
|
+
expect(() => assertZcodeDbPathAllowed(isolatedDbPath, agentDir)).not.toThrow()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('纯 pi 宿主边界(D5):agentDir 无对应 dataDir 布局时集合外路径照常拒绝', () => {
|
|
59
|
+
const outside = join(root, 'somewhere-else', 'db.sqlite')
|
|
60
|
+
mkdirSync(join(root, 'somewhere-else'), { recursive: true })
|
|
61
|
+
writeFileSync(outside, '', 'utf8')
|
|
62
|
+
expect(() => assertZcodeDbPathAllowed(outside, agentDir)).toThrow('zcode_db_path_forbidden')
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('assertZcodeDbPathAllowed:检查顺序三段递进(存在性先于路径)', () => {
|
|
67
|
+
it('第 1 段存在性:白名单内路径但文件不存在 → zcode_db_unreadable(不误报 forbidden)', () => {
|
|
68
|
+
expect(() => assertZcodeDbPathAllowed(isolatedDbPath, agentDir)).toThrow(
|
|
69
|
+
'zcode_db_unreadable',
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('第 1 段先于第 2 段:集合外且文件不存在的路径 → 仍是 unreadable(不是 forbidden)', () => {
|
|
74
|
+
const outsideMissing = join(root, 'nowhere', 'evil.sqlite')
|
|
75
|
+
expect(() => assertZcodeDbPathAllowed(outsideMissing, agentDir)).toThrow(
|
|
76
|
+
'zcode_db_unreadable',
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('第 2 段路径:存在但集合外 → zcode_db_path_forbidden(安全面错误语义更重)', () => {
|
|
81
|
+
const outside = join(root, 'evil.sqlite')
|
|
82
|
+
writeFileSync(outside, '', 'utf8')
|
|
83
|
+
try {
|
|
84
|
+
assertZcodeDbPathAllowed(outside, agentDir)
|
|
85
|
+
expect.unreachable('should have thrown')
|
|
86
|
+
} catch (e) {
|
|
87
|
+
const msg = (e as Error).message
|
|
88
|
+
expect(msg).toContain('zcode_db_path_forbidden')
|
|
89
|
+
expect(msg).toContain(outside)
|
|
90
|
+
expect(msg).toContain('👉')
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('通过闸(集合内且存在)→ 不抛错(开库由调用方进行)', () => {
|
|
95
|
+
mkdirSync(join(root, ...ISOLATED_DB_SEGMENTS.slice(0, -1)), { recursive: true })
|
|
96
|
+
writeFileSync(isolatedDbPath, '', 'utf8')
|
|
97
|
+
expect(() => assertZcodeDbPathAllowed(isolatedDbPath, agentDir)).not.toThrow()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('unreadable 指引含动态推导的隔离库路径与 -wal 告诫(§3.4 文案)', () => {
|
|
101
|
+
try {
|
|
102
|
+
assertZcodeDbPathAllowed(isolatedDbPath, agentDir)
|
|
103
|
+
expect.unreachable('should have thrown')
|
|
104
|
+
} catch (e) {
|
|
105
|
+
const msg = (e as Error).message
|
|
106
|
+
expect(msg).toContain(isolatedDbPath)
|
|
107
|
+
expect(msg).toContain('-wal')
|
|
108
|
+
expect(msg).toContain('👉')
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('宿主库第二项 = join(homedir(), SDK 宿主段)(存量兼容锚点,不在本梯队触碰)', () => {
|
|
113
|
+
const allow = zcodeDbAllowlistFor(agentDir)
|
|
114
|
+
// 第二项是 homedir 派生的宿主库(真实 HOME 下的 .zcode/cli/db/db.sqlite 形态)——
|
|
115
|
+
// 测试不触碰真实文件,只断言其位于集合且 ≠ 隔离库项
|
|
116
|
+
expect(allow[1]).not.toBe(allow[0])
|
|
117
|
+
expect(allow[1].startsWith(homedir())).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
@@ -437,7 +437,9 @@ export async function buildExecutionTree(
|
|
|
437
437
|
sessionId: rootSessionId,
|
|
438
438
|
// MF-1:main root 携带 sessionFile(调用方传入),否则 attachWorkflowChildren 对无
|
|
439
439
|
// sessionFile 节点直接 return,main 自身发起的 workflow run 永远进不了执行树。
|
|
440
|
-
|
|
440
|
+
// falsy 归一为 undefined:调用方的「路径不可得」哨兵是空串(zcode family 路由
|
|
441
|
+
// 反查未命中),root 节点 details 不携带 sessionFile:"" 这类非路径占位值。
|
|
442
|
+
sessionFile: mainSessionFile || undefined,
|
|
441
443
|
depth: 0,
|
|
442
444
|
rootSessionId,
|
|
443
445
|
children: [],
|
package/src/core/family.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Entry } from '
|
|
1
|
+
import type { Entry } from '@zhushanwen/session-core'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 家族索引与解析(design §3.3 D-7)。
|
|
@@ -78,6 +78,74 @@ export interface FamilyIndex {
|
|
|
78
78
|
fileStats: Map<string, { mtime: number; size: number }>
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// ---- zcode 节点(design session-reader-shared-core §3.3 D5-1)----
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* zcode subagent 节点的判据输入视图(discovery 层 zcode-manifest.ts 枚举产出,
|
|
85
|
+
* core 层零 IO 消费)。扁平化:dbPath 取自 manifest engineHandle.sessionRef.dbPath
|
|
86
|
+
* (zcode 锚),dbFileExists 由 discovery 枚举时 statSync 预计算——stat 只查存在性
|
|
87
|
+
* **不开库**(D5-1:静息态 CANTOPEN 会把活会话误判已 GC,且列表视图每节点开库的
|
|
88
|
+
* 成本不该付;库内 session 已被 zcode GC 的情形由 read 路径 zcode_session_not_found
|
|
89
|
+
* 权威承接,列表视图保持廉价是本判据的设计核心)。
|
|
90
|
+
*/
|
|
91
|
+
export interface ZcodeFamilyNode {
|
|
92
|
+
/** record id(= sa-id,manifest 文件名 stem)。作 SubagentRef.sessionId(pi 孤儿链同款:entry.id = manifest.id) */
|
|
93
|
+
id: string
|
|
94
|
+
/** subagent 的发起 session id(subagentsByRoot 挂载键,与 pi identity.data.rootSessionId 同语义) */
|
|
95
|
+
rootSessionId: string
|
|
96
|
+
/** slug 标签(discovery 组装时已兜底 m.slug ?? m.agentName ?? '',与 pi 孤儿链同款) */
|
|
97
|
+
slug: string
|
|
98
|
+
/** agent 类型名 */
|
|
99
|
+
agentName?: string
|
|
100
|
+
task?: string
|
|
101
|
+
model?: string
|
|
102
|
+
status?: string
|
|
103
|
+
/** zcode 锚的库路径(engineHandle.sessionRef.dbPath;枚举谓词已保证非空 string) */
|
|
104
|
+
dbPath: string
|
|
105
|
+
/** 库文件存在性(discovery 枚举时 statSync 预计算;D5-1 第二判据的唯一输入) */
|
|
106
|
+
dbFileExists: boolean
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* zcode 节点 → subagentsByRoot(D5-1 判据落地)。
|
|
111
|
+
*
|
|
112
|
+
* 判据(两项、不开库):**锚可解析 ∧ 库文件存在 → cleanedUp=false;缺一 → true**。
|
|
113
|
+
* 「锚可解析」在枚举层已构造性成立(listZcodeManifests 谓词 = engine==='zcode' ∧
|
|
114
|
+
* sessionRef 双键齐,未通过的记录不产出)——core 层只需消费 dbFileExists。
|
|
115
|
+
*
|
|
116
|
+
* 与 pi 节点**刻意不同构**(D5-1 显式声明):pi 判据 = fileStats JSONL 文件存在性
|
|
117
|
+
* (buildSubagentsByRoot 的 !fileStats.has(ident.id));zcode 节点住在 SQLite 库里、
|
|
118
|
+
* 没有 JSONL 文件,「文件在不在」对它是无效判据。二者不互相回归。
|
|
119
|
+
*/
|
|
120
|
+
export function buildZcodeSubagentsByRoot(
|
|
121
|
+
nodes: readonly ZcodeFamilyNode[],
|
|
122
|
+
): Map<string, SubagentRef[]> {
|
|
123
|
+
const byRoot = new Map<string, SubagentRef[]>()
|
|
124
|
+
for (const n of nodes) {
|
|
125
|
+
const ref: SubagentRef = {
|
|
126
|
+
// zcode 节点无 subagent session 文件,sessionId 用 record id(pi 孤儿链同款占位)
|
|
127
|
+
sessionId: n.id,
|
|
128
|
+
rootSessionId: n.rootSessionId,
|
|
129
|
+
slug: n.slug,
|
|
130
|
+
fileName: '', // 占位空串(family 路径不回填,与 pi 一致)
|
|
131
|
+
mtime: 0, // 无 JSONL 文件可 stat,恒 0(占位约定,见文件头注释)
|
|
132
|
+
sizeBytes: 0,
|
|
133
|
+
cwd: '',
|
|
134
|
+
// D5-1:锚可解析(枚举谓词已过)∧ 库文件存在 → false;缺一 → true(GC 语义)
|
|
135
|
+
cleanedUp: !n.dbFileExists,
|
|
136
|
+
task: n.task,
|
|
137
|
+
agentName: n.agentName,
|
|
138
|
+
model: n.model,
|
|
139
|
+
status: n.status,
|
|
140
|
+
// sessionFile 不设:zcode 无 session.jsonl(pi 孤儿保留 GC 路径,zcode 无路径可保留)
|
|
141
|
+
}
|
|
142
|
+
const list = byRoot.get(n.rootSessionId)
|
|
143
|
+
if (list) list.push(ref)
|
|
144
|
+
else byRoot.set(n.rootSessionId, [ref])
|
|
145
|
+
}
|
|
146
|
+
return byRoot
|
|
147
|
+
}
|
|
148
|
+
|
|
81
149
|
// ---- 类型守卫:从 unknown 的 entry.data 提取 subagent identity 字段 ----
|
|
82
150
|
|
|
83
151
|
/**
|
|
@@ -225,6 +293,8 @@ function buildSubagentsByRoot(
|
|
|
225
293
|
* - headers(type=session)→ byId + childrenOf(parentSession 文件路径反查父 sessionId)
|
|
226
294
|
* - subagentIdentities(type=custom, customType=subagent-identity)→ subagentsByRoot
|
|
227
295
|
* - fileStats 原样存入 index,供 cleanedUp 判断
|
|
296
|
+
* - zcodeNodes(可选,U6)→ zcode subagent 节点并入 subagentsByRoot(D5-1 判据,
|
|
297
|
+
* 与 pi 判据不同构;不传 = pi 行为逐字节不变——零涟漪)
|
|
228
298
|
*
|
|
229
299
|
* 坏数据容错:identity 缺 rootSessionId/slug 跳过;parentSession 反查不到父(父文件
|
|
230
300
|
* 未被扫描到)该 entry 不进 childrenOf——均不报错,符合 pi 坏 session 容错(design §2)。
|
|
@@ -233,10 +303,19 @@ export function buildFamilyIndex(
|
|
|
233
303
|
headers: Entry[],
|
|
234
304
|
subagentIdentities: Entry[],
|
|
235
305
|
fileStats: Map<string, { mtime: number; size: number }>,
|
|
306
|
+
zcodeNodes?: readonly ZcodeFamilyNode[],
|
|
236
307
|
): FamilyIndex {
|
|
237
308
|
const byId = buildById(headers, fileStats)
|
|
238
309
|
const childrenOf = buildChildrenOf(headers, byId)
|
|
239
310
|
const subagentsByRoot = buildSubagentsByRoot(subagentIdentities, fileStats)
|
|
311
|
+
if (zcodeNodes !== undefined && zcodeNodes.length > 0) {
|
|
312
|
+
const zcodeByRoot = buildZcodeSubagentsByRoot(zcodeNodes)
|
|
313
|
+
for (const [rootId, refs] of zcodeByRoot) {
|
|
314
|
+
const existing = subagentsByRoot.get(rootId)
|
|
315
|
+
if (existing) existing.push(...refs)
|
|
316
|
+
else subagentsByRoot.set(rootId, refs)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
240
319
|
return { byId, childrenOf, subagentsByRoot, fileStats }
|
|
241
320
|
}
|
|
242
321
|
|
package/src/core/render.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Entry } from '
|
|
1
|
+
import type { Entry } from '@zhushanwen/session-core'
|
|
2
|
+
import { BYTES_PER_KB } from './units.js'
|
|
2
3
|
import { extractToolCalls, formatToolCallSummary, type ToolCallInfo } from './toolcall.js'
|
|
3
4
|
import type { Turn } from './turns.js'
|
|
4
5
|
import type { TreeView } from './tree.js'
|
|
@@ -7,8 +8,6 @@ import type { TreeView } from './tree.js'
|
|
|
7
8
|
// 模块常量(渲染口径:截断宽度 / 换算基数 / 降级档位)
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
|
|
10
|
-
/** bytes→KB 换算基数(omitted 字节 / read 结果规模的 KB 显示)。 */
|
|
11
|
-
const BYTES_PER_KB = 1024
|
|
12
11
|
/** userBrief 截断字符数(TurnBrief.userBrief 文档口径「截 60 字符」)。 */
|
|
13
12
|
const USER_BRIEF_MAX_CHARS = 60
|
|
14
13
|
/** assistantBrief 截断字符数(TurnBrief.assistantBrief 文档口径「截 80 字符」)。 */
|
package/src/core/toolcall.ts
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* message.toolCalls 顶层字段从未存在——v1 render.ts 读 toolCalls 恒返 [] 是 O1 要修的 bug。
|
|
7
7
|
* - arguments 始终是 object(10 工具 100%);string 形态做 JSON.parse 兜底(失败返 {}),防御历史/异类实现。
|
|
8
8
|
* - toolResult.message 自带 toolName + toolCallId(515/515,全部匹配 toolCall.id),
|
|
9
|
-
*
|
|
9
|
+
* Entry 接口(session-core 基座)已 additive 透出这两个字段,O2/O3 据此精确关联取参数。
|
|
10
10
|
*/
|
|
11
|
-
import type { Entry } from '
|
|
11
|
+
import type { Entry } from '@zhushanwen/session-core'
|
|
12
|
+
import { BYTES_PER_KB } from './units.js'
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// 模块常量(参数摘要的截断宽度 / 换算基数)
|
|
@@ -20,8 +21,6 @@ const BASH_CMD_MAX_CHARS = 60
|
|
|
20
21
|
const SUBAGENT_TASK_MAX_CHARS = 40
|
|
21
22
|
/** 未知工具 arguments JSON 摘要截断字符数。 */
|
|
22
23
|
const ARGS_JSON_MAX_CHARS = 50
|
|
23
|
-
/** bytes→KB 换算基数(write content 的 KB 显示)。 */
|
|
24
|
-
const BYTES_PER_KB = 1024
|
|
25
24
|
|
|
26
25
|
/** 单次工具调用信息(从 assistant content 的 toolCall block 提取)。 */
|
|
27
26
|
export interface ToolCallInfo {
|
package/src/core/tree.ts
CHANGED
package/src/core/turns.ts
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 字节量纲换算基数(本包内 KB/MB 换算的唯一来源)。
|
|
3
|
+
*
|
|
4
|
+
* 本包是独立 npm 发布的 pi extension(仅依赖 pi-ext-guards),不 import taiji 内部
|
|
5
|
+
* workspace 包(@taiji/shared 的 BYTES_PER_MB 单点不含本包)——量纲基数本地定义,
|
|
6
|
+
* 数值对齐由注释锚定(1MB = 1024×1024)。三个消费域(render 的 omitted 标记 /
|
|
7
|
+
* toolcall 的 write 摘要 / search-across 的预算与人性化格式化)共用本单点,
|
|
8
|
+
* 避免同值常量在本包内多份分裂。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** bytes→KB 换算基数(omitted 字节 / read 结果规模 / write content 的 KB 显示)。 */
|
|
12
|
+
export const BYTES_PER_KB = 1024
|
|
13
|
+
/** bytes→MB 换算基数(跨会话检索预算与人性化格式化)。 */
|
|
14
|
+
export const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
import { parseSessionContent, type Entry } from '@zhushanwen/session-core'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* zcode 锚的 entry 兜底定位(设计 session-reader-shared-core §3.1 第②步)。
|
|
7
|
+
*
|
|
8
|
+
* 定位链第②层:zcode manifest 直读(第①层,zcode-manifest.ts)缺位/残缺时,
|
|
9
|
+
* 在候选主 session 文件里按 sa-id 扫 `subagent-record` custom entry,取其
|
|
10
|
+
* `engineHandle.sessionRef`(zcode 形态 = `{sessionId, dbPath}`)。
|
|
11
|
+
*
|
|
12
|
+
* 边界承载(设计四要素登记):本函数只认「候选文件列表」,越界语义(不做根内
|
|
13
|
+
* 全扫、liveSessionDir 优先排序)由调用方构造候选集时决定——第一梯队只传
|
|
14
|
+
* liveSessionDir 内的主 session 文件,越界查询返回 not-found 信号(undefined,
|
|
15
|
+
* 调用方映射 `zcode_record_not_found` 错误码,§3.4)。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* zcode 锚(zcode 引擎自描述定位符的 zcode 形态)。与 manifest 直读产出的锚同形;
|
|
20
|
+
* `sessionRef` 写侧整体透传不枚举内部键(subagent-core record-entry.ts
|
|
21
|
+
* engineHandle.sessionRef 注释),本单元按 zcode 形态校验并窄化为双键。
|
|
22
|
+
*/
|
|
23
|
+
export interface ZcodeAnchor {
|
|
24
|
+
sessionId: string
|
|
25
|
+
dbPath: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `subagent-record` custom entry 的 customType 值。
|
|
30
|
+
*
|
|
31
|
+
* 磁盘 JSONL 协议字符串(跨侧契约):写侧单源 = subagent-core record-entry.ts 的
|
|
32
|
+
* SUBAGENT_RECORD_CUSTOM_TYPE。reader 生产依赖面不引 subagent-core(该包是
|
|
33
|
+
* devDependency + vitest alias 形态),故按协议字符串本地持有;两侧漂移由
|
|
34
|
+
* entry-anchor.test.ts 的跨包断言守卫。
|
|
35
|
+
*/
|
|
36
|
+
const SUBAGENT_RECORD_CUSTOM_TYPE = 'subagent-record'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 从单条 entry 提取 zcode 锚。形状校验窄而严,任一不满足 = 该条不算命中(返回
|
|
40
|
+
* undefined,调用方继续扫——缺键条目不遮蔽更早的完整条目):
|
|
41
|
+
* - `type === 'custom'` ∧ `customType === 'subagent-record'`
|
|
42
|
+
* - `data.v === 1`(schema 版本守卫,不认识的版本跳过而非猜测——写侧 record-entry.ts v 注释同口径)
|
|
43
|
+
* - `data.id === saId`(record id 即 sa-id,record-access.ts `sa-${uuid}`)
|
|
44
|
+
* - `engineHandle.sessionRef.sessionId` / `.dbPath` 均为非空 string(F8 双键齐判据的
|
|
45
|
+
* 锚字段部分;pi record 的 sessionRef 无 dbPath → 天然不命中,无需 engine 判别)
|
|
46
|
+
*/
|
|
47
|
+
function zcodeAnchorOfEntry(entry: Entry, saId: string): ZcodeAnchor | undefined {
|
|
48
|
+
if (entry.type !== 'custom' || entry.customType !== SUBAGENT_RECORD_CUSTOM_TYPE) return undefined
|
|
49
|
+
const data: unknown = entry.data
|
|
50
|
+
if (typeof data !== 'object' || data === null) return undefined
|
|
51
|
+
const d = data as Record<string, unknown>
|
|
52
|
+
if (d.v !== 1 || d.id !== saId) return undefined
|
|
53
|
+
const handle: unknown = d.engineHandle
|
|
54
|
+
if (typeof handle !== 'object' || handle === null) return undefined
|
|
55
|
+
const ref: unknown = (handle as Record<string, unknown>).sessionRef
|
|
56
|
+
if (typeof ref !== 'object' || ref === null) return undefined
|
|
57
|
+
const r = ref as Record<string, unknown>
|
|
58
|
+
const { sessionId, dbPath } = r
|
|
59
|
+
if (typeof sessionId !== 'string' || sessionId === '') return undefined
|
|
60
|
+
if (typeof dbPath !== 'string' || dbPath === '') return undefined
|
|
61
|
+
return { sessionId, dbPath }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 在候选主 session 文件里按 sa-id 定位 zcode 锚。
|
|
66
|
+
*
|
|
67
|
+
* 取条语义(设计 §2.5 裁决,subagent-core collectLastRecordEntries 逐字同构——
|
|
68
|
+
* 「主 session 全文 → 每 id 末条 record data」,record-store-rebuild.ts:212-227):
|
|
69
|
+
* 同一 sa-id 每次状态迁移写一条全量快照 entry 且 sessionRef 每轮覆写(F16),故
|
|
70
|
+
* 同文件内后条覆盖前条 = 文件顺序末条。**禁止另创取首条/合并语义**(附录 B-6)。
|
|
71
|
+
*
|
|
72
|
+
* 多候选文件:按候选列表顺序逐文件扫,文件内有命中锚(含末条裁决后)即止——
|
|
73
|
+
* 候选顺序 = 调用方声明的优先级(liveSessionDir 优先),不跨文件合并。
|
|
74
|
+
*
|
|
75
|
+
* 行级容错:逐行解析由基座 parseSessionContent 承担(坏行跳过继续、末行半行记
|
|
76
|
+
* lastLinePartial 不中断)——best-effort,不因一行坏 JSON 失败整文件。
|
|
77
|
+
*
|
|
78
|
+
* 候选文件读失败(不存在/不可读)= 该候选无命中,跳过继续下一候选;本函数契约
|
|
79
|
+
* 是「锚或 not-found」,IO 错误同样收敛到 not-found 信号(调用方映射错误码),
|
|
80
|
+
* 不向上抛。
|
|
81
|
+
*
|
|
82
|
+
* 读取用 node:fs/promises 的 async readFile——本函数跑在 pi 扩展进程内,同步
|
|
83
|
+
* readFileSync 全文读入会阻塞事件循环(同函数族 classifyIncompleteEntryAnchor
|
|
84
|
+
* 同形态;对照 readSessionHeaderIdSync 只在首行 4KB 有界读时才允许同步)。
|
|
85
|
+
*
|
|
86
|
+
* @returns 命中的锚;所有候选均未命中(或全部候选不可读)→ undefined(not-found 信号)
|
|
87
|
+
*/
|
|
88
|
+
export async function findZcodeEntryAnchor(
|
|
89
|
+
candidateFiles: readonly string[],
|
|
90
|
+
saId: string,
|
|
91
|
+
): Promise<ZcodeAnchor | undefined> {
|
|
92
|
+
for (const file of candidateFiles) {
|
|
93
|
+
let content: string
|
|
94
|
+
try {
|
|
95
|
+
content = await readFile(file, 'utf8')
|
|
96
|
+
} catch {
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
const { entries } = parseSessionContent(content)
|
|
100
|
+
let anchor: ZcodeAnchor | undefined
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
const hit = zcodeAnchorOfEntry(entry, saId)
|
|
103
|
+
if (hit !== undefined) anchor = hit
|
|
104
|
+
}
|
|
105
|
+
if (anchor !== undefined) return anchor
|
|
106
|
+
}
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
package/src/discovery/env.ts
CHANGED
|
@@ -12,35 +12,35 @@ import { dirname } from 'node:path'
|
|
|
12
12
|
* 齐备),不 import roots.ts 的信号类型,避免并行单元耦合;接线层分别组装两种信号包。
|
|
13
13
|
*
|
|
14
14
|
* 信号可靠性依据(design §4.4 活体实测表,非推断):
|
|
15
|
-
* -
|
|
16
|
-
* ENV_WHITELIST_PREFIXES 含裸 `
|
|
15
|
+
* - TAIJI_AGENT_EXT_LOG 单独使用可靠性「中」——runtime 无条件注入,但
|
|
16
|
+
* ENV_WHITELIST_PREFIXES 含裸 `TAIJI_` 前缀,用户 shell 里的同名变量会透传进任何进程;
|
|
17
17
|
* - PI_CODING_AGENT_DIR 单独使用可靠性「中」——pi 原生变量,裸 pi 用户也可能自设,
|
|
18
|
-
* 但其值形态是
|
|
19
|
-
* -
|
|
18
|
+
* 但其值形态是 taiji 专属约定:`<*>/.taiji*` 目录后接 `/agent`;
|
|
19
|
+
* - TAIJI_AGENT_PACKAGED **不可用**——在 SPAWN_ENV_OUTBOUND_DENY_LIST 被剥除
|
|
20
20
|
* (§4.4 在案结论),故不引入该信号,防后人重蹈。
|
|
21
21
|
* 因此托管判定必须是双信号合取:任一信号单独成立只判 standalone-pi,
|
|
22
22
|
* 透传/自设污染提示写入 evidence。
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
export const
|
|
27
|
-
/**
|
|
25
|
+
/** taiji 托管信号 1:runtime 无条件注入的 extension 日志开关(恒 '1') */
|
|
26
|
+
export const TAIJI_AGENT_EXT_LOG_ENV = 'TAIJI_AGENT_EXT_LOG'
|
|
27
|
+
/** taiji 托管信号 2:pi agentDir 注入变量;其**值形态**是 taiji 专属约定 */
|
|
28
28
|
export const PI_CODING_AGENT_DIR_ENV = 'PI_CODING_AGENT_DIR'
|
|
29
29
|
/** 数据目录推导首选:runtime getDataDir() 的权威注入值 */
|
|
30
|
-
export const
|
|
30
|
+
export const TAIJI_AGENT_DATA_DIR_ENV = 'TAIJI_AGENT_DATA_DIR'
|
|
31
31
|
|
|
32
32
|
/** 托管信号 1 的唯一合法值(注释「托管语义恒为 '1'」) */
|
|
33
33
|
const EXT_LOG_MANAGED_VALUE = '1'
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
// - B 后新布局:`<*>/.
|
|
37
|
-
// - B 前旧布局:`<*>/.
|
|
38
|
-
// `.
|
|
39
|
-
// 三条正则都要求匹配到路径末尾(`$`),调用前输入先经
|
|
35
|
+
// taiji agentDir 值形态(§6.2 双形态兼容——迁移窗口期 B 前后两种布局并存):
|
|
36
|
+
// - B 后新布局:`<*>/.taiji*` 目录后接 `/agent`(一层)
|
|
37
|
+
// - B 前旧布局:`<*>/.taiji*` 目录后接 `/pi/agent`(两层)
|
|
38
|
+
// `.taiji*` = 以 `.taiji` 开头的目录段(prod `.taiji` / dev `.taiji-dev`)。
|
|
39
|
+
// 三条正则都要求匹配到路径末尾(`$`),调用前输入先经 normalizeTaijiPath 剥尾斜杠。
|
|
40
40
|
// (用行注释:形态文本含星号斜杠序列,写进块注释会提前终止注释。)
|
|
41
|
-
const AGENT_DIR_SHAPE_ANY = /(^|\/)\.
|
|
42
|
-
const AGENT_DIR_SHAPE_OLD = /(^|\/)\.
|
|
43
|
-
const AGENT_DIR_SHAPE_NEW = /(^|\/)\.
|
|
41
|
+
const AGENT_DIR_SHAPE_ANY = /(^|\/)\.taiji[^/]*\/(pi\/)?agent$/
|
|
42
|
+
const AGENT_DIR_SHAPE_OLD = /(^|\/)\.taiji[^/]*\/pi\/agent$/
|
|
43
|
+
const AGENT_DIR_SHAPE_NEW = /(^|\/)\.taiji[^/]*\/agent$/
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* 打包形态判据(§6.2:bundle 路径判发行形态;无法判定 → null 不猜)。
|
|
@@ -56,7 +56,7 @@ const MACOS_PACKAGED_PATTERN = /\.app\/Contents\/Resources\/extensions\//
|
|
|
56
56
|
const REPO_STAGED_PATTERN = /(^|\/)apps\/electron\/resources\/extensions\//
|
|
57
57
|
const WIN_LINUX_PACKAGED_PATTERN = /(^|\/)resources\/extensions\//
|
|
58
58
|
|
|
59
|
-
export type EnvironmentKind = '
|
|
59
|
+
export type EnvironmentKind = 'taiji' | 'standalone-pi'
|
|
60
60
|
export type Distribution = 'packaged' | 'dev' | null
|
|
61
61
|
|
|
62
62
|
export interface EnvironmentSignals {
|
|
@@ -71,19 +71,19 @@ export interface EnvironmentSignals {
|
|
|
71
71
|
export interface DetectedEnvironment {
|
|
72
72
|
kind: EnvironmentKind
|
|
73
73
|
distribution: Distribution
|
|
74
|
-
/** 托管时的
|
|
74
|
+
/** 托管时的 taiji 数据目录;standalone-pi 恒 undefined */
|
|
75
75
|
dataDir?: string
|
|
76
76
|
/** 每条命中/未命中信号的原文 + 判定依据,恒非空(§6.2:判定可被证据推翻,非黑盒断言) */
|
|
77
77
|
evidence: string[]
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** 形态匹配前的归一化:Windows 分隔符 → `/`,剥尾部斜杠 */
|
|
81
|
-
function
|
|
81
|
+
function normalizeTaijiPath(p: string): string {
|
|
82
82
|
return p.replace(/\\/g, '/').replace(/\/+$/, '')
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function
|
|
86
|
-
return AGENT_DIR_SHAPE_ANY.test(
|
|
85
|
+
function matchesTaijiAgentDir(value: string): boolean {
|
|
86
|
+
return AGENT_DIR_SHAPE_ANY.test(normalizeTaijiPath(value))
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/** distribution 判定,判定行写入 evidence */
|
|
@@ -110,19 +110,19 @@ function detectDistribution(bundleUrl: string | undefined, evidence: string[]):
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* 托管时的数据目录推导:
|
|
113
|
+
* 托管时的数据目录推导:TAIJI_AGENT_DATA_DIR 优先(runtime 注入的权威值,与
|
|
114
114
|
* getDataDir() 同源),缺失时按布局形态从 PI_CODING_AGENT_DIR 剥层。来源写入 evidence。
|
|
115
115
|
*/
|
|
116
116
|
function deriveDataDir(env: Record<string, string | undefined>, evidence: string[]): string | undefined {
|
|
117
|
-
const dataDirEnv = env[
|
|
117
|
+
const dataDirEnv = env[TAIJI_AGENT_DATA_DIR_ENV]
|
|
118
118
|
if (dataDirEnv) {
|
|
119
|
-
evidence.push(`数据目录:'${dataDirEnv}'(来源:${
|
|
119
|
+
evidence.push(`数据目录:'${dataDirEnv}'(来源:${TAIJI_AGENT_DATA_DIR_ENV})`)
|
|
120
120
|
return dataDirEnv
|
|
121
121
|
}
|
|
122
122
|
const agentDirEnv = env[PI_CODING_AGENT_DIR_ENV]
|
|
123
123
|
if (agentDirEnv) {
|
|
124
|
-
const normalized =
|
|
125
|
-
// 旧形态更长,先判:`.
|
|
124
|
+
const normalized = normalizeTaijiPath(agentDirEnv)
|
|
125
|
+
// 旧形态更长,先判:`.taiji*/pi/agent` → 剥 pi/agent 两层
|
|
126
126
|
if (AGENT_DIR_SHAPE_OLD.test(normalized)) {
|
|
127
127
|
const dataDir = dirname(dirname(normalized))
|
|
128
128
|
evidence.push(`数据目录:'${dataDir}'(来源:${PI_CODING_AGENT_DIR_ENV} 剥 pi/agent 两层,B 前旧布局)`)
|
|
@@ -134,7 +134,7 @@ function deriveDataDir(env: Record<string, string | undefined>, evidence: string
|
|
|
134
134
|
return dataDir
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
evidence.push(`数据目录:无法推导(${
|
|
137
|
+
evidence.push(`数据目录:无法推导(${TAIJI_AGENT_DATA_DIR_ENV} 未设置且 ${PI_CODING_AGENT_DIR_ENV} 形态不可剥层)`)
|
|
138
138
|
return undefined
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -146,16 +146,16 @@ function pushExtLogEvidence(
|
|
|
146
146
|
): void {
|
|
147
147
|
evidence.push(
|
|
148
148
|
extLogHit
|
|
149
|
-
? `${
|
|
150
|
-
: `${
|
|
149
|
+
? `${TAIJI_AGENT_EXT_LOG_ENV}='${extLog}' → 命中`
|
|
150
|
+
: `${TAIJI_AGENT_EXT_LOG_ENV}=${extLog === undefined ? '<未设置>' : `'${extLog}'`} → 未命中(要求值为 '1')`,
|
|
151
151
|
)
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
/** 信号 2 的形态标注(§6.2 双形态兼容:B 前旧布局 / B 后新布局 / 非
|
|
154
|
+
/** 信号 2 的形态标注(§6.2 双形态兼容:B 前旧布局 / B 后新布局 / 非 taiji 形态)。 */
|
|
155
155
|
function describeAgentDirShape(normalized: string): string {
|
|
156
156
|
if (AGENT_DIR_SHAPE_OLD.test(normalized)) return 'B 前旧布局 pi/agent'
|
|
157
157
|
if (AGENT_DIR_SHAPE_NEW.test(normalized)) return 'B 后新布局 agent'
|
|
158
|
-
return '非
|
|
158
|
+
return '非 taiji 形态'
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
/** 信号 2 evidence 行(未设置 → 未命中;有值 → 形态匹配与否 + 形态标注)。 */
|
|
@@ -168,19 +168,19 @@ function pushAgentDirEvidence(
|
|
|
168
168
|
evidence.push(`${PI_CODING_AGENT_DIR_ENV}=<未设置> → 未命中`)
|
|
169
169
|
return
|
|
170
170
|
}
|
|
171
|
-
const shape = describeAgentDirShape(
|
|
171
|
+
const shape = describeAgentDirShape(normalizeTaijiPath(agentDirEnv))
|
|
172
172
|
evidence.push(
|
|
173
|
-
`${PI_CODING_AGENT_DIR_ENV}='${agentDirEnv}' → ${agentDirHit ? `形态匹配(${shape})` : `形态不匹配(${shape},要求 <*>/.
|
|
173
|
+
`${PI_CODING_AGENT_DIR_ENV}='${agentDirEnv}' → ${agentDirHit ? `形态匹配(${shape})` : `形态不匹配(${shape},要求 <*>/.taiji*/agent)`}`,
|
|
174
174
|
)
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/** 判定行:托管(双信号合取成立)/ standalone-pi(孤置信号点名,提示透传/自设污染)。 */
|
|
178
178
|
function judgedEvidenceLine(extLogHit: boolean, agentDirHit: boolean): string {
|
|
179
179
|
if (extLogHit && agentDirHit) {
|
|
180
|
-
return `判定:
|
|
180
|
+
return `判定:taiji 托管(${TAIJI_AGENT_EXT_LOG_ENV}='1' 且 ${PI_CODING_AGENT_DIR_ENV} 形态匹配,双信号合取成立)`
|
|
181
181
|
}
|
|
182
182
|
const orphaned: string[] = []
|
|
183
|
-
if (extLogHit) orphaned.push(
|
|
183
|
+
if (extLogHit) orphaned.push(TAIJI_AGENT_EXT_LOG_ENV)
|
|
184
184
|
if (agentDirHit) orphaned.push(PI_CODING_AGENT_DIR_ENV)
|
|
185
185
|
const orphanNote =
|
|
186
186
|
orphaned.length > 0
|
|
@@ -192,8 +192,8 @@ function judgedEvidenceLine(extLogHit: boolean, agentDirHit: boolean): string {
|
|
|
192
192
|
/**
|
|
193
193
|
* 判定当前宿主环境(design §6.2)。
|
|
194
194
|
*
|
|
195
|
-
* 托管(
|
|
196
|
-
* 值匹配
|
|
195
|
+
* 托管(taiji)= 合取:`TAIJI_AGENT_EXT_LOG === '1'` **且** `PI_CODING_AGENT_DIR`
|
|
196
|
+
* 值匹配 taiji 形态(`<*>/.taiji*` 目录后接 `/agent`;B 前旧布局接
|
|
197
197
|
* `/pi/agent` 同样接受)。任一信号单独成立 → standalone-pi,evidence 点名孤置
|
|
198
198
|
* 信号(透传污染提示)。
|
|
199
199
|
* evidence 恒非空:每条命中/未命中信号的原文都在列,让 agent 能核对并推翻判定。
|
|
@@ -205,19 +205,19 @@ export function detectEnvironment(signals: EnvironmentSignals): DetectedEnvironm
|
|
|
205
205
|
const evidence: string[] = []
|
|
206
206
|
const { env } = signals
|
|
207
207
|
|
|
208
|
-
// 信号 1:
|
|
209
|
-
const extLog = env[
|
|
208
|
+
// 信号 1:TAIJI_AGENT_EXT_LOG === '1'
|
|
209
|
+
const extLog = env[TAIJI_AGENT_EXT_LOG_ENV]
|
|
210
210
|
const extLogHit = extLog === EXT_LOG_MANAGED_VALUE
|
|
211
211
|
pushExtLogEvidence(evidence, extLog, extLogHit)
|
|
212
212
|
|
|
213
213
|
// 信号 2:PI_CODING_AGENT_DIR 值形态
|
|
214
214
|
const agentDirEnv = env[PI_CODING_AGENT_DIR_ENV]
|
|
215
|
-
const agentDirHit = agentDirEnv !== undefined &&
|
|
215
|
+
const agentDirHit = agentDirEnv !== undefined && matchesTaijiAgentDir(agentDirEnv)
|
|
216
216
|
pushAgentDirEvidence(evidence, agentDirEnv, agentDirHit)
|
|
217
217
|
evidence.push(`agentDir='${signals.agentDir}' → pi getAgentDir() 透传记录`)
|
|
218
218
|
|
|
219
219
|
const managed = extLogHit && agentDirHit
|
|
220
|
-
const kind: EnvironmentKind = managed ? '
|
|
220
|
+
const kind: EnvironmentKind = managed ? 'taiji' : 'standalone-pi'
|
|
221
221
|
evidence.push(judgedEvidenceLine(extLogHit, agentDirHit))
|
|
222
222
|
|
|
223
223
|
const distribution = detectDistribution(signals.bundleUrl, evidence)
|
package/src/discovery/find.ts
CHANGED
|
@@ -327,7 +327,7 @@ function isFlatRoot(root: SessionRoot): boolean {
|
|
|
327
327
|
* 加载标题元数据索引(u11)。仅 keyword 路径调用(惰性触发:uuid 精确/归一化两级零命中
|
|
328
328
|
* 且 query 非纯 hex 才到达此处,§6.6 调用策略 ①)。
|
|
329
329
|
*
|
|
330
|
-
* 窄化目标(策略 ②,按序):未剥层 liveSessionDir 本身(
|
|
330
|
+
* 窄化目标(策略 ②,按序):未剥层 liveSessionDir 本身(taiji 下即平铺主根;纯 pi 下
|
|
331
331
|
* 即当前 cwd 目录,小)+ 实扫结果无子目录的候选根。两条 guard:
|
|
332
332
|
* - 仅对存在的根/目录调用(root.exists / stat liveSessionDir),且永传非空串——pi 的
|
|
333
333
|
* listAll 对空串 falsy 走默认全盘分支(数千项 / 秒级,hash-provider.ts 空参灾难先例);
|
package/src/discovery/roots.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { performance } from 'node:perf_hooks'
|
|
|
24
24
|
|
|
25
25
|
/** 发现层信号包(index.ts 采集,全部可降级;本模块只消费不采集) */
|
|
26
26
|
export interface SessionRootSignals {
|
|
27
|
-
/** pi agent 数据目录(两宿主均成立;B 后
|
|
27
|
+
/** pi agent 数据目录(两宿主均成立;B 后 taiji = `<dataDir>/agent`) */
|
|
28
28
|
agentDir: string
|
|
29
29
|
/**
|
|
30
30
|
* 宿主已解析的 live session 目录(`ctx.sessionManager.getSessionDir()`,§6.1 信号 1)。
|
|
@@ -66,7 +66,7 @@ const SUBAGENTS_DIRNAME = 'subagents'
|
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* `[live]` 规范化(§6.1/§7B 要点 3):`getSessionDir()` 在纯 pi 返回按 cwd 编码的子目录
|
|
69
|
-
*(`<agentDir>/sessions/--Users-x--`),在
|
|
69
|
+
*(`<agentDir>/sessions/--Users-x--`),在 taiji 覆盖态返回根本身。判据:basename
|
|
70
70
|
* 匹配 encodeCwd 形态(`--` 开头 `--` 结尾,实证见 `~/.pi/agent/sessions/` 全部子目录
|
|
71
71
|
* 与 real-data.ts)则取 dirname,否则取自身。
|
|
72
72
|
*/
|