@ogulcancelik/pi-session-recall 1.0.4 → 1.0.6

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,6 @@
1
1
  # pi-session-recall
2
2
 
3
- Recall past [pi](https://github.com/badlogic/pi-mono) sessions. Search through conversation history and query specific sessions with an LLM.
3
+ Recall past [pi](https://github.com/earendil-works/pi) sessions. Search through conversation history and query specific sessions with an LLM.
4
4
 
5
5
  ## Install
6
6
 
@@ -28,7 +28,7 @@ Spaces mean exact spaces in an exact phrase. For unrelated concepts, the agent s
28
28
 
29
29
  ### `session_query`
30
30
 
31
- Deep-dives into a specific session file. Loads the conversation, sends it to an LLM, and answers your question about it.
31
+ Deep-dives into a specific session file. Loads the conversation, sends it to an LLM, and answers your question about it. It includes user and assistant text plus tool calls, while omitting assistant thinking and tool results to keep queries efficient.
32
32
 
33
33
  For large sessions that exceed the model's context window, it uses smart windowing: keeps the first/last messages plus keyword-relevant sections, marking gaps with `[... N messages omitted ...]`.
34
34
 
@@ -55,7 +55,7 @@ If no model is configured (or the configured model isn't available), it falls ba
55
55
 
56
56
  ## Requirements
57
57
 
58
- - [pi](https://github.com/badlogic/pi-mono) v0.40+
58
+ - [pi](https://github.com/earendil-works/pi) v0.40+
59
59
  - [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) — recommended for fast search, falls back to `grep` or Node-native scan
60
60
 
61
61
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogulcancelik/pi-session-recall",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Search and query past pi sessions. Recall previous conversations on demand.",
5
5
  "type": "module",
6
6
  "keywords": [
package/session-recall.ts CHANGED
@@ -23,8 +23,14 @@
23
23
  * session_query(path, "what approach did we take for X?")
24
24
  */
25
25
 
26
- import { complete, type Model, type Api, type Message } from "@mariozechner/pi-ai";
27
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
26
+ import {
27
+ cleanupSessionResources,
28
+ complete,
29
+ type Model,
30
+ type Api,
31
+ type Message,
32
+ } from "@earendil-works/pi-ai";
33
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
28
34
  import {
29
35
  getAgentDir,
30
36
  SessionManager,
@@ -32,7 +38,7 @@ import {
32
38
  getMarkdownTheme,
33
39
  serializeConversation,
34
40
  type SessionEntry,
35
- } from "@mariozechner/pi-coding-agent";
41
+ } from "@earendil-works/pi-coding-agent";
36
42
  import {
37
43
  Container,
38
44
  Input,
@@ -42,9 +48,10 @@ import {
42
48
  type SelectListTheme,
43
49
  Spacer,
44
50
  Text,
45
- } from "@mariozechner/pi-tui";
51
+ } from "@earendil-works/pi-tui";
46
52
  import { Type } from "@sinclair/typebox";
47
53
  import { execFileSync } from "node:child_process";
54
+ import { randomBytes } from "node:crypto";
48
55
  import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from "node:fs";
49
56
  import { join, dirname } from "node:path";
50
57
  import { homedir } from "node:os";
@@ -174,6 +181,63 @@ function estimateTokens(text: string): number {
174
181
  return Math.ceil(text.length / 4);
175
182
  }
176
183
 
184
+ /** Create a UUIDv7 for Codex request routing, which is unreliable with UUIDv4. */
185
+ function createUuidV7(): string {
186
+ const bytes = randomBytes(16);
187
+ const timestamp = BigInt(Date.now());
188
+
189
+ bytes[0] = Number((timestamp >> 40n) & 0xffn);
190
+ bytes[1] = Number((timestamp >> 32n) & 0xffn);
191
+ bytes[2] = Number((timestamp >> 24n) & 0xffn);
192
+ bytes[3] = Number((timestamp >> 16n) & 0xffn);
193
+ bytes[4] = Number((timestamp >> 8n) & 0xffn);
194
+ bytes[5] = Number(timestamp & 0xffn);
195
+ bytes[6] = 0x70 | (bytes[6] & 0x0f);
196
+ bytes[8] = 0x80 | (bytes[8] & 0x3f);
197
+
198
+ const hex = bytes.toString("hex");
199
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
200
+ }
201
+
202
+ async function completeRecallRequest(
203
+ model: Model<Api>,
204
+ context: Parameters<typeof complete>[1],
205
+ options: NonNullable<Parameters<typeof complete>[2]>,
206
+ ): ReturnType<typeof complete> {
207
+ if (model.provider !== "openai-codex") return complete(model, context, options);
208
+
209
+ const sessionId = createUuidV7();
210
+ try {
211
+ return await complete(model, context, {
212
+ ...options,
213
+ sessionId,
214
+ transport: "websocket",
215
+ });
216
+ } finally {
217
+ cleanupSessionResources(sessionId);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Keep the conversational record and actions while dropping the two largest,
223
+ * least useful sources of recall context: assistant thinking and tool output.
224
+ */
225
+ function prepareRecallMessages(messages: Message[]): Message[] {
226
+ const prepared: Message[] = [];
227
+
228
+ for (const message of messages) {
229
+ if (message.role === "toolResult") continue;
230
+ if (message.role === "assistant") {
231
+ const content = message.content.filter((block) => block.type !== "thinking");
232
+ if (content.length > 0) prepared.push({ ...message, content });
233
+ } else {
234
+ prepared.push(message);
235
+ }
236
+ }
237
+
238
+ return prepared;
239
+ }
240
+
177
241
  /**
178
242
  * Extract keywords from a question for matching.
179
243
  * Strips common stop words and short words.
@@ -795,7 +859,7 @@ export default function sessionRecallExtension(pi: ExtensionAPI) {
795
859
  label: (params) => `Session Query: ${params.question}`,
796
860
  description:
797
861
  "Query a specific session file to get detailed information. Use after session_search to dig into a particular session, " +
798
- "or when you already have a session path (e.g., from a handoff). Sends the full conversation to an LLM for analysis.",
862
+ "or when you already have a session path (e.g., from a handoff). Sends the conversation and tool calls, without assistant thinking or tool output, to an LLM for analysis.",
799
863
  renderResult: (result, options, theme) => {
800
864
  const container = new Container();
801
865
  const text = result.content?.[0]?.type === "text" ? result.content[0].text : "";
@@ -897,7 +961,7 @@ export default function sessionRecallExtension(pi: ExtensionAPI) {
897
961
  }
898
962
 
899
963
  // Serialize and check if we need windowing
900
- const llmMessages = convertToLlm(messages);
964
+ const llmMessages = prepareRecallMessages(convertToLlm(messages));
901
965
  const fullText = serializeConversation(llmMessages);
902
966
  const fullTokens = estimateTokens(fullText);
903
967
 
@@ -945,7 +1009,7 @@ export default function sessionRecallExtension(pi: ExtensionAPI) {
945
1009
  timestamp: Date.now(),
946
1010
  };
947
1011
 
948
- const response = await complete(
1012
+ const response = await completeRecallRequest(
949
1013
  queryModel,
950
1014
  { systemPrompt: QUERY_SYSTEM_PROMPT, messages: [userMessage] },
951
1015
  { apiKey: auth.apiKey, headers: auth.headers, signal },
@@ -960,9 +1024,17 @@ export default function sessionRecallExtension(pi: ExtensionAPI) {
960
1024
  };
961
1025
  }
962
1026
 
963
- if (response.stopReason === "error" || response.content.length === 0) {
1027
+ if (response.stopReason === "error") {
1028
+ const providerError =
1029
+ response.errorMessage?.trim() || "The provider returned an error without details.";
1030
+ return errorResult(
1031
+ `Error querying ${queryModel.provider}/${queryModel.id}: ${providerError}`,
1032
+ );
1033
+ }
1034
+
1035
+ if (response.content.length === 0) {
964
1036
  return errorResult(
965
- `Error: LLM returned empty response (${response.stopReason}). Session may be too large even after windowing.`,
1037
+ `Error querying ${queryModel.provider}/${queryModel.id}: LLM returned an empty response.`,
966
1038
  );
967
1039
  }
968
1040