dsh-deja 0.22.2 → 0.23.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.
- package/README.md +1 -1
- package/docs/zh.md +1 -1
- package/index.js +30 -19
- package/lib.js +41 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ DeepSeek Harness can already search its own sessions — that is what the built-
|
|
|
8
8
|
`session-query` subsystem does. This plugin answers the other question: what you
|
|
9
9
|
did in the *other* agents on this machine.
|
|
10
10
|
|
|
11
|
-
deja indexes the session files that forty-
|
|
11
|
+
deja indexes the session files that forty-two other coding agents — Claude Code,
|
|
12
12
|
Codex, Cursor, opencode, Antigravity, Grok Build, Kimi, Cline and Zed among
|
|
13
13
|
them — already write to disk. Nothing has to have been recorded ahead of time: the history is already
|
|
14
14
|
there, including the months before deja was installed. If you moved to dsh last
|
package/docs/zh.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
DeepSeek Harness 自带的 `session-query` 已经可以检索它自己的会话。这个插件回答的是另一个问题:你在这台机器上**其他**智能体里做过什么。
|
|
8
8
|
|
|
9
|
-
deja 索引 Claude Code、Codex、Cursor、opencode、Antigravity、Grok Build、Kimi、Cline、Zed
|
|
9
|
+
deja 索引 Claude Code、Codex、Cursor、opencode、Antigravity、Grok Build、Kimi、Cline、Zed 等其他四十二个智能体本来就写在磁盘上的会话文件。不需要提前录制:历史已经在那里,包括安装 deja 之前的那些月份。如果你上周才转到 dsh,上周之前做过的事仍然可以在 dsh 里查到。
|
|
10
10
|
|
|
11
11
|
## 安装
|
|
12
12
|
|
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// dsh answers questions about its own sessions through the built-in
|
|
4
4
|
// session-query subsystem. This plugin answers the other question: what you did
|
|
5
|
-
// in Claude Code, Codex, Cursor, opencode and thirty-
|
|
5
|
+
// in Claude Code, Codex, Cursor, opencode and thirty-nine more agents, on this
|
|
6
6
|
// machine, before dsh existed. The index is deja's; this file is the seam.
|
|
7
7
|
|
|
8
8
|
import { createRequire } from "node:module";
|
|
@@ -12,7 +12,7 @@ import { existsSync } from "node:fs";
|
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
|
|
15
|
-
import { argv, contributions, exitStatus, guarded, newestEvents, noteBoard, resultText, splitHookReply } from "./lib.js";
|
|
15
|
+
import { argv, contributions, recallArgv, recallSilence, exitStatus, guarded, inert, newestEvents, noteBoard, resultText, splitHookReply } from "./lib.js";
|
|
16
16
|
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
18
|
|
|
@@ -60,9 +60,10 @@ const DEJA = resolveDeja();
|
|
|
60
60
|
|
|
61
61
|
// run returns stdout, or "" when deja is missing or says nothing. Memory is
|
|
62
62
|
// optional everywhere: a throw here would end the turn.
|
|
63
|
-
function run(args, input) {
|
|
63
|
+
function run(args, input, cwd) {
|
|
64
64
|
try {
|
|
65
65
|
return execFileSync(DEJA, args, {
|
|
66
|
+
cwd,
|
|
66
67
|
encoding: "utf8",
|
|
67
68
|
timeout: 20000,
|
|
68
69
|
maxBuffer: 8 * 1024 * 1024,
|
|
@@ -95,8 +96,8 @@ function tools(ctx) {
|
|
|
95
96
|
if (!defineTool) return;
|
|
96
97
|
|
|
97
98
|
// Every tool here answers with the text deja printed, so one output
|
|
98
|
-
// declaration serves them all. Printed with --framed:
|
|
99
|
-
//
|
|
99
|
+
// declaration serves them all. Printed with --framed: ctx, blame, fix and
|
|
100
|
+
// how write for a person (recall frames its own page), and without the frame a tag planted in a
|
|
100
101
|
// transcript reached the model as though deja had closed its own block. The schema is plain JSON Schema — a
|
|
101
102
|
// schemastery instance is rejected as "schema must be a value schema object",
|
|
102
103
|
// because the host validates that this is an ordinary JSON record.
|
|
@@ -119,14 +120,19 @@ function tools(ctx) {
|
|
|
119
120
|
type: "number",
|
|
120
121
|
description: "How many sessions to return. Default 5.",
|
|
121
122
|
},
|
|
123
|
+
offset: {
|
|
124
|
+
type: "number",
|
|
125
|
+
description: "Sessions to skip, from the offset the previous page named. Default 0.",
|
|
126
|
+
},
|
|
122
127
|
},
|
|
123
128
|
output: TEXT_OUTPUT,
|
|
124
|
-
execute(args) {
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
129
|
+
execute(args, exec) {
|
|
130
|
+
// The page the MCP recall tool serves, framed and budgeted by deja. It
|
|
131
|
+
// puts the project of its working directory first, so it runs in the
|
|
132
|
+
// calling session's workspace rather than wherever dsh was launched.
|
|
133
|
+
const call = recallArgv(args);
|
|
134
|
+
const out = run(call, undefined, sessionCwd(exec && exec.agent));
|
|
135
|
+
return Promise.resolve(answer(out || (INSTALLED ? recallSilence(call) : "")));
|
|
130
136
|
},
|
|
131
137
|
})));
|
|
132
138
|
|
|
@@ -282,19 +288,20 @@ function autoDigest(ctx, board) {
|
|
|
282
288
|
ctx.systemPrompt.context({
|
|
283
289
|
name: "deja:project",
|
|
284
290
|
order: 110,
|
|
291
|
+
interpolate: false,
|
|
285
292
|
text: (assembly) => {
|
|
286
293
|
const agent = assembly && assembly.agent;
|
|
287
294
|
if (!agent) return "";
|
|
288
295
|
const sid = sessionId(agent);
|
|
289
296
|
if (sid && seen.has(sid)) return "";
|
|
290
297
|
if (sid) seen.add(sid);
|
|
291
|
-
return askHook(
|
|
298
|
+
return inert(askHook(
|
|
292
299
|
["hook-context", "--plain"],
|
|
293
300
|
["hook-context"],
|
|
294
301
|
JSON.stringify({ session_id: sid, cwd: sessionCwd(agent), source: "startup", deja_once: true }),
|
|
295
302
|
sid,
|
|
296
303
|
board,
|
|
297
|
-
);
|
|
304
|
+
));
|
|
298
305
|
},
|
|
299
306
|
}),
|
|
300
307
|
);
|
|
@@ -344,6 +351,7 @@ function autoRecall(ctx, board) {
|
|
|
344
351
|
ctx.systemPrompt.context({
|
|
345
352
|
name: "deja:recall",
|
|
346
353
|
order: 120,
|
|
354
|
+
interpolate: false,
|
|
347
355
|
text: (assembly) => {
|
|
348
356
|
const agent = assembly && assembly.agent;
|
|
349
357
|
if (!agent) return "";
|
|
@@ -360,13 +368,13 @@ function autoRecall(ctx, board) {
|
|
|
360
368
|
asked = prompt;
|
|
361
369
|
askedIn = cwd;
|
|
362
370
|
askedBy = sid;
|
|
363
|
-
recalled = askHook(
|
|
371
|
+
recalled = inert(askHook(
|
|
364
372
|
["hook-prompt", "--plain"],
|
|
365
373
|
["hook-prompt"],
|
|
366
374
|
JSON.stringify({ prompt, cwd, session_id: sid }),
|
|
367
375
|
sid,
|
|
368
376
|
board,
|
|
369
|
-
);
|
|
377
|
+
));
|
|
370
378
|
}
|
|
371
379
|
// Silence is the common case: this speaks only when the history answers.
|
|
372
380
|
return recalled;
|
|
@@ -505,20 +513,23 @@ function toolNotes(ctx) {
|
|
|
505
513
|
// be answered with it now rather than twenty minutes from now.
|
|
506
514
|
function sessionEnds(ctx) {
|
|
507
515
|
if (typeof ctx.on !== "function") return;
|
|
508
|
-
|
|
516
|
+
// Each live session with the directory it opened in: the end refreshes that
|
|
517
|
+
// project's start digest, so the next session there sees this one.
|
|
518
|
+
const live = new Map();
|
|
509
519
|
const end = (sid) => {
|
|
510
520
|
if (!sid || !live.has(sid)) return;
|
|
521
|
+
const cwd = live.get(sid);
|
|
511
522
|
live.delete(sid);
|
|
512
|
-
run(["hook-session-end"], JSON.stringify({ session_id: sid }));
|
|
523
|
+
run(["hook-session-end"], JSON.stringify({ session_id: sid, cwd }));
|
|
513
524
|
};
|
|
514
525
|
try {
|
|
515
526
|
ctx.on("session/created", (session) => {
|
|
516
527
|
const sid = bareSessionId(session && session.id);
|
|
517
|
-
if (sid) live.
|
|
528
|
+
if (sid) live.set(sid, (session.header && session.header.cwd) || process.cwd());
|
|
518
529
|
});
|
|
519
530
|
ctx.on("session/disposed", (session) => end(bareSessionId(session && session.id)));
|
|
520
531
|
process.on("exit", () => {
|
|
521
|
-
for (const sid of [...live]) end(sid);
|
|
532
|
+
for (const sid of [...live.keys()]) end(sid);
|
|
522
533
|
});
|
|
523
534
|
} catch {}
|
|
524
535
|
}
|
package/lib.js
CHANGED
|
@@ -21,6 +21,35 @@ export function argv(cmd, flags, text) {
|
|
|
21
21
|
return arg.startsWith("-") ? [cmd, ...flags, "--", arg] : [cmd, ...flags, arg];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// recallArgv is the call behind the deja_recall tool: `deja recall`, the page
|
|
25
|
+
// the MCP recall tool serves, framed and held to its 4 KB budget by deja
|
|
26
|
+
// itself. The tool used to run `search --json`, which carries every hit's
|
|
27
|
+
// matched messages whole and handed a model 661,528 characters for five hits
|
|
28
|
+
// (#5568). The page ends with "call recall again with offset=N"; offset is
|
|
29
|
+
// how the tool asks for that, and it is sent only past the first page, so a
|
|
30
|
+
// deja too old to know --offset still answers the first one.
|
|
31
|
+
export function recallArgv(args) {
|
|
32
|
+
const count = (v, fallback) => {
|
|
33
|
+
const n = Math.trunc(Number(v));
|
|
34
|
+
return v == null || v === "" || !Number.isFinite(n) ? fallback : n;
|
|
35
|
+
};
|
|
36
|
+
const limit = Math.min(20, Math.max(1, count(args && args.limit, 5)));
|
|
37
|
+
const offset = Math.min(1e9, Math.max(0, count(args && args.offset, 0)));
|
|
38
|
+
const flags = ["--limit", String(limit)];
|
|
39
|
+
if (offset > 0) flags.push("--offset", String(offset));
|
|
40
|
+
return argv("recall", flags, args && args.query);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// recallSilence is the answer when `deja recall` printed nothing. Past the
|
|
44
|
+
// first page that is a deja older than --offset refusing the flag, not an
|
|
45
|
+
// empty history: recall always prints a line, "No more matches" included.
|
|
46
|
+
export function recallSilence(call) {
|
|
47
|
+
if (call.includes("--offset")) {
|
|
48
|
+
return "This deja cannot page recall. Run deja update, then ask again with the same offset.";
|
|
49
|
+
}
|
|
50
|
+
return "Nothing in this machine's history matches that.";
|
|
51
|
+
}
|
|
52
|
+
|
|
24
53
|
// guarded runs one registration, and swallows the host's refusal of a name it
|
|
25
54
|
// already holds.
|
|
26
55
|
//
|
|
@@ -62,6 +91,18 @@ export function exitStatus(text) {
|
|
|
62
91
|
return { body: text, failed: false };
|
|
63
92
|
}
|
|
64
93
|
|
|
94
|
+
// inert keeps recalled text out of dsh's prompt templating. dsh reads every
|
|
95
|
+
// {{name}} in a context as a prompt variable and throws on one it does not
|
|
96
|
+
// know, which ends the turn before the model is called. Past sessions quote
|
|
97
|
+
// templates, and that very error, so one recall carrying {{cwd}} broke every
|
|
98
|
+
// turn after it (#5421). `interpolate: false` tells dsh 0.2.1-alpha.2 and later
|
|
99
|
+
// to leave the text alone; older hosts ignore it, and a listener on
|
|
100
|
+
// system-prompt/assemble may drop it, so the braces are split as well:
|
|
101
|
+
// { {cwd} } still reads as braces and holds no {{ to resolve.
|
|
102
|
+
export function inert(text) {
|
|
103
|
+
return String(text || "").replace(/\{(?=\{)/g, "{ ").replace(/\}(?=\})/g, "} ");
|
|
104
|
+
}
|
|
105
|
+
|
|
65
106
|
// contributions says what this package adds, given what `deja install` already
|
|
66
107
|
// wrote into DSH_HOME and what the profile turned off. The rule is the same
|
|
67
108
|
// everywhere: fill the gaps, never repeat the installer.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-deja",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "deja-vu memory for DeepSeek Harness: the session history of forty-
|
|
3
|
+
"version": "0.23.1",
|
|
4
|
+
"description": "deja-vu memory for DeepSeek Harness: the session history of forty-two other coding agents, searchable and recalled before each step — a local index, no LLM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@vshulcz/deja-vu": "^0.
|
|
71
|
+
"@vshulcz/deja-vu": "^0.23.1"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"test": "node --test test/*.test.js"
|