@vshulcz/pi-deja 0.19.4 → 0.19.5
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 +8 -2
- package/index.ts +80 -2
- package/lib.mjs +21 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @vshulcz/pi-deja
|
|
2
2
|
|
|
3
3
|
pi remembers its own sessions. This extension answers the other question: what
|
|
4
|
-
was done in the twenty-
|
|
4
|
+
was done in the twenty-four other coding agents on this machine — Claude Code,
|
|
5
5
|
Codex, Cursor, Gemini, OpenClaw and Hermes among them — including the months
|
|
6
6
|
before pi was installed.
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ The deja binary comes with the package; a deja you installed yourself
|
|
|
21
21
|
`deja install pi-auto` wires pi too, and is the shorter path if you have the
|
|
22
22
|
CLI: it adds deja's MCP server and writes an extension of its own. When that
|
|
23
23
|
extension is present this package stands down, so having both never injects
|
|
24
|
-
twice.
|
|
24
|
+
twice — and the same for omp, whose extension the installer writes elsewhere.
|
|
25
25
|
|
|
26
26
|
omp loads `pi.extensions` manifests and emits the same `before_agent_start`
|
|
27
27
|
event, so `pi install`'s omp counterpart picks this package up as well.
|
|
@@ -33,6 +33,12 @@ event, so `pi install`'s omp counterpart picks this package up as well.
|
|
|
33
33
|
- **Every prompt** (`before_agent_start`): the prompt is matched against the
|
|
34
34
|
index and, when a past session answers it, that session goes to the model
|
|
35
35
|
with the prompt. Silence is the common case.
|
|
36
|
+
- **After a failed command** (`tool_result`): the repair this machine used the
|
|
37
|
+
last time that error came up goes in beside the error, in the same turn.
|
|
38
|
+
- **Before an edit** (`tool_result` on a read): what earlier sessions decided
|
|
39
|
+
about the file the agent just opened.
|
|
40
|
+
- **After a compaction** (`session_compact`): the list of what this session was
|
|
41
|
+
already shown is cleared, so the recall it lost can come back.
|
|
36
42
|
- **`/deja <query>`**: search the history by hand.
|
|
37
43
|
|
|
38
44
|
## Which binary
|
package/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { createRequire } from "node:module";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
|
-
import { argv, contextText,
|
|
9
|
+
import { argv, contextText, installerExtensionPaths, sessionKey } from "./lib.mjs";
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
|
|
@@ -72,11 +72,16 @@ export default function (pi: any) {
|
|
|
72
72
|
// it already does everything below, and two copies would inject the
|
|
73
73
|
// recall twice and register /deja twice.
|
|
74
74
|
try {
|
|
75
|
-
if (
|
|
75
|
+
if (installerExtensionPaths(homedir()).some((p) => existsSync(p))) return;
|
|
76
76
|
} catch {}
|
|
77
77
|
|
|
78
78
|
let injected = false;
|
|
79
79
|
let toldBuilding = false;
|
|
80
|
+
// The tool and compaction events carry no session id of their own, and
|
|
81
|
+
// recall dedupes per session: without one it repeats itself and forgets
|
|
82
|
+
// nothing. The prompt handler passes through first and pi keeps the id on
|
|
83
|
+
// the session manager, so it is read there and held.
|
|
84
|
+
let session = "";
|
|
80
85
|
|
|
81
86
|
// pi keeps a footer status line: while the first index builds, that is
|
|
82
87
|
// where the user can see it happening instead of wondering why recall is
|
|
@@ -141,6 +146,7 @@ export default function (pi: any) {
|
|
|
141
146
|
return;
|
|
142
147
|
}
|
|
143
148
|
const key = sessionKey(event, ctx);
|
|
149
|
+
if (key) session = key;
|
|
144
150
|
const raw = run(["hook-prompt"], JSON.stringify({ prompt: event.prompt || "", session_id: key }));
|
|
145
151
|
if (!raw) return;
|
|
146
152
|
const resp = JSON.parse(raw);
|
|
@@ -152,4 +158,76 @@ export default function (pi: any) {
|
|
|
152
158
|
// memory is optional: never break the session over it
|
|
153
159
|
}
|
|
154
160
|
});
|
|
161
|
+
|
|
162
|
+
// The other half of the point of action, and the half this package was
|
|
163
|
+
// missing: a command has just failed, and this machine has fixed that same
|
|
164
|
+
// error before. pi hands tool_result the content the model is about to read
|
|
165
|
+
// and uses what the handler returns, so the repair goes in beside the error
|
|
166
|
+
// in the same turn. The installer's own extension has carried this since
|
|
167
|
+
// #3112/#3113; anyone who installed from npm had neither it nor the
|
|
168
|
+
// compaction handler below (#3180).
|
|
169
|
+
const repaired: Record<string, string> = {};
|
|
170
|
+
pi.on("tool_result", async (event: any, ctx: any) => {
|
|
171
|
+
try {
|
|
172
|
+
if (!event) return;
|
|
173
|
+
const key = sessionKey(event, ctx);
|
|
174
|
+
if (key) session = key;
|
|
175
|
+
// A file the agent just opened is the step before it changes that file,
|
|
176
|
+
// and pi has no handler that runs earlier whose return the model reads.
|
|
177
|
+
// So the file's own history goes out here: what was decided about it,
|
|
178
|
+
// from the sessions that decided it. deja answers once per session per
|
|
179
|
+
// fact, so re-reading the same file stays quiet.
|
|
180
|
+
if (event.toolName === "read") {
|
|
181
|
+
const path = String((event.input && (event.input.path || event.input.file_path)) || "");
|
|
182
|
+
if (!path) return;
|
|
183
|
+
const parts = Array.isArray(event.content) ? event.content : [];
|
|
184
|
+
const id = "read:" + String(event.toolCallId || "");
|
|
185
|
+
if (!(id in repaired)) {
|
|
186
|
+
repaired[id] = run(["hook-tool", "--plain"], JSON.stringify({
|
|
187
|
+
tool_name: "read",
|
|
188
|
+
tool_input: { file_path: path },
|
|
189
|
+
session_id: session,
|
|
190
|
+
cwd: process.cwd(),
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
const note = repaired[id];
|
|
194
|
+
if (!note) return;
|
|
195
|
+
return { content: parts.concat([{ type: "text", text: note }]) };
|
|
196
|
+
}
|
|
197
|
+
if (!event.isError) return;
|
|
198
|
+
if (event.toolName !== "bash") return;
|
|
199
|
+
const parts = Array.isArray(event.content) ? event.content : [];
|
|
200
|
+
const output = parts
|
|
201
|
+
.filter((p: any) => p && p.type === "text" && typeof p.text === "string")
|
|
202
|
+
.map((p: any) => p.text)
|
|
203
|
+
.join("\n");
|
|
204
|
+
if (!output.trim()) return;
|
|
205
|
+
const id = String(event.toolCallId || "");
|
|
206
|
+
if (!(id in repaired)) {
|
|
207
|
+
repaired[id] = run(["hook-tool-after", "--plain"], JSON.stringify({
|
|
208
|
+
tool_name: "bash",
|
|
209
|
+
tool_response: output,
|
|
210
|
+
session_id: session,
|
|
211
|
+
cwd: process.cwd(),
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
const line = repaired[id];
|
|
215
|
+
if (!line) return;
|
|
216
|
+
return { content: parts.concat([{ type: "text", text: line }]) };
|
|
217
|
+
} catch {
|
|
218
|
+
// never turn a failed command into a failed session
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Compaction throws away the blocks this session was shown, and the list
|
|
223
|
+
// that stops them repeating outlives them. session_compact fires once the
|
|
224
|
+
// summary has replaced the history, which is exactly when the session should
|
|
225
|
+
// be allowed to see those blocks again.
|
|
226
|
+
pi.on("session_compact", async (_event: any, ctx: any) => {
|
|
227
|
+
try {
|
|
228
|
+
const key = sessionKey(_event, ctx);
|
|
229
|
+
if (key) session = key;
|
|
230
|
+
run(["hook-precompact"], JSON.stringify({ session_id: session }));
|
|
231
|
+
} catch {}
|
|
232
|
+
});
|
|
155
233
|
}
|
package/lib.mjs
CHANGED
|
@@ -13,6 +13,17 @@ export function installerExtensionPath(home) {
|
|
|
13
13
|
return join(home || "", ".pi", "agent", "extensions", "deja.ts")
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// installerExtensionPaths are every place `deja install` writes an extension
|
|
17
|
+
// this package would duplicate. omp runs the same package and the installer
|
|
18
|
+
// writes it under ~/.omp/agent/extensions/deja/index.js, so checking pi's path
|
|
19
|
+
// alone left both running side by side (#3180).
|
|
20
|
+
export function installerExtensionPaths(home) {
|
|
21
|
+
return [
|
|
22
|
+
installerExtensionPath(home),
|
|
23
|
+
join(home || "", ".omp", "agent", "extensions", "deja", "index.js"),
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
// contextText pulls the recall out of whatever hook-context printed. deja
|
|
17
28
|
// answers in the Claude Code hook shape; older builds and `--plain` print
|
|
18
29
|
// bare text, so both are accepted.
|
|
@@ -40,8 +51,16 @@ export function argv(cmd, flags, text) {
|
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
// sessionKey is what per-prompt recall dedups on: a hit shown once in a
|
|
43
|
-
// session is not shown again. Without one it repeats itself every message
|
|
44
|
-
//
|
|
54
|
+
// session is not shown again. Without one it repeats itself every message —
|
|
55
|
+
// which it did, because pi keeps the id on ctx.sessionManager and nowhere the
|
|
56
|
+
// event or ctx.session shapes below look (#3179). The manager first, the same
|
|
57
|
+
// order as the extension `deja install pi-auto` writes; the rest for hosts
|
|
58
|
+
// that put an id on the event.
|
|
45
59
|
export function sessionKey(event, ctx) {
|
|
60
|
+
try {
|
|
61
|
+
const m = ctx && ctx.sessionManager
|
|
62
|
+
const id = m && (typeof m.getSessionId === "function" ? m.getSessionId() : m.sessionId)
|
|
63
|
+
if (id) return String(id)
|
|
64
|
+
} catch {}
|
|
46
65
|
return (event && (event.sessionId || event.session_id)) || (ctx && ctx.session && ctx.session.id) || ""
|
|
47
66
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vshulcz/pi-deja",
|
|
3
|
-
"version": "0.19.
|
|
4
|
-
"description": "deja-vu memory for pi: the coding sessions already on this machine — pi, Claude Code, Codex, Cursor and
|
|
3
|
+
"version": "0.19.5",
|
|
4
|
+
"description": "deja-vu memory for pi: the coding sessions already on this machine — pi, Claude Code, Codex, Cursor and 21 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",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=18"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@vshulcz/deja-vu": "^0.19.
|
|
36
|
+
"@vshulcz/deja-vu": "^0.19.5"
|
|
37
37
|
},
|
|
38
38
|
"pi": {
|
|
39
39
|
"extensions": [
|