@zhushanwen/pi-session-reader 0.5.1 → 0.6.1
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 +6 -3
- package/src/__tests__/cross-package-subagent-core.test.ts +42 -49
- package/src/__tests__/env.test.ts +54 -54
- package/src/__tests__/family.test.ts +0 -7
- package/src/__tests__/find.test.ts +6 -6
- package/src/__tests__/index.test.ts +1 -1
- package/src/__tests__/real-data.ts +23 -10
- package/src/__tests__/render.test.ts +36 -1
- package/src/__tests__/result.test.ts +20 -23
- package/src/__tests__/roots.test.ts +47 -81
- package/src/__tests__/session-command.test.ts +23 -0
- package/src/__tests__/subagents.test.ts +5 -7
- package/src/__tests__/tool-handler.test.ts +481 -216
- 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 +41 -14
- package/src/core/toolcall.ts +1 -2
- package/src/core/units.ts +14 -0
- package/src/core/workflow.ts +7 -20
- package/src/discovery/env.ts +41 -41
- package/src/discovery/find.ts +43 -135
- package/src/discovery/roots.ts +18 -89
- package/src/discovery/session-header.ts +103 -0
- package/src/discovery/subagents.ts +23 -94
- package/src/doctor.ts +12 -143
- package/src/extract.ts +6 -19
- package/src/handler-utils.ts +28 -5
- package/src/index.ts +2 -2
- package/src/no-match.ts +3 -2
- package/src/result-action.ts +29 -34
- package/src/search-across.ts +6 -8
- package/src/tool-handler.ts +151 -176
- package/src/tui/hash-provider.ts +7 -3
- package/src/tui/session-command.ts +22 -10
- package/vitest.config.ts +2 -4
package/src/result-action.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* result action
|
|
2
|
+
* result action。
|
|
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
11
|
* 设计来源:docs/design/subagent-sync-collect.md §3.1.3(已删除,git 可追溯)——
|
|
10
12
|
* session_read {"action":"result","session":"sa-aaa"} // 单个
|
|
@@ -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,23 +51,20 @@ 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
|
/**
|
|
@@ -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 {
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
import { parseSessionFile } from './core/parser.js'
|
|
18
17
|
import { segmentTurns, type Turn } from './core/turns.js'
|
|
19
18
|
import { buildTreeView } from './core/tree.js'
|
|
19
|
+
import { BYTES_PER_KB, BYTES_PER_MB } from './core/units.js'
|
|
20
20
|
import { err, pad } from './handler-utils.js'
|
|
21
21
|
import type { SessionReadSignals } from './doctor.js'
|
|
22
22
|
import type { SessionReadParams, ToolResult } from './tool-handler.js'
|
|
@@ -164,10 +164,6 @@ export function formatSearchText(
|
|
|
164
164
|
*/
|
|
165
165
|
export const MULTI_SEARCH_MAX_SESSIONS = 10
|
|
166
166
|
|
|
167
|
-
/** 字节展示/预算换算基数(人话格式化与预算常量共用同一量纲)。 */
|
|
168
|
-
const BYTES_PER_KB = 1024
|
|
169
|
-
const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB
|
|
170
|
-
|
|
171
167
|
/** 跨会话单次检索的预算 MB 数(§8.2 V8 字节上限的量纲;换算见 SEARCH_SCAN_BYTE_BUDGET)。 */
|
|
172
168
|
const SEARCH_SCAN_BUDGET_MB = 64
|
|
173
169
|
|
|
@@ -348,7 +344,9 @@ function formatCrossSearchText(d: CrossSearchDetails): string {
|
|
|
348
344
|
return `${formatCrossSearchHead(d, hitSessions.length)}\n${lines.join('\n')}`
|
|
349
345
|
}
|
|
350
346
|
|
|
351
|
-
/** searchAcrossSessions
|
|
347
|
+
/** searchAcrossSessions 的可选参数。byteBudget 为测试注入缝(E10 定性):非模型可见参数
|
|
348
|
+
* (session_read schema 无此入参),存在动机 = 测试注入小预算触发预算截断用例,不必构造
|
|
349
|
+
* 64MB fixture;生产路径(doSearch)不传,走 SEARCH_SCAN_BYTE_BUDGET 默认。 */
|
|
352
350
|
interface CrossSearchOptions {
|
|
353
351
|
scope?: NonNullable<SessionReadParams['scope']>
|
|
354
352
|
limit?: number
|
|
@@ -463,8 +461,8 @@ async function attachScannedTitles(
|
|
|
463
461
|
}
|
|
464
462
|
|
|
465
463
|
/**
|
|
466
|
-
* 跨会话内容检索主体(u12 导出:byteBudget
|
|
467
|
-
* 走默认 SEARCH_SCAN_BYTE_BUDGET)。
|
|
464
|
+
* 跨会话内容检索主体(u12 导出:byteBudget 为测试注入缝,非模型可见——工具路径经
|
|
465
|
+
* doSearch 走默认 SEARCH_SCAN_BYTE_BUDGET)。
|
|
468
466
|
*
|
|
469
467
|
* 流程:窄化拒绝(候选 > MULTI_SEARCH_MAX_SESSIONS,V8)→ buildSessionFileIndex(一次
|
|
470
468
|
* 根扫描,id 语义与 find 候选同源)→ 按传入顺序逐个「复用单会话扫描管线」(safeParse →
|