dsh-mem0-memory 0.1.2 → 0.1.4

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 CHANGED
@@ -5,6 +5,17 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ### 0.1.4(2026-08-23)
9
+
10
+ - **server 自动拉起(autoStartServer)**:DSH 启动时插件探测 mem0 server 的 `/health`(3s 超时)——未在跑则自动 spawn 一次(`detached` 子进程,不随 DSH 退出);已在跑则不动。用户装了插件 = 期望「与平台同步启动」,忘记手动开 server 也能工作。
11
+ - 设计铁律:① 只探测 HTTP /health(端口有响应 = 在跑,无论谁起的);② 每个 DSH 进程最多 spawn 一次,失败仅 WARN 绝不重试(OpenClaw 侧 16 进程抢锁事故教训);③ server 常驻,DSH 退出不带走(最小化动作)。
12
+ - 配置新增:`autoStartServer: true`(默认开,可关)+ `pythonPath`(可选,spawn 用 python 路径,默认 `python`/`python3`)。
13
+
14
+ ### 0.1.3(2026-08-22)
15
+
16
+ - **工作区排除(召回侧)**:排除工作区的会话不再召回/注入记忆——插件通过 `ctx.agents.requireInitiator()` 获取当前会话 cwd,判定与 G1 写入侧一致:cwd 路径段 `0_` 前缀(写死约定)+ 新增 `excludeWorkspaces` 配置(名单子串匹配,对应 G1 excluded-workspaces.json)。排除工作区对话省去召回延时(每轮 5-10s),内容本就不入库。
17
+ - 配置新增:`excludeWorkspaces: []`(默认空;如 `["财富密码"]`)。
18
+
8
19
  ### 0.1.2(2026-08-22)
9
20
 
10
21
  - **降级可见性**:embedding API 故障触发关键词降级时,召回注入块现在会带警告行「⚠️ 注意:embedding 服务超时/故障,以下为降级关键词搜索结果(语义通道暂不可用),召回覆盖度可能下降。」——插件此前丢弃了 server 响应的 `degraded/mode` 标记,降级期间会话对结果质量下降毫不知情;已透传并对齐 OpenClaw 侧 mem0-memory 的 recall.js 实现。
package/README.md CHANGED
@@ -209,6 +209,9 @@ dsh-mem0-search "关键词" --json # 原始 JS
209
209
  | `source` | (空) | 只召回指定来源(多源场景) |
210
210
  | `probePath` | (空=关) | 排障探针日志文件路径(设置后才写文件) |
211
211
  | `autoImport` | true | 会话结束自动增量导入(关 = 只用手动/定时) |
212
+ | `excludeWorkspaces` | `[]` | 不召回的工作区名单(子串匹配会话 cwd;另 `0_` 前缀工作区写死排除) |
213
+ | `autoStartServer` | true | DSH 启动时确保 mem0 server 在跑(探测 /health,未在跑自动拉起一次;DSH 退出不带走 server) |
214
+ | `pythonPath` | (空) | 自动拉起 server 用的 python 路径(默认 `python`/`python3`;venv 用户可指定) |
212
215
 
213
216
  ## 工作原理
214
217
 
package/lib/index.js CHANGED
@@ -210,6 +210,52 @@ function extractLastUserText(messages) {
210
210
  export const name = "dsh-mem0-memory";
211
211
  export const inject = ["agents"];
212
212
 
213
+ // ─── 工作区排除(2026-08-22,与 G1 判定一致)────────────────────────────
214
+ // 排除工作区的会话:不召回也不注入(该区内容本就不入库,召回只会带来延时开销)。
215
+ // 判定:① cwd 路径任一段以 `0_` 开头(写死约定,与 G1 EXCLUDE_PREFIX 一致)
216
+ // ② config.excludeWorkspaces 名单子串匹配(对应 G1 excluded-workspaces.json)
217
+ function isExcludedWorkspace(cwd, list) {
218
+ if (!cwd) return false;
219
+ if (String(cwd).split(/[\\/]/).some((s) => s.startsWith("0_"))) return true;
220
+ return (list ?? []).some((e) => e && String(cwd).includes(String(e)));
221
+ }
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
+
213
259
  export async function apply(ctx, config = {}) {
214
260
  // 探针(排障用,默认关闭):设置 config.probePath 后,apply 执行即写文件——
215
261
  // 用于区分「插件未加载」vs「加载了但 pre-step 未触发」。默认不写任何文件。
@@ -232,12 +278,18 @@ export async function apply(ctx, config = {}) {
232
278
  timeoutMs: config.timeoutMs ?? MEM0_SEARCH_TIMEOUT_MS,
233
279
  source: config.source, // 可选
234
280
  autoImport: config.autoImport !== false, // 会话结束自动增量导入(默认开,三档机制①)
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)
235
284
  };
236
285
  if (!cfg.enabled) {
237
286
  ctx.logger?.info("[mem0-memory] disabled");
238
287
  return;
239
288
  }
240
289
 
290
+ // server ensure(fire-and-forget,不阻塞插件加载):探测不到就拉起一次
291
+ ensureServer(ctx, cfg).catch(() => {});
292
+
241
293
  // ─── 会话结束自动增量导入(P2.4:session/disposed 钩子)───────────────
242
294
  // fire-and-forget:不阻塞 dispose;子进程间有 .import.lock 互斥(并发安全)。
243
295
  // import 脚本定位:插件包内 bin/dsh-mem0-import.mjs(无需用户配置路径)。
@@ -267,6 +319,20 @@ export async function apply(ctx, config = {}) {
267
319
 
268
320
  if (decision.kind !== "enter" || !Array.isArray(decision.messages)) return decision;
269
321
 
322
+ // 工作区排除(2026-08-22):当前会话 cwd 属于排除工作区 → 跳过召回(不注入)。
323
+ // 判定与 G1 一致(0_ 前缀 + excludeWorkspaces 名单子串);拿不到 cwd 则不排除。
324
+ let cwd = "";
325
+ try {
326
+ const agent = ctx.agents?.requireInitiator?.();
327
+ cwd = agent?.session?.cwd ?? "";
328
+ } catch (e) {}
329
+ await probe(`workspace cwd=${cwd || "unknown"}`);
330
+ if (isExcludedWorkspace(cwd, cfg.excludeWorkspaces)) {
331
+ ctx.logger?.info(`[mem0-memory] workspace excluded (skip recall): ${cwd || "(unknown)"}`);
332
+ await probe("workspace excluded, skip recall");
333
+ return decision;
334
+ }
335
+
270
336
  try {
271
337
  const query = extractLastUserText(decision.messages);
272
338
  await probe(`query extracted: ${query ? query.slice(0, 80) : "NULL"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mem0-memory",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",