@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 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 with no model calls. Local search recalls earlier work without carrying every transcript in current context or adding standing prompt cost.
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. The top hit is hydrated with a ±5 message window and first/last-3 bookends. Lower hits include the matched anchor message and metadata. `detail:"full"` hydrates all. |
45
- | Scroll | `sessionId` + `aroundMessageId` | ±`window` messages ([1,20]) around the anchor on its branch. 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. |
46
- | Read | `sessionId` | The whole session. Large sessions return head 20 + tail 10. Oversized content is bounded to 50k characters and marked with `contentTruncated`. |
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
- ## Data, cost, and privacy
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
- Everything stays local. Transcripts are read in place, and nothing leaves the machine beyond what tool results already show the model. Search makes no model calls.
97
+ No index migration or cleanup is needed.
80
98
 
81
99
  ## Limits and recovery
82
100
 
@@ -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 msgs = branchMessages(entriesById, tip);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-session-recall",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
4
  "description": "Local FTS5 search over past Pi sessions plus a skill for turning recurring work into deterministic automation.",
5
5
  "keywords": [
6
6
  "pi-package",