@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.
@@ -1,10 +1,12 @@
1
1
  /**
2
- * result action(subagent-sync-collect U6:design subagent-sync-collect.md §3.1.3)。
2
+ * result action。
3
3
  *
4
- * 从 tool-handler.ts 机械提取(max-lines 拆分轮):纯移动零行为变更。tool-handler 的
5
- * 定位/解析 helper(err/stripHash/requireStr/resolveSessionId/disambiguate/safeParse)
6
- * 为文件私有且被其余 9 个 action 共用,不便搬移——经 {@link ResultActionDeps} 注入,
7
- * tool-handler.ts 构造 RESULT_ACTION_DEPS 常量传入(构造期绑定,运行时零查找开销)。
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 私有 helper 的注入面(构造期常量绑定)。 */
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 登记] 本包内第 4 个同構「text 块提取」变体(其余三处均在
56
- * tool-handler.ts / discovery/find.ts):messageReadableText('' join + 占位符)、
57
- * extractContentText('\n' join)、extractTextFromContent(' ' join + 空返 undefined)。
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
- if (typeof content === 'string') return content
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 截断尾提示(design §3.1.3:超出截断 + 提示读原文件)。 */
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, deps: ResultActionDeps): string[] {
162
- const trimmed = deps.requireStr(raw, 'session', 'result')
163
- const ids = trimmed.split(',').map((s) => deps.stripHash(s.trim()))
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 deps.err(
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 deps.err(
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, deps)
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 deps.err(
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, deps.sessionIdPrefixLen)}…)\n${it.text}`,
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) }],
@@ -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 的可选参数(byteBudget 供测试注入小预算)。 */
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 参数供测试注入小预算,工具路径经 doSearch
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 →