@modusensus/dsh-mneme 0.9.3 → 0.9.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 +56 -10
- package/bin/cli.mjs +215 -2
- package/docs/CONFIGURATION.md +10 -2
- package/docs/DAEMON.md +9 -2
- package/docs/DISTILL.md +66 -4
- package/docs/SHARE.md +552 -0
- package/docs/SLEEP.md +1 -1
- package/docs/STORAGE.md +1 -1
- package/lib/api-standalone.js +286 -8
- package/lib/api.js +254 -3
- package/lib/client.js +1000 -50
- package/lib/config.js +50 -1
- package/lib/document.js +15 -2
- package/lib/dream/decisions.js +88 -3
- package/lib/dream/sleep.js +63 -13
- package/lib/dream.js +66 -10
- package/lib/index.js +6 -1
- package/lib/inject.js +10 -25
- package/lib/lang.js +48 -6
- package/lib/memory-source.js +9 -0
- package/lib/organize.js +9 -3
- package/lib/pipeline.js +62 -1
- package/lib/profile-extract.js +161 -0
- package/lib/provenance-http.js +126 -0
- package/lib/serve.js +13 -0
- package/lib/service.js +70 -6
- package/lib/settings.js +17 -13
- package/lib/share-http.js +281 -0
- package/lib/share-import.js +234 -0
- package/lib/share.js +335 -0
- package/lib/store.js +272 -11
- package/lib/summarize.js +118 -8
- package/lib/tools.js +240 -1
- package/lib/user-settings.js +78 -0
- package/package.json +1 -1
- package/src/api-standalone.js +286 -8
- package/src/api.js +254 -3
- package/src/config.js +50 -1
- package/src/document.js +15 -2
- package/src/dream/decisions.js +88 -3
- package/src/dream/sleep.js +63 -13
- package/src/dream.js +66 -10
- package/src/index.js +6 -1
- package/src/inject.js +10 -25
- package/src/lang.js +48 -6
- package/src/memory-source.js +9 -0
- package/src/organize.js +9 -3
- package/src/pipeline.js +62 -1
- package/src/profile-extract.js +161 -0
- package/src/provenance-http.js +126 -0
- package/src/serve.js +13 -0
- package/src/service.js +70 -6
- package/src/settings.js +17 -13
- package/src/share-http.js +281 -0
- package/src/share-import.js +234 -0
- package/src/share.js +335 -0
- package/src/store.js +272 -11
- package/src/summarize.js +118 -8
- package/src/tools.js +240 -1
- package/src/user-settings.js +78 -0
- package/test/api.test.js +384 -3
- package/test/audit.test.js +2 -2
- package/test/cli-receive.test.js +390 -0
- package/test/cli-share.test.js +437 -0
- package/test/client-share.test.js +225 -0
- package/test/client.test.js +215 -6
- package/test/dedupe-candidate-window.test.js +180 -0
- package/test/dream-candidate-hybrid.test.js +2 -2
- package/test/dream-fence.test.js +164 -0
- package/test/dream.test.js +87 -5
- package/test/helpers/dream-mock.js +6 -2
- package/test/lang.test.js +11 -0
- package/test/llm-message-source.test.js +1 -1
- package/test/mint-source.test.js +1 -1
- package/test/organize-tool.test.js +232 -0
- package/test/persona-addressing.test.js +83 -0
- package/test/persona-settings.test.js +30 -8
- package/test/pipeline-r1r2.test.js +129 -0
- package/test/policy-epoch.test.js +2 -2
- package/test/profile-extract.test.js +269 -0
- package/test/provenance-http.test.js +184 -0
- package/test/readme-claims.test.js +45 -0
- package/test/reasoning-effort.test.js +10 -10
- package/test/scope-candidates.test.js +7 -4
- package/test/scope-state.test.js +130 -1
- package/test/serve.test.js +62 -0
- package/test/share-foreign-rows.test.js +144 -0
- package/test/share-http.test.js +144 -0
- package/test/share-import.test.js +577 -0
- package/test/share-imported-guard.test.js +311 -0
- package/test/share-imported-identity.test.js +111 -0
- package/test/share-pack.test.js +511 -0
- package/test/share-receive-http.test.js +756 -0
- package/test/share-routes.test.js +337 -0
- package/test/share-store.test.js +133 -0
- package/test/share-validate.test.js +101 -0
- package/test/standalone-api.test.js +524 -5
- package/test/summarize-parse-diagnostics.test.js +75 -0
- package/test/summarize.test.js +240 -2
- package/test/tools-dto-schema-matrix.test.js +48 -5
- package/test/tools.test.js +6 -1
- package/test/user-settings-preview.test.js +127 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
8
|
-
[](https://github.com/slow-stack/mneme)
|
|
9
9
|
[](https://github.com/slow-stack/mneme/actions)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
@@ -49,7 +49,8 @@ dsh web
|
|
|
49
49
|
- 逐 type 记录 `committed / failed / pending` 回执,健康端点区分 `ok / degraded / unknown`
|
|
50
50
|
- 状态写失败不静默:同步失败落日志并留债务,重启自动收敛
|
|
51
51
|
|
|
52
|
-
###
|
|
52
|
+
### 模型工具(默认注册 11 个;开 `organizeEnabled` 后 12 个)
|
|
53
|
+
|
|
53
54
|
|
|
54
55
|
| 工具 | 功能 |
|
|
55
56
|
|------|------|
|
|
@@ -61,7 +62,9 @@ dsh web
|
|
|
61
62
|
| `memory_delete` | 删除记忆(按记忆 ID 精确删除) |
|
|
62
63
|
| `memory_forget` | 抑制注入(降权不删除,可恢复) |
|
|
63
64
|
| `memory_archive` | 归档/恢复记忆(v0.2.5;归档后隐藏于列表/搜索/注入/整理,`archived=false` 可恢复) |
|
|
65
|
+
| `set_scope_state` | 按下/松开某个作用域的**可逆闸门**(#471:政策轴。暂停期间该作用域的记忆照旧存着,只是不注入、不参与巩固,松开即原样回来;不改任何一条记忆的标注,与上面逐条操作的 `memory_forget` / `memory_archive` 是两根不同的轴)。两级开关 `scopeState.enabled` / `.enforce` 默认都关,没开时按下不产生拦截效果 |
|
|
64
66
|
| `memory_register_document` | 登记 agent 产长文档的指针行(#230:摘要 + `doc_path` + evidence 落 `document` 类型;正文留在磁盘、由 agent 自管,注册口唯一,旁路写入被拒) |
|
|
67
|
+
| `memory_organize` | 主动整理(#231,**要 `organizeEnabled` 打开才注册**):`mode=dryRun` 先出比对报告(每条候选 `exact` / `near` / `new` + 命中行,**不写一行库**、只留审计回执),agent 判断后 `mode=apply` 引用那份报告的 `run_id` 落地。两种「不要它」要分清——**`discard`** = 这条候选不写,什么都不动(只进回执);**`archive`** = 把报告标为被取代的那条**既有行**归档(可恢复,绝不物理删除)。没有比对过的候选写不进库,一份报告也只许落地一次 |
|
|
65
68
|
| `memory_runtime` | 本地推理运行时的状态 / 供给 / 校验(v0.7.32,自管运行时三档取件) |
|
|
66
69
|
|
|
67
70
|
### 工作流(M.O.,v0.9 beta)
|
|
@@ -254,6 +257,8 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
254
257
|
|
|
255
258
|
图谱 ↔ 记忆双向互跳:图谱详情侧的关联记忆可点击,记忆边的「来源记忆」按 memory_id 直跳三栏视图并自动定位。
|
|
256
259
|
|
|
260
|
+
- **分享包三步(面板)**:「⋯」菜单里的**分享记忆包…**(报告先于下载:进包条数、同键折叠、六个跳过原因、可展开的跳过清单,超水位回 413 同屏)与**导入分享包…**(选文件 → 干跑预览将新建几条 / 撞名几条 → 确认才写库),外来行带 `from-<别名>` 徽标、工具条「外来」chip 按 `source=imported` 服务端筛、抽屉「收起这个包」两段式整包退出(先清点后物删)。口径与判据一条都不在面板里复述——出包在 `src/share.js`、入包闸门在 `src/share-import.js`、协议层在 `src/share-http.js`,细则与实测见 [docs/SHARE.md](docs/SHARE.md)
|
|
261
|
+
|
|
257
262
|
### 四路召回融合与会话热记忆 🔎(v0.5.0,v0.8.4 扩四路)
|
|
258
263
|
|
|
259
264
|
- **BM25 稀疏第三路召回**(`src/search/bm25.js`):与向量召回、FTS5/LIKE 关键词并列——ASCII 词元 + CJK bigram 分词、IDF 加权(归一化 [0,1]),专有名词 / ID / 代码片段等散词查询不再依赖子串命中。融合规则:未召回行按 `0.3×BM25分` 回填;仅向量召回行获得词法加分;LIKE 已命中行不叠分。`bm25SearchEnabled` 可关
|
|
@@ -275,6 +280,9 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
275
280
|
| **v0.9.1**(2026-10-18) | M.O. 从「看得见」跑到「跑得出账」:执行窗口由 `/mo-run start` / `/mo-run stop` 显式划开、运行账落 `<项目>/.mneme/pipelines/<id>.runs.jsonl`(账本坏了与从没跑过严格分开)、归属只出候选、观察旁挂与脱节检出、步骤 `assert` / `optional`、R1/R2 提议;沉淀链从真实会话日志挖 draft 工作流(判据恒 `null`,答案只能由人补);人格定义页 v1 → v1.2(按 agent 寻址多卡,默认关);面板、向量与检查更新的一圈修复(#448 / #450 / #460);Release 正文改分层渲染 |
|
|
276
281
|
| **v0.9.2**(2026-10-19) | 让「看不见的失败」显形:功能开关的第三态「待重启」把「面板显示已改 ≠ 运行时已改」摆到界面上(#481);作用域级可逆闸门让一路记忆能被单独按下与松开、不必改配置重启(#471,默认关);巩固用的 `dreamMaxTokens` 默认值撞服务商上限、八天 180 次全失败,面板现在能改、超上限还会自动降级重试(#478);CLI 把 `--` 开头的值当新旗标的真实缺陷(一次 1/4096 概率的 CI 红揪出来的)、人格卡删除 × 永远不可见、自动蒸馏写入的行 100% 拿不到向量(#477) |
|
|
277
282
|
| **v0.9.3**(2026-10-20) | M.O. 从「挖得出来」到「会被叫去挖」:沉淀链算法自 v0.9.1 起就完整实现(多帧 zstd → 重复序列 → 缺席对照 → draft 草稿),运行时却没有任何调用点——机器挖得出来、却永远不会被叫去挖。这一版把归属与触发接上:按会话头 `cwd` 反查项目桶(桶名是路径的有损变形,查不到整跑跳过而不是降级成全局普查)、挂 `turn/end`、冷却 10 分钟、单次预算 4 秒、延到宏任务、一次一个项目、抛错只 `warn`,**只产提议不产草稿**(`actionNudgeEnabled`,beta,默认关);面板加「现在沉淀一次」,与自动跑同一条入口,区别只有它不改仓库;另修作用域反查自 v0.8.0 起每次都抛错走降级(#487)、`/nudge` 三处「没看全」被报成「确实没有」(#489 评审)、多帧 zstd 反证断言绑了 Node 整块解语义(#490),并把整个 M.O. 的「手法」改回「工作流」 |
|
|
283
|
+
| **v0.9.4**(2026-10-21) | 三条链各自补上「最后一环」——东西早就造好了,只差没人能叫它:**agent 主动整理**(`memory_organize`,先 dryRun 出比对报告 → agent 判断 → apply 落库,筛除只归档不删、全程留审计回执;`organizeEnabled` 默认关,关时工具根本不注册);**画像提取草稿**(面板按钮读活跃记忆 + 一次模型调用,产一段「助手眼中的用户」草稿,`profileExtractEnabled` 默认关)——**只出草稿**,采用是用户再点一次,手写的画像原文不会被模型改写,空库与解析失败都直接报错、绝不回空草稿冒充「没提炼出东西」;**人格寻址收口**(`default` 卡以前无条件落给所有没建卡的身份,宿主里自带身份的 bot 每帧收到两句「你是谁」;`personaStrictAddressing` 默认关,把「没建卡」与「卡被显式关掉」都收回为不注入)。同版补蒸馏的**转录围栏 + 解析失败护栏**(#494:一条会话被同一窗口卡死、重试 20 次越试越贵的根因;失败行现在恒带原始输出形状),把面板自称「注入生效文本」的只读区改成后端单源渲染(并修它从不显示人格行),评审另收掉两处「坏了却不报」:整理报告侧的存在性探测口(`strictScope` 下按调用方可见性过滤落点并重算计数)、画像保存不判 `res.ok` 却提示已保存 |
|
|
284
|
+
| **v0.9.5**(2026-10-30) | 记忆能安全地递给别人,也能整包收回来:**分享包 `mneme.share/v1`**(`dsh-mneme share` / `receive` / `undo-share`,面板「⋯」里同一批三件事)只含活跃行,`agent_scope` / `workspace_scope` 与本机 home 前缀一律不进包,命中密钥 / PII 的行**整条不进**(不打码);出包先给报告(进包数 / 折叠数 / 六个跳过原因)再给文件,3 MiB 水位量在紧凑序列化的字节上,超限回 413 与同一份报告,分批靠 `--type` / `--tag`;入包一条不合格**整包拒**、干跑是默认、按 `share_external_id` 幂等,整包退出是物删而不是归档(归档会占住去重键,重导即撞)。「本机巩固不许改写别人导入的原文」由单一谓词 `isImportedRow` 守在 `saveWithDedupe` 汇流口与九个行池上。同版给记忆来源加了读出口 `GET /memory/provenance`(#421,@z2Ace0107 实现,依附既有 `distillRecords.enabled`:409 = 用户关了、503 = 装配坏了),修掉同标题记忆被重复写第二份(#505)、模式扫描谎报「没东西可扫」(#515)、五处巩固 prompt 裸放导致的 46K token 空转(#501 / #493)与 dream 合并护栏的整类误拦(#521),面板新增设置页粘顶分组目录(#518)与宿主极简预设的在场说明(#514) |
|
|
285
|
+
|
|
278
286
|
| **v0.8.x** | 作用域隔离(agent / workspace 双维 + opt-in 硬过滤)、冲突队列人工裁决、生态化(stdio MCP server / 图召回轴 / 冷启动 / 注入截断) |
|
|
279
287
|
|
|
280
288
|
## 📦 安装
|
|
@@ -371,6 +379,7 @@ dsh plugin --profile web add "C:/path/to/modusensus-dsh-mneme-0.9.0.tgz"
|
|
|
371
379
|
| `summarizeMaxEntriesPerRun` | `0` | 单次蒸馏产出记忆条数上限(0-50,0=不限):节流防单会话大量重复条目(#127) |
|
|
372
380
|
| `summarizeMinWindowChars` | `0` | 蒸馏前零 LLM 预判:窗口可蒸馏文本不足此字符数直接跳过调用(0-100000,0=关);skip 原因写入审计(#239) |
|
|
373
381
|
| `summarizeMaxRunsPerSession` | `0` | 每会话最多发起多少次蒸馏 LLM 调用(0-1000,0=不限);只计真实调用,被预判拦下的不占额度(#239) |
|
|
382
|
+
| `summarizeParseFailureMaxRetries` | `0` | 连续几次解析失败后放弃当前窗口(0-10,0=关):确定性失败(模型把转录当成该自己回话,`finish_reason=stop`)重试收益为 0,而窗口只会越攒越大;设 2-3 可止血,达到阈值强制消费游标跳过,失败行 metadata 带 `raw_head`/`raw_has_bracket`/`streak`(#493) |
|
|
374
383
|
| `summarizePeakHours` | 空 | 高峰时段(本地时间,逗号分隔、可带星期前缀,支持跨零点):空=关;命中时蒸馏顺延到非高峰、窗口累积后一次蒸、skip 原因入审计。例:按高峰计费的供应商可写 `mon-fri 08:00-12:00,14:00-18:00`(如 DeepSeek,以其官方定价页为准)(#239) |
|
|
375
384
|
| `summarizePeakMaxDeferMinutes` | `120` | 高峰顺延上限(0-1440 分钟,0=不设上限):到点仍处高峰就照常跑,避免长高峰把蒸馏饿死(#239) |
|
|
376
385
|
| `summarizeDedupeMode` | `off` | 落库前去重档位:`off`(默认=现状)/ `title`(零成本,仅拦完全同名)/ `vector`(复用 embedding 列做同会话语义近邻,无 LLM 调用,#127) |
|
|
@@ -498,7 +507,7 @@ dsh plugin --profile web add "C:/path/to/modusensus-dsh-mneme-0.9.0.tgz"
|
|
|
498
507
|
| `conflictFreezeMaxPending` | `100` | 冲突冻结队列最大挂起数(1-1000):超出后不再入队(防队列无限膨胀) |
|
|
499
508
|
| `escapePromptVariables` | `true` | 注入文本花括号转义(v0.8.0 恢复,issue #162):注入边界把 `{{...}}` 转义,防止 hot memory / 记忆原文里的 Go template / Vue 语法触发宿主 interpolate 抛错卡死会话(v0.7.4 曾修复、v0.7.11 误删) |
|
|
500
509
|
|
|
501
|
-
> 🔐 **API 安全**:DSH 无内置鉴权且默认仅监听 `127.0.0.1`。插件 API 默认开放(便于 Web 面板即装即用)——**未配 `apiToken` 时读和写都开放**,真正的围栏是回环 + 宿主监听器的 Host/Origin 校验(它防 DNS rebinding 与跨站浏览器请求,但不建立身份)。如需防护(如局域网暴露),在配置中设置 `apiToken
|
|
510
|
+
> 🔐 **API 安全**:DSH 无内置鉴权且默认仅监听 `127.0.0.1`。插件 API 默认开放(便于 Web 面板即装即用)——**未配 `apiToken` 时读和写都开放**,真正的围栏是回环 + 宿主监听器的 Host/Origin 校验(它防 DNS rebinding 与跨站浏览器请求,但不建立身份)。如需防护(如局域网暴露),在配置中设置 `apiToken`:此时**写操作**(新增/更新/删除/导入/收包(`/receive`、`/receive/undo`——写的是**本机自己的库**,归 `token-write`,所以免 token 的 GET 在那两条路径上只会拿到 405)/画像/规则/命令/开关/开关计费类)与**敏感读**(`/export`、`/external-api`、`/vector-config`、`/vector-reindex`、`/maintenance/*`、`/runtime/provision`、`/share`、`/memory/provenance`(蒸馏窗口的逐条会话原文,另受 `distillRecords.enabled` 门控))需携带 `Authorization: Bearer <token>`(前端设置面板填同一个 token);只读的 `list` / `search` / `semantic` / `inject-preview` 等**仍然开放**,不要把它们当成被 token 保护的面。`/api/dsh-mneme/vector-config` 返回的 `apiKey` 已掩码(`sk-***…`),存储仍保留明文供调用;前端回传空或掩码值表示"不改 key"。
|
|
502
511
|
>
|
|
503
512
|
> 安全模型全表(哪些路由免鉴权、为什么)见 [SECURITY.md](../SECURITY.md#5-security-model-what-actually-protects-what)。
|
|
504
513
|
|
|
@@ -524,14 +533,36 @@ dsh plugin --profile web add "C:/path/to/modusensus-dsh-mneme-0.9.0.tgz"
|
|
|
524
533
|
| `GET` | `/profile` | 用户画像,返回 `{profile:"..."}`;未设置时为空字符串 |
|
|
525
534
|
| `GET` | `/rules` | 行为规则列表,返回 `{rules:[...]}`;未设置时为空数组 |
|
|
526
535
|
| `GET` | `/memories?limit&offset&type&minImportance&source&order=chrono&include_archived&occurred_from&occurred_to` | 分页列出记忆(`include_archived=true` 含归档;occurred 时间窗按 `occurred_at` 回退 `created_at`) |
|
|
536
|
+
| `GET` | `/memory/provenance?id=<memoryId>` | 记忆级溯源读出口(#399 写、#421 读;宿主内面板路由 `/api/dsh-mneme/memory/provenance` 同一口径):一条溯源链接一个窗口 `{session_id, seq_start, seq_end, created_at, records[]}`,`records` 是那段蒸馏窗口的**逐条会话原文**(按 `seq` 升序、不越出区间)。没有链接的记忆返回**空 `windows`**——「不是蒸馏来的」是有效答案,不是错误。状态码阶梯:`401` 无 token → `400` 缺 `id` → `409 {"error":"distill-records-disabled"}`(`distillRecords.enabled` 关着;该开关是**启动快照**,面板改完要到下次启动生效,面板会显示「待重启」)→ `503 {"error":"provenance-unavailable"}`(装配没接线)→ `200`。`409` 与 `503` 必须分开读数:前者是「用户关了」,后者是「装配坏了」。**关开关 ≠ 删掉已留存原文**——只是不再经 HTTP 提供,盘上的行要 `dsh-mneme reclaim`(或重新开启后的启动裁剪)才走,详见 [docs/DISTILL.md](docs/DISTILL.md) |
|
|
527
537
|
| `GET` | `/memories/:id` | 单条记忆 |
|
|
528
538
|
| `POST` | `/memories` | 新增记忆 `{type,title,content,importance?,tags?,source?,sensitivity?,occurred_at?,agent_scope?,workspace_scope?}`;响应含 `action: created\|merged` |
|
|
529
539
|
| `PUT` | `/memories/:id` | 局部更新 `{title?,content?,type?,importance?,tags?,reason?,agent_scope?,workspace_scope?}`;content 改写按 human_override 入档 |
|
|
530
540
|
| `DELETE` | `/memories/:id` | 删除记忆 |
|
|
531
541
|
| `GET` | `/search?q&mode=keyword\|vector\|hybrid\|auto&topK&occurred_from&occurred_to&agent_scope&workspace_scope` | 搜索(关键词 / 向量 / hybrid=向量领位关键词补位 / 自动)。scope 两参声明调用方身份(不传 = 不解析,行为与旧版一致;任一给出则 strictScope 硬过滤与 A2 软加权随之生效) |
|
|
532
|
-
| `GET` | `/context?q&topK&threshold&agent_scope&workspace_scope` | 注入候选一站式聚合(issue #370,#363 承诺):宿主注入管线 `injectCandidates` 全语义(优先级分层 / 编码门控 / heat / scope 软加权 / hybrid 语义路 / pin 前置)+ 画像 + 规则,返回 `{profile, rules, items, pinnedCount}` 结构化条目,不含渲染文本。`threshold` 是 **importance 阈值**(默认 3,与 `/search` 的相似度阈值同名不同义);`topK` 默认 5(对齐宿主 `maxInjectedItems`);scope 语义与 `/search` 同口径;跨轮轮换 / 热记忆 / 时间前缀不含(调用方自管);recall_runs 注入分账与宿主注入同口径(#217 `mode='inject'`) |
|
|
542
|
+
| `GET` | `/context?q&topK&threshold&agent_scope&workspace_scope` | 注入候选一站式聚合(issue #370,#363 承诺):宿主注入管线 `injectCandidates` 全语义(优先级分层 / 编码门控 / heat / scope 软加权 / hybrid 语义路 / pin 前置)+ 画像 + 规则,返回 `{profile, rules, items, pinnedCount, tier}` 结构化条目,不含渲染文本。`threshold` 是 **importance 阈值**(默认 3,与 `/search` 的相似度阈值同名不同义);`topK` 默认 5(对齐宿主 `maxInjectedItems`);scope 语义与 `/search` 同口径;跨轮轮换 / 热记忆 / 时间前缀不含(调用方自管);recall_runs 注入分账与宿主注入同口径(#217 `mode='inject'`) |
|
|
533
543
|
| `POST` | `/bootstrap` | 冷启动(v0.8.4):从仓库目录反向构建初始记忆,body `{dir}` 必填;幂等((type,title,scope) 去重 + `_overwrite` 原地刷新),零 LLM、必有产出 |
|
|
534
|
-
| `POST` | `/maintenance/reclaim` | 无损回收(#275 / #411,与面板同口径):**只有 POST
|
|
544
|
+
| `POST` | `/maintenance/reclaim` | 无损回收(#275 / #411,与面板同口径):**只有 POST**(本路径不提供 GET 预估分支)。字段只从 JSON body 读;`dryRun` 默认 `true`,只有 `dryRun:false` 且 `confirm:true` 才真删;`vacuum` 只允许显式指定 |
|
|
545
|
+
| `POST` | `/share` | 记忆分享包:把活跃记忆按口径打成可外发的 JSON 包,纯读不写库;取行与跳过口径、脱敏规则与 3 MiB 体积水位见 [docs/SHARE.md](docs/SHARE.md)。本路径的 GET 一律 405 |
|
|
546
|
+
| `POST` | `/receive` | 吃进一份分享包(body `{pack, confirm?}`,包内容整个送上来、服务端不读路径):**默认干跑**,只有 `confirm: true` 才写库;整包一个事务、按 `share_external_id` 幂等(同一个包重跑 `created = 0`),外来行标 `source=imported`,不参与本机巩固/降级。包里一条不合格就**整包拒**(收侧不做逐条修复,也不跑敏感扫描)。入站上限 4 MiB;GET 一律 405。细则与实测见 [docs/SHARE.md](docs/SHARE.md) |
|
|
547
|
+
| `POST` | `/receive/undo` | 整包退出(body `{packId, confirm?}`,同样**默认干跑**只清点):按 `share_pack_id` **物删**(不是归档——归档行会占住 UNIQUE 位,撤了反而导不回来),`deleted` 是全量、`sample` 只给前 20 条预览;什么都没导入时答 0,不抛。GET 一律 405 |
|
|
548
|
+
|
|
549
|
+
#### `/context` 的 `tier` 档位信号(#383)
|
|
550
|
+
|
|
551
|
+
`GET /context` 的响应多一个 `tier` 字段,回答「这次注入跑在哪一档」。嵌入降级在调用方视角原本完全静默——`items` 看着正常,只是召回质量悄悄变差,外部集成方无从感知自己长期跑在降级档上。
|
|
552
|
+
|
|
553
|
+
| `tier` | 含义 |
|
|
554
|
+
|--------|------|
|
|
555
|
+
| `full` | 语义档:查询嵌入成功,且与库里的向量维度可比 |
|
|
556
|
+
| `degraded` | 规则档:语义路本次不可用(原因见日志) |
|
|
557
|
+
| `unknown` | 语义路未启用(`q` 为空),或判不出来(维度探针没跑成 / 一条向量都读不出)——那不是降级,但也不宣称 `full` |
|
|
558
|
+
|
|
559
|
+
**两套消费者的分工:** `tier` 是**响应读数**,给调用方——三态、fail-closed(判不出来报 `unknown`,不报 `full`),用于在集成侧告警或切换策略。降级的**具体原因**是**日志读数**,给运维——走 daemon 的 stderr(`dsh-mneme: /context degraded to the rule tier (<reason>)`),不进响应体。原因枚举六值,互不吞并(成例是「修法不同就分开」):`embed-unavailable`(无可用嵌入接口:embedder 对象在,上面没有 `embedSingle` / `embed`)/ `embed-empty`(返回了空或非数组向量)/ `embed-timeout`(接口挂起,被超时护栏按缺失处理)/ `embed-error`(接口抛错)/ `embed-dimension`(接口成功了,但库里没有一条维度相符的活跃向量)/ `no-embedder`(压根没装配 embedder——daemon `--embed off`、lightMode、宿主未装配;**这是预期态,不是故障**)。六值的唯一真源是 `src/api-standalone.js` 导出的 `CONTEXT_DEGRADE_REASONS`,契约测试从它派生断言集合。
|
|
560
|
+
|
|
561
|
+
`embed-dimension` 值得单说:它是「拿到了向量却不可比」——`searchVector` 对维度不符的行直接跳过,不抛错、只是命中为零,于是召回静默退回关键词/规则档,而嵌入侧一切正常。修法是重建索引,不是查端点,所以它必须和前四个分开。`no-embedder` 与前四个也分开:前者(`embed-unavailable`)是适配器坏了、要修代码,后者是语义路没开、要把嵌装扮上或开起来,主体与动作都不同;并成一个值会让按 `embed-unavailable` 告警的面板对每个轻量模式安装报警。用词则与宿主面 `/api/dsh-mneme/semantic` 的 `reason` 对齐——同一个物理条件在两个面叫同一个名字(那边是另一套枚举、另一个消费者,别顺手统一)。
|
|
562
|
+
|
|
563
|
+
**`unknown` 之下的判档故障另有一条 warn:** `unknown` 有三个生产者(空 `q` / 维度探针没跑成 / 有行却一条向量都读不出来),响应体只有 `tier` 一个键、分不出它们,而调用方对 `unknown` 的策略常常是「不告警」——所以判不出来的情形会另打一行 `dsh-mneme: /context tier left unknown: <what>`(探针抛错 / 没有可读的向量可比)。**它不是上面那六值的一员**(`tier` 不是 `degraded`,没有降级原因可记),运维程序不要按原因枚举解析它。
|
|
564
|
+
|
|
565
|
+
**预算:** 判档不是免费的。`q` 非空且装配了 embedder 时,一次 `/context` 真的会起一次查询嵌入(一次网络往返 + `embedTimeoutMs` 的超时窗口)——档位要回答的正是「这次嵌入成不成」。这里没有复用 `/health` 的数据源:standalone 的 `/health` 只回 `{ok:true}`,而 `embeddingDimensionBreakdown` 是宿主面的一次全表扫(每行 JSON.parse 一个向量),按请求跑会把语义路成本翻倍。维度探针因此走 `service.vectorDimensionState()`——**按匹配短路的存在性判据**(有没有一条活跃向量的维度等于查询维度),不是「读一行的维度」:混合维度库里 `LIMIT 1` 会抽错维(明明有可比的向量却报 `degraded`),首条恰好解析失败时又会漏报成 `full`。成本是一次无 `LIMIT` 的全 id 扫描 + 按匹配短路的解析:全库同维度(常态)时循环第一行就返回,只解析 1 个向量;只有一条都不匹配时才走完整个循环,而那正是要报 `degraded` 的情形,值得。解析复用 `getParsedEmbedding` 的缓存,缓存热不热取决于 `injectCandidates` 是否真的走了 vectorIndex 分支。
|
|
535
566
|
|
|
536
567
|
### curl 示例
|
|
537
568
|
|
|
@@ -576,9 +607,16 @@ dsh-mneme add --type decision --title "采用 SQLite" \
|
|
|
576
607
|
dsh-mneme get 42 # 查看单条
|
|
577
608
|
dsh-mneme delete 42 # 删除
|
|
578
609
|
dsh-mneme config show # 查看当前配置(token 打码)
|
|
610
|
+
dsh-mneme share --dry-run # 分享包:只看会进几条、跳过几条,不落盘
|
|
611
|
+
dsh-mneme share --out pack.json # 写一个可外发的脱敏包(3 MiB 水位在源头拒)
|
|
612
|
+
dsh-mneme receive pack.json # 吃包:默认干跑,一个字都不写
|
|
613
|
+
dsh-mneme receive pack.json --apply # 确认后真的吃进自己的库(整包一个事务,可重跑)
|
|
614
|
+
dsh-mneme undo-share pack.json --apply # 按 pack_id 整包收回(物删,不是归档)
|
|
579
615
|
```
|
|
580
616
|
|
|
581
617
|
> 所有读取/写入命令支持 `--json` 输出原始 JSON;`config path` 打印配置文件路径(`~/.dsh-mneme/cli.json`)。
|
|
618
|
+
> 人类可读档只转达服务端文案里的 `hint` / `bytes` / `limitBytes`;闸门的 `detail`(坏在哪一格)与
|
|
619
|
+
> 413 的 `limit` 要 `--json` 才看得见——排查收包失败时先加它。
|
|
582
620
|
|
|
583
621
|
### 独立服务(daemon,`dsh-mneme-serve`)
|
|
584
622
|
|
|
@@ -592,7 +630,7 @@ dsh-mneme-serve --embed off # 纯关键词 + BM
|
|
|
592
630
|
|
|
593
631
|
- **鉴权与端口**:Bearer token 与 DSH 面板 / CLI 共用同一份(kv `external_api`,首次启动自动生成并持久化到 `memory.db`);端口/主机解析链与「外部访问」一致(显式参数 > 持久值 > 默认 8790/127.0.0.1)。配置端口被占会**直接报错退出**(不做端口顺延)——第三方把 URL 写死,静默换端口等于坏。因此 **daemon 与 DSH 的「外部访问」二选一**,不要同端口同开。
|
|
594
632
|
- **安全**:daemon 使用明文 HTTP,不提供原生 TLS。指定非回环 `--host` 时,请勿直接把服务暴露给不可信网络;远程访问请走 TLS 终止代理或 SSH 隧道。
|
|
595
|
-
- **能力边界(第一期,无 LLM)**:存储 / 检索(关键词 + BM25 + 向量)/ 镜像同步与人改合并 / `POST /maintenance/reclaim` / `/bootstrap` 全可用;巩固(autoDream)与蒸馏不在 daemon 内——巩固只属于 DSH 宿主进程,这是与宿主「单写者」的机械保证。`--embed` 默认 `local`(自管 runtime 与嵌入模型缺失时自动取件,约 200MB;失败降级关键词并打日志),也可选 `ollama` / `openai`(读宿主面板的 vector-config)/ `off`。
|
|
633
|
+
- **能力边界(第一期,无 LLM)**:存储 / 检索(关键词 + BM25 + 向量)/ 镜像同步与人改合并 / `POST /maintenance/reclaim` / `POST /receive`(吃包入库与整包退出:结构落库 + 随提交排空嵌入,**不建实体**——外来行本来就不做抽取,见 [docs/SHARE.md](docs/SHARE.md))/ `/bootstrap` 全可用;巩固(autoDream)与蒸馏不在 daemon 内——巩固只属于 DSH 宿主进程,这是与宿主「单写者」的机械保证。`--embed` 默认 `local`(自管 runtime 与嵌入模型缺失时自动取件,约 200MB;失败降级关键词并打日志),也可选 `ollama` / `openai`(读宿主面板的 vector-config)/ `off`。
|
|
596
634
|
- **检索回执**:daemon 的 `/search` 同样落 `recall_runs`,第三方检索的复用统计不缺数。
|
|
597
635
|
- **生命周期**:stdout 仅就绪时打一行 `dsh-mneme-serve listening on http://host:port (pid N)`(供脚本解析实际端口),日志走 stderr;SIGINT/SIGTERM 优雅收库,Windows 强杀由 WAL 回放兜底。
|
|
598
636
|
- **已知限制**:与 DSH 同时运行属设计内场景(WAL 多进程并发),但去重是先查后写、库层无 UNIQUE 约束,双进程并发写同一 `(type, title, scope)` 有极小概率产生重复;daemon 与插件请同版本升级。细节与坑清单见 [docs/DAEMON.md](docs/DAEMON.md)。
|
|
@@ -642,7 +680,7 @@ Claude Code 挂载示例(项目根 `.mcp.json`;token 在面板「设置 →
|
|
|
642
680
|
│ 服务层:saveWithDedupe / injectCandidates │
|
|
643
681
|
│ / mergeHumanEdits / onWrite 钩子 │
|
|
644
682
|
├─────────────────────────────────────────────────┤
|
|
645
|
-
│ 模型接口:
|
|
683
|
+
│ 模型接口:11 个工具 + 自动注入 + 会话摘要 │
|
|
646
684
|
├─────────────────────────────────────────────────┤
|
|
647
685
|
│ autoDream:阈值调度 → LLM 决策清单 │
|
|
648
686
|
│ → 校验(fail-safe)→ 应用 → 摘要 │
|
|
@@ -660,7 +698,10 @@ src/
|
|
|
660
698
|
├── service.js # 领域逻辑(去重合并、注入筛选、写入钩子、scope 加权/硬过滤、冲突队列)
|
|
661
699
|
├── scope.js # 作用域解析(agentPreset + workspace registry 反查,issue #17)
|
|
662
700
|
├── config.js # schemastery 配置 schema
|
|
663
|
-
├── tools.js #
|
|
701
|
+
├── tools.js # 11 个模型工具(defineTool;memory_organize 要 organizeEnabled 打开才注册,开了是 12 个)
|
|
702
|
+
├── user-settings.js # 「用户设置」注入块(人格 → 画像 → 规则)的唯一渲染处,注入与面板预览共用
|
|
703
|
+
├── profile-extract.js # 从活跃记忆提取「助手眼中的用户」画像草稿(只出草稿,不写 user_settings)
|
|
704
|
+
├── organize.js # agent 主动整理:dryRun 比对报告 → agent 判断 → apply 落库(筛除=归档不删,留 dream_runs 回执)
|
|
664
705
|
├── inject.js # systemPrompt.context 动态注入(含花括号转义)
|
|
665
706
|
├── summarize.js # 会话结束 LLM 摘要
|
|
666
707
|
├── dream.js # autoDream 调度 + runDream(LLM 决策 + 摘要)
|
|
@@ -676,7 +717,7 @@ src/
|
|
|
676
717
|
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
|
|
677
718
|
└── index.js # 插件接线
|
|
678
719
|
lib/ # src 的同步分发产物(npm run sync,prepack 钩子在发布时自动跑;一致性由 scripts/check-sync.js 在 CI 与 release.yml 校验——它只查不同步;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
|
|
679
|
-
test/ #
|
|
720
|
+
test/ # 2541 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
|
|
680
721
|
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
|
|
681
722
|
```
|
|
682
723
|
|
|
@@ -685,7 +726,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
|
|
|
685
726
|
```bash
|
|
686
727
|
cd dsh-mneme
|
|
687
728
|
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
|
|
688
|
-
npm test # 运行
|
|
729
|
+
npm test # 运行 2541 个测试
|
|
689
730
|
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
|
|
690
731
|
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
|
|
691
732
|
```
|
|
@@ -707,6 +748,7 @@ npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动
|
|
|
707
748
|
- [连续性抢救(#249 N3):触发器口径与事件契约](docs/CONTINUITY.md)
|
|
708
749
|
- [Sleep Mode:空闲四阶段深度维护](docs/SLEEP.md)
|
|
709
750
|
- [存储与回收:表结构、保留期、`dsh-mneme reclaim`](docs/STORAGE.md)
|
|
751
|
+
- [记忆分享包:出包与收包的口径、信任边界与实测(`dsh-mneme share` / `receive` / `undo-share`)](docs/SHARE.md)
|
|
710
752
|
- [独立服务 daemon(`dsh-mneme-serve`)](docs/DAEMON.md)
|
|
711
753
|
- [与 Zep / Graphiti / Mem0 / Letta / Hindsight 的能力对比](../docs/COMPARISON.md)
|
|
712
754
|
|
|
@@ -735,3 +777,7 @@ dsh-mneme 的机制设计与下列公开研究相互印证(以下引用均逐
|
|
|
735
777
|
## 📜 License
|
|
736
778
|
|
|
737
779
|
MIT
|
|
780
|
+
|
|
781
|
+
## 🤝 参与之前
|
|
782
|
+
|
|
783
|
+
行为守则:[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)。它管三件事——**署名归人**(模型不进作者行)、**责任落在签下作者行的人身上**、**发言权按证据分配,不按身份**。参与 issue / PR / 评审即受其约束;使用本插件不受约束,MIT 已经授权。
|
package/bin/cli.mjs
CHANGED
|
@@ -77,6 +77,18 @@ const USAGE = `${CLI_NAME} —— DSH 记忆插件(@modusensus/dsh-mneme)外
|
|
|
77
77
|
--vacuum 执行后整库 VACUUM(需配 --apply)
|
|
78
78
|
只清「可由记忆库重建的输入快照」与「归档行的向量」,
|
|
79
79
|
一条记忆都不删;收益按 VACUUM 前后体积量
|
|
80
|
+
share [--alias 名字] [--type a,b] [--tag 标签] --out <文件>
|
|
81
|
+
打一个可外发的脱敏分享包(不改库,纯读)
|
|
82
|
+
--alias 名字 在包里署名,对方看到的是这个而不是你的路径
|
|
83
|
+
--type/--tag 收窄到某几类/某个标签(超限分批用这两个)
|
|
84
|
+
--out <文件> 写出包文件;--dry-run 只报数不落盘
|
|
85
|
+
跳过归档/已遗忘/只读指针行/summary,密钥 PII 整条不进包
|
|
86
|
+
receive <包文件> [--apply]
|
|
87
|
+
把对方 share 出来的包吃进自己的库(默认干跑,一个字都不写)
|
|
88
|
+
外来行标 source=imported,不参与本机的巩固/降级,也不并入你的条目
|
|
89
|
+
--apply 才真的写;重复吃同一个包不会多出第二份
|
|
90
|
+
undo-share <包文件> [--apply]
|
|
91
|
+
按 pack_id 收起整包(物删:包还能重新导入,归档位会占住 UNIQUE)
|
|
80
92
|
|
|
81
93
|
选项:
|
|
82
94
|
--url <url> 覆盖服务地址
|
|
@@ -98,7 +110,12 @@ const USAGE = `${CLI_NAME} —— DSH 记忆插件(@modusensus/dsh-mneme)外
|
|
|
98
110
|
${CLI_NAME} get 42
|
|
99
111
|
${CLI_NAME} delete 42
|
|
100
112
|
${CLI_NAME} reclaim (先看:会清几行、库多大)
|
|
101
|
-
${CLI_NAME} reclaim --apply --vacuum
|
|
113
|
+
${CLI_NAME} reclaim --apply --vacuum (确认后执行,并把空间真的收回来)
|
|
114
|
+
${CLI_NAME} share --dry-run (先看:会进几条、为什么跳过那些,不落盘)
|
|
115
|
+
${CLI_NAME} share --out pack.json (写包;~/… 由 CLI 自己展开,PowerShell/cmd 不会)
|
|
116
|
+
${CLI_NAME} receive pack.json (吃包:默认干跑,只报账,一个字都不写)
|
|
117
|
+
${CLI_NAME} receive pack.json --apply (确认数字后真的吃进自己的库;重跑同一个包 created=0)
|
|
118
|
+
${CLI_NAME} undo-share pack.json --apply (按包里那个 pack_id 整包收回:物删,之后还能重新导入)`;
|
|
102
119
|
|
|
103
120
|
/* ---------------------------------- 参数解析 ---------------------------------- */
|
|
104
121
|
|
|
@@ -124,8 +141,10 @@ const KNOWN_FLAGS = new Set([
|
|
|
124
141
|
'url', 'token', 'type', 'title', 'content', 'importance', 'tags', 'source',
|
|
125
142
|
'limit', 'offset', 'mode', 'topk', 'top-k', 'min-importance', 'minimportance',
|
|
126
143
|
'older-than', 'olderthandays',
|
|
144
|
+
'alias', 'tag', 'out',
|
|
127
145
|
// 布尔旗标
|
|
128
|
-
'apply', 'vacuum', 'json', 'help', 'version'
|
|
146
|
+
'apply', 'vacuum', 'json', 'help', 'version',
|
|
147
|
+
'dry-run', 'dryrun',
|
|
129
148
|
]);
|
|
130
149
|
|
|
131
150
|
/** `--foo=bar` / `--foo` 取出小写键名;非旗标词返回 null。 */
|
|
@@ -246,6 +265,24 @@ function failHttp(cfg, status, pathname, data, text) {
|
|
|
246
265
|
} else if (status === 404) {
|
|
247
266
|
hint = `\n资源不存在:${pathname}`;
|
|
248
267
|
}
|
|
268
|
+
// 拒答体自己带的下一步必须转出去,不然命令侧只能再写一份错误分支。撞上的这一刀是
|
|
269
|
+
// share 的 413:响应体是 {error:"too-large", bytes, limitBytes, hint, report},
|
|
270
|
+
// 旧实现只取 data.error,于是现场只剩「请求失败(HTTP 413):too-large」——「大了
|
|
271
|
+
// 多少」与「用 --type/--tag 分批」那两句留在体里没人看得见(措辞只许有一份,就在
|
|
272
|
+
// 拒发的那侧,同 RECLAIM_CONFIRM_HINT 的口径)。完整 report(含 skip_list)走
|
|
273
|
+
// --json:下面 rawJson 把整个响应体原样交给 stderr,这里不复制第二份打印器。
|
|
274
|
+
//
|
|
275
|
+
// 已知取舍(C10 记档,见 docs/SHARE.md「已知还没做」):这里只转 hint / bytes /
|
|
276
|
+
// limitBytes 三样,所以 receive 那侧 400 的 `detail`(闸门指到哪一格)与数据面 413 的
|
|
277
|
+
// `limit` 到不了人类可读的输出。刻意不补:补一格就得在这里多一条按路由分支的判断,
|
|
278
|
+
// 而「错误文案只许有一份」是 #411 那条纪律的另一半。要逐格看就加 --json——rawJson
|
|
279
|
+
// 那行给的正是原样响应体,一格都不丢。
|
|
280
|
+
if (data && typeof data === 'object') {
|
|
281
|
+
if (typeof data.hint === 'string' && data.hint.trim()) hint += `\n${data.hint.trim()}`;
|
|
282
|
+
if (Number.isFinite(data.bytes) && Number.isFinite(data.limitBytes)) {
|
|
283
|
+
hint += `\n量到 ${formatBytes(data.bytes)},水位 ${formatBytes(data.limitBytes)}`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
249
286
|
fail(cfg, `请求失败(HTTP ${status})${serverError ? `:${serverError}` : ''} [${pathname}]${hint}`, {
|
|
250
287
|
rawJson: cfg.jsonMode && text ? text : undefined,
|
|
251
288
|
});
|
|
@@ -687,6 +724,176 @@ async function cmdReclaim(cfg, flags) {
|
|
|
687
724
|
}
|
|
688
725
|
}
|
|
689
726
|
|
|
727
|
+
// --out 的例子写的是 `~/Desktop/pack.json`。Git Bash 会替她展开,PowerShell 与 cmd
|
|
728
|
+
// 不会——那两侧 `~/…` 是字面量,writeFileSync 直接 ENOENT,现场只剩一坨堆栈。CLI 不
|
|
729
|
+
// 接管 shell 的活,但引号里这一格得自己补(同 CONFIG_DIR 走 os.homedir())。
|
|
730
|
+
function expandHome(p) {
|
|
731
|
+
if (typeof p !== 'string') return p;
|
|
732
|
+
if (p !== '~' && !p.startsWith('~/') && !p.startsWith('~\\')) return p;
|
|
733
|
+
const rest = p.slice(1).replace(/^[/\\]+/, '');
|
|
734
|
+
return path.join(os.homedir(), rest);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function printShare(pack, { dryRun }) {
|
|
738
|
+
const r = pack?.report ?? {};
|
|
739
|
+
const s = r.skipped ?? {};
|
|
740
|
+
console.log(`${dryRun ? '干跑报告' : '分享包'}: ${r.included ?? 0} 条` +
|
|
741
|
+
(pack?.alias ? ` 来自 ${pack.alias}` : ''));
|
|
742
|
+
const named = [['archived', '归档'], ['forgotten', '已遗忘'], ['readonly_type', '只读指针行'],
|
|
743
|
+
['excluded_type', '不随包走'], ['blank_or_noise', '空白/噪声'], ['sensitive', '密钥/PII']];
|
|
744
|
+
const parts = named.filter(([k]) => (s[k] ?? 0) > 0).map(([k, zh]) => `${zh} ${s[k]}`);
|
|
745
|
+
console.log(` 跳过: ${parts.length ? parts.join(', ') : '无'}`);
|
|
746
|
+
// 折叠不是跳过:那条记忆够格进包,只是包里已经有了它(同标题同正文同内容锚、scope
|
|
747
|
+
// 不同的两条)。不印出来的话 `included` 会比她心里的「够格的条数」少几条,而那正好是
|
|
748
|
+
// 会让人怀疑口径少发了的那类差额。src/share.js 的 report.deduped。
|
|
749
|
+
if ((r.deduped ?? 0) > 0) console.log(` 包内重复已折叠: ${r.deduped} 条(对面只吃一条)`);
|
|
750
|
+
for (const item of (r.skip_list ?? []).slice(0, 20)) {
|
|
751
|
+
// 标题为空有两种:这条本来没标题,或它整行被密钥判据命中、标题在源头就留空了
|
|
752
|
+
// (src/share.js:skip_list 随包外发,而凭据常常就写在标题里)。终端要让人看清
|
|
753
|
+
// 后一种是「有意不给」,不是「数据缺失」。
|
|
754
|
+
console.log(` - [${item.reason}] ${item.title || '(标题不外发)'}`);
|
|
755
|
+
}
|
|
756
|
+
// 判据用「六个计数之和 > 清单长度」而不是「清单等于 50」:前者是「账比清单长」这个事实本身,
|
|
757
|
+
// 恰好 50 条被跳时后者会误报截断(面板侧同一条判据)。50 这个数不在这里出现,
|
|
758
|
+
// 水位改了判据仍然对。
|
|
759
|
+
const skippedTotal = ['archived', 'forgotten', 'readonly_type', 'excluded_type', 'blank_or_noise', 'sensitive']
|
|
760
|
+
.reduce((n, k) => n + (r.skipped?.[k] ?? 0), 0);
|
|
761
|
+
if (skippedTotal > (r.skip_list ?? []).length) console.log(' … 跳过清单已截断,计数仍为全量');
|
|
762
|
+
if (dryRun) console.log(` 没写文件:加 --out <路径> 才会落盘`);
|
|
763
|
+
else console.log(` 发给对方即可,她用自己的 ${CLI_NAME} receive 吃进去`);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async function cmdShare(cfg, flags) {
|
|
767
|
+
requireToken(cfg);
|
|
768
|
+
const out = expandHome(flagValue(flags, 'out'));
|
|
769
|
+
const dryRun = flagBool(flags, 'dry-run') || flagBool(flags, 'dryrun');
|
|
770
|
+
if (!out && !dryRun) {
|
|
771
|
+
fail(
|
|
772
|
+
cfg,
|
|
773
|
+
`share 要么写文件(--out <路径>),要么只看报告(--dry-run):` +
|
|
774
|
+
`不写文件又把整库打到 stdout 会让报告被 JSON 淹掉。\n` +
|
|
775
|
+
`例子:${CLI_NAME} share --out ~/Desktop/pack.json`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
const typeRaw = flagValue(flags, 'type');
|
|
779
|
+
const body = {};
|
|
780
|
+
if (typeRaw) body.type = typeRaw.split(',').map((t) => t.trim()).filter(Boolean);
|
|
781
|
+
const alias = flagValue(flags, 'alias');
|
|
782
|
+
if (alias) body.alias = alias;
|
|
783
|
+
const tag = flagValue(flags, 'tag');
|
|
784
|
+
if (tag) body.tag = tag;
|
|
785
|
+
// 超水位那次服务端答 413,apiRequest 对非 2xx 走的是 failHttp:它把响应体里的 hint
|
|
786
|
+
// 与量到的字节转出去再 exit(1)。所以这里**没有**可达的 error 分支——加 `if
|
|
787
|
+
// (pack?.error)` 就是第二份出口,措辞迟早跟拒发那一侧分家(#411 的同一口径)。
|
|
788
|
+
const pack = await apiRequest(cfg, 'POST', '/share', { body });
|
|
789
|
+
const r = pack?.report ?? {};
|
|
790
|
+
if (cfg.jsonMode) {
|
|
791
|
+
console.log(stringify(dryRun ? { report: r, pack } : { report: r, file: out }));
|
|
792
|
+
} else {
|
|
793
|
+
printShare(pack, { dryRun });
|
|
794
|
+
}
|
|
795
|
+
if (dryRun) return;
|
|
796
|
+
fs.writeFileSync(out, JSON.stringify(pack), 'utf8');
|
|
797
|
+
// --json 时 stdout 只许那一份 JSON:后面追一行「已写出」会把 JSON.parse 撞在
|
|
798
|
+
// position 536(jq 同理)。落盘路径已在上面的 payload 里(file 键),这行只对
|
|
799
|
+
// 人有用,就只给人看(同 cmdReclaim:json 模式打印完就结束)。
|
|
800
|
+
if (!cfg.jsonMode) console.log(` 已写出: ${out}`);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// 入库面(车道 C)。包文件在**本地**读、把包内容整个送上去:服务端永远看不到路径,
|
|
804
|
+
// 也就不需要任何文件系统权限——这是 receive 协议选「传包不传路径」的全部理由。
|
|
805
|
+
// confirm 的脾气照 reclaim:缺省即干跑,写收件人的库(或物删整包)要 --apply 显式确认。
|
|
806
|
+
async function cmdReceive(cfg, rest, flags) {
|
|
807
|
+
requireToken(cfg);
|
|
808
|
+
const file = rest[0];
|
|
809
|
+
if (!file) {
|
|
810
|
+
fail(cfg, `receive 需要包文件:${CLI_NAME} receive <包文件>(不带 --apply 只干跑,一个字都不写)`);
|
|
811
|
+
}
|
|
812
|
+
let pack;
|
|
813
|
+
try {
|
|
814
|
+
pack = JSON.parse(fs.readFileSync(expandHome(file), 'utf8'));
|
|
815
|
+
} catch (e) {
|
|
816
|
+
// 读坏/读不到都在发请求之前拒掉:半读出来的包发过去只会换回一个闸门错,
|
|
817
|
+
// 而那一格现场已经说不清是「文件本来就这样」还是「传输坏了」。
|
|
818
|
+
fail(cfg, `读不到包文件 ${file}:${String(e?.message ?? e)}\n包文件是对方 ${CLI_NAME} share --out 出来的那个 .json。`);
|
|
819
|
+
}
|
|
820
|
+
const applied = flagBool(flags, 'apply');
|
|
821
|
+
const body = { pack };
|
|
822
|
+
if (applied) body.confirm = true;
|
|
823
|
+
const data = await apiRequest(cfg, 'POST', '/receive', { body });
|
|
824
|
+
if (cfg.jsonMode) {
|
|
825
|
+
console.log(stringify(data));
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
printReceive(data, { applied });
|
|
829
|
+
if (!applied) console.log(' 没写库:确认数字之后加 --apply 真的导入');
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// 撤销收的是**包文件**而不是裸 pack_id:让人抄 16 位十六进制去删自己库里的行,
|
|
833
|
+
// 抄错一位就是「删了个没删成的包还自以为收掉了」。pack_id 就写在包文件第一层。
|
|
834
|
+
async function cmdUndoShare(cfg, rest, flags) {
|
|
835
|
+
requireToken(cfg);
|
|
836
|
+
const file = rest[0];
|
|
837
|
+
if (!file) {
|
|
838
|
+
fail(cfg, `undo-share 需要当初那个包文件(pack_id 从文件里取):${CLI_NAME} undo-share <包文件> [--apply]`);
|
|
839
|
+
}
|
|
840
|
+
let pack;
|
|
841
|
+
try {
|
|
842
|
+
pack = JSON.parse(fs.readFileSync(expandHome(file), 'utf8'));
|
|
843
|
+
} catch (e) {
|
|
844
|
+
fail(cfg, `读不到包文件 ${file}:${String(e?.message ?? e)}\n要的是当初 ${CLI_NAME} share --out 出来、发给对面那个 .json。`);
|
|
845
|
+
}
|
|
846
|
+
// trim 在这儿做:只有空格的 pack_id 与「没有 pack_id」是同一种「这个文件收不了」,
|
|
847
|
+
// 放它出门只是绕一圈换回 400 missing-pack-id,而那一格会被读成服务端的问题(服务端
|
|
848
|
+
// parseUndoBody 同样 trim,这里不改它的行为,只是不必为空格 round-trip)。
|
|
849
|
+
const packId = typeof pack?.pack_id === 'string' ? pack.pack_id.trim() : '';
|
|
850
|
+
if (!packId) {
|
|
851
|
+
fail(cfg, `这个文件里没有 pack_id,收起不了:${file}\nundo-share 收的是整包,编号就写在包文件的第一层,不用也不该手抄 16 位十六进制。`);
|
|
852
|
+
}
|
|
853
|
+
const applied = flagBool(flags, 'apply');
|
|
854
|
+
const body = { packId };
|
|
855
|
+
if (applied) body.confirm = true;
|
|
856
|
+
const data = await apiRequest(cfg, 'POST', '/receive/undo', { body });
|
|
857
|
+
if (cfg.jsonMode) {
|
|
858
|
+
console.log(stringify(data));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
printReceive({ ...data, created: data?.deleted, alias: '' }, { applied, undo: true });
|
|
862
|
+
if (!applied) console.log(' 没删:确认条数之后加 --apply 真的收起这个包');
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function printReceive(d, { applied, undo = false }) {
|
|
866
|
+
const conflicts = Array.isArray(d?.title_conflicts) ? d.title_conflicts : [];
|
|
867
|
+
console.log(`${undo ? (applied ? '已收起' : '将收起') : applied ? '已导入' : '干跑'}: ${d?.created ?? 0} 条` +
|
|
868
|
+
(d?.alias ? ` 来自 ${d.alias}` : '') + (d?.skipped_existing ? ` 已存在 ${d.skipped_existing}` : ''));
|
|
869
|
+
if (undo && applied) {
|
|
870
|
+
// 服务端是物删不是归档(src/share-import.js 的 deleteSharePack):这句话是 --apply
|
|
871
|
+
// 与人之间唯一的说明书,不许软化成「移除」。干跑那一笔一个字都没动,说「直接从库里
|
|
872
|
+
// 带走」是假话,还会把下面的样例压成「已经删掉的这几条」——干跑那侧的话术是 cmdUndo-
|
|
873
|
+
// Share 结尾的「没删:…加 --apply」,两句不许同时出现(同一件事说两遍就没人细看)。
|
|
874
|
+
console.log(' 物删:这些行直接从库里带走,不是归档;包文件还在,重新 receive 一遍即可再来');
|
|
875
|
+
}
|
|
876
|
+
if (conflicts.length) {
|
|
877
|
+
// 撞名是 advisory 读数(src/share-import.js:只对本轮真正会新建的条目判),
|
|
878
|
+
// 所以这里只说「这几条要你看一眼」,永远不许说「除此之外没有别的撞名」。
|
|
879
|
+
console.log(` 撞名提醒:和你自己的条目同名 ${conflicts.length} 条(同类型同标题,不会被并入,需要你处置):`);
|
|
880
|
+
for (const c of conflicts.slice(0, 10)) console.log(` - [${c.type}] ${c.title}`);
|
|
881
|
+
if (conflicts.length > 10) console.log(' … 只列前 10 条');
|
|
882
|
+
}
|
|
883
|
+
const sample = Array.isArray(d?.sample) ? d.sample : [];
|
|
884
|
+
if (sample.length) {
|
|
885
|
+
// 撞名那段有标题、有截断话,样例这段原先两样都没有:服务端把 sample 截到 20
|
|
886
|
+
// (src/share-import.js 的 DELETE_SAMPLE_MAX),CLI 再截到 10,而 deleted 才是全集。
|
|
887
|
+
// 一个 500 行的包就这样留下 10 条无名要点,人会当成「要收走的就是这些」。
|
|
888
|
+
// 全集那一格只报服务端的数,不报「还剩几条」——两次截断混在一起就没人能对得上账。
|
|
889
|
+
console.log(' 样例:下面只是这一批里的几条,不是全部清单');
|
|
890
|
+
const shown = sample.slice(0, 10);
|
|
891
|
+
for (const s of shown) console.log(` · [${s.type}] ${s.title}`);
|
|
892
|
+
const total = typeof d?.deleted === 'number' ? d.deleted : (d?.created ?? 0);
|
|
893
|
+
if (shown.length < total) console.log(` … 只列前 ${shown.length} 条;整批共 ${total} 条(这个数来自服务端计数)`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
690
897
|
/* ---------------------------------- 入口 ---------------------------------- */
|
|
691
898
|
async function main(argv) {
|
|
692
899
|
const { positionals, flags } = parseArgv(argv);
|
|
@@ -724,6 +931,12 @@ async function main(argv) {
|
|
|
724
931
|
return cmdDelete(cfg, rest);
|
|
725
932
|
case 'reclaim':
|
|
726
933
|
return cmdReclaim(cfg, flags);
|
|
934
|
+
case 'share':
|
|
935
|
+
return cmdShare(cfg, flags);
|
|
936
|
+
case 'receive':
|
|
937
|
+
return cmdReceive(cfg, rest, flags);
|
|
938
|
+
case 'undo-share':
|
|
939
|
+
return cmdUndoShare(cfg, rest, flags);
|
|
727
940
|
default:
|
|
728
941
|
fail(cfg, `未知命令: ${cmd}\n运行 \`${CLI_NAME} --help\` 查看帮助。`);
|
|
729
942
|
}
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
| `summarizeMaxEntriesPerRun` | `0` | 单次蒸馏最多落库条数(≤50) | 0 = 不限 |
|
|
93
93
|
| `summarizeMinWindowChars` | `0` | 窗口可蒸馏文本下限(字符),不足直接跳过 LLM(#239) | 被挡窗口照常消费游标并留 skip 审计 |
|
|
94
94
|
| `summarizeMaxRunsPerSession` | `0` | 每会话蒸馏次数预算,只计真实发起的调用(≤1000) | 0 = 不限;进程内计数,重启清零 |
|
|
95
|
+
| `summarizeParseFailureMaxRetries` | `0` | 连续解析失败几次后放弃当前窗口(0–10,0 = 关,#493) | 达到阈值强制消费游标跳过该窗口;失败行 metadata 恒带 `raw_head`/`raw_has_bracket`/`streak` |
|
|
95
96
|
| `summarizePeakHours` | `""` | 高峰时段串(`"09:00-18:00"` / `"mon-fri 08:00-12:00,14:00-18:00"`,支持跨零点) | 命中高峰不调 LLM、顺延补跑;任一写法非法整串按未配置处理 |
|
|
96
97
|
| `summarizePeakMaxDeferMinutes` | `120` | 高峰顺延上限(分钟,≤1440) | 0 = 不设上限;到点仍处高峰照跑(bypassPeak) |
|
|
97
98
|
| `summarizeDedupeMode` | `off` | 落库前去重档位:`off` / `title`(拦完全同名)/ `vector`(同会话语义近邻) | `vector` 复用已有 embedding 列,无 LLM 调用 |
|
|
@@ -116,11 +117,13 @@
|
|
|
116
117
|
|
|
117
118
|
| 键 | 默认 | 作用 | 开启后果 / 冲突 |
|
|
118
119
|
|---|---|---|---|
|
|
119
|
-
| `distillRecords.enabled` **light** | `false` | 蒸馏时顺带留存窗口原文 +
|
|
120
|
+
| `distillRecords.enabled` **light** | `false` | 蒸馏时顺带留存窗口原文 + 写记忆级溯源链接;同时是溯源读出口(`GET /memory/provenance` 与宿主内 `/api/dsh-mneme/memory/provenance`)的总闸 | opt-in;关 = 一行不写、连收集都不做,行为与 #399 之前逐字节一致;读出口此时回 `409 {"error":"distill-records-disabled"}`(与「装配没接线」的 503 分开读数)。**关开关 ≠ 删掉已留存的原文**:只不再经 HTTP 提供,盘上的行要 `dsh-mneme reclaim` 或重新开启后的启动裁剪才走。开关是**启动快照**(与 `panel_mode` 同节奏),面板改完要到下次启动生效,面板会显示「待重启」 |
|
|
120
121
|
| `distillRecords.retentionDays` | `90` | 原文保留天数(1–3650) | **只管启动期裁剪**(`src/index.js`,`enabled` 时动手)。`dsh-mneme reclaim` 不读这个键:它对三个表用同一条 `--older-than` 截止(默认 7 天),想对齐就显式传成与 `retentionDays` 相同的值(见 STORAGE.md)。两处都**只删 `distill_records`**,溯源链接保留指向空区间(显示「来源已过期」),绝不级联删记忆 |
|
|
121
122
|
|
|
122
123
|
两个键都走 feature_flags 白名单(面板可启停);`enabled` 进 lightMode 预设关闭清单,`retentionDays` 不进(它是时长不是行为面,用户显式设过就照旧)。`dsh-mneme reclaim` 的报告从两项变三项(`planned` / `column_bytes` / `cleared` 各多一个 `distill_records`),入口语义(默认 dry-run、`--apply` 才动手)不变。已知边界:无历史回填、从未蒸馏过的窗口不留记录、不覆盖工具结果与 code 输出。
|
|
123
124
|
|
|
125
|
+
`enabled` 这一格**同时管读写两侧**(#509 评审整改后):写侧是蒸馏时是否落盘(`src/summarize.js:988`),读侧是溯源读出口是否把原文读出去(判据在 `src/provenance-http.js`,与写侧逐字同源,只此一份)。关着时读出口回 `409 {"error":"distill-records-disabled"}`——但**盘上的原文不会因此消失**:保留期裁剪与写入同在 `src/index.js:270` 一道门里,关掉开关意味着「不再写、启动期也不再裁」,已留的行会一直躺在库里,直到 `dsh-mneme reclaim`(它不读这个键,手动可删)或重新开启后的启动裁剪。所以「关掉就干净了」是误读,诚实的表述是「关掉 = 不再经 HTTP 提供」。
|
|
126
|
+
|
|
124
127
|
## 注入
|
|
125
128
|
|
|
126
129
|
| 键 | 默认 | 作用 | 开启后果 / 冲突 |
|
|
@@ -140,6 +143,8 @@
|
|
|
140
143
|
| `escapePromptVariables` | `true` | 注入内容连续花括号转义(#162) | 关 = 原样透传(DSH interpolate 可能 throw) |
|
|
141
144
|
| `injectTimePrefix` | `false` | 对话开始注入一次当前时间(#34,v0.7.11 误删后随 #333 恢复) | opt-in;格式与键名与 v0.7.2 一致,存量 feature_flags 恢复即生效 |
|
|
142
145
|
| `personaEnabled` | `false` | 人格段:用户自写的一小段常驻注入文本 | opt-in;只是轻量常驻文本、不引入重型后台路径,故不进 `LIGHT_MODE_OFF` |
|
|
146
|
+
| `personaStrictAddressing` | `false` | 人格寻址口径。关(历史行为):会话身份没建卡、或该卡被显式关掉时,落 `default` 卡 | 开:这两种情况都不注入人格,`default` 只兜「解析不到会话身份」的无名会话。用途是宿主里跑着自带身份的外部 bot 时,别让某个人格无条件进它每一帧。默认关 = 升级零变化 |
|
|
147
|
+
| `profileExtractEnabled` **light** | `false` | 画像提取草稿:`POST /api/dsh-mneme/profile/extract` 从活跃记忆反向写一段「助手眼中的用户」草稿(面板「用户画像」卡上的按钮) | opt-in;一次调用一轮 LLM + 读上百行记忆,故 `LIGHT_MODE_OFF` 里有它。**只出草稿**:它不写 `user_settings`,采用草稿是面板里另一次显式保存——画像正文必须是用户手写的原文。空库直接报 `no-input`、模型没按格式回报 `unparseable`,两者都不回空草稿 |
|
|
143
148
|
|
|
144
149
|
## 巩固(autoDream)
|
|
145
150
|
|
|
@@ -167,7 +172,7 @@
|
|
|
167
172
|
| `dreamMinExplicitCoverage` | `0.5` | 显式决策覆盖率下限(0–1) | 防截断输出被隐式 keep 洗白 |
|
|
168
173
|
| `dreamSkipInvalid` | `true` | 跳过单条非法决策、应用合法子集、run 记 degraded(#89) | `false` = 恢复整单拒绝 |
|
|
169
174
|
| `allowCrossTypeMerge` | `false` | 放宽跨类型合并检查 | opt-in;`dreamSkipInvalid` 关时跨类型 merge 直接整单拒绝 |
|
|
170
|
-
| `dreamMergeGuard` | `true` | guarded 类型 merge 护栏(#339/E8):合并对象命中长保留类型(与 archive 护栏同表:preference/pattern/rejected_solution/constraint/pitfall)的 merge
|
|
175
|
+
| `dreamMergeGuard` | `true` | guarded 类型 merge 护栏(#339/E8):合并对象命中长保留类型(与 archive 护栏同表:preference/pattern/rejected_solution/constraint/pitfall)的 merge 决策跳过——E8 实测巩固损耗里 10/26 条被丢约束已归位 guarded 类型仍被 merge 吃掉。**#521 起带逃生门**:所有 id 同类型且 `reason` 命中「重复/过时」词表(与 archive 护栏同一份 `ARCHIVE_RATIONALE_RE`)时放行——archive 早就有这道门,merge 没有,于是一份真重复的 preference 永远合不掉而提示词还在催模型合并它 | 默认开(同类压缩管线实测:安全规则被同速压缩后一轮只剩 53%、五轮只剩 10%,全程无报错);关闭 = guarded 类型的 merge 不再被跳过,「更精炼的摘要」重新成为约束失真的主通道。`dreamSkipInvalid`(默认开)时被跳条目进 `dream_runs.skipped`、run 记 degraded,关闭时整单拒绝 |
|
|
171
176
|
| `dreamNarrativeEnabled` **light** | `false` | dream 期间按共享 tag 聚类合成叙述条(#164 对齐) | opt-in;按需检索、不常驻注入 |
|
|
172
177
|
| `dreamNarrativeMinCluster` | `3` | 成簇门槛(共享同一 tag 的记忆数,2–20) | — |
|
|
173
178
|
| `documentMemoryEnabled` **light** | `false` | document 型记忆:长文档指针行,全文归 agent(#230) | opt-in;注册校验 + C2 去重 + supersede 记账 |
|
|
@@ -283,6 +288,7 @@
|
|
|
283
288
|
| `externalApiHost` | `127.0.0.1` | 绑定地址 | 改成非回环 = 把整个记忆库暴露给网络,自负其责 |
|
|
284
289
|
| `disableMemorySearch` | `false` | 对模型隐藏 `memory_search` 工具(v0.8.5) | 慢/轻量模型的工具往返节流;隐藏即不可调 |
|
|
285
290
|
| `disableMemoryArchive` | `false` | 对模型隐藏 `memory_archive` 工具 | 同上 |
|
|
291
|
+
| `organizeEnabled` | `false` | 对模型开放 `memory_organize` 工具(#231:先 dryRun 出比对报告,agent 判断后 apply 落库) | 与上面两个**反向**:那两个藏默认在场的工具,本键开默认不在场的能力。关 = 工具根本不注册(模型看不到就不会调)。dryRun→apply 是两步协议,弱模型容易漏 `run_id` 或跳过报告,所以 `LIGHT_MODE_OFF` 里有它;apply 只能引用一次真实报告、且一份报告只许落地一次 |
|
|
286
292
|
| `actionPipelineEnabled` | `false` | M.O. 只读视图的门控:做法图谱 + 对话行结算卡 | opt-in beta;只是纯 UI 入口、不引入重型后台路径,故不进 `LIGHT_MODE_OFF`。写账另有独立的执行记账键(`/mo-run`),看图 ≠ 写账 |
|
|
287
293
|
| `actionRunEnabled` | `false` | M.O. 执行记账:`/mo-run` 开一次执行窗口,窗口内命令执行才可能被记成一条 run | opt-in beta;挂在热路径(每个会话事件都过一次监听),故 `LIGHT_MODE_OFF` 里有它 |
|
|
288
294
|
| `actionNudgeEnabled` | `false` | M.O. 自动沉淀:每轮结束(`turn/end`)后挖一次「这个项目重复走的动作」,结果落成 `memoryDir/proposals/<项目键>/` 里的提议,等人确认 | opt-in beta;三个 M.O. 键里最重的一条——挂 `session/event` 且要**同步解压 zstd 会话日志**(本机 118 份实测 16.2s,单次预算 `budgetMs`=4s 兜底),故 `LIGHT_MODE_OFF` 里有它。**永不自动落草稿**:`materialize` 恒 false,进仓库必须是人的显式动作 |
|
|
@@ -319,6 +325,8 @@
|
|
|
319
325
|
`graphAnchoringEnabled` · `graphWeightEnabled` · `graphInjectHint` · `graphPassiveConfirm` ·
|
|
320
326
|
`actionRunEnabled` · `actionNudgeEnabled` ·
|
|
321
327
|
`dreamNarrativeEnabled` · `documentMemoryEnabled` · `heatEnabled` · `continuityRescueEnabled` ·
|
|
328
|
+
`organizeEnabled`(#231) ·
|
|
329
|
+
`profileExtractEnabled`(#497) ·
|
|
322
330
|
`preInjectGate.enabled`(#380) · `distillRecords.enabled`(#399)
|
|
323
331
|
|
|
324
332
|
预设只是默认值而非强制:用户显式写进 feature_flags 的值在装配顺序上后展开、仍然生效
|
package/docs/DAEMON.md
CHANGED
|
@@ -6,14 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
daemon 是**数据面**,不是第二个宿主:
|
|
8
8
|
|
|
9
|
-
- **有**:存储(SQLite)、检索(关键词 + BM25 + 向量,`/search` 统一召回,与宿主共用 `src/semantic.js` 同一套装配)、注入候选(`/context`,issue #370,与宿主注入管线同一份 `injectCandidates`)、镜像同步与人改合并、`/maintenance/reclaim`、`/bootstrap`、recall_runs 检索/注入回执。
|
|
9
|
+
- **有**:存储(SQLite)、检索(关键词 + BM25 + 向量,`/search` 统一召回,与宿主共用 `src/semantic.js` 同一套装配)、注入候选(`/context`,issue #370,与宿主注入管线同一份 `injectCandidates`)、镜像同步与人改合并、`/maintenance/reclaim`、`/share`(记忆分享包,纯读,见 [SHARE.md](SHARE.md))、`/receive` 与 `/receive/undo`(吃包入库与整包退出,车道 C;写的是**本机自己的库**,与 `/share` 同一条 4 MiB 入站上限)、`/bootstrap`、recall_runs 检索/注入回执。
|
|
10
10
|
- **没有(第一期,无 LLM)**:巩固(autoDream)、蒸馏(autoSummarize)、实体抽取、sleep、注入/工具/面板路由。前两者是**结构性缺失**而非开关——daemon 装配里没有 LLM 句柄,巩固只属于 DSH 宿主进程。这就是 daemon 与宿主「单写者」的机械保证(AGENTS.md externalApi/autoDream 单侧纪律的 daemon 版),不依赖用户自觉。
|
|
11
|
+
- **收包在 daemon 上付的是什么**:一个事务里逐条先查 `share_external_id` 再 `store.save`,嵌入随最外层 COMMIT 排空(`--embed local` 时真打向量,`off` 时只落结构),镜像重渲染照 daemon 自己那条回灌路走。**实体一个都不建**——不是 daemon 缺 LLM 才不建,车道 C 对外来行本来就不做抽取(有向量、无实体,实体与图检索看不见它们),宿主侧同样如此。成本的量级读数见 [SHARE.md](SHARE.md)「收侧」那一节实测。
|
|
11
12
|
|
|
12
13
|
与宿主装配(`src/index.js` apply)的关系:`src/serve.js` 只搬数据面那一半,每步注释锚定 index.js 来源行号;刻意不抽公共装配函数(apply 其余环节与宿主 ctx 纠缠,防御段纪律「最后动或不动」)。装配漂移风险由 `test/serve-bin.test.js` 的多进程共存用例兜底(两进程真开同一个库互写互读)。
|
|
13
14
|
|
|
14
15
|
## 2. 对外接口
|
|
15
16
|
|
|
16
|
-
路由面 = `src/api-standalone.js` 全表(health/status/profile/rules/memories
|
|
17
|
+
路由面 = `src/api-standalone.js` 全表(health/status/profile/rules/memories 读写/`/memory/provenance`(记忆级溯源读出口,#421)/search/context/share/receive(含 `/receive/undo`)/maintenance/bootstrap),鉴权同源(Bearer + timingSafeEqual,`GET /health` 免鉴权)。daemon 自身零新路由;#370 的 `/context` 落在 api-standalone 工厂里,宿主外部访问与 daemon 自动同享(参数、scope 语义与 recall_runs 注入分账见包 README 路由表)。daemon 侧唯一自有选项是 `strictPort`(见 §4)。
|
|
18
|
+
|
|
19
|
+
`/memory/provenance` 的开关门(#509 评审整改):这个面返回蒸馏窗口的**逐条会话原文**,所以它吃 `distillRecords.enabled`——与写侧 `src/summarize.js:988` 同一枚开关、同一个判据表达式(读侧唯一一份在 `src/provenance-http.js` 的 `isDistillRecordsEnabled`)。关着时回 `409 {"error":"distill-records-disabled"}`,不是 404(本端点刻意不就「这条记忆是否存在」作断言:未知 id 是 200 + 空 windows),也不是 503(那是「装配没接线」,`src/index.js:589-591` 立的可区分性原则)。状态码阶梯:`401` 无 token → `400` 缺 id → `409` 功能关了 → `503` 装配没接线 → `200`。daemon 读的是**面板持久化的开关**(`serve.js` 把 `feature_flags` 里的 `distillRecords.enabled` 展开进 cfg,与宿主 `index.js:217-233` 同款),与宿主共用同一个 memory.db、同一份 kv——所以用户在面板里开着,daemon 这一面就读得到。
|
|
20
|
+
|
|
21
|
+
残留不对称要知道:daemon 结构上看不到宿主的 **bundle config**(第一期不吃宿主 config schema),所以「只写在部署侧 bundle 配置里打开、从没在面板点过」的用户,宿主认、daemon 不认——daemon 会对每个请求回 409。这不是故障,是 daemon 装配面的已知边界;要让 daemon 也认,去面板里把开关显式打开一次(面板的「待重启」提示同样适用:开关是启动快照,改完要重启才生效)。
|
|
22
|
+
|
|
23
|
+
`/context` 的档位信号(#383):响应多一个 `tier`(`full` 语义档 / `degraded` 规则档 / `unknown` 语义路未启用或判不出来,fail-closed),**具体降级原因只在 stderr**——`dsh-mneme: /context degraded to the rule tier (<reason>)`,不进响应体。原因是给运维的读数,六值互不吞并(成例「修法不同就分开」):`embed-unavailable` 无可用接口(embedder 对象在、缺 `embedSingle`/`embed`,修适配器)/ `embed-empty` 空向量 / `embed-timeout` 挂起 / `embed-error` 抛错 / `embed-dimension` 维度不符(有向量但没有一条与查询同维,修法是重建索引)/ `no-embedder` 压根没装配 embedder(`--embed off` / lightMode / 宿主未装配,**预期态不是故障**,修法是把嵌装扮上或开起来)。`tier` 是给调用方的读数,两套消费者分开;六值的唯一真源是 `src/api-standalone.js` 的 `CONTEXT_DEGRADE_REASONS`,契约测试从它派生。`unknown` 之下(维度探针没跑成 / 有行却一条向量都读不出来)另有一行 `dsh-mneme: /context tier left unknown: <what>`,**不是**那六值的一员(`tier` 不是 degraded,没有降级原因可记),运维程序不要按原因枚举解析它——没有它,调用方对 unknown 的策略是「不告警」时,一次真实的判档故障会整个消失。`no-embedder` 不并进 `embed-unavailable` 是故意的:那两者共用「语义路没通」的结果,但一个是适配器坏了(异常)、一个是语义路没开(设计内常态),并成一个值会让按 `embed-unavailable` 告警的面板对每个轻量模式安装报警;用词则与宿主面 `/api/dsh-mneme/semantic` 的 `reason` 对齐(那边是另一套枚举、另一个消费者,别顺手统一)。这里最值得盯的是 `embed-dimension`:嵌入接口一切正常,但库里是旧维度向量,`searchVector` 直接跳过、不抛错,召回静默退回关键词档——修法是重建索引,不是查端点。判档有成本:`q` 非空且装了 embedder 时一次 `/context` 会真起一次查询嵌入(一次往返 + `embedTimeoutMs` 窗口),维度探针本身是**按匹配短路的存在性判据**(有没有一条活跃向量与查询同维,不是 `LIMIT 1` 读一行——混合维度库里那会抽错维误报 degraded、首条损坏又会漏报 full):一次无 `LIMIT` 的全 id 扫描 + 短路解析,同维度健康库只解析 1 个向量,只有要报 degraded 时才走完全表;不复用 `/health`(它只回 `{ok:true}`)也不做全表扫。
|
|
17
24
|
|
|
18
25
|
CLI:
|
|
19
26
|
|