dsh-deja 0.18.0
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/LICENSE +21 -0
- package/README.md +65 -0
- package/README.zh.md +47 -0
- package/cordis.patch.yml +3 -0
- package/index.js +216 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vladislav Shulcz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# dsh-deja
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness can already search its own sessions — that is what the built-in
|
|
6
|
+
`session-query` subsystem does. This plugin answers the other question: what you
|
|
7
|
+
did in the *other* agents on this machine.
|
|
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
|
|
12
|
+
there, including the months before deja was installed. If you moved to dsh last
|
|
13
|
+
week, everything you did before last week is still reachable from inside it.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
dsh plugin --profile web add dsh-deja
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The plugin runs the `deja` binary. It is pulled in as a dependency, so npm
|
|
22
|
+
installs it with the plugin; a `deja` already on `PATH` is used as-is, and
|
|
23
|
+
`DEJA_BIN` overrides both.
|
|
24
|
+
|
|
25
|
+
## What it adds
|
|
26
|
+
|
|
27
|
+
Three tools the model can call:
|
|
28
|
+
|
|
29
|
+
| Tool | Answers |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `deja_recall` | Sessions matching an error string, function name, file path or flag. |
|
|
32
|
+
| `deja_session` | The full digest of one past session, when the reasoning behind it matters. |
|
|
33
|
+
| `deja_blame` | The sessions that discussed a file, before you edit or delete it. |
|
|
34
|
+
|
|
35
|
+
One command: `/deja <what to look for>`.
|
|
36
|
+
|
|
37
|
+
And, unless you turn it off, automatic recall: before each step the plugin asks
|
|
38
|
+
deja whether this machine's history answers the prompt, and adds the answer to
|
|
39
|
+
the runtime context. Silence is the common case — it speaks only when there is
|
|
40
|
+
something to say.
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
- insert:
|
|
44
|
+
- id: deja
|
|
45
|
+
name: dsh-deja
|
|
46
|
+
config:
|
|
47
|
+
autoRecall: false # tools and /deja only
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## How it is wired
|
|
51
|
+
|
|
52
|
+
Recall arrives through `ctx.systemPrompt.context`, evaluated on every assembly.
|
|
53
|
+
The obvious alternative — a middleware on `agent/pre-step` that splices a
|
|
54
|
+
message into the step — loads fine, completes the turn, and never reaches the
|
|
55
|
+
model: a later listener in that waterfall rebuilds its answer from the payload
|
|
56
|
+
and the added message is dropped with nothing reported. This was settled by
|
|
57
|
+
reading the requests dsh 0.1.1-rc.2 actually sent.
|
|
58
|
+
|
|
59
|
+
## What it does not do
|
|
60
|
+
|
|
61
|
+
No LLM, no embeddings, no network. The index is a local BM25 store over files
|
|
62
|
+
that already exist, so a query answers in about a millisecond and nothing leaves
|
|
63
|
+
the machine. Secrets are redacted at index time.
|
|
64
|
+
|
|
65
|
+
Part of [deja-vu](https://github.com/vshulcz/deja-vu). MIT.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# dsh-deja
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness 自带的 `session-query` 已经可以检索它自己的会话。这个插件回答的是另一个问题:你在这台机器上**其他**智能体里做过什么。
|
|
6
|
+
|
|
7
|
+
deja 索引 Claude Code、Codex、Cursor、opencode、Antigravity、Grok Build、Kimi、Cline、Zed 等二十个智能体本来就写在磁盘上的会话文件。不需要提前录制:历史已经在那里,包括安装 deja 之前的那些月份。如果你上周才转到 dsh,上周之前做过的事仍然可以在 dsh 里查到。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
dsh plugin --profile web add dsh-deja
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
插件调用 `deja` 可执行文件。它作为依赖随插件一起安装;如果 `PATH` 上已有 `deja` 就直接使用,`DEJA_BIN` 可覆盖两者。
|
|
16
|
+
|
|
17
|
+
## 提供的能力
|
|
18
|
+
|
|
19
|
+
三个模型可调用的工具:
|
|
20
|
+
|
|
21
|
+
| 工具 | 回答什么 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `deja_recall` | 匹配报错文本、函数名、文件路径或命令行参数的历史会话。 |
|
|
24
|
+
| `deja_session` | 单个历史会话的完整摘要,用于需要当时的判断依据时。 |
|
|
25
|
+
| `deja_blame` | 在修改或删除某个文件之前,讨论过这个文件的历史会话。 |
|
|
26
|
+
|
|
27
|
+
一个命令:`/deja <要查什么>`。
|
|
28
|
+
|
|
29
|
+
以及默认开启的自动召回:每一步之前,插件会问 deja 这台机器的历史能否回答当前提问,并把结果加入运行时上下文。多数情况下它保持沉默,只有确实有内容时才开口。
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
- insert:
|
|
33
|
+
- id: deja
|
|
34
|
+
name: dsh-deja
|
|
35
|
+
config:
|
|
36
|
+
autoRecall: false # 只保留工具和 /deja
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 接入方式
|
|
40
|
+
|
|
41
|
+
召回通过 `ctx.systemPrompt.context` 注入,该回调在每次 assembly 时求值。另一种看起来可行的做法——在 `agent/pre-step` 中间件里往这一步的消息列表里插入一条消息——插件能加载、回合也能正常结束,但内容到不了模型:该 waterfall 中后续的监听器会基于 payload 重建返回值,插入的消息被丢弃且没有任何报错。这一点是通过读取 dsh 0.1.1-rc.2 实际发出的请求确认的。
|
|
42
|
+
|
|
43
|
+
## 不做什么
|
|
44
|
+
|
|
45
|
+
不用大模型,不用向量嵌入,不联网。索引是本地 BM25,建立在本来就存在的文件之上,一次查询约一毫秒,数据不离开本机。密钥在建立索引时被脱敏。
|
|
46
|
+
|
|
47
|
+
属于 [deja-vu](https://github.com/vshulcz/deja-vu) 项目。MIT 许可。
|
package/cordis.patch.yml
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// dsh-deja — the history you already have, inside DeepSeek Harness.
|
|
2
|
+
//
|
|
3
|
+
// dsh answers questions about its own sessions through the built-in
|
|
4
|
+
// session-query subsystem. This plugin answers the other question: what you did
|
|
5
|
+
// in Claude Code, Codex, Cursor, opencode and sixteen more agents, on this
|
|
6
|
+
// machine, before dsh existed. The index is deja's; this file is the seam.
|
|
7
|
+
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
|
|
13
|
+
// The tool registry lives in the host, not here. A plugin that throws on a
|
|
14
|
+
// missing peer takes the whole profile down with it, so both imports degrade:
|
|
15
|
+
// without them the command and automatic recall still work, only the
|
|
16
|
+
// model-facing tools are skipped.
|
|
17
|
+
let defineTool = null;
|
|
18
|
+
let z = null;
|
|
19
|
+
let createUserMessage = null;
|
|
20
|
+
try {
|
|
21
|
+
({ defineTool } = await import("@deepseek-ai/dsh-tools"));
|
|
22
|
+
z = (await import("@deepseek-ai/schemastery")).default;
|
|
23
|
+
({ createUserMessage } = await import("@deepseek-ai/dsh-llm"));
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
const PLATFORM = process.platform === "win32" ? "windows" : process.platform;
|
|
27
|
+
const ARCH = process.arch === "x64" ? "amd64" : process.arch;
|
|
28
|
+
|
|
29
|
+
// resolveDeja finds the binary: an explicit override first, then the platform
|
|
30
|
+
// package npm installed next to this one, then whatever is on PATH.
|
|
31
|
+
function resolveDeja() {
|
|
32
|
+
if (process.env.DEJA_BIN) return process.env.DEJA_BIN;
|
|
33
|
+
const exe = PLATFORM === "windows" ? "deja.exe" : "deja";
|
|
34
|
+
try {
|
|
35
|
+
return require.resolve(`@vshulcz/deja-vu-${PLATFORM}-${ARCH}/bin/${exe}`);
|
|
36
|
+
} catch {
|
|
37
|
+
return exe;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DEJA = resolveDeja();
|
|
42
|
+
|
|
43
|
+
// run returns stdout, or "" when deja is missing or says nothing. Memory is
|
|
44
|
+
// optional everywhere: a throw here would end the turn.
|
|
45
|
+
function run(args, input) {
|
|
46
|
+
try {
|
|
47
|
+
return execFileSync(DEJA, args, {
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
timeout: 20000,
|
|
50
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
51
|
+
input,
|
|
52
|
+
stdio: [input === undefined ? "ignore" : "pipe", "pipe", "ignore"],
|
|
53
|
+
}).trim();
|
|
54
|
+
} catch {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const NOTHING = "Nothing in this machine's history matches that.";
|
|
60
|
+
|
|
61
|
+
function tools(ctx) {
|
|
62
|
+
if (!defineTool || !z) return;
|
|
63
|
+
|
|
64
|
+
// Every tool here answers with the text deja printed, so one output
|
|
65
|
+
// declaration serves all three.
|
|
66
|
+
const TEXT_OUTPUT = {
|
|
67
|
+
schema: z.string(),
|
|
68
|
+
render: (_args, value) => [{ type: "text", text: String(value) }],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
ctx.tools.register(defineTool({
|
|
72
|
+
name: "deja_recall",
|
|
73
|
+
description:
|
|
74
|
+
"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.",
|
|
75
|
+
parameters: {
|
|
76
|
+
query: {
|
|
77
|
+
type: "string",
|
|
78
|
+
required: true,
|
|
79
|
+
description: "Specific tokens to match. Several words are ANDed.",
|
|
80
|
+
},
|
|
81
|
+
limit: {
|
|
82
|
+
type: "number",
|
|
83
|
+
description: "How many sessions to return. Default 5.",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
output: TEXT_OUTPUT,
|
|
87
|
+
execute(args) {
|
|
88
|
+
const limit = Number.isFinite(args.limit) ? String(Math.max(1, Math.trunc(args.limit))) : "5";
|
|
89
|
+
return Promise.resolve(run(["search", "--json", "--limit", limit, String(args.query)]) || NOTHING);
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
ctx.tools.register(defineTool({
|
|
94
|
+
name: "deja_session",
|
|
95
|
+
description:
|
|
96
|
+
"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.",
|
|
97
|
+
parameters: {
|
|
98
|
+
query: {
|
|
99
|
+
type: "string",
|
|
100
|
+
required: true,
|
|
101
|
+
description: "A query, or a session id prefix returned by deja_recall.",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
output: TEXT_OUTPUT,
|
|
105
|
+
execute(args) {
|
|
106
|
+
return Promise.resolve(run(["ctx", String(args.query)]) || NOTHING);
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
ctx.tools.register(defineTool({
|
|
111
|
+
name: "deja_blame",
|
|
112
|
+
description:
|
|
113
|
+
"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.",
|
|
114
|
+
parameters: {
|
|
115
|
+
path: {
|
|
116
|
+
type: "string",
|
|
117
|
+
required: true,
|
|
118
|
+
description: "Path to the file, absolute or relative to the workspace.",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
output: TEXT_OUTPUT,
|
|
122
|
+
execute(args) {
|
|
123
|
+
return Promise.resolve(run(["blame", String(args.path), "--json"]) || NOTHING);
|
|
124
|
+
},
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function command(ctx) {
|
|
129
|
+
ctx.commands.register({
|
|
130
|
+
name: "deja",
|
|
131
|
+
description: "Search this machine's past AI coding sessions",
|
|
132
|
+
input: { hint: "what to look for" },
|
|
133
|
+
async handler(invocation) {
|
|
134
|
+
const query = String((invocation && invocation.rawInput) || "").trim();
|
|
135
|
+
if (!query) {
|
|
136
|
+
return { kind: "error", text: "Say what to look for: /deja <error, file, or decision>" };
|
|
137
|
+
}
|
|
138
|
+
const out = run([query]);
|
|
139
|
+
return { kind: "success", text: out || NOTHING };
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function userText(message) {
|
|
145
|
+
const content = message && message.content;
|
|
146
|
+
if (!Array.isArray(content)) return typeof content === "string" ? content.trim() : "";
|
|
147
|
+
return content
|
|
148
|
+
.filter((part) => part && part.type === "text" && typeof part.text === "string")
|
|
149
|
+
.map((part) => part.text)
|
|
150
|
+
.join("\n")
|
|
151
|
+
.trim();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// autoRecall puts the answer in front of the model without anyone asking for
|
|
155
|
+
// it, through the seam the host evaluates on every assembly. The obvious
|
|
156
|
+
// alternative — splicing a message into the "agent/pre-step" waterfall — looks
|
|
157
|
+
// like it works and does not: a later listener rebuilds its answer from the
|
|
158
|
+
// payload, and the added message is dropped with nothing reported.
|
|
159
|
+
function autoRecall(ctx) {
|
|
160
|
+
let asked = "";
|
|
161
|
+
let recalled = "";
|
|
162
|
+
|
|
163
|
+
ctx.systemPrompt.context({
|
|
164
|
+
name: "deja:recall",
|
|
165
|
+
order: 120,
|
|
166
|
+
text: (assembly) => {
|
|
167
|
+
const agent = assembly && assembly.agent;
|
|
168
|
+
if (!agent) return "";
|
|
169
|
+
const prompt = lastHumanText(agent);
|
|
170
|
+
if (!prompt) return "";
|
|
171
|
+
if (prompt !== asked) {
|
|
172
|
+
asked = prompt;
|
|
173
|
+
recalled = run(["hook-prompt", "--plain"], JSON.stringify({ prompt, cwd: process.cwd() }));
|
|
174
|
+
}
|
|
175
|
+
// Silence is the common case: this speaks only when the history answers.
|
|
176
|
+
return recalled;
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// lastHumanText is the newest thing the person actually typed. At assembly
|
|
182
|
+
// time the message has already left the inbox and has not been appended as a
|
|
183
|
+
// "user/message" yet — the only durable record of it is the inbox splice that
|
|
184
|
+
// carried it in, so both are read. Anything a plugin contributed is skipped:
|
|
185
|
+
// those carry a source of their own.
|
|
186
|
+
function lastHumanText(agent) {
|
|
187
|
+
const events = agent && agent.session && agent.session.events;
|
|
188
|
+
if (!Array.isArray(events)) return "";
|
|
189
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
190
|
+
const event = events[i];
|
|
191
|
+
if (!event) continue;
|
|
192
|
+
if (event.type === "user/message" && isHuman(event.data)) return userText(event.data);
|
|
193
|
+
if (event.type === "agent/inbox/spliced") {
|
|
194
|
+
const inserted = event.data && event.data.inserted;
|
|
195
|
+
if (!Array.isArray(inserted)) continue;
|
|
196
|
+
for (let j = inserted.length - 1; j >= 0; j--) {
|
|
197
|
+
if (isHuman(inserted[j])) return userText(inserted[j]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isHuman(message) {
|
|
205
|
+
return Boolean(message) && (!message.source || message.source.kind === "user");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function apply(ctx, config) {
|
|
209
|
+
tools(ctx);
|
|
210
|
+
command(ctx);
|
|
211
|
+
if (!config || config.autoRecall !== false) autoRecall(ctx);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
apply.inject = ["tools", "commands", "systemPrompt"];
|
|
215
|
+
|
|
216
|
+
export default apply;
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-deja",
|
|
3
|
+
"version": "0.18.0",
|
|
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.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./package.json": "./package.json"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"cordis.patch.yml",
|
|
14
|
+
"README.md",
|
|
15
|
+
"README.zh.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/vshulcz/deja-vu.git",
|
|
22
|
+
"directory": "packages/dsh-deja"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/vshulcz/deja-vu/tree/main/packages/dsh-deja",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/vshulcz/deja-vu/issues"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"dsh",
|
|
30
|
+
"dsh-plugin",
|
|
31
|
+
"deepseek-harness",
|
|
32
|
+
"memory",
|
|
33
|
+
"recall",
|
|
34
|
+
"session-history",
|
|
35
|
+
"migration"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"dsh": {
|
|
41
|
+
"bundle": {
|
|
42
|
+
"patch": "./cordis.patch.yml"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@vshulcz/deja-vu": "0.18.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@deepseek-ai/dsh-tools": "^0.1.0"
|
|
50
|
+
}
|
|
51
|
+
}
|