@henryqw/pi-session-recall 1.0.2 → 2.0.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 +24 -6
- package/extensions/hydrate.ts +12 -2
- package/extensions/session-recall.ts +7 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# `@henryqw/pi-session-recall`
|
|
2
2
|
|
|
3
|
-
Find decisions and context in past Pi sessions through a local FTS5 index
|
|
3
|
+
Find decisions and context in past Pi sessions through a local FTS5 index.
|
|
4
|
+
|
|
5
|
+
Saved transcripts are not injected on every turn. The active tool registration still adds standing prompt cost through its schema, descriptions, and guideline. Returned content enters active model context.
|
|
4
6
|
|
|
5
7
|
The bundled `pi-session-pattern-miner` skill finds repeated work that may deserve automation.
|
|
6
8
|
|
|
@@ -41,9 +43,9 @@ BM25 is a text-ranking method. Hydrated results include messages read from saved
|
|
|
41
43
|
|
|
42
44
|
| Mode | Call | Result |
|
|
43
45
|
| --- | --- | --- |
|
|
44
|
-
| Discovery | `query` | BM25-ranked top sessions.
|
|
45
|
-
| Scroll | `sessionId` + `aroundMessageId` | ±`window`
|
|
46
|
-
| Read | `sessionId` |
|
|
46
|
+
| Discovery | `query` | BM25-ranked top sessions. Adaptive retrieval uses user and assistant text for windows, bookends, anchors, and counts. It omits tool-result messages and sets `toolResultsOmitted:true` when it removes one. Lower hits still include their indexed anchor. Use `detail:"full"` to hydrate every hit with tool-result messages included. |
|
|
47
|
+
| Scroll | `sessionId` + `aroundMessageId` | Raw message roles, including tool results, within ±`window` ([1,20]) of the anchor. Re-anchor on the last or first message ID to scroll. Across forks, pass the previous response's `branchTip`; `aroundMessageId` only centers the window and must lie on that branch. |
|
|
48
|
+
| Read | `sessionId` | Raw message roles, including tool results, from the session. Large sessions return head 20 + tail 10. Oversized content is bounded to 50k characters and marked with `contentTruncated`. |
|
|
47
49
|
| Browse | no args | Recent sessions with path, name, cwd, started date, and preview. |
|
|
48
50
|
|
|
49
51
|
In the interactive TUI, the collapsed tool block shows the last five visual lines and the earlier-line count. Press `Ctrl+O` to expand the full bounded response. The model always receives the complete tool result.
|
|
@@ -54,6 +56,8 @@ Run `/skill:pi-session-pattern-miner` to find repeated workflows in past session
|
|
|
54
56
|
|
|
55
57
|
## Flow
|
|
56
58
|
|
|
59
|
+
Search makes no model calls.
|
|
60
|
+
|
|
57
61
|
### Query and index
|
|
58
62
|
|
|
59
63
|
- Prefer distinctive identifiers, package names, issue numbers, or uncommon terms. Use quoted phrases only when exact wording is known.
|
|
@@ -68,15 +72,29 @@ Hits inside the current session's live context are suppressed. Compacted-away or
|
|
|
68
72
|
|
|
69
73
|
Before browse or discovery, the extension lazily syncs the index from the session tree.
|
|
70
74
|
|
|
75
|
+
### Retrieval safety
|
|
76
|
+
|
|
77
|
+
Adaptive discovery leaves tool-result messages out of returned context. Use `detail:"full"`, READ, or SCROLL when you explicitly need them.
|
|
78
|
+
|
|
79
|
+
Historical tool output may contain secrets or other sensitive data. Raw retrieval places that output in active model context.
|
|
80
|
+
|
|
71
81
|
## State and storage
|
|
72
82
|
|
|
73
83
|
The extension maintains the derived SQLite search index at `~/.pi/agent/config/pi-session-recall/index.db`.
|
|
74
84
|
|
|
75
85
|
This is derived state. Delete it and it rebuilds from your session files.
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
The index and transcript reads stay local. Transcripts are read in place. Returned content follows the data path of your configured model provider.
|
|
88
|
+
|
|
89
|
+
## Roll back
|
|
90
|
+
|
|
91
|
+
Pin the previous release:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pi install npm:@henryqw/pi-session-recall@1.0.3
|
|
95
|
+
```
|
|
78
96
|
|
|
79
|
-
|
|
97
|
+
No index migration or cleanup is needed.
|
|
80
98
|
|
|
81
99
|
## Limits and recovery
|
|
82
100
|
|
package/extensions/hydrate.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface WindowResult {
|
|
|
14
14
|
branchMessages: WindowMessage[];
|
|
15
15
|
messagesBefore: number;
|
|
16
16
|
messagesAfter: number;
|
|
17
|
+
/** Present only when discovery filtering removed at least one tool result. */
|
|
18
|
+
toolResultsOmitted?: true;
|
|
17
19
|
/** Tip of the branch the window was resolved on — pass back as branchTip to
|
|
18
20
|
* keep scrolling on this branch across forks. May be a non-message entry id. */
|
|
19
21
|
branchTip: string;
|
|
@@ -198,7 +200,7 @@ export function getWindow(
|
|
|
198
200
|
sessionPath: string,
|
|
199
201
|
anchorEntryId: string,
|
|
200
202
|
windowN: number,
|
|
201
|
-
opts?: { branchTip?: string },
|
|
203
|
+
opts?: { branchTip?: string; userAssistantTextOnly?: boolean },
|
|
202
204
|
): WindowResult {
|
|
203
205
|
const n = Math.max(0, Math.min(50, windowN));
|
|
204
206
|
const entries = parseSessionEntries(sessionPath);
|
|
@@ -220,8 +222,15 @@ export function getWindow(
|
|
|
220
222
|
} else {
|
|
221
223
|
tip = deepestDescendant(entriesById, entries, anchorEntryId);
|
|
222
224
|
}
|
|
223
|
-
const
|
|
225
|
+
const rawMessages = branchMessages(entriesById, tip);
|
|
226
|
+
const toolResultsOmitted = opts?.userAssistantTextOnly && rawMessages.some((m) => m.role === "toolResult")
|
|
227
|
+
? true
|
|
228
|
+
: undefined;
|
|
229
|
+
const msgs = opts?.userAssistantTextOnly
|
|
230
|
+
? rawMessages.filter((m) => (m.role === "user" || m.role === "assistant") && m.content.length > 0)
|
|
231
|
+
: rawMessages;
|
|
224
232
|
const idx = msgs.findIndex((m) => m.entryId === anchorEntryIdMsg);
|
|
233
|
+
if (idx < 0) throw new Error(`anchor entry ${anchorEntryId} is not a user/assistant text message`);
|
|
225
234
|
const start = Math.max(0, idx - n);
|
|
226
235
|
const end = Math.min(msgs.length - 1, idx + n);
|
|
227
236
|
return {
|
|
@@ -231,6 +240,7 @@ export function getWindow(
|
|
|
231
240
|
),
|
|
232
241
|
messagesBefore: idx,
|
|
233
242
|
messagesAfter: msgs.length - 1 - idx,
|
|
243
|
+
...(toolResultsOmitted ? { toolResultsOmitted } : {}),
|
|
234
244
|
branchTip: tip,
|
|
235
245
|
};
|
|
236
246
|
}
|
|
@@ -113,6 +113,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
113
113
|
label: "Session Search",
|
|
114
114
|
description: DESCRIPTION,
|
|
115
115
|
promptSnippet: "Search past Pi sessions for prior decisions and context",
|
|
116
|
+
promptGuidelines: [
|
|
117
|
+
"Use session_search only when the user explicitly asks about past Pi sessions, historical decisions, or repeated work not available in the current conversation. Do not use it for current-session continuation or ordinary repository inspection.",
|
|
118
|
+
],
|
|
116
119
|
parameters: Type.Object({
|
|
117
120
|
query: Type.Optional(Type.String({ description: "Search query (discovery). FTS5 syntax supported." })),
|
|
118
121
|
sessionId: Type.Optional(Type.String({ description: "Absolute path of the session file." })),
|
|
@@ -319,18 +322,18 @@ export default function (pi: ExtensionAPI): void {
|
|
|
319
322
|
if (!hydrateFull) {
|
|
320
323
|
// Compact hits still carry the matched anchor message.
|
|
321
324
|
try {
|
|
322
|
-
const win = getWindow(hit.path, hit.entryId, 0);
|
|
325
|
+
const win = getWindow(hit.path, hit.entryId, 0, { userAssistantTextOnly: true });
|
|
323
326
|
// Mark when the fixed compact cap already removed content, so a
|
|
324
327
|
// hit that still fits the budget isn't mistaken for complete.
|
|
325
328
|
const overCompactCap = win.messages.some((m) => m.content.length > 2000);
|
|
326
|
-
return fitOrTruncate({ ...meta, detail: "compact", ...(overCompactCap ? { contentTruncated: true } : {}), messages: truncateContent(win.messages, 2000), bookends: { start: [], end: [] }, messagesBefore: win.messagesBefore, messagesAfter: win.messagesAfter }, win.messages);
|
|
329
|
+
return fitOrTruncate({ ...meta, detail: "compact", ...(overCompactCap ? { contentTruncated: true } : {}), ...(win.toolResultsOmitted ? { toolResultsOmitted: true } : {}), messages: truncateContent(win.messages, 2000), bookends: { start: [], end: [] }, messagesBefore: win.messagesBefore, messagesAfter: win.messagesAfter }, win.messages);
|
|
327
330
|
} catch (error) {
|
|
328
331
|
return hydrationFallback(error);
|
|
329
332
|
}
|
|
330
333
|
}
|
|
331
334
|
try {
|
|
332
335
|
// One bounded snapshot feeds both window and branch bookends.
|
|
333
|
-
const win = getWindow(hit.path, hit.entryId, 5);
|
|
336
|
+
const win = getWindow(hit.path, hit.entryId, 5, full ? undefined : { userAssistantTextOnly: true });
|
|
334
337
|
// Same branch as the anchor — following the file's final leaf
|
|
335
338
|
// would attach unrelated sibling messages.
|
|
336
339
|
const bookends = { start: win.branchMessages.slice(0, 3), end: win.branchMessages.slice(-3) };
|
|
@@ -338,6 +341,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
338
341
|
{
|
|
339
342
|
...meta,
|
|
340
343
|
detail: "full" as const,
|
|
344
|
+
...(win.toolResultsOmitted ? { toolResultsOmitted: true } : {}),
|
|
341
345
|
messages: win.messages,
|
|
342
346
|
bookends,
|
|
343
347
|
messagesBefore: win.messagesBefore,
|
package/package.json
CHANGED