@vshulcz/pi-deja 0.21.7 → 0.22.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.
Files changed (3) hide show
  1. package/README.md +12 -7
  2. package/index.ts +31 -15
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  English | [中文](https://github.com/vshulcz/deja-vu/blob/main/extensions/pi/docs/zh.md)
4
4
 
5
5
  pi remembers its own sessions. This extension answers the other question: what
6
- was done in the thirty-seven other coding agents on this machine — Claude Code,
6
+ was done in the forty other coding agents on this machine — Claude Code,
7
7
  Codex, Cursor, Gemini, OpenClaw and Hermes among them — including the months
8
8
  before pi was installed.
9
9
 
10
10
  It runs [deja](https://github.com/vshulcz/deja-vu), a local Go binary that
11
11
  indexes the transcripts those agents already wrote to disk. No LLM, no
12
- embeddings, no network path unless you ask for one.
12
+ embeddings, no network path besides a daily release check, which DEJA_OFFLINE=1 turns off.
13
13
 
14
14
  ## Install
15
15
 
@@ -36,12 +36,16 @@ event, so `pi install`'s omp counterpart picks this package up as well.
36
36
  - **Every prompt** (`before_agent_start`): the prompt is matched against the
37
37
  index and, when a past session answers it, that session goes to the model
38
38
  with the prompt. Silence is the common case.
39
- - **After a failed command** (`tool_result`): the repair this machine used the
39
+ - **After a failed command** (`tool_result` on `bash` or `powershell`): the repair this machine used the
40
40
  last time that error came up goes in beside the error, in the same turn.
41
- - **Before an edit** (`tool_result` on a read): what earlier sessions decided
42
- about the file the agent just opened.
41
+ - **Before an edit** (`tool_result` on a read, edit or write): what earlier
42
+ sessions decided about the file the agent just opened.
43
43
  - **After a compaction** (`session_compact`): the list of what this session was
44
- already shown is cleared, so the recall it lost can come back.
44
+ already shown is cleared, so the recall it lost can come back, and deja reads
45
+ the turns the summary replaced so the next prompt carries what the agent was
46
+ in the middle of.
47
+ - **When a session ends** (`session_shutdown`): its live stamp goes, so the next
48
+ session's recall can answer with it.
45
49
  - **`/deja <query>`**: search the history by hand.
46
50
 
47
51
  ## Which binary
@@ -53,6 +57,7 @@ the copy bundled through `@vshulcz/deja-vu`.
53
57
 
54
58
  deja reads session files where the agents wrote them and builds a local index
55
59
  under `~/.cache/deja` (or `DEJA_INDEX_DIR`). Keys and tokens are stripped as
56
- the index is built, and indexing and search make no network calls.
60
+ the index is built, and without semantic search (`deja embed`) indexing and
61
+ search make no network calls.
57
62
 
58
63
  Part of [deja-vu](https://github.com/vshulcz/deja-vu). MIT. Found it useful? [Star deja-vu on GitHub](https://github.com/vshulcz/deja-vu).
package/index.ts CHANGED
@@ -75,7 +75,10 @@ export default function (pi: any) {
75
75
  if (installerExtensionPaths(homedir()).some((p) => existsSync(p))) return;
76
76
  } catch {}
77
77
 
78
- let injected = false;
78
+ // Once per session, not once per process: /new, a resume and a fork keep the
79
+ // process and switch the session, and a flag set on the first one left every
80
+ // later session with no digest.
81
+ const injected = new Set<string>();
79
82
  let toldBuilding = false;
80
83
  // The tool and compaction events carry no session id of their own, and
81
84
  // recall dedupes per session: without one it repeats itself and forgets
@@ -123,17 +126,17 @@ export default function (pi: any) {
123
126
 
124
127
  pi.on("before_agent_start", async (event: any, ctx: any) => {
125
128
  try {
126
- if (!injected) {
129
+ const key = sessionKey(event, ctx);
130
+ if (key) session = key;
131
+ if (!injected.has(session)) {
127
132
  // The session goes with it: hook-context marks the one starting as
128
133
  // live, which keeps it out of its own MCP recall on this first turn
129
134
  // (#4394, as #4246 and #4273 did for Hermes and opencode).
130
- const key = sessionKey(event, ctx);
131
- if (key) session = key;
132
135
  const { context: digest, receipt } = contextText(
133
- run(["hook-context"], JSON.stringify({ session_id: key, cwd: process.cwd() })),
136
+ run(["hook-context"], JSON.stringify({ session_id: session, cwd: process.cwd() })),
134
137
  );
135
138
  if (digest) {
136
- injected = true;
139
+ injected.add(session);
137
140
  ctx.ui.setStatus("deja", "");
138
141
  // The receipt is what tells the user memory arrived; without it the
139
142
  // recall is invisible and reads as the model guessing.
@@ -152,9 +155,7 @@ export default function (pi: any) {
152
155
  }
153
156
  return;
154
157
  }
155
- const key = sessionKey(event, ctx);
156
- if (key) session = key;
157
- const raw = run(["hook-prompt"], JSON.stringify({ prompt: event.prompt || "", session_id: key }));
158
+ const raw = run(["hook-prompt"], JSON.stringify({ prompt: event.prompt || "", session_id: session }));
158
159
  if (!raw) return;
159
160
  const resp = JSON.parse(raw);
160
161
  if (resp && resp.systemMessage) ctx.ui.notify(resp.systemMessage, "info");
@@ -183,15 +184,16 @@ export default function (pi: any) {
183
184
  // and pi has no handler that runs earlier whose return the model reads.
184
185
  // So the file's own history goes out here: what was decided about it,
185
186
  // from the sessions that decided it. deja answers once per session per
186
- // fact, so re-reading the same file stays quiet.
187
- if (event.toolName === "read") {
187
+ // fact, so re-reading the same file stays quiet. An edit or a write
188
+ // without a read before it gets the same line, for the edits after it.
189
+ if (event.toolName === "read" || event.toolName === "edit" || event.toolName === "write") {
188
190
  const path = String((event.input && (event.input.path || event.input.file_path)) || "");
189
191
  if (!path) return;
190
192
  const parts = Array.isArray(event.content) ? event.content : [];
191
- const id = "read:" + String(event.toolCallId || "");
193
+ const id = "file:" + String(event.toolCallId || "");
192
194
  if (!(id in repaired)) {
193
195
  repaired[id] = run(["hook-tool", "--plain"], JSON.stringify({
194
- tool_name: "read",
196
+ tool_name: event.toolName,
195
197
  tool_input: { file_path: path },
196
198
  session_id: session,
197
199
  cwd: process.cwd(),
@@ -230,12 +232,26 @@ export default function (pi: any) {
230
232
  // Compaction throws away the blocks this session was shown, and the list
231
233
  // that stops them repeating outlives them. session_compact fires once the
232
234
  // summary has replaced the history, which is exactly when the session should
233
- // be allowed to see those blocks again.
235
+ // be allowed to see those blocks again. The session file keeps the turns the
236
+ // summary replaced, so deja reads them from it and the next prompt carries
237
+ // what the agent was in the middle of.
234
238
  pi.on("session_compact", async (_event: any, ctx: any) => {
235
239
  try {
236
240
  const key = sessionKey(_event, ctx);
237
241
  if (key) session = key;
238
- run(["hook-precompact"], JSON.stringify({ session_id: session }));
242
+ const m = ctx && ctx.sessionManager;
243
+ const file = m && m.getSessionFile ? String(m.getSessionFile() || "") : "";
244
+ run(["hook-precompact"], JSON.stringify({ session_id: session, transcript_path: file, cwd: process.cwd() }));
245
+ } catch {}
246
+ });
247
+
248
+ // The session is over: quit, /new, /resume, /fork or a reload. Its live
249
+ // stamp goes with it, so the next session's MCP recall can answer with it
250
+ // now rather than twenty minutes from now. The id is the one held so far;
251
+ // on a switch ctx may already name the next session.
252
+ pi.on("session_shutdown", async (_event: any) => {
253
+ try {
254
+ if (session) run(["hook-session-end"], JSON.stringify({ session_id: session }), 2000);
239
255
  } catch {}
240
256
  });
241
257
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vshulcz/pi-deja",
3
- "version": "0.21.7",
4
- "description": "deja-vu memory for pi: the coding sessions already on this machine — pi, Claude Code, Codex, Cursor and 34 more — recalled at session start and on every prompt, searchable with /deja. A local index, no LLM.",
3
+ "version": "0.22.1",
4
+ "description": "deja-vu memory for pi: the coding sessions already on this machine — pi, Claude Code, Codex, Cursor and 37 more — recalled at session start and on every prompt, searchable with /deja. A local index, no LLM.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "index.ts",
@@ -45,7 +45,7 @@
45
45
  "node": ">=18"
46
46
  },
47
47
  "dependencies": {
48
- "@vshulcz/deja-vu": "^0.21.7"
48
+ "@vshulcz/deja-vu": "^0.22.1"
49
49
  },
50
50
  "pi": {
51
51
  "extensions": [