dsh-deja 0.20.4 → 0.20.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 CHANGED
@@ -1,14 +1,14 @@
1
1
  # dsh-deja
2
2
 
3
- English | [中文](docs/zh.md)
3
+ English | [中文](https://github.com/vshulcz/deja-vu/blob/main/extensions/dsh/docs/zh.md)
4
4
 
5
5
  DeepSeek Harness can already search its own sessions — that is what the built-in
6
6
  `session-query` subsystem does. This plugin answers the other question: what you
7
7
  did in the *other* agents on this machine.
8
8
 
9
- deja indexes the session files that Claude Code, Codex, Cursor, opencode,
10
- Antigravity, Grok Build, Kimi, Cline, Zed and ten more agents already write to
11
- disk. Nothing has to have been recorded ahead of time: the history is already
9
+ deja indexes the session files that twenty-one other coding agents — Claude Code,
10
+ Codex, Cursor, opencode, Antigravity, Grok Build, Kimi, Cline and Zed among
11
+ them — already write to disk. Nothing has to have been recorded ahead of time: the history is already
12
12
  there, including the months before deja was installed. If you moved to dsh last
13
13
  week, everything you did before last week is still reachable from inside it.
14
14
 
@@ -18,6 +18,13 @@ week, everything you did before last week is still reachable from inside it.
18
18
  dsh plugin --profile web add dsh-deja
19
19
  ```
20
20
 
21
+ From the repository instead of npm — the root manifest re-exports this
22
+ directory, so either form works:
23
+
24
+ ```sh
25
+ dsh plugin --profile web add github:vshulcz/deja-vu#path:extensions/dsh
26
+ ```
27
+
21
28
  The plugin runs the `deja` binary. It is pulled in as a dependency, so npm
22
29
  installs it with the plugin; a `deja` already on `PATH` is used as-is, and
23
30
  `DEJA_BIN` overrides both.
package/docs/zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  DeepSeek Harness 自带的 `session-query` 已经可以检索它自己的会话。这个插件回答的是另一个问题:你在这台机器上**其他**智能体里做过什么。
6
6
 
7
- deja 索引 Claude Code、Codex、Cursor、opencode、Antigravity、Grok Build、Kimi、Cline、Zed 等二十个智能体本来就写在磁盘上的会话文件。不需要提前录制:历史已经在那里,包括安装 deja 之前的那些月份。如果你上周才转到 dsh,上周之前做过的事仍然可以在 dsh 里查到。
7
+ deja 索引 Claude Code、Codex、Cursor、opencode、Antigravity、Grok Build、Kimi、Cline、Zed 等二十一个智能体本来就写在磁盘上的会话文件。不需要提前录制:历史已经在那里,包括安装 deja 之前的那些月份。如果你上周才转到 dsh,上周之前做过的事仍然可以在 dsh 里查到。
8
8
 
9
9
  ## 安装
10
10
 
package/index.js CHANGED
@@ -11,7 +11,7 @@ import { existsSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { join } from "node:path";
13
13
 
14
- import { contributions } from "./lib.js";
14
+ import { argv, contributions, guarded } from "./lib.js";
15
15
 
16
16
  const require = createRequire(import.meta.url);
17
17
 
@@ -89,6 +89,7 @@ function answer(text) {
89
89
  return MISSING;
90
90
  }
91
91
 
92
+
92
93
  function tools(ctx) {
93
94
  if (!defineTool) return;
94
95
 
@@ -101,7 +102,7 @@ function tools(ctx) {
101
102
  render: (_args, value) => [{ type: "text", text: String(value) }],
102
103
  };
103
104
 
104
- ctx.tools.register(defineTool({
105
+ guarded(() => ctx.tools.register(defineTool({
105
106
  name: "deja_recall",
106
107
  description:
107
108
  "Search this machine's own past AI coding sessions — every agent used on it, including months before deja was installed. Use before debugging an error or re-implementing anything that may already exist. Match on the most specific token available: an exact error string, function name, file path or flag.",
@@ -122,11 +123,11 @@ function tools(ctx) {
122
123
  // spend the model's context on a tail nobody reads.
123
124
  const asked = Number.isFinite(args.limit) ? Math.trunc(args.limit) : 5;
124
125
  const limit = String(Math.min(20, Math.max(1, asked)));
125
- return Promise.resolve(answer(run(["search", "--json", "--limit", limit, String(args.query)])));
126
+ return Promise.resolve(answer(run(argv("search", ["--json", "--limit", limit], args.query))));
126
127
  },
127
- }));
128
+ })));
128
129
 
129
- ctx.tools.register(defineTool({
130
+ guarded(() => ctx.tools.register(defineTool({
130
131
  name: "deja_session",
131
132
  description:
132
133
  "A full digest of the single best-matching past session — what was tried, what was decided, what it cost. Use after deja_recall when the reasoning behind an earlier decision matters, not just that it happened.",
@@ -139,11 +140,11 @@ function tools(ctx) {
139
140
  },
140
141
  output: TEXT_OUTPUT,
141
142
  execute(args) {
142
- return Promise.resolve(answer(run(["ctx", String(args.query)])));
143
+ return Promise.resolve(answer(run(argv("ctx", [], args.query))));
143
144
  },
144
- }));
145
+ })));
145
146
 
146
- ctx.tools.register(defineTool({
147
+ guarded(() => ctx.tools.register(defineTool({
147
148
  name: "deja_blame",
148
149
  description:
149
150
  "The past sessions that discussed a file, so you know why it is shaped the way it is before editing, refactoring or deleting it. Session history, not git authorship.",
@@ -156,11 +157,11 @@ function tools(ctx) {
156
157
  },
157
158
  output: TEXT_OUTPUT,
158
159
  execute(args) {
159
- return Promise.resolve(answer(run(["blame", String(args.path), "--json"])));
160
+ return Promise.resolve(answer(run(argv("blame", ["--json"], args.path))));
160
161
  },
161
- }));
162
+ })));
162
163
 
163
- ctx.tools.register(defineTool({
164
+ guarded(() => ctx.tools.register(defineTool({
164
165
  name: "deja_fix",
165
166
  description:
166
167
  "What this machine ran after that same error before, in the sessions where the error did not come back. Paste the failing output verbatim rather than a paraphrase — the match is on the error's own words.",
@@ -173,11 +174,11 @@ function tools(ctx) {
173
174
  },
174
175
  output: TEXT_OUTPUT,
175
176
  execute(args) {
176
- return Promise.resolve(answer(run(["fix", String(args.error)])));
177
+ return Promise.resolve(answer(run(argv("fix", [], args.error))));
177
178
  },
178
- }));
179
+ })));
179
180
 
180
- ctx.tools.register(defineTool({
181
+ guarded(() => ctx.tools.register(defineTool({
181
182
  name: "deja_how",
182
183
  description:
183
184
  "The real invocation this machine uses for a build, test, deploy or script, with the flags it actually ran, ordered by how many sessions ran it. A guessed command is plausible and fails on this setup.",
@@ -190,11 +191,11 @@ function tools(ctx) {
190
191
  },
191
192
  output: TEXT_OUTPUT,
192
193
  execute(args) {
193
- return Promise.resolve(answer(run(["how", String(args.what)])));
194
+ return Promise.resolve(answer(run(argv("how", [], args.what))));
194
195
  },
195
- }));
196
+ })));
196
197
 
197
- ctx.tools.register(defineTool({
198
+ guarded(() => ctx.tools.register(defineTool({
198
199
  name: "deja_remember",
199
200
  description:
200
201
  "Store one durable decision once it is settled, as a single self-contained fact that will make sense months later. Not transcripts, not a summary of the conversation, and not anything already obvious from the code.",
@@ -207,15 +208,15 @@ function tools(ctx) {
207
208
  },
208
209
  output: TEXT_OUTPUT,
209
210
  execute(args) {
210
- const written = run(["remember", String(args.text)]);
211
+ const written = run(argv("remember", [], args.text));
211
212
  if (!INSTALLED) return Promise.resolve(MISSING);
212
213
  return Promise.resolve(written || "deja did not record that.");
213
214
  },
214
- }));
215
+ })));
215
216
  }
216
217
 
217
218
  function command(ctx) {
218
- ctx.commands.register({
219
+ guarded(() => ctx.commands.register({
219
220
  name: "deja",
220
221
  description: "Search this machine's past AI coding sessions",
221
222
  input: { hint: "what to look for" },
@@ -224,10 +225,14 @@ function command(ctx) {
224
225
  if (!query) {
225
226
  return { kind: "error", text: "Say what to look for: /deja <error, file, or decision>" };
226
227
  }
227
- const out = run([query]);
228
+ // Named, not handed over as deja's first word: the bare-query path
229
+ // dispatches a word that happens to be a command, so `/deja version`
230
+ // printed a version number and `/deja index` rebuilt the index, and one
231
+ // of the words people most want history about is `install`.
232
+ const out = run(argv("search", [], query));
228
233
  return { kind: INSTALLED ? "success" : "error", text: answer(out) };
229
234
  },
230
- });
235
+ }));
231
236
  }
232
237
 
233
238
  function userText(message) {
@@ -245,26 +250,36 @@ function userText(message) {
245
250
  // alternative — splicing a message into the "agent/pre-step" waterfall — looks
246
251
  // like it works and does not: a later listener rebuilds its answer from the
247
252
  // payload, and the added message is dropped with nothing reported.
253
+ //
254
+ // The registration is guarded because the host throws "prompt context
255
+ // deja:recall is already registered" on a second copy in the same profile, and
256
+ // that failure is not local: the whole profile fails to load, so a duplicate
257
+ // memory plugin costs the user their agent. One registration is all recall
258
+ // needs. installedByCLI() below is the first line of defence; this is the one
259
+ // that holds when the installer wrote to a different DSH_HOME than the profile
260
+ // boots from.
248
261
  function autoRecall(ctx) {
249
262
  let asked = "";
250
263
  let recalled = "";
251
264
 
252
- ctx.systemPrompt.context({
253
- name: "deja:recall",
254
- order: 120,
255
- text: (assembly) => {
256
- const agent = assembly && assembly.agent;
257
- if (!agent) return "";
258
- const prompt = lastHumanText(agent);
259
- if (!prompt) return "";
260
- if (prompt !== asked) {
261
- asked = prompt;
262
- recalled = run(["hook-prompt", "--plain"], JSON.stringify({ prompt, cwd: process.cwd() }));
263
- }
264
- // Silence is the common case: this speaks only when the history answers.
265
- return recalled;
266
- },
267
- });
265
+ guarded(() =>
266
+ ctx.systemPrompt.context({
267
+ name: "deja:recall",
268
+ order: 120,
269
+ text: (assembly) => {
270
+ const agent = assembly && assembly.agent;
271
+ if (!agent) return "";
272
+ const prompt = lastHumanText(agent);
273
+ if (!prompt) return "";
274
+ if (prompt !== asked) {
275
+ asked = prompt;
276
+ recalled = run(["hook-prompt", "--plain"], JSON.stringify({ prompt, cwd: process.cwd() }));
277
+ }
278
+ // Silence is the common case: this speaks only when the history answers.
279
+ return recalled;
280
+ },
281
+ }),
282
+ );
268
283
  }
269
284
 
270
285
  // lastHumanText is the newest thing the person actually typed. At assembly
package/lib.js CHANGED
@@ -2,6 +2,43 @@
2
2
  // anything a plugin module exports as part of the plugin — opencode does
3
3
  // exactly that with its own — and this is a helper, not a second plugin.
4
4
 
5
+ // argv builds the call for a query somebody typed, rather than handing the text
6
+ // to deja as it stands.
7
+ //
8
+ // The command is always named. deja's bare-query path dispatches a first word
9
+ // that happens to be a command, so a one-word query went to the command of that
10
+ // name: `version` printed a version number, `index` rebuilt the index, and
11
+ // neither searched anything.
12
+ //
13
+ // A query that starts with a dash is read as a flag: `deja search --no-verify`
14
+ // exits on an unknown flag, the caller turns that into "", and the model is
15
+ // told the history holds nothing about --no-verify while eleven sessions
16
+ // discuss it — a confident wrong answer, which is worse than an error. `--`
17
+ // ends the flags. It is sent only when the query needs it, so a deja too old to
18
+ // know the terminator on this subcommand still answers every ordinary query.
19
+ export function argv(cmd, flags, text) {
20
+ const arg = String(text);
21
+ return arg.startsWith("-") ? [cmd, ...flags, "--", arg] : [cmd, ...flags, arg];
22
+ }
23
+
24
+ // guarded runs one registration, and swallows the host's refusal of a name it
25
+ // already holds.
26
+ //
27
+ // dsh answers a duplicate with "prompt context deja:recall is already
28
+ // registered" or "command deja is already registered", and that failure is not
29
+ // local: the whole plugin tree fails to load, so a second copy of this plugin —
30
+ // the npm package next to the installer's files, a --patch overlay naming it
31
+ // twice — costs the user their agent over an optional memory plugin. One
32
+ // registration is all any of these need, so the loser stands down.
33
+ export function guarded(register) {
34
+ try {
35
+ register();
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
5
42
  // contributions says what this package adds, given what `deja install` already
6
43
  // wrote into DSH_HOME and what the profile turned off. The rule is the same
7
44
  // 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.20.4",
4
- "description": "Brings the session history of nineteen other coding agents into DeepSeek Harness: recall, session digest and per-file history tools over a local index, plus optional automatic recall before each step.",
3
+ "version": "0.20.5",
4
+ "description": "Brings the session history of twenty-one other coding agents into DeepSeek Harness: recall, session digest and per-file history tools over a local index, plus optional automatic recall before each step.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "exports": {
@@ -44,7 +44,7 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@vshulcz/deja-vu": "^0.18.0"
47
+ "@vshulcz/deja-vu": "^0.19.2"
48
48
  },
49
49
  "scripts": {
50
50
  "test": "node --test test/*.test.js"