@zhushanwen/pi-session-reader 0.5.0 → 0.6.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 -2
- package/src/__tests__/cross-package-subagent-core.test.ts +13 -10
- package/src/__tests__/execution-tree.test.ts +139 -31
- package/src/__tests__/family.test.ts +0 -7
- package/src/__tests__/find.test.ts +5 -5
- package/src/__tests__/real-data.ts +1 -1
- package/src/__tests__/rebuild-manifest-dual-write.test.ts +177 -0
- package/src/__tests__/render.test.ts +36 -1
- package/src/__tests__/result.test.ts +20 -23
- package/src/__tests__/roots.test.ts +46 -80
- package/src/__tests__/session-command.test.ts +23 -0
- package/src/__tests__/subagents.test.ts +4 -3
- package/src/__tests__/tool-handler.test.ts +419 -141
- package/src/__tests__/workflow.test.ts +1 -7
- package/src/core/execution-tree.ts +1 -0
- package/src/core/family.ts +12 -9
- package/src/core/render.ts +40 -12
- package/src/core/workflow.ts +7 -20
- package/src/discovery/find.ts +42 -134
- package/src/discovery/roots.ts +16 -87
- package/src/discovery/session-header.ts +103 -0
- package/src/discovery/subagents.ts +23 -94
- package/src/doctor.ts +11 -77
- package/src/extract.ts +6 -19
- package/src/handler-utils.ts +28 -5
- package/src/no-match.ts +3 -2
- package/src/result-action.ts +30 -35
- package/src/search-across.ts +5 -4
- package/src/tool-handler.ts +151 -176
- package/src/tui/hash-provider.ts +7 -3
- package/src/tui/session-command.ts +22 -10
package/src/handler-utils.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* tool-handler 共享小工具(max-lines
|
|
2
|
+
* tool-handler 共享小工具(max-lines 拆分轮机械提取;ext-simplify-04 E5 扩充,零行为变更)。
|
|
3
3
|
*
|
|
4
4
|
* 从 tool-handler.ts「小工具」与「turn/turns 索引解析」段搬出:pad/err/turn 索引解析
|
|
5
|
-
* 被留守的 tool-handler 与拆出的 search-across.ts / extract.ts
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
*
|
|
5
|
+
* 被留守的 tool-handler 与拆出的 search-across.ts / extract.ts 共同消费;
|
|
6
|
+
* err 原属此列,E5 起同包 helper 获取范式单一化——result-action.ts 不再经
|
|
7
|
+
* ResultActionDeps 注入 err/stripHash/requireStr/SESSION_ID_PREFIX_LEN,与本模块
|
|
8
|
+
* 其他消费者一样直接 import。formatDate/shortCwd 仅 tool-handler 留守部分使用,不在此列。
|
|
9
|
+
* requireStr 的 action 参数类型经 type import 取自 tool-handler.ts(编译期擦除,
|
|
10
|
+
* 无运行时循环——与域模块对本模块「仅 type import」同一先例)。
|
|
9
11
|
*/
|
|
12
|
+
import type { SessionReadAction } from './tool-handler.js'
|
|
10
13
|
|
|
11
14
|
/** turn 索引显示宽度(T013 三位补零)。 */
|
|
12
15
|
const TURN_INDEX_WIDTH = 3
|
|
13
16
|
|
|
17
|
+
/** sessionId 列表行内的短显前缀长度。 */
|
|
18
|
+
export const SESSION_ID_PREFIX_LEN = 8
|
|
19
|
+
|
|
14
20
|
export const pad = (n: number): string => String(n).padStart(TURN_INDEX_WIDTH, '0')
|
|
15
21
|
|
|
16
22
|
/** 构造带 👉 恢复指引的 Error。契约:handler 直接 throw,execute 不 catch、原样传播给 pi;
|
|
@@ -20,6 +26,23 @@ export function err(message: string): Error {
|
|
|
20
26
|
return new Error(message)
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
/** 剥 # 前缀(TUI `#e6c96` 引用 → 纯片段,design §3.3 D-3/D-4)。 */
|
|
30
|
+
export function stripHash(s: string): string {
|
|
31
|
+
return s.replace(/^#+/, '')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** F5 必填参数校验。 */
|
|
35
|
+
export function requireStr(
|
|
36
|
+
val: string | undefined,
|
|
37
|
+
name: string,
|
|
38
|
+
action: SessionReadAction,
|
|
39
|
+
): string {
|
|
40
|
+
if (val === undefined || val === null || val.trim() === '') {
|
|
41
|
+
throw err(`action:"${action}" 需要参数 "${name}"。👉 补上 "${name}" 重试。`)
|
|
42
|
+
}
|
|
43
|
+
return val.trim()
|
|
44
|
+
}
|
|
45
|
+
|
|
23
46
|
// ---------------------------------------------------------------------------
|
|
24
47
|
// turn / turns 索引解析
|
|
25
48
|
// ---------------------------------------------------------------------------
|
package/src/no-match.ts
CHANGED
|
@@ -136,8 +136,9 @@ function guidanceLines(): string[] {
|
|
|
136
136
|
*
|
|
137
137
|
* ① 事实型自检行:main 根 N 文件 / subagent 根 M 文件 /「候选集非空|为空」/「查询已做
|
|
138
138
|
* uuid 归一化匹配(小写+去连字符)后仍无命中」。计数取 roots——调用方在 F1 路径
|
|
139
|
-
* 恒以无 options 的 resolveSessionRoots
|
|
140
|
-
*
|
|
139
|
+
* 恒以无 options 的 resolveSessionRoots 实扫(根扫描无缓存——doctor 缓存机已删除,
|
|
140
|
+
* ext-simplify-04 U3,§7B 要点 8 新态;原 PS-14「缓存计数误报主根为空」的防污染
|
|
141
|
+
* 规则随之失效),即本次实扫结果。
|
|
141
142
|
* 只陈述事实,**不得**出现「真的没有这个 session」类归因断言(§3.3 教训:大写/去连
|
|
142
143
|
* 字符输入在归一化前会被错误归因为「不存在」)。
|
|
143
144
|
* ② 编辑距离最近候选 top-3(标注 source 与差异位),候选 = roots 实扫文件名提取的
|
package/src/result-action.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* result action(subagent-sync-collect U6:design subagent-sync-collect.md §3.1.3)。
|
|
3
3
|
*
|
|
4
|
-
* 从 tool-handler.ts 机械提取(max-lines
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* tool-handler
|
|
4
|
+
* 从 tool-handler.ts 机械提取(max-lines 拆分轮):纯移动零行为变更。纯函数 helper
|
|
5
|
+
*(err/stripHash/requireStr/SESSION_ID_PREFIX_LEN)直接 import 自 handler-utils
|
|
6
|
+
*(ext-simplify-04 E5 收敛:同包 helper 获取范式单一化);resolveSessionId/disambiguate/
|
|
7
|
+
* safeParse 仍为 tool-handler 文件私有且被其余 9 个 action 共用,不便搬移——经
|
|
8
|
+
* {@link ResultActionDeps} 注入,入口分发处 per-call 构造(resolveSessionId 被信号包
|
|
9
|
+
* liveSessionDir 的包装覆盖)。
|
|
8
10
|
*
|
|
9
|
-
* 设计来源:docs/design/subagent-sync-collect.md §3.1.3
|
|
11
|
+
* 设计来源:docs/design/subagent-sync-collect.md §3.1.3(已删除,git 可追溯)——
|
|
10
12
|
* session_read {"action":"result","session":"sa-aaa"} // 单个
|
|
11
13
|
* session_read {"action":"result","session":"sa-aaa,sa-bbb,sa-ccc"} // 批量(≤10)
|
|
12
14
|
* → 每条返回该 subagent session 的最终 assistant 正文(与通知 record.result 同源),
|
|
@@ -14,8 +16,10 @@
|
|
|
14
16
|
* 定位复用既有发现机制(sa-xxx manifest 反查 / uuid 片段 / 路径),不新造目录或文件。
|
|
15
17
|
*/
|
|
16
18
|
import type { Entry, ParseResult } from './core/parser.js'
|
|
19
|
+
import { textBlockParts } from './core/render.js'
|
|
17
20
|
import type { MatchedSession } from './discovery/find.js'
|
|
18
21
|
import { listRecordManifests, type RecordManifest } from './discovery/subagents.js'
|
|
22
|
+
import { err, requireStr, SESSION_ID_PREFIX_LEN, stripHash } from './handler-utils.js'
|
|
19
23
|
import type { ResolveResult, SessionReadAction, SessionReadParams, ToolResult } from './tool-handler.js'
|
|
20
24
|
|
|
21
25
|
/** result 批量上限(design subagent-sync-collect §3.1.3:一次最多 10 个)。 */
|
|
@@ -27,11 +31,8 @@ const RESULT_DEFAULT_LIMIT = 8000
|
|
|
27
31
|
/** result 批量条目分隔符(与 notifier 批通知 "\n\n---\n\n" join 同款)。 */
|
|
28
32
|
const RESULT_BATCH_SEPARATOR = '\n\n---\n\n'
|
|
29
33
|
|
|
30
|
-
/** result action 对 tool-handler
|
|
34
|
+
/** result action 对 tool-handler 文件私有 helper 的注入面(入口分发处 per-call 构造)。 */
|
|
31
35
|
export interface ResultActionDeps {
|
|
32
|
-
err(message: string): Error
|
|
33
|
-
stripHash(s: string): string
|
|
34
|
-
requireStr(val: string | undefined, name: string, action: SessionReadAction): string
|
|
35
36
|
resolveSessionId(
|
|
36
37
|
rawSession: string | undefined,
|
|
37
38
|
action: SessionReadAction,
|
|
@@ -42,8 +43,6 @@ export interface ResultActionDeps {
|
|
|
42
43
|
): Promise<ResolveResult>
|
|
43
44
|
disambiguate(query: string, candidates: MatchedSession[]): ToolResult
|
|
44
45
|
safeParse(fileName: string): Promise<ParseResult>
|
|
45
|
-
/** 批量头行短 id 截断宽度(tool-handler SESSION_ID_PREFIX_LEN 同源)。 */
|
|
46
|
-
sessionIdPrefixLen: number
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
/**
|
|
@@ -52,29 +51,26 @@ export interface ResultActionDeps {
|
|
|
52
51
|
* 与 record 侧 text_delta 直累积同构:同一 message 内多个 text 块无分隔拼接
|
|
53
52
|
*(流式 delta 逐段 append),thinking/toolCall 块不入 record.text,此处同样排除。
|
|
54
53
|
*
|
|
55
|
-
* [S7 code-simplify 登记]
|
|
56
|
-
*
|
|
57
|
-
*
|
|
54
|
+
* [S7 code-simplify 登记] 本变体经 E11 归一到共享核 textBlockParts(core/render.ts,
|
|
55
|
+
* 块 → string[]),join 语义留在本调用点。包内完整映射(共享核 1 + 特异 3):
|
|
56
|
+
* - 共享核 textBlockParts:白名单 type==='text' 取 text;本函数('' join)/
|
|
57
|
+
* extract.ts extractContentText('\n' join)/ find.ts extractTextFromContent
|
|
58
|
+
* (' ' join + 空返 undefined)三个纯 text 变体均为其组合调用
|
|
59
|
+
* - 特异保留 3 个(filter 集不同,不可归一):tool-handler.ts messageReadableText
|
|
60
|
+
* (thinking/toolCall 占位)、search-across.ts searchableText(JSON 兜底)、
|
|
61
|
+
* render.ts extractText(排除法——白名单与排除法在未知 type 块上不等价)
|
|
62
|
+
*
|
|
58
63
|
* 本变体的 '' 无分隔拼接是 A4 取回逐字节一致锁定的硬理由(对齐 record.result 的
|
|
59
64
|
* 流式 delta 无分隔累积),不可与带分隔符的变体合并——差异是行为敏感点,勿「顺手统一」。
|
|
60
65
|
*/
|
|
61
66
|
function assistantMessageText(content: unknown): string {
|
|
62
|
-
|
|
63
|
-
if (!Array.isArray(content)) return ''
|
|
64
|
-
let out = ''
|
|
65
|
-
for (const block of content) {
|
|
66
|
-
if (block !== null && typeof block === 'object') {
|
|
67
|
-
const o = block as Record<string, unknown>
|
|
68
|
-
if (o.type === 'text' && typeof o.text === 'string') out += o.text
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return out
|
|
67
|
+
return textBlockParts(content).join('')
|
|
72
68
|
}
|
|
73
69
|
|
|
74
70
|
/**
|
|
75
71
|
* 从 session entries 提取最终结果正文(与完成通知 record.result 同源,A4 逐字节一致的前置)。
|
|
76
72
|
*
|
|
77
|
-
* 同源语义(packages/subagent-core/src/execution/execution-record.ts getFullText,只读参考):
|
|
73
|
+
* 同源语义(packages/subagent-core/src/execution/persistence/execution-record.ts getFullText,只读参考):
|
|
78
74
|
* record.result = AgentResult.text = getFullText(record) = 全部 assistant turn 文本
|
|
79
75
|
* filter(非空,空白串按非空) join("\\n\\n");pi 每 turn_end 对应一条 assistant message
|
|
80
76
|
*(agent-session.js turn_end 携带 message),故文件侧重建 = 每条 assistant message 的
|
|
@@ -120,8 +116,7 @@ interface ResultItem {
|
|
|
120
116
|
text: string
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
/** result
|
|
124
|
-
/** result 条目截断提示行。
|
|
119
|
+
/** result 条目截断提示行(design §3.1.3:超出截断 + 提示读原文件)。
|
|
125
120
|
*
|
|
126
121
|
* [S8 code-simplify 口径注] 本函数的 X = **保留**字符数(limit);而 subagent-core
|
|
127
122
|
* notifier.ts buildTruncationPointer 同模板的 X = **丢弃**字符数(total - kept)。
|
|
@@ -158,16 +153,16 @@ function resolveResultLimit(raw: number | undefined): number {
|
|
|
158
153
|
* result 的 session 列表解析:单 id 或逗号批量(≤10)。
|
|
159
154
|
* 空条目(如 "sa-a," 或连续逗号)与超限均明确报错(含上限数字与 👉)。
|
|
160
155
|
*/
|
|
161
|
-
function parseResultSessionList(raw: string | undefined
|
|
162
|
-
const trimmed =
|
|
163
|
-
const ids = trimmed.split(',').map((s) =>
|
|
156
|
+
function parseResultSessionList(raw: string | undefined): string[] {
|
|
157
|
+
const trimmed = requireStr(raw, 'session', 'result')
|
|
158
|
+
const ids = trimmed.split(',').map((s) => stripHash(s.trim()))
|
|
164
159
|
if (ids.some((id) => id === '')) {
|
|
165
|
-
throw
|
|
160
|
+
throw err(
|
|
166
161
|
`session 列表含空条目:"${trimmed}"。👉 检查逗号分隔格式(如 "sa-aaa,sa-bbb")重试。`,
|
|
167
162
|
)
|
|
168
163
|
}
|
|
169
164
|
if (ids.length > RESULT_MAX_BATCH) {
|
|
170
|
-
throw
|
|
165
|
+
throw err(
|
|
171
166
|
`批量取回 ${ids.length} 个 session 超上限:一次最多 ${RESULT_MAX_BATCH} 个。` +
|
|
172
167
|
`\n👉 分批取回(每批 ≤${RESULT_MAX_BATCH} 个 id)。`,
|
|
173
168
|
)
|
|
@@ -194,7 +189,7 @@ export async function doResult(
|
|
|
194
189
|
agentDir: string,
|
|
195
190
|
deps: ResultActionDeps,
|
|
196
191
|
): Promise<ToolResult> {
|
|
197
|
-
const ids = parseResultSessionList(params.session
|
|
192
|
+
const ids = parseResultSessionList(params.session)
|
|
198
193
|
const limit = resolveResultLimit(params.limit)
|
|
199
194
|
// S3(code-simplify):sa- 形态走 manifest 反查,逐 id 调用会重复全量扫 subagents/ 树
|
|
200
195
|
//(N+1)——批量入口预取一次注入。uuid 片段/路径形态不经 manifest,不预取。
|
|
@@ -208,7 +203,7 @@ export async function doResult(
|
|
|
208
203
|
const { entries } = await deps.safeParse(resolved.fileName)
|
|
209
204
|
const text = extractFinalAssistantText(entries)
|
|
210
205
|
if (text.length === 0) {
|
|
211
|
-
throw
|
|
206
|
+
throw err(
|
|
212
207
|
`session "${id}" 尚无 assistant 输出(运行中或文件尚未 flush 完整)。` +
|
|
213
208
|
`\n👉 稍后重试,或用 session_read { action:"outline", session:"${id}" } 看当前进度。`,
|
|
214
209
|
)
|
|
@@ -235,7 +230,7 @@ export async function doResult(
|
|
|
235
230
|
}
|
|
236
231
|
const blocks = items.map(
|
|
237
232
|
(it, i) =>
|
|
238
|
-
`[${i + 1}/${items.length}] ${it.session} (session ${it.sessionId.slice(0,
|
|
233
|
+
`[${i + 1}/${items.length}] ${it.session} (session ${it.sessionId.slice(0, SESSION_ID_PREFIX_LEN)}…)\n${it.text}`,
|
|
239
234
|
)
|
|
240
235
|
return {
|
|
241
236
|
content: [{ type: 'text', text: blocks.join(RESULT_BATCH_SEPARATOR) }],
|
package/src/search-across.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* pattern 编译/文本提取/命中收集/渲染与跨会话检索共享同一扫描管线(safeParse →
|
|
6
6
|
* buildTreeView → segmentTurns → collectSearchHits),拆为独立低层模块保持单向依赖
|
|
7
7
|
*(tool-handler 的 doSearch 消费本模块导出,本模块只反向 type import 公共类型)。
|
|
8
|
-
* searchAcrossSessions 导出面不变:tool-handler re-export(单测白盒 import 路径不变)。
|
|
9
8
|
*/
|
|
10
9
|
import { dirname } from 'node:path'
|
|
11
10
|
import {
|
|
@@ -348,7 +347,9 @@ function formatCrossSearchText(d: CrossSearchDetails): string {
|
|
|
348
347
|
return `${formatCrossSearchHead(d, hitSessions.length)}\n${lines.join('\n')}`
|
|
349
348
|
}
|
|
350
349
|
|
|
351
|
-
/** searchAcrossSessions
|
|
350
|
+
/** searchAcrossSessions 的可选参数。byteBudget 为测试注入缝(E10 定性):非模型可见参数
|
|
351
|
+
* (session_read schema 无此入参),存在动机 = 测试注入小预算触发预算截断用例,不必构造
|
|
352
|
+
* 64MB fixture;生产路径(doSearch)不传,走 SEARCH_SCAN_BYTE_BUDGET 默认。 */
|
|
352
353
|
interface CrossSearchOptions {
|
|
353
354
|
scope?: NonNullable<SessionReadParams['scope']>
|
|
354
355
|
limit?: number
|
|
@@ -463,8 +464,8 @@ async function attachScannedTitles(
|
|
|
463
464
|
}
|
|
464
465
|
|
|
465
466
|
/**
|
|
466
|
-
* 跨会话内容检索主体(u12 导出:byteBudget
|
|
467
|
-
* 走默认 SEARCH_SCAN_BYTE_BUDGET)。
|
|
467
|
+
* 跨会话内容检索主体(u12 导出:byteBudget 为测试注入缝,非模型可见——工具路径经
|
|
468
|
+
* doSearch 走默认 SEARCH_SCAN_BYTE_BUDGET)。
|
|
468
469
|
*
|
|
469
470
|
* 流程:窄化拒绝(候选 > MULTI_SEARCH_MAX_SESSIONS,V8)→ buildSessionFileIndex(一次
|
|
470
471
|
* 根扫描,id 语义与 find 候选同源)→ 按传入顺序逐个「复用单会话扫描管线」(safeParse →
|