dsh-tdai-memory 0.3.6 → 0.4.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 +38 -24
- package/README.zh.md +24 -17
- package/client.js +27 -35
- package/index.js +145 -64
- package/package.json +97 -97
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# dsh-tdai-memory
|
|
2
2
|
|
|
3
|
+
## Configuration page (DSH 0.2.0-rc.2 and later)
|
|
4
|
+
|
|
5
|
+
Open **Plugins → Installed → dsh-tdai-memory** from the homepage sidebar to configure and save this plugin. The page uses the official `plugins.bundle.config` interface, without a duplicate entry in global Settings. Web and Desktop share the page. This version requires DSH 0.2.0-rc.2 or a later 0.2.x host; existing configuration is retained.
|
|
6
|
+
|
|
3
7
|
[](README.zh.md)
|
|
4
8
|
|
|
5
9
|
**GitHub**: [Scorp1o117/dsh-tdai-memory](https://github.com/Scorp1o117/dsh-tdai-memory) · **npm**: [dsh-tdai-memory](https://www.npmjs.com/package/dsh-tdai-memory)
|
|
@@ -11,22 +15,22 @@ Part of the [DeepSeek Harness Enhancement Suite](https://github.com/Scorp1o117/d
|
|
|
11
15
|
A port of **TencentDB Agent Memory** (Tencent Cloud's open-source four-layer
|
|
12
16
|
memory system, originally an OpenClaw plugin) into DeepSeek Harness.
|
|
13
17
|
|
|
14
|
-
## Compatibility (v0.
|
|
15
|
-
|
|
16
|
-
Verified with DSH `0.1.7-rc.2` (Web) and `0.2.0-rc.2` (Desktop runtime) in isolated profiles. The Desktop app uses its own `desktop` profile. Other DSH prereleases remain unverified.
|
|
17
|
-
|
|
18
|
-
## Desktop install
|
|
19
|
-
|
|
20
|
-
Use the Desktop-installed `dsh` command (Application → Manage dsh Command), or the app’s Plugins page. Then install into the Desktop profile:
|
|
21
|
-
|
|
22
|
-
```powershell
|
|
23
|
-
dsh plugin --profile desktop add dsh-tdai-memory@0.3.6
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Restart the Desktop app to load the client bundle. Desktop keeps its profile under `$DSH_HOME/profiles/desktop`.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## Features
|
|
18
|
+
## Compatibility (v0.4.1)
|
|
19
|
+
|
|
20
|
+
Verified with DSH `0.1.7-rc.2` (Web) and `0.2.0-rc.2` (Desktop runtime) in isolated profiles. The Desktop app uses its own `desktop` profile. Other DSH prereleases remain unverified.
|
|
21
|
+
|
|
22
|
+
## Desktop install
|
|
23
|
+
|
|
24
|
+
Use the Desktop-installed `dsh` command (Application → Manage dsh Command), or the app’s Plugins page. Then install into the Desktop profile:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
dsh plugin --profile desktop add dsh-tdai-memory@0.3.6
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Restart the Desktop app to load the client bundle. Desktop keeps its profile under `$DSH_HOME/profiles/desktop`.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Features
|
|
30
34
|
|
|
31
35
|
- **L0 conversation capture**: every turn (turn end, request boundary) is
|
|
32
36
|
written to raw conversation storage (JSONL + SQLite + FTS + vectors)
|
|
@@ -65,14 +69,14 @@ Hard-won wiring details:
|
|
|
65
69
|
|
|
66
70
|
## Configuration (profile patch + settings)
|
|
67
71
|
|
|
68
|
-
Configuration is stored in the `tdai-memory` entry of the active Profile patch.
|
|
69
|
-
On first launch, DSH imports the old `$DSH_HOME/settings.yaml` values into that
|
|
70
|
-
entry. The **Web UI Settings → 记忆**
|
|
72
|
+
Configuration is stored in the `tdai-memory` entry of the active Profile patch.
|
|
73
|
+
On first launch, DSH imports the old `$DSH_HOME/settings.yaml` values into that
|
|
74
|
+
entry. The **Web UI Settings → 记忆**
|
|
71
75
|
section edits every field (v0.2.0, write-only keys); TdaiCore is built at
|
|
72
76
|
startup, so changes apply **after a restart**.
|
|
73
77
|
|
|
74
|
-
```yaml
|
|
75
|
-
# $DSH_HOME/profiles/web/cordis.patch.yml
|
|
78
|
+
```yaml
|
|
79
|
+
# $DSH_HOME/profiles/web/cordis.patch.yml
|
|
76
80
|
- id: tdai-memory
|
|
77
81
|
name: 'dsh-tdai-memory'
|
|
78
82
|
config:
|
|
@@ -103,11 +107,11 @@ then mount it in `$DSH_HOME/profiles/web/cordis.patch.yml`:
|
|
|
103
107
|
- insert:
|
|
104
108
|
- id: tdai-memory
|
|
105
109
|
name: 'dsh-tdai-memory'
|
|
106
|
-
config: {} # fill through Settings → 记忆
|
|
110
|
+
config: {} # fill through Settings → 记忆
|
|
107
111
|
```
|
|
108
112
|
|
|
109
|
-
and restart `dsh web`. LLM/embedding API keys can be set in the Web UI
|
|
110
|
-
settings page (记忆 / Memory) or in the `tdai-memory` Profile patch entry.
|
|
113
|
+
and restart `dsh web`. LLM/embedding API keys can be set in the Web UI
|
|
114
|
+
settings page (记忆 / Memory) or in the `tdai-memory` Profile patch entry.
|
|
111
115
|
|
|
112
116
|
> **Note for users**
|
|
113
117
|
> - This plugin is a standard **profile bundle** (`dsh.bundle.patch`):
|
|
@@ -136,6 +140,14 @@ not need it. Users who select the local backend should install and approve
|
|
|
136
140
|
stored=0); off by default; enable only with a more reliable model
|
|
137
141
|
- **L1 memory vectors**: written with storage (8088 embedding is fast); L0
|
|
138
142
|
vectors run as a background task, drained by `destroy()` on headless exit
|
|
143
|
+
- **Search tools** (`toolsEnabled`, on by default): the switch lives in the
|
|
144
|
+
Plugins page and applies in place — the row config is `volatile`, so the
|
|
145
|
+
plugin re-syncs its registration without a restart. If both tools are
|
|
146
|
+
missing, look for the plugin's
|
|
147
|
+
`[tdai-memory] tools registered: tdai_memory_search, tdai_conversation_search`
|
|
148
|
+
log line (v0.4.1+). Before v0.4.1 it read `config.toolsEnabled` off the
|
|
149
|
+
Volatile wrapper cordis hands to `apply` — a property that never exists — so
|
|
150
|
+
the failure was completely silent (issue #3)
|
|
139
151
|
- **Upgrades**: after pulling new upstream code, rerun
|
|
140
152
|
`npx tsc -p dsh-tsconfig.json` in the tdai project dir (output in `dist-dsh/`)
|
|
141
153
|
|
|
@@ -143,3 +155,5 @@ not need it. Users who select the local backend should install and approve
|
|
|
143
155
|
|
|
144
156
|
MIT
|
|
145
157
|
|
|
158
|
+
|
|
159
|
+
Saves use the configuration form API. Rejected writes retain the draft and show an error. Changing a model preserves credentials, and disabling a default-on option explicitly stores `false`.
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# dsh-tdai-memory
|
|
2
2
|
|
|
3
|
+
## 配置入口(DSH 0.2.0-rc.2 起)
|
|
4
|
+
|
|
5
|
+
在首页侧边栏打开 **插件 → 已安装 → dsh-tdai-memory**,直接在插件详情页配置并保存。配置页注册到官方的 `plugins.bundle.config` 接口;全局设置页不再重复显示配置入口。Web 与桌面版使用相同界面,本版要求 DSH 0.2.0-rc.2 或更新的 0.2.x 版本。现有配置无需迁移。
|
|
6
|
+
|
|
3
7
|
**GitHub**: [Scorp1o117/dsh-tdai-memory](https://github.com/Scorp1o117/dsh-tdai-memory) · **npm**: [dsh-tdai-memory](https://www.npmjs.com/package/dsh-tdai-memory) · [English](README.md)
|
|
4
8
|
|
|
5
9
|
[](https://github.com/Scorp1o117/dsh-enhancement-suite) [](https://www.npmjs.com/package/dsh-enhancement-suite)
|
|
@@ -8,22 +12,22 @@
|
|
|
8
12
|
|
|
9
13
|
把 **TencentDB Agent Memory**(腾讯云开源的四层记忆系统,原为 OpenClaw 插件)移植进 DeepSeek Harness。
|
|
10
14
|
|
|
11
|
-
## 兼容性(v0.
|
|
12
|
-
|
|
13
|
-
已在 DSH `0.1.7-rc.2`(Web)与 `0.2.0-rc.2`(桌面端运行时)的一次性 Profile 验证。桌面端使用独立的 `desktop` Profile;其他预发布版本暂未验证。
|
|
14
|
-
|
|
15
|
-
## 桌面端安装
|
|
16
|
-
|
|
17
|
-
在桌面端的“插件”页面安装,或使用桌面端“应用 → 管理 dsh 命令”注册的命令:
|
|
18
|
-
|
|
19
|
-
```powershell
|
|
20
|
-
dsh plugin --profile desktop add dsh-tdai-memory@0.3.6
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
重启桌面端以加载客户端插件。配置位于 `$DSH_HOME/profiles/desktop`。
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## 能力
|
|
15
|
+
## 兼容性(v0.4.1)
|
|
16
|
+
|
|
17
|
+
已在 DSH `0.1.7-rc.2`(Web)与 `0.2.0-rc.2`(桌面端运行时)的一次性 Profile 验证。桌面端使用独立的 `desktop` Profile;其他预发布版本暂未验证。
|
|
18
|
+
|
|
19
|
+
## 桌面端安装
|
|
20
|
+
|
|
21
|
+
在桌面端的“插件”页面安装,或使用桌面端“应用 → 管理 dsh 命令”注册的命令:
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
dsh plugin --profile desktop add dsh-tdai-memory@0.3.6
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
重启桌面端以加载客户端插件。配置位于 `$DSH_HOME/profiles/desktop`。
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## 能力
|
|
27
31
|
|
|
28
32
|
- **L0 对话捕获**:每轮对话(turn 结束、请求边界)自动写入原始对话(JSONL + SQLite + FTS + 向量)
|
|
29
33
|
- **L1 结构化记忆**:后台管线用 LLM 从对话中提取事实/偏好/事件(persona / episodic / instruction),写入 `records/` + SQLite + FTS + 向量
|
|
@@ -56,7 +60,7 @@ dsh plugin --profile desktop add dsh-tdai-memory@0.3.6
|
|
|
56
60
|
|
|
57
61
|
## 配置(profile patch + settings)
|
|
58
62
|
|
|
59
|
-
配置以 **settings 命名空间**驱动:profile patch 作为 base 层,`$DSH_HOME/settings.yaml` 的 `tdai-memory:` 节覆盖(LLM/embedding 密钥已迁到 settings.yaml
|
|
63
|
+
配置以 **settings 命名空间**驱动:profile patch 作为 base 层,`$DSH_HOME/settings.yaml` 的 `tdai-memory:` 节覆盖(LLM/embedding 密钥已迁到 settings.yaml)。首页侧边栏插件 → dsh-tdai-memory 可编辑全部字段(v0.2.0,含写-only 密钥);TdaiCore 启动时构建,改动**重启后生效**。
|
|
60
64
|
|
|
61
65
|
```yaml
|
|
62
66
|
# $DSH_HOME/settings.yaml
|
|
@@ -92,5 +96,8 @@ tdai-memory:
|
|
|
92
96
|
- **提取模型**:`mimo-v2.5` 提取正确但单次 20-30s(后台执行,不阻塞对话);`deepseek-v4-flash` 快但 JSON 输出不合规(提取 0 条)
|
|
93
97
|
- **dedup**:LLM 冲突检测输出解析不稳(曾导致 stored=0),默认关闭;开启需换更稳的模型
|
|
94
98
|
- **L1 记忆向量**:随存储写入(8088 embedding 快);L0 向量走后台任务,headless 退出时由 `destroy()` drain
|
|
99
|
+
- **搜索工具**(`toolsEnabled`,默认开启):开关在插件页,切换后**原地生效**(行配置是 `volatile`,无需重启即重新注册)。若两个工具完全缺失,可查插件日志 `[tdai-memory] tools registered: …`(v0.4.1 起);v0.4.1 之前它读的是 cordis 交给 `apply` 的 Volatile 包装对象上的 `config.toolsEnabled` —— 那个属性永远不存在,因此整个失败过程完全静默(issue #3)
|
|
95
100
|
- **升级**:上游拉新代码后,在 tdai 项目目录重跑 `npx tsc -p dsh-tsconfig.json`(产物在 `dist-dsh/`)
|
|
96
101
|
|
|
102
|
+
|
|
103
|
+
保存使用配置表单 API;宿主拒绝写入时保留草稿并提示错误。只修改模型不会覆盖密钥,关闭默认开启的选项会显式保存 `false`。
|
package/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-tdai-memory — browser half.
|
|
3
3
|
*
|
|
4
|
-
* A
|
|
4
|
+
* A configuration page inside the sidebar Plugins page: edits `tdai-memory`
|
|
5
5
|
* settings namespace (data dir, extraction LLM, embeddings, capture/extract/
|
|
6
6
|
* recall switches, tools) through the settings scope transport plus nested
|
|
7
7
|
* `settings.mutate` ops. TdaiCore is built at startup, so changes apply
|
|
@@ -47,7 +47,7 @@ window.__ModuleLoader__.load({
|
|
|
47
47
|
|
|
48
48
|
// ── locale ────────────────────────────────────────────────────────────
|
|
49
49
|
var NS = "tdaiMemory";
|
|
50
|
-
var inject = ["slots", "locale", "configForms"
|
|
50
|
+
var inject = ["slots", "locale", "configForms"];
|
|
51
51
|
var zh = {
|
|
52
52
|
nav: "记忆",
|
|
53
53
|
intro: "TDAI 记忆配置:L0 捕获 → L1 结构化提取 → 召回注入。密钥只写不读。TdaiCore 在启动时构建,修改后需重启生效。",
|
|
@@ -88,6 +88,7 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
saved: "已保存(重启后生效)",
|
|
89
89
|
saving: "保存中…",
|
|
90
90
|
error: "保存失败",
|
|
91
|
+
notApplied: "写入未生效,请检查配置后重试",
|
|
91
92
|
unavailable: "设置命名空间不可用(服务端未注册 tdai-memory 命名空间?)",
|
|
92
93
|
overridden: "已覆盖",
|
|
93
94
|
loading: "加载中…"
|
|
@@ -132,6 +133,7 @@ window.__ModuleLoader__.load({
|
|
|
132
133
|
saved: "Saved (applies after restart)",
|
|
133
134
|
saving: "Saving…",
|
|
134
135
|
error: "Save failed",
|
|
136
|
+
notApplied: "Write did not take effect; check the configuration and retry",
|
|
135
137
|
unavailable: "Settings namespace unavailable (tdai-memory namespace not registered server-side?)",
|
|
136
138
|
overridden: "overridden",
|
|
137
139
|
loading: "Loading…"
|
|
@@ -176,7 +178,6 @@ window.__ModuleLoader__.load({
|
|
|
176
178
|
function MemorySection(props) {
|
|
177
179
|
var t = props.t;
|
|
178
180
|
var scope = props.scope;
|
|
179
|
-
var api = props.api;
|
|
180
181
|
var [snapshot, setSnapshot] = react.useState(function () { return scope.getSnapshot(); });
|
|
181
182
|
var ready = snapshot.status === "ready" && snapshot.value !== void 0;
|
|
182
183
|
var [draft, setDraft] = react.useState({});
|
|
@@ -216,6 +217,7 @@ window.__ModuleLoader__.load({
|
|
|
216
217
|
var user = snapshot.user || {};
|
|
217
218
|
|
|
218
219
|
function fieldDraft(f) {
|
|
220
|
+
if (f.secret) return draft[f.key] || "";
|
|
219
221
|
if (f.type === "checkbox") return draft[f.key] !== void 0 ? draft[f.key] : Boolean(getPath(value, f.path));
|
|
220
222
|
return draft[f.key] !== void 0 ? draft[f.key] : String(getPath(value, f.path) ?? "");
|
|
221
223
|
}
|
|
@@ -226,6 +228,7 @@ window.__ModuleLoader__.load({
|
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
function onSave() {
|
|
231
|
+
if (busy || !snapshot.writable) return;
|
|
229
232
|
setBusy(true); setNotice(null); setError(null);
|
|
230
233
|
var ops = [];
|
|
231
234
|
for (var i = 0; i < FIELDS.length; i += 1) {
|
|
@@ -240,7 +243,7 @@ window.__ModuleLoader__.load({
|
|
|
240
243
|
}
|
|
241
244
|
if (f.type === "checkbox") {
|
|
242
245
|
if (Boolean(d) === Boolean(current)) continue;
|
|
243
|
-
ops.push(
|
|
246
|
+
ops.push({ op: "set", path: f.path, value: Boolean(d) });
|
|
244
247
|
continue;
|
|
245
248
|
}
|
|
246
249
|
if (String(d) === String(current ?? "")) continue;
|
|
@@ -248,35 +251,27 @@ window.__ModuleLoader__.load({
|
|
|
248
251
|
ops.push(String(d).trim() === "" ? { op: "unset", path: f.path } : { op: "set", path: f.path, value: f.type === "number" ? Number(d) : d });
|
|
249
252
|
}
|
|
250
253
|
if (ops.length === 0) { setBusy(false); setNotice(t("saved")); return; }
|
|
251
|
-
|
|
252
|
-
ns: "tdai-memory",
|
|
253
|
-
ops: ops,
|
|
254
|
-
...snapshot.revision === void 0 ? {} : { expectedRevision: snapshot.revision }
|
|
255
|
-
}).then(function (response) {
|
|
256
|
-
setBusy(false);
|
|
257
|
-
if (!response.result.ok) {
|
|
258
|
-
var detail = response.result.error || {};
|
|
259
|
-
setError(t("error") + ": " + String(detail.message || detail.code || "unknown"));
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
setNotice(t("saved"));
|
|
263
|
-
if (response.result.value) setDraft(Object.assign({}, valueToDraft(response.result.value)));
|
|
264
|
-
}).catch(function (e) {
|
|
265
|
-
setBusy(false); setError(t("error") + ": " + String(e && e.message || e));
|
|
266
|
-
});
|
|
254
|
+
commit(ops);
|
|
267
255
|
}
|
|
268
256
|
|
|
269
257
|
function onReset() {
|
|
258
|
+
if (busy || !snapshot.writable) return;
|
|
270
259
|
setBusy(true); setNotice(null); setError(null);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
260
|
+
commit(FIELDS.map(function (f) { return { op: "unset", path: f.path }; }));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function commit(ops) {
|
|
264
|
+
// The form owns the transport and the recovery read. A refused Host
|
|
265
|
+
// write resolves false; it must not be displayed as a successful save.
|
|
266
|
+
Promise.resolve().then(function () {
|
|
267
|
+
return scope.mutate(ops, snapshot.revision);
|
|
268
|
+
}).then(function (ok) {
|
|
269
|
+
var next = scope.getSnapshot();
|
|
270
|
+
setSnapshot(next);
|
|
276
271
|
setBusy(false);
|
|
277
|
-
if (!
|
|
272
|
+
if (!ok) { setError(t("error") + ": " + t("notApplied")); return; }
|
|
278
273
|
setNotice(t("saved"));
|
|
279
|
-
if (
|
|
274
|
+
if (next.value) setDraft(valueToDraft(next.value));
|
|
280
275
|
}).catch(function (e) {
|
|
281
276
|
setBusy(false); setError(t("error") + ": " + String(e && e.message || e));
|
|
282
277
|
});
|
|
@@ -336,7 +331,7 @@ window.__ModuleLoader__.load({
|
|
|
336
331
|
var out = {};
|
|
337
332
|
for (var i = 0; i < FIELDS.length; i += 1) {
|
|
338
333
|
var f = FIELDS[i];
|
|
339
|
-
out[f.key] = f.type === "checkbox" ? Boolean(getPath(value, f.path)) : String(getPath(value, f.path) ?? "");
|
|
334
|
+
out[f.key] = f.secret ? "" : f.type === "checkbox" ? Boolean(getPath(value, f.path)) : String(getPath(value, f.path) ?? "");
|
|
340
335
|
}
|
|
341
336
|
return out;
|
|
342
337
|
}
|
|
@@ -346,16 +341,13 @@ window.__ModuleLoader__.load({
|
|
|
346
341
|
var t = ctx.locale.bind(NS);
|
|
347
342
|
ctx.effect(function () { return ctx.locale.register(NS, { zh: zh, en: en }); }, "dsh-tdai-memory: dictionaries");
|
|
348
343
|
var scope = ctx.configForms.get("tdai-memory");
|
|
349
|
-
|
|
350
|
-
ctx.slots.inject("settings.section", function () {
|
|
344
|
+
ctx.slots.inject("plugins.bundle.config", function () {
|
|
351
345
|
return ctx.slots.register({
|
|
352
|
-
name: "
|
|
353
|
-
|
|
354
|
-
order: 26,
|
|
355
|
-
label: function () { return t("nav"); },
|
|
346
|
+
name: "plugins.bundle.config",
|
|
347
|
+
key: "dsh-tdai-memory",
|
|
356
348
|
locale: NS
|
|
357
349
|
}, function (props) {
|
|
358
|
-
return h(MemorySection, Object.assign({}, props, { scope: scope,
|
|
350
|
+
return h(MemorySection, Object.assign({}, props, { scope: scope, t: t }));
|
|
359
351
|
});
|
|
360
352
|
});
|
|
361
353
|
}
|
package/index.js
CHANGED
|
@@ -106,18 +106,31 @@ function extractText(content) {
|
|
|
106
106
|
.trim();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the config value handed to `apply`.
|
|
111
|
+
*
|
|
112
|
+
* The row schema ends in `.volatile()` (hot-reloadable config). Config
|
|
113
|
+
* validation runs through the standard-schema path, which returns the
|
|
114
|
+
* validated object wrapped in a cosmokit `Volatile`: a frozen `{ get() }`
|
|
115
|
+
* reference whose OWN properties are always `undefined`. Reading
|
|
116
|
+
* `config.toolsEnabled` off it can therefore never be true — the tools guard
|
|
117
|
+
* did exactly that and silently stopped registering the search tools in
|
|
118
|
+
* v0.3.4 (issue #3). Read config through this helper only.
|
|
119
|
+
*
|
|
120
|
+
* The wrapper is a live reference (`updateVolatile` swaps its inner value), so
|
|
121
|
+
* calling this at use time — not once at startup — sees volatile edits.
|
|
122
|
+
*/
|
|
123
|
+
function runtimeConfigOf(config) {
|
|
124
|
+
return typeof config?.get === "function" ? config.get() : config;
|
|
125
|
+
}
|
|
126
|
+
|
|
109
127
|
function apply(ctx, config) {
|
|
110
128
|
// ── build-once state (rebuildable) ───────────────────────────────────────
|
|
111
|
-
// TdaiCore is built from the
|
|
112
|
-
// base
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
// timer — so the no-settings fallback below cannot be allowed to win the
|
|
117
|
-
// race. Instead of racing, we let the fallback build from the entry config
|
|
118
|
-
// first (guaranteeing a working core even without settings), and REBUILD
|
|
119
|
-
// from the resolved settings when the settings mount arrives. The fallback
|
|
120
|
-
// is a one-shot; the settings build always wins afterwards.
|
|
129
|
+
// TdaiCore is built once from the resolved row config: the composition entry
|
|
130
|
+
// as base with the Plugins-page values merged in by the host (settings.yaml is
|
|
131
|
+
// no longer a config source since v0.4.0 — see client.js `configForms`). The
|
|
132
|
+
// build is deferred past the 0ms window so web/headless boots are never
|
|
133
|
+
// blocked on TdaiCore's stores and embedding client.
|
|
121
134
|
let core = null;
|
|
122
135
|
let tdaiConfig = null;
|
|
123
136
|
let dataDir = "";
|
|
@@ -339,69 +352,137 @@ function apply(ctx, config) {
|
|
|
339
352
|
}
|
|
340
353
|
|
|
341
354
|
// ── tools ────────────────────────────────────────────────────────────────
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
schema: { type: "string" },
|
|
354
|
-
render: (_args, value) => [{ type: "text", text: value }],
|
|
355
|
-
},
|
|
356
|
-
async execute(args) {
|
|
357
|
-
const result = await core.searchMemories({ query: args.query, limit: args.limit ?? 5 });
|
|
358
|
-
return result.text;
|
|
359
|
-
},
|
|
360
|
-
}));
|
|
361
|
-
ctx.tools.register(defineTool({
|
|
362
|
-
name: "tdai_conversation_search",
|
|
363
|
-
description:
|
|
364
|
-
"Search raw past conversation logs (L0) by keyword. Use this to look up exact quotes or details " +
|
|
365
|
-
"that structured memory search does not cover.",
|
|
366
|
-
parameters: {
|
|
367
|
-
query: { type: "string", required: true, description: "Text to find in past conversations." },
|
|
368
|
-
limit: { type: "integer", description: "Maximum results (default 5)." },
|
|
369
|
-
},
|
|
370
|
-
output: {
|
|
371
|
-
schema: { type: "string" },
|
|
372
|
-
render: (_args, value) => [{ type: "text", text: value }],
|
|
373
|
-
},
|
|
374
|
-
async execute(args) {
|
|
375
|
-
const result = await core.searchConversations({ query: args.query, limit: args.limit ?? 5 });
|
|
376
|
-
return result.text;
|
|
377
|
-
},
|
|
378
|
-
}));
|
|
355
|
+
// `toolsEnabled` defaults to true (schema `.default(true)`), so only an
|
|
356
|
+
// explicit `false` turns the two search tools off. Read through
|
|
357
|
+
// `runtimeConfigOf` — the volatile row config has no plain properties (#3).
|
|
358
|
+
let toolsRegistered = false;
|
|
359
|
+
let toolsDeferred = false;
|
|
360
|
+
let toolsDisposers = [];
|
|
361
|
+
|
|
362
|
+
/** The core, or a clear error while the first build is still in flight. */
|
|
363
|
+
function requireCore() {
|
|
364
|
+
if (!core) throw new Error("tdai-memory is still initializing; retry this call in a moment");
|
|
365
|
+
return core;
|
|
379
366
|
}
|
|
380
367
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
368
|
+
function registerTools(tools) {
|
|
369
|
+
if (toolsRegistered) return;
|
|
370
|
+
if (typeof tools?.register !== "function") {
|
|
371
|
+
ctx.logger.warn("[tdai-memory] tools service does not expose register(); search tools stay unavailable");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
toolsRegistered = true;
|
|
375
|
+
toolsDisposers = [
|
|
376
|
+
tools.register(defineTool({
|
|
377
|
+
name: "tdai_memory_search",
|
|
378
|
+
description:
|
|
379
|
+
"Search structured long-term memories (facts, preferences, and experiences extracted from past conversations). " +
|
|
380
|
+
"Use this to recall what the user told you or what happened in earlier sessions.",
|
|
381
|
+
parameters: {
|
|
382
|
+
query: { type: "string", required: true, description: "The memory to search for." },
|
|
383
|
+
limit: { type: "integer", description: "Maximum results (default 5)." },
|
|
384
|
+
},
|
|
385
|
+
output: {
|
|
386
|
+
schema: { type: "string" },
|
|
387
|
+
render: (_args, value) => [{ type: "text", text: value }],
|
|
388
|
+
},
|
|
389
|
+
async execute(args) {
|
|
390
|
+
const result = await requireCore().searchMemories({ query: args.query, limit: args.limit ?? 5 });
|
|
391
|
+
return result.text;
|
|
392
|
+
},
|
|
393
|
+
})),
|
|
394
|
+
tools.register(defineTool({
|
|
395
|
+
name: "tdai_conversation_search",
|
|
396
|
+
description:
|
|
397
|
+
"Search raw past conversation logs (L0) by keyword. Use this to look up exact quotes or details " +
|
|
398
|
+
"that structured memory search does not cover.",
|
|
399
|
+
parameters: {
|
|
400
|
+
query: { type: "string", required: true, description: "Text to find in past conversations." },
|
|
401
|
+
limit: { type: "integer", description: "Maximum results (default 5)." },
|
|
402
|
+
},
|
|
403
|
+
output: {
|
|
404
|
+
schema: { type: "string" },
|
|
405
|
+
render: (_args, value) => [{ type: "text", text: value }],
|
|
406
|
+
},
|
|
407
|
+
async execute(args) {
|
|
408
|
+
const result = await requireCore().searchConversations({ query: args.query, limit: args.limit ?? 5 });
|
|
409
|
+
return result.text;
|
|
410
|
+
},
|
|
411
|
+
})),
|
|
412
|
+
];
|
|
413
|
+
ctx.logger.info("[tdai-memory] tools registered: tdai_memory_search, tdai_conversation_search");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function unregisterTools() {
|
|
417
|
+
for (const dispose of toolsDisposers.splice(0)) {
|
|
418
|
+
try {
|
|
419
|
+
dispose();
|
|
420
|
+
} catch (error) {
|
|
421
|
+
ctx.logger.warn(`[tdai-memory] tool dispose failed: ${String(error)}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
toolsRegistered = false;
|
|
425
|
+
ctx.logger.info("[tdai-memory] tools unregistered (toolsEnabled=false)");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Align tool registration with the live `toolsEnabled` value. */
|
|
429
|
+
function syncTools() {
|
|
430
|
+
if (runtimeConfigOf(config)?.toolsEnabled === false) {
|
|
431
|
+
if (toolsRegistered) unregisterTools();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (toolsRegistered || toolsDeferred) return;
|
|
435
|
+
// `tools` is in this plugin's `inject`, so the service is normally live
|
|
436
|
+
// here; `ctx.inject` is the fallback for hosts that mount it later. The old
|
|
437
|
+
// one-shot `ctx.tools` property read registered nothing and said nothing.
|
|
438
|
+
const tools = ctx.tools ?? ctx.get?.("tools");
|
|
439
|
+
if (tools === undefined) {
|
|
440
|
+
toolsDeferred = true;
|
|
441
|
+
ctx.logger.warn("[tdai-memory] tools service not ready; deferring search-tool registration");
|
|
442
|
+
ctx.inject(["tools"], (toolCtx) => {
|
|
443
|
+
toolsDeferred = false;
|
|
444
|
+
const deferred = toolCtx.tools ?? toolCtx.get?.("tools");
|
|
445
|
+
if (deferred === undefined) {
|
|
446
|
+
ctx.logger.warn("[tdai-memory] tools service still unavailable; search tools stay unavailable");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
registerTools(deferred);
|
|
450
|
+
});
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
registerTools(tools);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// `toolsEnabled` is a volatile config path, so flipping it (Plugins page)
|
|
457
|
+
// updates the running fiber in place instead of remounting this plugin: the
|
|
458
|
+
// loader reports that as `loader/volatile-update`, and this keeps the
|
|
459
|
+
// registration in sync with the new value.
|
|
460
|
+
ctx.on("loader/volatile-update", () => {
|
|
461
|
+
try {
|
|
462
|
+
syncTools();
|
|
463
|
+
} catch (error) {
|
|
464
|
+
ctx.logger.warn(`[tdai-memory] tool sync failed: ${String(error)}`);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
syncTools();
|
|
469
|
+
|
|
470
|
+
// ── configuration changes ─────────────────────────────────────────────────
|
|
471
|
+
// Configuration lives in the Plugins page (client.js → configForms), i.e. in
|
|
472
|
+
// this row's own config. Because the row schema is `.volatile()`, an edit is
|
|
473
|
+
// applied to the running fiber in place — the loader reports it as
|
|
474
|
+
// `loader/volatile-update` (the tools above re-sync on it) — while TdaiCore
|
|
475
|
+
// is built at startup, so an edit that must reach the core needs a plugin
|
|
476
|
+
// reload. Legacy settings documents are only reported here.
|
|
393
477
|
ctx.on("settings/document-updated", (id) => {
|
|
394
478
|
if (id === NS) ctx.logger.warn("[tdai-memory] settings updated; restart to apply (TdaiCore is built at startup)");
|
|
395
479
|
});
|
|
396
480
|
|
|
397
|
-
//
|
|
398
|
-
// config. One-shot — if the settings mount arrives later, its build
|
|
399
|
-
// supersedes this one. Delayed past the 0ms window so headless/web boots
|
|
400
|
-
// are never blocked on this timer.
|
|
481
|
+
// Deferred first build (see the note at the top of apply).
|
|
401
482
|
setTimeout(() => {
|
|
402
483
|
if (!builtOnce) {
|
|
403
484
|
builtOnce = true;
|
|
404
|
-
void build(
|
|
485
|
+
void build(runtimeConfigOf(config));
|
|
405
486
|
}
|
|
406
487
|
}, 500);
|
|
407
488
|
|
package/package.json
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-tdai-memory",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DeepSeek Harness 的 TencentDB Agent Memory 移植:L0 对话捕获 → L1 结构化记忆提取 → L2 场景/L3 画像,自动召回注入 + 记忆/对话搜索工具;复用现有 ~/.memory-tencentdb/memory-tdai 数据;附 Web UI 设置栏。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./index.js",
|
|
9
|
-
"./client": "./client.js",
|
|
10
|
-
"./package.json": "./package.json"
|
|
11
|
-
},
|
|
12
|
-
"dsh": {
|
|
13
|
-
"bundle": {
|
|
14
|
-
"patch": "./cordis.patch.yml"
|
|
15
|
-
},
|
|
16
|
-
"client": {
|
|
17
|
-
"platform": "web"
|
|
18
|
-
},
|
|
19
|
-
"compatibility": {
|
|
20
|
-
"dsh": ">=0.
|
|
21
|
-
"dshReleases": {
|
|
22
|
-
"0.1.5-rc.2": "incompatible",
|
|
23
|
-
"0.1.5-rc.3": "incompatible",
|
|
24
|
-
"0.1.6-alpha.1": "unknown",
|
|
25
|
-
"0.1.6-alpha.2": "unknown",
|
|
26
|
-
"0.1.7-alpha.1": "unknown",
|
|
27
|
-
"0.1.7-alpha.2": "unknown",
|
|
28
|
-
"0.1.7-rc.1": "
|
|
29
|
-
"0.1.7-rc.2": "
|
|
30
|
-
"0.2.0-rc.2": "compatible"
|
|
31
|
-
},
|
|
32
|
-
"node": ">=22.18",
|
|
33
|
-
"profiles": [
|
|
34
|
-
"web",
|
|
35
|
-
"desktop"
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
"homepage": "https://github.com/Scorp1o117/dsh-tdai-memory",
|
|
40
|
-
"files": [
|
|
41
|
-
"index.js",
|
|
42
|
-
"recall-inject.js",
|
|
43
|
-
"client.js",
|
|
44
|
-
"cordis.patch.yml",
|
|
45
|
-
"vendor",
|
|
46
|
-
"README.md",
|
|
47
|
-
"README.zh.md",
|
|
48
|
-
"LICENSE",
|
|
49
|
-
"THIRD-PARTY-NOTICES.md"
|
|
50
|
-
],
|
|
51
|
-
"keywords": [
|
|
52
|
-
"dsh",
|
|
53
|
-
"dsh-plugin",
|
|
54
|
-
"deepseek-harness",
|
|
55
|
-
"memory",
|
|
56
|
-
"tencentdb",
|
|
57
|
-
"agent-memory",
|
|
58
|
-
"rag",
|
|
59
|
-
"vector-search",
|
|
60
|
-
"persona"
|
|
61
|
-
],
|
|
62
|
-
"license": "MIT",
|
|
63
|
-
"engines": {
|
|
64
|
-
"node": ">=22.18"
|
|
65
|
-
},
|
|
66
|
-
"repository": {
|
|
67
|
-
"type": "git",
|
|
68
|
-
"url": "git+https://github.com/Scorp1o117/dsh-tdai-memory.git"
|
|
69
|
-
},
|
|
70
|
-
"bugs": {
|
|
71
|
-
"url": "https://github.com/Scorp1o117/dsh-tdai-memory/issues"
|
|
72
|
-
},
|
|
73
|
-
"dependencies": {
|
|
74
|
-
"@ai-sdk/openai": "^3.0.53",
|
|
75
|
-
"@deepseek-ai/schemastery": "^3.18.4",
|
|
76
|
-
"@node-rs/jieba": "^2.0.1",
|
|
77
|
-
"@tencentdb-agent-memory/tcvdb-text": "^0.1.1",
|
|
78
|
-
"ai": "^6.0.164",
|
|
79
|
-
"json5": "^2.2.3",
|
|
80
|
-
"sqlite-vec": "0.1.7-alpha.2",
|
|
81
|
-
"undici": "^8.1.0"
|
|
82
|
-
},
|
|
83
|
-
"peerDependencies": {
|
|
84
|
-
"react": "^18.2.0",
|
|
85
|
-
"@deepseek-ai/dsh-tools": "
|
|
86
|
-
"@deepseek-ai/dsh-settings": "
|
|
87
|
-
"node-llama-cpp": "^3.19.1"
|
|
88
|
-
},
|
|
89
|
-
"peerDependenciesMeta": {
|
|
90
|
-
"node-llama-cpp": {
|
|
91
|
-
"optional": true
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
"scripts": {
|
|
95
|
-
"test": "node tests/settings-scope-compat.test.mjs && node tests/install-contract.test.mjs"
|
|
96
|
-
}
|
|
97
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-tdai-memory",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "DeepSeek Harness 的 TencentDB Agent Memory 移植:L0 对话捕获 → L1 结构化记忆提取 → L2 场景/L3 画像,自动召回注入 + 记忆/对话搜索工具;复用现有 ~/.memory-tencentdb/memory-tdai 数据;附 Web UI 设置栏。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./client": "./client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"platform": "web"
|
|
18
|
+
},
|
|
19
|
+
"compatibility": {
|
|
20
|
+
"dsh": ">=0.2.0-rc.2 <0.3.0",
|
|
21
|
+
"dshReleases": {
|
|
22
|
+
"0.1.5-rc.2": "incompatible",
|
|
23
|
+
"0.1.5-rc.3": "incompatible",
|
|
24
|
+
"0.1.6-alpha.1": "unknown",
|
|
25
|
+
"0.1.6-alpha.2": "unknown",
|
|
26
|
+
"0.1.7-alpha.1": "unknown",
|
|
27
|
+
"0.1.7-alpha.2": "unknown",
|
|
28
|
+
"0.1.7-rc.1": "incompatible",
|
|
29
|
+
"0.1.7-rc.2": "incompatible",
|
|
30
|
+
"0.2.0-rc.2": "compatible"
|
|
31
|
+
},
|
|
32
|
+
"node": ">=22.18",
|
|
33
|
+
"profiles": [
|
|
34
|
+
"web",
|
|
35
|
+
"desktop"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/Scorp1o117/dsh-tdai-memory",
|
|
40
|
+
"files": [
|
|
41
|
+
"index.js",
|
|
42
|
+
"recall-inject.js",
|
|
43
|
+
"client.js",
|
|
44
|
+
"cordis.patch.yml",
|
|
45
|
+
"vendor",
|
|
46
|
+
"README.md",
|
|
47
|
+
"README.zh.md",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"THIRD-PARTY-NOTICES.md"
|
|
50
|
+
],
|
|
51
|
+
"keywords": [
|
|
52
|
+
"dsh",
|
|
53
|
+
"dsh-plugin",
|
|
54
|
+
"deepseek-harness",
|
|
55
|
+
"memory",
|
|
56
|
+
"tencentdb",
|
|
57
|
+
"agent-memory",
|
|
58
|
+
"rag",
|
|
59
|
+
"vector-search",
|
|
60
|
+
"persona"
|
|
61
|
+
],
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=22.18"
|
|
65
|
+
},
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/Scorp1o117/dsh-tdai-memory.git"
|
|
69
|
+
},
|
|
70
|
+
"bugs": {
|
|
71
|
+
"url": "https://github.com/Scorp1o117/dsh-tdai-memory/issues"
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@ai-sdk/openai": "^3.0.53",
|
|
75
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
76
|
+
"@node-rs/jieba": "^2.0.1",
|
|
77
|
+
"@tencentdb-agent-memory/tcvdb-text": "^0.1.1",
|
|
78
|
+
"ai": "^6.0.164",
|
|
79
|
+
"json5": "^2.2.3",
|
|
80
|
+
"sqlite-vec": "0.1.7-alpha.2",
|
|
81
|
+
"undici": "^8.1.0"
|
|
82
|
+
},
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"react": "^18.2.0",
|
|
85
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.2 <0.3.0",
|
|
86
|
+
"@deepseek-ai/dsh-settings": ">=0.2.0-rc.2 <0.3.0",
|
|
87
|
+
"node-llama-cpp": "^3.19.1"
|
|
88
|
+
},
|
|
89
|
+
"peerDependenciesMeta": {
|
|
90
|
+
"node-llama-cpp": {
|
|
91
|
+
"optional": true
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"scripts": {
|
|
95
|
+
"test": "node tests/settings-scope-compat.test.mjs && node tests/install-contract.test.mjs && node --test tests/plugin-page.test.mjs tests/client-write-flow.test.mjs tests/tools-registration.test.mjs"
|
|
96
|
+
}
|
|
97
|
+
}
|