@zhushanwen/pi-session-reader 0.2.4 → 0.3.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 +1 -1
- package/src/__tests__/render.test.ts +2 -2
- package/src/__tests__/tool-handler.test.ts +2 -2
- package/src/core/execution-tree.ts +0 -1
- package/src/core/family.ts +2 -3
- package/src/core/render.ts +1 -1
- package/src/discovery/subagents.ts +10 -15
- package/src/index.ts +9 -9
- package/src/tool-handler.ts +12 -19
- package/src/tui/hash-provider.ts +0 -3
- package/src/tui/session-command.ts +1 -1
package/package.json
CHANGED
|
@@ -93,7 +93,7 @@ describe('renderOutline', () => {
|
|
|
93
93
|
})
|
|
94
94
|
|
|
95
95
|
it('2a. 降级:单行 toolSummary 过长超 perTurnBudget → 砍 toolSummary,保 userBrief 骨架', () => {
|
|
96
|
-
// L1 行不含 assistantBrief
|
|
96
|
+
// 预算紧张降级后 L1 行不含 assistantBrief(预算充足 level 0 行含);budget=40, 3 turns → perTurnCharBudget≈53
|
|
97
97
|
// 行 = head + userBrief(10) + toolSummary(20 个工具名≈63 chars) → ~80 > 53 → 砍 toolSummary
|
|
98
98
|
const toolCalls = Array.from({ length: 20 }, (_, k) => ({ name: `t${String(k).padStart(2, '0')}` }))
|
|
99
99
|
const turns = [0, 1, 2].map((i) =>
|
|
@@ -183,7 +183,7 @@ describe('renderOutline', () => {
|
|
|
183
183
|
// stats 完整性
|
|
184
184
|
expect(result.stats.totalTurns).toBe(32)
|
|
185
185
|
// totalEntries 近似(leaf+branch+orphan)不含 session header(segmentTurns 规则1 跳过);
|
|
186
|
-
//
|
|
186
|
+
// 工具层仅覆盖 stats.totalBytes 与 skippedLines(ParseResult 无 totalEntries 字段)。M1 验量级。
|
|
187
187
|
expect(result.stats.totalEntries).toBeGreaterThan(1000)
|
|
188
188
|
// 5.6MB 全量解析在并发/高负载下可能超 vitest 默认 5s,显式放宽
|
|
189
189
|
}, 60000)
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
* 测试框架 vitest(禁止 node:test/tsx)。直接调 handleSessionRead(纯逻辑,agentDir 注入),
|
|
23
23
|
* 传真实 `/Users/zhushanwen/.pi/agent` 作 agentDir——用本机真实历史 session 数据,无需 mock。
|
|
24
24
|
*
|
|
25
|
-
* 覆盖
|
|
25
|
+
* 覆盖 9 action 主路径 + F1(find 零匹配)/F4(turn 越界)/F5(缺参)/resolveSessionId 片段等价。
|
|
26
26
|
*
|
|
27
27
|
* 真实数据用例全部带 skipIf 守卫(CI 无本机 ~/.pi/agent → skip,不硬失败);
|
|
28
28
|
* renderExtractItems F9 截断是纯 fixture,无条件跑。
|
|
@@ -782,7 +782,7 @@ describe('resolveSessionId ② sa-id 形态(w2 TC7-TC10 + CQ3)', () => {
|
|
|
782
782
|
await handleSessionRead({ action: 'outline', session: 'sa-nonexist-9999' }, dir)
|
|
783
783
|
} catch (e) {
|
|
784
784
|
const msg = (e as Error).message
|
|
785
|
-
expect(msg).toContain('
|
|
785
|
+
expect(msg).toContain('可能尚未落盘')
|
|
786
786
|
expect(msg).toContain('action:"family"')
|
|
787
787
|
expect(msg).toContain('👉')
|
|
788
788
|
}
|
|
@@ -173,7 +173,6 @@ function collectRelatedRecords(
|
|
|
173
173
|
// buildExecutionTree(IF3 核心)
|
|
174
174
|
// ============================================================
|
|
175
175
|
|
|
176
|
-
/** 建树过程的共享可变状态(避免递归函数参数爆炸,封装为 context)。 */
|
|
177
176
|
/** 建树过程的共享可变状态(避免递归函数参数爆炸,封装为 context)。 */
|
|
178
177
|
interface BuildContext {
|
|
179
178
|
rootSessionId: string
|
package/src/core/family.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Entry } from './parser.js'
|
|
|
11
11
|
* - SessionRef.mtime/sizeBytes:从 fileStats 取(M1 key=sessionId),取不到为 0
|
|
12
12
|
* - SubagentRef.sessionId:identity entry 不含 subagent session 的 id → 占位用 entry.id
|
|
13
13
|
* - SubagentRef.cwd:identity entry(custom 类型)无 cwd → 占位空串
|
|
14
|
-
* - fileStats 的 key:
|
|
14
|
+
* - fileStats 的 key:sessionId(M2 实现沿用此 key,cleanedUp 按 sessionId 命中),逻辑通用
|
|
15
15
|
*
|
|
16
16
|
* 隔代关联规则(design §3.3 D-7 Q1)见 resolveFamily 注释。
|
|
17
17
|
*/
|
|
@@ -24,7 +24,6 @@ export interface SessionRef {
|
|
|
24
24
|
cwd: string
|
|
25
25
|
/** fork 文件指向来源的路径(来自 header 的 parentSession,原始文件路径字符串) */
|
|
26
26
|
parentSession?: string
|
|
27
|
-
name?: string
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
export interface SubagentRef extends SessionRef {
|
|
@@ -194,7 +193,7 @@ export function buildFamilyIndex(
|
|
|
194
193
|
mtime: stat?.mtime ?? 0,
|
|
195
194
|
sizeBytes: stat?.size ?? 0,
|
|
196
195
|
cwd: '', // identity entry 无 cwd;M2 从 subagent 文件 header 补
|
|
197
|
-
//
|
|
196
|
+
// fileStats key=sessionId(buildFamilyFromFs 以 header.id 写入),cleanedUp 按 sessionId 命中
|
|
198
197
|
cleanedUp: !fileStats.has(ident.id),
|
|
199
198
|
// U4 富字段:从 identity data 读(manifest 主/P-fallback 由 buildFamilyFromFs 组装时决定)
|
|
200
199
|
task: ident.data.task,
|
package/src/core/render.ts
CHANGED
|
@@ -285,7 +285,7 @@ function formatHHMM(timestamp?: string): string {
|
|
|
285
285
|
return m ? `${m[1]}:${m[2]}` : ''
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
function formatBytesMarker(bytes: number): string {
|
|
288
|
+
export function formatBytesMarker(bytes: number): string {
|
|
289
289
|
if (bytes <= 0) return ''
|
|
290
290
|
if (bytes < BYTES_PER_KB) return `[${bytes}B omitted]`
|
|
291
291
|
return `[${Math.round(bytes / BYTES_PER_KB)}KB omitted]`
|
|
@@ -23,7 +23,8 @@ import { resolveWorkflows } from './workflows.js'
|
|
|
23
23
|
* 是 records/<sa-id>.json manifest(subagent 创建时写入,持久存在)。故 manifest 是孤儿/
|
|
24
24
|
* cleanedUp 的来源(design §3.3 D-7 "records/*.json manifest 作孤儿补充")。
|
|
25
25
|
* - workflows:M1 resolveFamily 恒返回 []。M2 在此单独读目标 session 的 workflow-state-link
|
|
26
|
-
* custom entry → link.data.path(wf-state 文件绝对路径)→
|
|
26
|
+
* custom entry → link.data.path(wf-state 文件绝对路径)→ 从文件尾向头找首个非空且
|
|
27
|
+
* JSON.parse 成功的行(最新快照,容错尾半截 JSON)取 calls。
|
|
27
28
|
*
|
|
28
29
|
* @throws sessionId 不在任意 main session header → Error(M3 tool-adapter 层转 F1 恢复指引)
|
|
29
30
|
*/
|
|
@@ -87,7 +88,7 @@ export async function buildFamilyFromFs(sessionId: string, agentDir: string): Pr
|
|
|
87
88
|
// manifest 主(TC-u4-manifest-enrich):透 task/slug/model/status/sessionFile 全字段
|
|
88
89
|
data = {
|
|
89
90
|
rootSessionId: manifest.rootSessionId,
|
|
90
|
-
slug: manifest.slug ?? '', // slug 兼容旧 manifest
|
|
91
|
+
slug: manifest.slug ?? '', // slug 兼容旧 manifest(缺→空串兜底,m0 契约)
|
|
91
92
|
task: manifest.task,
|
|
92
93
|
agent: manifest.agentName, // 同语义异名:manifest.agentName ↔ identity.data.agent
|
|
93
94
|
model: manifest.model,
|
|
@@ -163,7 +164,7 @@ export async function buildFamilyFromFs(sessionId: string, agentDir: string): Pr
|
|
|
163
164
|
const family = resolveFamily(sessionId, index)
|
|
164
165
|
|
|
165
166
|
// ---- 6. 补 M1 占位字段(fileName / subagent cwd)+ workflows ----
|
|
166
|
-
enrichRefs(family,
|
|
167
|
+
enrichRefs(family, pathToRef)
|
|
167
168
|
family.workflows = await resolveWorkflows(sessionId, sessionIdToPath, pathToRef)
|
|
168
169
|
|
|
169
170
|
return family
|
|
@@ -330,11 +331,6 @@ function isRecordManifest(v: unknown): v is RecordManifest {
|
|
|
330
331
|
)
|
|
331
332
|
}
|
|
332
333
|
|
|
333
|
-
/**
|
|
334
|
-
* 扫描 subagents/<cwdSlug>/records/*.json —— subagent 注册清单。
|
|
335
|
-
* 每个 manifest 在 subagent 创建时写入,持久存在即使 .jsonl 被 GC。坏 manifest(缺必填字段
|
|
336
|
-
* /JSON 损坏)跳过,不中断扫描。
|
|
337
|
-
*/
|
|
338
334
|
/** 读单个 manifest 文件并校验;坏 manifest(JSON 损坏/缺必填字段)返回 undefined。 */
|
|
339
335
|
async function tryReadManifest(path: string): Promise<RecordManifest | undefined> {
|
|
340
336
|
try {
|
|
@@ -345,6 +341,11 @@ async function tryReadManifest(path: string): Promise<RecordManifest | undefined
|
|
|
345
341
|
}
|
|
346
342
|
}
|
|
347
343
|
|
|
344
|
+
/**
|
|
345
|
+
* 扫描 subagents/<cwdSlug>/records/*.json —— subagent 注册清单。
|
|
346
|
+
* 每个 manifest 在 subagent 创建时写入,持久存在即使 .jsonl 被 GC。坏 manifest(缺必填字段
|
|
347
|
+
* /JSON 损坏)跳过,不中断扫描。
|
|
348
|
+
*/
|
|
348
349
|
export async function listRecordManifests(agentDir: string): Promise<RecordManifest[]> {
|
|
349
350
|
const root = join(agentDir, 'subagents')
|
|
350
351
|
const out: RecordManifest[] = []
|
|
@@ -395,11 +396,7 @@ export function extractSessionIdFromFilename(name: string): string {
|
|
|
395
396
|
*(identity 无 cwd)。此处用已扫描的真实文件信息补全:alive 的 ref 补 fileName + cwd;
|
|
396
397
|
* cleanedUp 孤儿(无文件)保持占位。
|
|
397
398
|
*/
|
|
398
|
-
function enrichRefs(
|
|
399
|
-
family: Family,
|
|
400
|
-
sessionIdToPath: Map<string, string>,
|
|
401
|
-
pathToRef: Map<string, SessionRef>,
|
|
402
|
-
): void {
|
|
399
|
+
function enrichRefs(family: Family, pathToRef: Map<string, SessionRef>): void {
|
|
403
400
|
// sessionId → 完整 ref(含真实 fileName/cwd),由 pathToRef 反建
|
|
404
401
|
const bySid = new Map<string, SessionRef>()
|
|
405
402
|
for (const ref of pathToRef.values()) bySid.set(ref.sessionId, ref)
|
|
@@ -426,6 +423,4 @@ function enrichRefs(
|
|
|
426
423
|
cwd: full.cwd || s.cwd,
|
|
427
424
|
} as SubagentRef
|
|
428
425
|
})
|
|
429
|
-
// sessionIdToPath 仅用于类型完整性占位引用,避免未用警告(实际路径信息已在 pathToRef)
|
|
430
|
-
void sessionIdToPath
|
|
431
426
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,8 +15,6 @@ import { createSessionCommand } from './tui/session-command.js'
|
|
|
15
15
|
* (错误直接 throw 给 pi——pi-agent-core agent-loop 只对 execute throw 置
|
|
16
16
|
* isError:true,返回值里的 isError 字段被丢弃;W4 修复,锚点
|
|
17
17
|
* agent-loop.js:453-483/525-547,pi 自带 bash 工具同范式)
|
|
18
|
-
*
|
|
19
|
-
* M4 将在此 addAutocompleteProvider(TUI # 补全,ctx.mode === 'tui' 时)。
|
|
20
18
|
*/
|
|
21
19
|
|
|
22
20
|
// ---- TypeBox 参数 schema(design §3.4 14 字段)----
|
|
@@ -32,7 +30,7 @@ const SessionReadSchema = Type.Object({
|
|
|
32
30
|
session: Type.Optional(
|
|
33
31
|
Type.String({
|
|
34
32
|
description:
|
|
35
|
-
'Session id, uuid fragment (e.g. e6c96), subagent record id (sa-xxx, precise lookup), or absolute .jsonl path (~ or ~/ allowed). Required for family/outline/expand/detail/search/export/workflow. # prefix auto-stripped.',
|
|
33
|
+
'Session id, uuid fragment (e.g. e6c96), subagent record id (sa-xxx, precise lookup), or absolute .jsonl path (~ or ~/ allowed). Required for family/outline/expand/detail/search/export/extract/workflow. # prefix auto-stripped.',
|
|
36
34
|
}),
|
|
37
35
|
),
|
|
38
36
|
query: Type.Optional(
|
|
@@ -43,7 +41,7 @@ const SessionReadSchema = Type.Object({
|
|
|
43
41
|
),
|
|
44
42
|
turns: Type.Optional(
|
|
45
43
|
Type.String({
|
|
46
|
-
description: 'detail action: turn range, "T013-T015" or "T013".',
|
|
44
|
+
description: 'detail/extract action: turn range, "T013-T015" or "T013".',
|
|
47
45
|
}),
|
|
48
46
|
),
|
|
49
47
|
turn: Type.Optional(
|
|
@@ -74,7 +72,8 @@ const SessionReadSchema = Type.Object({
|
|
|
74
72
|
),
|
|
75
73
|
allBranches: Type.Optional(
|
|
76
74
|
Type.Boolean({
|
|
77
|
-
description:
|
|
75
|
+
description:
|
|
76
|
+
"outline (and export's outline section): include abandoned side-branches. Not supported by family. Default false.",
|
|
78
77
|
}),
|
|
79
78
|
),
|
|
80
79
|
granularity: Type.Optional(
|
|
@@ -87,7 +86,8 @@ const SessionReadSchema = Type.Object({
|
|
|
87
86
|
),
|
|
88
87
|
source: Type.Optional(
|
|
89
88
|
StringEnum(['main', 'subagent'], {
|
|
90
|
-
description:
|
|
89
|
+
description:
|
|
90
|
+
'find and session-resolving actions: filter by source. "main" = sessions/, "subagent" = subagents/. Default both (merged).',
|
|
91
91
|
}),
|
|
92
92
|
),
|
|
93
93
|
limit: Type.Optional(
|
|
@@ -123,8 +123,8 @@ const SessionReadSchema = Type.Object({
|
|
|
123
123
|
// ---- guidelines(注入 LLM,design §3.4)----
|
|
124
124
|
|
|
125
125
|
const guidelines = [
|
|
126
|
-
'Progressive reading: outline (~
|
|
127
|
-
'find first to locate a session by uuid fragment or name. TUI #references are
|
|
126
|
+
'Progressive reading: outline (~1500 token overview) → expand (one turn) → detail (full text). Default omits toolResult/thinking noise.',
|
|
127
|
+
'find first to locate a session by uuid fragment or name. TUI #references are full uuids.',
|
|
128
128
|
'outline before detail. Never read raw .jsonl files—use this tool.',
|
|
129
129
|
'family traces fork parents/children, subagent sessions, and workflow runs.',
|
|
130
130
|
'extract what=<type> to pull user messages / commands / files / commits / tool results across turns (optional tool= filter for commands/tool-results).',
|
|
@@ -134,7 +134,7 @@ const guidelines = [
|
|
|
134
134
|
|
|
135
135
|
// ---- 工具 description(design §3.4,照搬措辞)----
|
|
136
136
|
|
|
137
|
-
const description = `Read pi session files (conversation history) by semantic structure instead of raw bytes. Use when you need to review another session, trace a fork/subagent/workflow family, or locate a past decision. Nine actions: find (locate by name/uuid fragment), family (fork/subagent/workflow relations), outline (turn-level overview, ~
|
|
137
|
+
const description = `Read pi session files (conversation history) by semantic structure instead of raw bytes. Use when you need to review another session, trace a fork/subagent/workflow family, or locate a past decision. Nine actions: find (locate by name/uuid fragment), family (fork/subagent/workflow relations), outline (turn-level overview, ~1500 token), expand (single-turn entry list), detail (full text of turns), search (full-text grep across a session), export (materialize to file), extract (pull user messages / commands / files / commits / tool results by type), workflow (workflow run overview: status/budget/steps, step call sessionId jumps to outline/detail). Progressive reading: outline → expand → detail. Do NOT use for the current session (the host provides current-session access) or to edit sessions (pi has /resume /fork).`
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
140
|
* 已注册过 TUI provider/command 的 pi 实例集合。
|
package/src/tool-handler.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 分层约定(同 scheduler/cw-tool):本文件零 pi 依赖——agentDir 作参数注入,
|
|
5
5
|
* 不调用 getAgentDir(),可完全单测;pi 注册与 getAgentDir() 调用在 index.ts。
|
|
6
6
|
*
|
|
7
|
-
* 按 action 分发到
|
|
7
|
+
* 按 action 分发到 9 条路径,串联 M1 core(parser/tree/turns/render)+ M2 discovery
|
|
8
8
|
*(find/subagents)。content 给 LLM 读(人类可读摘要),details 供程序化消费/测试断言。
|
|
9
9
|
*
|
|
10
10
|
* 错误规格 F1-F6:handler 抛 Error(message 含 👉 恢复指引),index.ts 的 execute 闭包
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
renderOutline,
|
|
29
29
|
renderExpand,
|
|
30
30
|
renderDetail,
|
|
31
|
+
formatBytesMarker,
|
|
31
32
|
type OutlineOptions,
|
|
32
33
|
type OutlineResult,
|
|
33
34
|
type EntryBrief,
|
|
@@ -126,15 +127,6 @@ function shortCwd(cwd: string): string {
|
|
|
126
127
|
return parts.slice(-SHORT_CWD_SEGMENTS).join('/')
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
/** bytes→KB 换算基数(formatOmitted 的 KB 显示)。 */
|
|
130
|
-
const BYTES_PER_KB = 1024
|
|
131
|
-
|
|
132
|
-
function formatOmitted(bytes: number): string {
|
|
133
|
-
if (bytes <= 0) return ''
|
|
134
|
-
if (bytes < BYTES_PER_KB) return `[${bytes}B omitted]`
|
|
135
|
-
return `[${Math.round(bytes / BYTES_PER_KB)}KB omitted]`
|
|
136
|
-
}
|
|
137
|
-
|
|
138
130
|
/** F5 必填参数校验。 */
|
|
139
131
|
function requireStr(
|
|
140
132
|
val: string | undefined,
|
|
@@ -347,7 +339,7 @@ function formatSessionGc(record: RecordManifest): string {
|
|
|
347
339
|
/** ES2(SA_ID_NO_MATCH):sa-id 无精确匹配(可能仍在运行 / 片段输入)。 */
|
|
348
340
|
function formatSaIdNotFound(saId: string): string {
|
|
349
341
|
return (
|
|
350
|
-
`subagent "${saId}" 无匹配 record
|
|
342
|
+
`subagent "${saId}" 无匹配 record(若刚启动,record 可能尚未落盘)。` +
|
|
351
343
|
`\n👉 用 session_read { action:"family" } 查活跃/已完成的 subagent;` +
|
|
352
344
|
`若是片段输入,请用完整 sa- id 或 action:"find" 重试。`
|
|
353
345
|
)
|
|
@@ -439,7 +431,7 @@ function formatOutlineText(r: OutlineResult): string {
|
|
|
439
431
|
if (b.toolSummary) parts.push(b.toolSummary)
|
|
440
432
|
// v2 O1:补 assistant 结论行(→ )让 outline 单独可决策
|
|
441
433
|
if (b.assistantBrief) parts.push('→ ' + b.assistantBrief)
|
|
442
|
-
const om =
|
|
434
|
+
const om = formatBytesMarker(b.omittedBytes)
|
|
443
435
|
if (om) parts.push(om)
|
|
444
436
|
if (b.branch) parts.push('[旁支]')
|
|
445
437
|
return parts.join(' · ')
|
|
@@ -449,7 +441,7 @@ function formatOutlineText(r: OutlineResult): string {
|
|
|
449
441
|
`${r.stats.totalTurns} turns · ${r.stats.totalEntries} entries · ~${r.tokenEstimate} tokens${
|
|
450
442
|
r.stats.skippedLines > 0 ? ` · ${r.stats.skippedLines} skipped lines` : ''
|
|
451
443
|
}`,
|
|
452
|
-
r.truncated ? `[还有 ${r.truncated} 轮未显示,用 detail
|
|
444
|
+
r.truncated ? `[还有 ${r.truncated} 轮未显示,用 detail 的 turns 参数看指定 turn 范围]` : '',
|
|
453
445
|
]
|
|
454
446
|
.filter(Boolean)
|
|
455
447
|
.join('\n')
|
|
@@ -460,7 +452,7 @@ function formatExpandText(turn: string, entries: EntryBrief[]): string {
|
|
|
460
452
|
const lines = entries.map(
|
|
461
453
|
(e) =>
|
|
462
454
|
` [${e.index}] ${e.type}${e.role ? '/' + e.role : ''} ${e.brief}${
|
|
463
|
-
e.omittedBytes > 0 ? ' ' +
|
|
455
|
+
e.omittedBytes > 0 ? ' ' + formatBytesMarker(e.omittedBytes) : ''
|
|
464
456
|
}`,
|
|
465
457
|
)
|
|
466
458
|
return `${turn}\n${lines.join('\n')}`
|
|
@@ -681,7 +673,7 @@ async function doFamily(params: SessionReadParams, agentDir: string): Promise<To
|
|
|
681
673
|
tree = await buildExecutionTree(resolved.sessionId, agentDir, resolved.fileName)
|
|
682
674
|
} catch (e) {
|
|
683
675
|
throw err(
|
|
684
|
-
`构建执行树失败:${resolved.sessionId}(${e instanceof Error ? e.message : String(e)})。👉 检查 session 或用 find 重新定位,或改用 recursive:false 看 flat family
|
|
676
|
+
`构建执行树失败:${resolved.sessionId}(${e instanceof Error ? e.message : String(e)})。👉 检查 session 或用 find 重新定位,或改用 recursive:false 看 flat family 兜底。`,
|
|
685
677
|
)
|
|
686
678
|
}
|
|
687
679
|
return {
|
|
@@ -702,7 +694,7 @@ async function doFamily(params: SessionReadParams, agentDir: string): Promise<To
|
|
|
702
694
|
return { content: [{ type: 'text', text: formatFamilyText(family) }], details: family }
|
|
703
695
|
}
|
|
704
696
|
|
|
705
|
-
/** outline:turn 级全貌 TOC(design §3.4 outline,~
|
|
697
|
+
/** outline:turn 级全貌 TOC(design §3.4 outline,~1500 token;render budget 硬编码 2000)。 */
|
|
706
698
|
async function doOutline(params: SessionReadParams, agentDir: string): Promise<ToolResult> {
|
|
707
699
|
const resolved = await resolveSessionId(params.session, 'outline', agentDir, params.source)
|
|
708
700
|
if (resolved.kind === 'multi') return disambiguate(resolved.query, resolved.candidates)
|
|
@@ -1385,8 +1377,9 @@ interface SkippedRun {
|
|
|
1385
1377
|
* session 会抛「session not found in family index」)——resolveSessionId 已把 session 解析到
|
|
1386
1378
|
* 真实文件(kind==='ok' 保证文件存在,三形态:绝对路径/sa-id 均 existsSync 校验,片段匹配
|
|
1387
1379
|
* 来自实际 fs 扫描),直接用 resolved.fileName 构造单条目 sessionIdToPath 调 resolveWorkflows
|
|
1388
|
-
*(与 buildFamilyFromFs 步骤 6 的 workflow 腿同源)。pathToRef
|
|
1389
|
-
* 走 sessionRefFromPath 文件名最小回退(sessionId+fileName
|
|
1380
|
+
*(与 buildFamilyFromFs 步骤 6 的 workflow 腿同源)。pathToRef 传空 Map(单条目链路无其他
|
|
1381
|
+
* 文件可反查),call 引用 100% 走 sessionRefFromPath 文件名最小回退(sessionId+fileName,
|
|
1382
|
+
* 足够 LLM 跳 outline/detail 深读)。
|
|
1390
1383
|
*
|
|
1391
1384
|
* 错误契约(C2):workflow 概览探索语义,三类错误均返回 ToolResult 不抛错。
|
|
1392
1385
|
* step 的 call sessionId/sessionFile 是 LLM 跳 outline/detail 的入口(m0 resolveSessionId
|
|
@@ -1468,7 +1461,7 @@ async function doWorkflow(params: SessionReadParams, agentDir: string): Promise<
|
|
|
1468
1461
|
if (requestedRunId !== undefined) details.requestedRunId = requestedRunId
|
|
1469
1462
|
if (skippedRuns.length > 0) details.skippedRuns = skippedRuns
|
|
1470
1463
|
|
|
1471
|
-
// 全部 run
|
|
1464
|
+
// 全部 run 都跳过的兜底提示(ES-wf-snapshot-read-fail 末段)
|
|
1472
1465
|
let text: string
|
|
1473
1466
|
if (runs.length === 0) {
|
|
1474
1467
|
text =
|
package/src/tui/hash-provider.ts
CHANGED
|
@@ -47,8 +47,6 @@ const AGE_NUM_DIGITS = 2
|
|
|
47
47
|
export interface AutocompleteCandidate {
|
|
48
48
|
/** 显示文本(满宽 label)。`${age} ${预览/name}`,如 "01m 看看 pi-session-reader..." */
|
|
49
49
|
label: string
|
|
50
|
-
/** 副信息(次列)。本 provider 不设(undefined)——触发 SelectList 满宽 label 分支 */
|
|
51
|
-
description?: string
|
|
52
50
|
/** 插入编辑器,如 "#019e6c96-aaaa-bbbb-cccc-dddddddddddd"(design D-3:完整 uuid,非名称;不显示给用户看) */
|
|
53
51
|
insertText: string
|
|
54
52
|
}
|
|
@@ -203,7 +201,6 @@ export function createHashAutocompleteProvider(
|
|
|
203
201
|
const items: AutocompleteItem[] = candidates.map((c) => ({
|
|
204
202
|
value: c.insertText,
|
|
205
203
|
label: c.label,
|
|
206
|
-
description: c.description,
|
|
207
204
|
}))
|
|
208
205
|
// prefix = 光标前匹配到的整段(# 及片段),applyCompletion 据此定位替换区间
|
|
209
206
|
return { items, prefix: `#${fragment}` }
|
|
@@ -36,7 +36,7 @@ export function createSessionCommand(
|
|
|
36
36
|
handler(args: string, ctx: ExtensionCommandContext): Promise<void>
|
|
37
37
|
} {
|
|
38
38
|
return {
|
|
39
|
-
description: 'Pick a session and insert a #uuid
|
|
39
|
+
description: 'Pick a session and insert a #uuid reference into the editor.',
|
|
40
40
|
async getArgumentCompletions(argumentPrefix) {
|
|
41
41
|
const trimmed = argumentPrefix.trim()
|
|
42
42
|
const all = await SessionManager.listAll(getCwdSessionDir())
|