dsh-mem0-memory 0.1.3 → 0.1.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/CHANGELOG.md +18 -0
- package/README.md +4 -0
- package/lib/index.js +50 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
### 0.1.5(2026-10-18)
|
|
9
|
+
|
|
10
|
+
- **适配 DSH 0.2.x(0.2.1-rc.2 实测)**,两处改动,缺一不可:
|
|
11
|
+
1. **peer 范围放宽**:`@deepseek-ai/dsh-agent-loop` 由 `^0.1.0-rc.6`(= `>=0.1.0-rc.6 <0.2.0`)改为 **`^0.1.0-rc.6 || ^0.2.1-rc.2`**。0.2.1 起底座新增「插件兼容性硬校验」,旧范围会被判为不兼容并**拒绝安装 + 回滚**(`installation rejected: Plugin dsh-mem0-memory@0.1.4 is incompatible with dsh 0.2.1-rc.2`)。
|
|
12
|
+
- 选**有界区间**而非 `*`/`>=`:本插件会向会话写入事件,声明「任何版本都支持」会在 DSH 大版本再变时静默踩雷(参见 2026-08-19 会话损坏事故)。0.3.x 起需重新验证后再放宽。
|
|
13
|
+
- 覆盖:0.1.5-rc.3(旧线)✅、0.2.1-rc.2(新线)✅、0.2.x 后续 rc/补丁 ✅。
|
|
14
|
+
2. **注入消息的 `source.kind` 改写**:`{ kind: "plugin", plugin: "dsh-mem0-memory", form: "recall" }` → **`{ kind: "plugin:dsh-mem0-memory", form: "recall" }`**。
|
|
15
|
+
- 根因:0.2.x 引入 session format **v4**,其 `source()` 校验**明确禁止 `kind:"plugin"`**(要求生产者自有 kind,`plugin:<name>` 即 V3→V4 迁移器对未知插件的标准映射)。若只放宽 peer 不改这里,结果是「装得上、但每轮对话在注入后报 `format v4 message requires a producer-owned source kind` 而失败」——比装不上更糟。
|
|
16
|
+
- 兼容性:0.1.x 的写入门槛只要求「kind 为非空字符串」,`kind:"plugin"` 的强校验仅针对 `system/message`,`user/message` 不受限 → 同一份代码在 0.1.x 与 0.2.x 上都能跑,无需按底座版本分支。
|
|
17
|
+
- 影响面:`lib/index.js`(注入点一处)、`package.json`(版本 + peer);server / G1 解析器 / 导入命令未改动。
|
|
18
|
+
- 验证:0.2.1-rc.2 真实环境实跑(安装通过、注入链完整、会话可关闭重开、转录 v4 事件形态正确)。
|
|
19
|
+
|
|
20
|
+
### 0.1.4(2026-08-23)
|
|
21
|
+
|
|
22
|
+
- **server 自动拉起(autoStartServer)**:DSH 启动时插件探测 mem0 server 的 `/health`(3s 超时)——未在跑则自动 spawn 一次(`detached` 子进程,不随 DSH 退出);已在跑则不动。用户装了插件 = 期望「与平台同步启动」,忘记手动开 server 也能工作。
|
|
23
|
+
- 设计铁律:① 只探测 HTTP /health(端口有响应 = 在跑,无论谁起的);② 每个 DSH 进程最多 spawn 一次,失败仅 WARN 绝不重试(OpenClaw 侧 16 进程抢锁事故教训);③ server 常驻,DSH 退出不带走(最小化动作)。
|
|
24
|
+
- 配置新增:`autoStartServer: true`(默认开,可关)+ `pythonPath`(可选,spawn 用 python 路径,默认 `python`/`python3`)。
|
|
25
|
+
|
|
8
26
|
### 0.1.3(2026-08-22)
|
|
9
27
|
|
|
10
28
|
- **工作区排除(召回侧)**:排除工作区的会话不再召回/注入记忆——插件通过 `ctx.agents.requireInitiator()` 获取当前会话 cwd,判定与 G1 写入侧一致:cwd 路径段 `0_` 前缀(写死约定)+ 新增 `excludeWorkspaces` 配置(名单子串匹配,对应 G1 excluded-workspaces.json)。排除工作区对话省去召回延时(每轮 5-10s),内容本就不入库。
|
package/README.md
CHANGED
|
@@ -152,6 +152,8 @@ python scripts/mem0-server.py --port 18200 # 启动服务
|
|
|
152
152
|
> 重启后,**跑一次首次导入(见下节)**,历史会话就进记忆库了——之后自动增量,不用再管。
|
|
153
153
|
>
|
|
154
154
|
> **peer 依赖警告可忽略**:安装时 pnpm 可能提示 `missing peer @deepseek-ai/cordis` / `@deepseek-ai/dsh-agent-loop`——这是声明性警告,插件运行时会从 DSH 的 bundle 安装处解析这些依赖,无需处理。
|
|
155
|
+
>
|
|
156
|
+
> **兼容的 DSH 版本**:`dsh-agent-loop` peer 范围 = `^0.1.0-rc.6 || ^0.2.1-rc.2`(0.1.5-rc.3 与 0.2.1-rc.2 均已验证)。DSH 0.2.1 起底座会做插件兼容性硬校验,版本号不在范围内会被**拒绝安装**;若你用的是更晚的大版本(0.3.x 起),请先确认插件已跟进再装。
|
|
155
157
|
|
|
156
158
|
## 首次导入:把你的历史会话装进记忆库
|
|
157
159
|
|
|
@@ -210,6 +212,8 @@ dsh-mem0-search "关键词" --json # 原始 JS
|
|
|
210
212
|
| `probePath` | (空=关) | 排障探针日志文件路径(设置后才写文件) |
|
|
211
213
|
| `autoImport` | true | 会话结束自动增量导入(关 = 只用手动/定时) |
|
|
212
214
|
| `excludeWorkspaces` | `[]` | 不召回的工作区名单(子串匹配会话 cwd;另 `0_` 前缀工作区写死排除) |
|
|
215
|
+
| `autoStartServer` | true | DSH 启动时确保 mem0 server 在跑(探测 /health,未在跑自动拉起一次;DSH 退出不带走 server) |
|
|
216
|
+
| `pythonPath` | (空) | 自动拉起 server 用的 python 路径(默认 `python`/`python3`;venv 用户可指定) |
|
|
213
217
|
|
|
214
218
|
## 工作原理
|
|
215
219
|
|
package/lib/index.js
CHANGED
|
@@ -220,6 +220,42 @@ function isExcludedWorkspace(cwd, list) {
|
|
|
220
220
|
return (list ?? []).some((e) => e && String(cwd).includes(String(e)));
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
// ─── server ensure(2026-08-23):DSH 启动时确保 mem0 server 在跑 ─────────
|
|
224
|
+
// 目的:用户装了插件 = 期望「与平台同步启动」——忘记手动开 server 时自动拉起一次。
|
|
225
|
+
// 铁律:① 只探测 HTTP /health(端口有响应 = 在跑,无论谁起的);② 每个 DSH 进程
|
|
226
|
+
// 最多 spawn 一次,失败仅 WARN 绝不重试(OpenClaw 侧 16 进程抢锁事故教训);
|
|
227
|
+
// ③ detached 子进程 + unref——不随 DSH 退出(记忆服务常驻,最小化动作)。
|
|
228
|
+
async function ensureServer(ctx, cfg) {
|
|
229
|
+
if (!cfg.autoStartServer) return;
|
|
230
|
+
try {
|
|
231
|
+
let alive = false;
|
|
232
|
+
const controller = new AbortController();
|
|
233
|
+
const timer = setTimeout(() => controller.abort(), 3000);
|
|
234
|
+
try {
|
|
235
|
+
const resp = await fetch(`${cfg.serverUrl}/health`, { signal: controller.signal });
|
|
236
|
+
alive = resp.ok;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
alive = false;
|
|
239
|
+
} finally {
|
|
240
|
+
clearTimeout(timer);
|
|
241
|
+
}
|
|
242
|
+
if (alive) return; // 已在跑(无论谁起的),不动
|
|
243
|
+
|
|
244
|
+
// 未在跑:拉起一次(fire-and-forget,不阻塞插件加载)
|
|
245
|
+
// 注意:PKG_DIR 是 lib/,scripts/ 在包根(上一级)
|
|
246
|
+
const script = path.join(PKG_DIR, "..", "scripts", "mem0-server.py");
|
|
247
|
+
const python = cfg.pythonPath || (process.platform === "win32" ? "python" : "python3");
|
|
248
|
+
let port = "18200";
|
|
249
|
+
try { port = new URL(cfg.serverUrl).port || "18200"; } catch (e) {}
|
|
250
|
+
const child = spawn(python, [script, "--port", port], { detached: true, stdio: "ignore" });
|
|
251
|
+
child.unref();
|
|
252
|
+
child.once("spawn", () => ctx.logger?.info(`[mem0-memory] mem0 server not detected, auto-started (pid=${child.pid ?? "?"})`));
|
|
253
|
+
child.on("error", (e) => ctx.logger?.warn(`[mem0-memory] server auto-start failed: ${e.message}`));
|
|
254
|
+
} catch (e) {
|
|
255
|
+
ctx.logger?.warn(`[mem0-memory] server auto-start skipped: ${e.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
223
259
|
export async function apply(ctx, config = {}) {
|
|
224
260
|
// 探针(排障用,默认关闭):设置 config.probePath 后,apply 执行即写文件——
|
|
225
261
|
// 用于区分「插件未加载」vs「加载了但 pre-step 未触发」。默认不写任何文件。
|
|
@@ -243,12 +279,17 @@ export async function apply(ctx, config = {}) {
|
|
|
243
279
|
source: config.source, // 可选
|
|
244
280
|
autoImport: config.autoImport !== false, // 会话结束自动增量导入(默认开,三档机制①)
|
|
245
281
|
excludeWorkspaces: config.excludeWorkspaces ?? [], // 可选:工作区名单子串(对应 G1 excluded-workspaces.json)
|
|
282
|
+
autoStartServer: config.autoStartServer !== false, // DSH 启动时确保 mem0 server 在跑(默认开)
|
|
283
|
+
pythonPath: config.pythonPath, // 可选:spawn server 用的 python 路径(默认 python/python3)
|
|
246
284
|
};
|
|
247
285
|
if (!cfg.enabled) {
|
|
248
286
|
ctx.logger?.info("[mem0-memory] disabled");
|
|
249
287
|
return;
|
|
250
288
|
}
|
|
251
289
|
|
|
290
|
+
// server ensure(fire-and-forget,不阻塞插件加载):探测不到就拉起一次
|
|
291
|
+
ensureServer(ctx, cfg).catch(() => {});
|
|
292
|
+
|
|
252
293
|
// ─── 会话结束自动增量导入(P2.4:session/disposed 钩子)───────────────
|
|
253
294
|
// fire-and-forget:不阻塞 dispose;子进程间有 .import.lock 互斥(并发安全)。
|
|
254
295
|
// import 脚本定位:插件包内 bin/dsh-mem0-import.mjs(无需用户配置路径)。
|
|
@@ -309,13 +350,21 @@ export async function apply(ctx, config = {}) {
|
|
|
309
350
|
// 注入:在最后一条 claimed 消息之后插入记忆块。
|
|
310
351
|
// 必须带消息 id 且 content 用文本块数组 —— DSH 的 user/message 事件校验要求
|
|
311
352
|
// message.id 为非空字符串、content 为块数组,缺失会把整个 session 持久化日志判为损坏。
|
|
353
|
+
//
|
|
354
|
+
// source.kind(2026-10-18 适配 DSH 0.2.x / session format v4):
|
|
355
|
+
// 0.2.x 的 v4 格式禁止 `kind:"plugin"` 这个 catch-all 包装
|
|
356
|
+
// (dsh-session-format-v3-to-v4 的 source() 校验:kind 必须非空且 ≠ "plugin"),
|
|
357
|
+
// 要求生产者声明自有 kind;V3→V4 迁移器对未知插件的映射结果就是 `plugin:<name>`,
|
|
358
|
+
// 此处直接用同一命名,保证新写记录与迁移后的历史记录同形。
|
|
359
|
+
// 兼容性:0.1.x 的写入门槛只要求「kind 是非空字符串」,`kind:"plugin"` 的强校验
|
|
360
|
+
// 仅针对 system/message,user/message 不受限制 → 该写法 0.1.x 与 0.2.x 同时合法。
|
|
312
361
|
const lastClaimedIndex = decision.messages.findLastIndex((message) => messages.includes(message));
|
|
313
362
|
await probe(`lastClaimedIndex=${lastClaimedIndex}`);
|
|
314
363
|
const injected = {
|
|
315
364
|
role: "user",
|
|
316
365
|
id: randomUUID(),
|
|
317
366
|
content: [{ type: "text", text: result.block }],
|
|
318
|
-
source: { kind: "plugin
|
|
367
|
+
source: { kind: "plugin:dsh-mem0-memory", form: "recall" },
|
|
319
368
|
};
|
|
320
369
|
const finalMessages = decision.messages.toSpliced(lastClaimedIndex + 1, 0, injected);
|
|
321
370
|
await probe(`injected! finalMessages=${finalMessages.length}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mem0-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Thin bridge: recall Mem0 memories into DSH agent context via agent/pre-step injection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
35
|
-
"@deepseek-ai/dsh-agent-loop": "^0.1.0-rc.6"
|
|
35
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.0-rc.6 || ^0.2.1-rc.2"
|
|
36
36
|
},
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"repository": {
|