dsh-memsys 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -2
  3. package/client.js +110 -0
  4. package/cordis.patch.yml +6 -0
  5. package/index.mjs +345 -0
  6. package/package.json +63 -4
  7. package/scripts/acceptance-boundaries.mjs +127 -0
  8. package/scripts/build.mjs +61 -0
  9. package/scripts/check-bom.mjs +62 -0
  10. package/scripts/check-db.mjs +69 -0
  11. package/scripts/checkpoint.mjs +83 -0
  12. package/scripts/feed.mjs +37 -0
  13. package/scripts/import-legacy.mjs +261 -0
  14. package/scripts/live-check.mjs +555 -0
  15. package/scripts/migrate.mjs +76 -0
  16. package/scripts/restore-check.mjs +166 -0
  17. package/scripts/test.mjs +36 -0
  18. package/src/clock.mjs +52 -0
  19. package/src/config.mjs +71 -0
  20. package/src/context/budget.mjs +78 -0
  21. package/src/context/compile.mjs +504 -0
  22. package/src/context/delivery.mjs +109 -0
  23. package/src/context/pending-due.mjs +91 -0
  24. package/src/context/rank.mjs +120 -0
  25. package/src/context/select.mjs +175 -0
  26. package/src/context/snapshots.mjs +367 -0
  27. package/src/contracts.mjs +161 -0
  28. package/src/control/commit.mjs +228 -0
  29. package/src/control/expected.mjs +73 -0
  30. package/src/control/ledger.mjs +380 -0
  31. package/src/control/maintenance.mjs +35 -0
  32. package/src/control/readback.mjs +101 -0
  33. package/src/control/recovery.mjs +480 -0
  34. package/src/dream/confirm.mjs +558 -0
  35. package/src/dream/organize.mjs +243 -0
  36. package/src/dream/replay.mjs +89 -0
  37. package/src/dream/store.mjs +263 -0
  38. package/src/dream/surface.mjs +105 -0
  39. package/src/feed/filter.mjs +157 -0
  40. package/src/gates/checks.mjs +168 -0
  41. package/src/identity.mjs +120 -0
  42. package/src/import/legacy.mjs +611 -0
  43. package/src/import/pointers.mjs +167 -0
  44. package/src/import/rollback.mjs +132 -0
  45. package/src/read/activity.mjs +57 -0
  46. package/src/read/dream.mjs +39 -0
  47. package/src/read/get.mjs +321 -0
  48. package/src/read/pager.mjs +24 -0
  49. package/src/read/project.mjs +94 -0
  50. package/src/read/review.mjs +187 -0
  51. package/src/read/search.mjs +205 -0
  52. package/src/read/self.mjs +209 -0
  53. package/src/runtime/capabilities.mjs +58 -0
  54. package/src/runtime/identities.mjs +47 -0
  55. package/src/runtime/identity.mjs +53 -0
  56. package/src/runtime/inject.mjs +65 -0
  57. package/src/runtime/session-events.mjs +92 -0
  58. package/src/runtime/summary.mjs +197 -0
  59. package/src/runtime/tools.mjs +348 -0
  60. package/src/storage/backup.mjs +157 -0
  61. package/src/storage/migrate.mjs +124 -0
  62. package/src/storage/migrations/control-001.sql +102 -0
  63. package/src/storage/migrations/control-002.sql +90 -0
  64. package/src/storage/migrations/control-003.sql +7 -0
  65. package/src/storage/migrations/control-004.sql +31 -0
  66. package/src/storage/migrations/dream-001.sql +10 -0
  67. package/src/storage/migrations/dream-002.sql +105 -0
  68. package/src/storage/migrations/main-001.sql +354 -0
  69. package/src/storage/migrations/main-002.sql +89 -0
  70. package/src/storage/migrations/main-003.sql +23 -0
  71. package/src/storage/migrations/main-004.sql +33 -0
  72. package/src/storage/migrations/main-005.sql +125 -0
  73. package/src/storage/migrations/main-006.sql +33 -0
  74. package/src/storage/migrations/main-007.sql +59 -0
  75. package/src/storage/migrations/main-008.sql +55 -0
  76. package/src/storage/open.mjs +120 -0
  77. package/src/storage/replay.mjs +277 -0
  78. package/src/storage/repositories.mjs +354 -0
  79. package/src/tools/dream.mjs +75 -0
  80. package/src/tools/feed.mjs +62 -0
  81. package/src/tools/project.mjs +45 -0
  82. package/src/tools/read.mjs +95 -0
  83. package/src/tools/record.mjs +303 -0
  84. package/src/tools/schemas.mjs +177 -0
  85. package/src/tools/self.mjs +60 -0
  86. package/src/write/authority.mjs +148 -0
  87. package/src/write/candidate.mjs +796 -0
  88. package/src/write/capture.mjs +273 -0
  89. package/src/write/due-actions.mjs +429 -0
  90. package/src/write/due.mjs +89 -0
  91. package/src/write/judgment-review.mjs +170 -0
  92. package/src/write/judgment.mjs +736 -0
  93. package/src/write/observe.mjs +160 -0
  94. package/src/write/origin.mjs +89 -0
  95. package/src/write/owner-actions.mjs +249 -0
  96. package/src/write/project.mjs +681 -0
  97. package/src/write/promote.mjs +165 -0
  98. package/src/write/review.mjs +221 -0
  99. package/src/write/self-note.mjs +395 -0
  100. package/src/write/topic.mjs +95 -0
  101. package/test/e2e.test.mjs +326 -0
  102. package/test/fixtures/p3/c1-baseline.json +90 -0
  103. package/test/fixtures/p3-feed.json +13 -0
  104. package/test/helpers/faults.mjs +35 -0
  105. package/test/helpers/fixtures.mjs +245 -0
  106. package/test/helpers/runtime.mjs +26 -0
  107. package/test/helpers/worker.mjs +291 -0
  108. package/test/m1-storage.test.mjs +641 -0
  109. package/test/m2-write.test.mjs +814 -0
  110. package/test/m3-reliable.test.mjs +692 -0
  111. package/test/m4-context.test.mjs +527 -0
  112. package/test/m5-tools.test.mjs +304 -0
  113. package/test/p2-integration.test.mjs +285 -0
  114. package/test/p2-m1-self.test.mjs +505 -0
  115. package/test/p2-m2-judgment.test.mjs +630 -0
  116. package/test/p2-m3-project.test.mjs +444 -0
  117. package/test/p2-m4-due.test.mjs +449 -0
  118. package/test/p2-m5-replay.test.mjs +313 -0
  119. package/test/p3-c1-dream.test.mjs +579 -0
  120. package/test/p3-c1-recovery.test.mjs +182 -0
  121. package/test/p3-c2-import.test.mjs +231 -0
  122. package/test/p3-c2-migration.test.mjs +187 -0
  123. package/test/p3-c3-feed.test.mjs +200 -0
  124. package/test/p3-c4-boundaries.test.mjs +116 -0
  125. package/test/p3-c4-gates.test.mjs +79 -0
  126. package/test/p3-c5-tools.test.mjs +96 -0
  127. package/test/p4-p1-auth.test.mjs +185 -0
  128. package/test/p4-p2-recovery.test.mjs +314 -0
  129. package/test/p4-p3-fidelity.test.mjs +221 -0
  130. package/test/p4-p4-c.test.mjs +149 -0
  131. package/test/p4-p4-capacity.test.mjs +131 -0
  132. package/test/p4-p4-delivery.test.mjs +116 -0
  133. package/test/p4-p4-output.test.mjs +21 -0
  134. package/test/p5-a-recovery.test.mjs +71 -0
  135. package/test/p5-a2-dream.test.mjs +72 -0
  136. package/test/p5-b.test.mjs +94 -0
  137. package/test/p5-b2-legacy.test.mjs +71 -0
  138. package/test/p5-b3-cutover.test.mjs +30 -0
  139. package/test/p5-c.test.mjs +70 -0
  140. package/test/p5-d-budget.test.mjs +34 -0
  141. package/test/p5-e-search.test.mjs +25 -0
  142. package/test/p6-b1-merge.test.mjs +60 -0
  143. package/test/p6-b2-pointers.test.mjs +77 -0
  144. package/test/p6-e2-actions.test.mjs +122 -0
  145. package/test/p6-e2-lock.test.mjs +38 -0
  146. package/test/p6-e2-receipt.test.mjs +25 -0
  147. package/test/p6-e8-compile.test.mjs +26 -0
  148. package/test/p6-identity.test.mjs +33 -0
  149. package/test/p6-l1-expand.test.mjs +67 -0
  150. package/test/p6-l1-expected.test.mjs +33 -0
  151. package/test/p6-l6-rollback.test.mjs +38 -0
  152. package/test/p6-ui-summary.test.mjs +309 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tok-ryan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,275 @@
1
- # Temporary Holding Version
1
+ # dsh-memsys
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **给 DeepSeek Harness 用的长期记忆插件。** 事实只追加、候选可审、判断与裁决分开、做梦独立成库、上下文按预算注入。零依赖,只用 Node 内置的 `node:sqlite`。
4
+
5
+ ```bash
6
+ node scripts/migrate.mjs --data-dir ./data # 建库(三库:main / control / dream)
7
+ npm test # 324 项测试
8
+ node scripts/live-check.mjs --case tools # 真机:插件能否装进宿主、工具能否注册
9
+ ```
10
+
11
+ 已实测(隔离 profile,带 key):插件加载 → 10 个 `memsys_*` 工具注册进宿主 → 模型调用 `memsys_search` 返回真实命中 → 模型调用 `memsys_record` 写入候选与记忆,`origin.author_kind=model`、会话与调用 ID 与宿主记录一致。
12
+
13
+ ## 安装
14
+
15
+ **已发布到 npm**:`dsh-memsys@0.1.0`(https://www.npmjs.com/package/dsh-memsys)。
16
+
17
+ 装进一个**独立 profile**(不要直接装进你日常用的那个):
18
+
19
+ ```bash
20
+ dsh --profile memsys headless --from-default-profile --dump-config # 初始化隔离 profile
21
+ dsh plugin --profile memsys add dsh-memsys # 从 npm 装
22
+ ```
23
+
24
+ 或者不依赖 npm,从源码装:
25
+
26
+ ```bash
27
+ git clone https://github.com/tok-ryan/dsh-memsys.git dsh-memsys
28
+ cd dsh-memsys
29
+ npm test # 先跑一遍,确认本地环境能过(Node >= 22.13)
30
+ ```
31
+
32
+ ```bash
33
+ dsh plugin --profile memsys add "file:///全/路径/dsh-memsys" # 按实际路径改
34
+ ```
35
+
36
+ `file:///` 后面是**绝对路径、正斜杠**。Windows 上形如 `file:///C:/Users/you/dsh-memsys`。
37
+
38
+ **不要用 junction / 符号链接把本仓库链进 profile**:重建 profile 时递归删除会顺着链接把源码删掉(我们踩过,见 `docs/report-4.md` 的事故记录)。要装就先复制到临时目录再装。
39
+
40
+ 装完自检:
41
+
42
+ ```bash
43
+ dsh plugin --profile memsys --dump-config | grep dsh-memsys # 应该能看到插件条目
44
+ node scripts/live-check.mjs --case tools --profile memsys # 真机:插件加载 + 工具注册
45
+ ```
46
+
47
+ 移除:`dsh plugin --profile memsys remove dsh-memsys`。
48
+
49
+ ## 模块与分层
50
+
51
+ | 目录 | 职责 |
52
+ |---|---|
53
+ | `src/storage/` | 三库连接、迁移(SQL)、只追加触发器、repo |
54
+ | `src/control/` | 控制账:reserve / attempt / 精确读回 / 三败冻结 / 恢复 / 容量账 |
55
+ | `src/write/` | 观察 → 候选 → 提升六步、裁决与回执、项目、惦记、自述 |
56
+ | `src/context/` | 选择、排序、预算、编译(full/compressed/grouped)、快照、交付回执 |
57
+ | `src/read/` | 取回:get / search / review / self / project / dream / activity |
58
+ | `src/tools/` | 工具 schema 与 handler;`src/runtime/` 负责宿主注册与身份 |
59
+ | `src/dream/` | 梦库:命题、确认链、浮现、整理 |
60
+ | `src/import/` | 旧内容导入、门牌回填、回退核验 |
61
+ | `src/feed/` | 三闸筛选(相关 → 细节 → 时间) |
62
+ | `src/gates/` | 验收边界,可失败 |
63
+
64
+ **设计原则**:事实只追加;当前状态是可重建投影;模型只能提交候选,不能直接落事实;一切写后读回按固化预期比对,不是"行还在就算成功"。
65
+
66
+ ## 界面(0.2.0 起)
67
+
68
+ 装上之后 Web GUI 里会多三样东西,都来自同一个浏览器半 `client.js`:
69
+
70
+ - **左侧栏「🐳 记忆」**:点开是一块面板——记忆条数 / 候选条数 / 待确认惦记数,加最近 5 条记忆正文(超长截断)
71
+ - **设置 →「记忆系统」**:`auto_inject` 开关 + 当前数据目录;开关改完**立即生效**(重挂/撤下 system prompt 里的记忆段),刷新后仍在
72
+ - 面板与设置页共用数据口 `/dsh-memsys/api/*`(loopback + 同源校验;跨源 403)
73
+
74
+ **设置优先级**:`<data_dir>/ui-config.json`(存在且合法)> profile 的 `config.auto_inject` > 默认 `false`。
75
+ `/api/summary` 会返回 `auto_inject_source`,告诉你这个值是从哪来的(`ui-config` / `profile` / `default`)。
76
+
77
+ **headless 不受影响**:没有 `webServer` 时那段注册不激活,浏览器半也不加载。
78
+
79
+ ## 状态
80
+
81
+ 已实现并在真机验证**读**与**写**(`dispatch=ok` 返回真实命中、`record=ok` 写入候选与记忆);**界面**已落地并有 7 条测试,但"真机 GUI 里三个 slot 真的画出来"要装进 profile 目测才算完。
82
+
83
+ 仍有 4 条设计条款未达标:**混版分页测试**(C3,代码已接 `stale_cursor`)、**空分组回执边界**(C1)、**证据原文分片续页**(C4)、以及 `L6` 的"逐项重放 relations 内容"(现在只核到内容 hash + 关联)。逐条见文末"已知边界",不粉饰。
84
+
85
+ MIT License。
86
+
87
+ ---
88
+
89
+ _以下为原始施工期文档(安装、默认行为、边界),保留以便对照:_
90
+
91
+ # dsh-memsys · 第一批(存储 / 写入 / 可靠提交 / 注入 / 取回)
92
+
93
+ 本包是 DSH 的长期记忆器官第一批实现。行为规范只有一份:`whale-unify/memsys-design-v0.38.md`;本文件只讲**怎么装、怎么跑、默认行为是什么、哪些边界还没闭合**。
94
+
95
+ ## 一、这一批做了什么
96
+
97
+ - 三库底座:主库(事实/候选/记忆/证据/事件)、控制账(操作、尝试、编译、快照、容量)、梦库(仅 schema 占位)。
98
+ - 写入三段:观察(输入原话先落库并读回)→ 候选(capture / 工具候选)→ 提升六步(memories / timeline / pointers / 证据 / 候选状态 / promote 事件,同一事务)。
99
+ - 可靠提交:控制账预扣尝试、写后读回三态(found / missing / unknown)、最多一次即时补写、失败三次冻结、跨重启保留、操作者可解冻。
100
+ - 注入器官:按已验证钩子 `ctx.inject(['systemPrompt'], …)` 注册一个段(`memsys:context`,order=500),全集 S、排序、字符/字节/token 分账、full / compressed / grouped / protected_overflow 四档、分组三件套、快照分页、投递回执与曝光。
101
+ - 取回工具:`memsys_get / memsys_search / memsys_review_get / memsys_context_get` 的离线处理器,`memsys_record` 写入处理器。
102
+
103
+ **不包含**:做梦、self note 工具、judgments、projects、旧数据迁移、feed、主人专有动作入口。
104
+
105
+ ## 二、环境要求
106
+
107
+ - Node >= 22.13(本机实测 v24.19.0)。
108
+ - 只用内置 `node:sqlite`,**零第三方依赖**,不编译原生模块。
109
+ - 不需要 API key、不需要网络;`live-check` 的模型回合由人另外执行。
110
+
111
+ ## 三、迁移与自检
112
+
113
+ ```powershell
114
+ # 迁移并打印固定验收行
115
+ node whale-unify/memsys/scripts/migrate.mjs --data-dir whale-unify/_build/test-data/m1
116
+ # 期望:MIGRATIONS_OK main=3 control=2 dream=1
117
+
118
+ # 三库完整性
119
+ node whale-unify/memsys/scripts/check-db.mjs --data-dir whale-unify/_build/test-data/m1
120
+ # 期望:每库 integrity_check=ok、foreign_key_check=0;migrations 3/2/1
121
+ ```
122
+
123
+ - 生产默认目录是 `~/.dsh/memsys/`(由受控启动配置决定,模型不能选库或目录)。
124
+ - 验收/测试必须显式传 `--data-dir`,不要写生产库。
125
+ - 迁移按库各自事务执行;已应用脚本的哈希不一致会拒绝启动;库版本高于当前代码只读报错。
126
+
127
+ ## 四、测试与门禁
128
+
129
+ ```powershell
130
+ npm --prefix whale-unify/memsys test # 全部离线套件,当前 118 条
131
+ node whale-unify/memsys/scripts/build.mjs # 语法/入口/bundle 检查 → BUILD_OK
132
+ node whale-unify/memsys/scripts/check-bom.mjs # UTF-8 与 BOM 检查 → UTF8_OK BOM=0
133
+ ```
134
+
135
+ 单套件示例:
136
+
137
+ ```powershell
138
+ node --test whale-unify/memsys/test/m1-storage.test.mjs
139
+ node --test whale-unify/memsys/test/m2-write.test.mjs
140
+ node --test whale-unify/memsys/test/m3-reliable.test.mjs
141
+ node --test whale-unify/memsys/test/m4-context.test.mjs
142
+ node --test whale-unify/memsys/test/m5-tools.test.mjs whale-unify/memsys/test/e2e.test.mjs
143
+ ```
144
+
145
+ ## 五、装进 DSH(独立 profile)
146
+
147
+ > **禁止把工作区目录以 junction / 符号链接 / 硬链接的方式链进 profile。** 重建 profile 时递归删除会跟随链接,把源码一起删掉(本项目真出过一次事故)。要装就复制。
148
+
149
+ ```powershell
150
+ # 1) 复制到 ASCII 临时目录(不要直接 add 工作区路径)
151
+ $stage = Join-Path $env:TEMP "dsh-memsys-pkg"
152
+ Remove-Item $stage -Recurse -Force -ErrorAction SilentlyContinue
153
+ New-Item -ItemType Directory $stage | Out-Null
154
+ Copy-Item whale-unify/memsys\package.json, whale-unify/memsys\index.mjs, whale-unify/memsys\cordis.patch.yml $stage
155
+ Copy-Item whale-unify/memsys\src $stage\src -Recurse
156
+
157
+ # 2) 建独立 profile(不要碰 desktop)并安装
158
+ dsh --profile memsys-plan1-probe --from-default-profile headless --dump-config
159
+ dsh plugin --profile memsys-plan1-probe add $stage
160
+
161
+ # 3) 验证 bundle 条目已组合
162
+ dsh --profile memsys-plan1-probe --dump-config # 应出现 id: memsys / name: dsh-memsys
163
+
164
+ # 4) 收尾:卸掉插件,删临时副本
165
+ dsh plugin --profile memsys-plan1-probe remove dsh-memsys
166
+ Remove-Item $stage -Recurse -Force
167
+ ```
168
+
169
+ 若某次 profile 里已有 `node_modules/memsys` 链接,**先按目录项删除**(`[System.IO.Directory]::Delete($link)` 或 `rmdir`),确认目标未被动过,再重建 profile。
170
+
171
+ ## 六、真机探针(无 key)
172
+
173
+ ```powershell
174
+ node whale-unify/memsys/scripts/live-check.mjs --case inject --profile memsys-plan1-probe
175
+ # 期望:LIVE_CHECK_OK case=inject plugin=loaded section=memsys:context order=500 auto_inject=false delivery=unknown
176
+ ```
177
+
178
+ 它在**不调用模型**的前提下证明:插件装载成功、bundle 条目已组合、启动时 `ctx.inject(['systemPrompt'])` 回调执行并注册了 `memsys:context / order=500`(插件会写一个临时标记文件)。启动最后会因缺少 key 停在 `MISSING_CREDENTIAL`,这是预期行为。
179
+
180
+ **"段真的进了 system prompt" 需要一次真模型回合来核对 `system/message`,这一步不由本脚本承担。** 证据写在 `whale-unify/_build/artifacts/live-check-inject.json`。
181
+
182
+ ## 七、默认行为:auto_inject = false(tools-only 档)
183
+
184
+ | 配置 | 行为 |
185
+ |---|---|
186
+ | `auto_inject: false`(默认) | **不注册** systemPrompt 段;不承诺逐轮交付;`delivery=unknown`;只能用取回工具主动查询 |
187
+ | `diagnostic_section: true`(仅探针) | 注册段(`memsys:context`,order=500),正文是诊断占位,**不是**真实记忆交付 |
188
+ | `auto_inject: true` 且预算/回执未验证 | 仍按 tools-only 处理:`compileRound` 没有 tokenizer 或宿主 H 时返回 `mode=tools-only`、`section=null` |
189
+
190
+ 为什么默认关:宿主 tokenizer、真实上下文预算 H、独立投递回执三项都还没在真实宿主上验证。缺任何一项就不许声称"每轮全量交付"。
191
+
192
+ ## 八、编译产物怎么读
193
+
194
+ `compileRound(...)` 的返回(节选):
195
+
196
+ - `delivery_level`:`full`(完整目录在软预算与 H 内)/ `compressed`(摘要按 40→20→8 码点压缩)/ `grouped`(逐条装不下,只给分组目录)/ `protected_overflow`(连固定层 + 分组头都超过 H)。
197
+ - `section`:`{ name: 'memsys:context', order: 500, text, payload_hash }`。
198
+ - `hard` / `soft`:分别记条数、UTF-8 字节、token;**字符 / 字节 / token 三列分开,不互相顶替**。
199
+ - `groups[]`:每组给 `group_id`、中文组名、`total_count`、`deferred_count`,以及可执行参数
200
+ `memsys_context_get(snapshot_revision=…, group_id=…, cursor=null, max_chars=8000)`。
201
+ 正文里不会出现"另有 N 条"这种不可执行的计数。
202
+ - `delivery`:默认 `unknown`;只有收到满足"空闲集合相等 + payload hash 匹配"的回执才会 `confirmed`,部分回执是 `incomplete`。
203
+ - 快照:`snapshot_revision` 指向控制账里的持久快照,分组页游标绑定"快照 + 组";快照过期返回 `stale_cursor` 并给新报告入口,不会把新版数据拼进旧页。
204
+
205
+ ## 九、工具面
206
+
207
+ 离线处理器(测试与 tools-only 档都走它):
208
+
209
+ ```js
210
+ import { createReadHandler } from './src/tools/read.mjs';
211
+ const tools = createReadHandler({ main, control, clock });
212
+ tools.get({ ids: ['mem_…'], view: 'current', include_evidence: true, max_chars: 8000 });
213
+ tools.search({ keywords: '葱', scene: 'life', topic_key: 'tk:…', time_range: { start, end } });
214
+ tools.review({ kind: 'candidate' }); // 或 kind: 'pending' / 'due-history'
215
+ tools.context({ snapshot_revision, group_id, cursor: null, max_chars: 8000 });
216
+ tools.touch({ roundId, memoryId, kind: 'lookup' }); // 只对真实读取记 lookup
217
+ ```
218
+
219
+ - `memsys_get`:1–10 个唯一 id;`missing_ids` 与读取错误分开;超长正文按码点分片,`next_cursor` 续页,拼回后与原文逐字一致。
220
+ - `memsys_search`:关键词/场景/主题/时间范围过滤;空命中是 `status=ok`,不是错误;`graph` 参数返回 `unsupported_feature=graph`(第一批没有骨架)。
221
+ - `memsys_review_get`:候选/权限提案/due-history,给期限、默认出路、可用操作;这里的文本是审核材料,不是事实正文。
222
+ - `memsys_context_get`:只读持久快照分组页,不扩大快照场景。
223
+ - `lookup` 与 `used` 分开:只有真实读取加 `uses`,同轮同条只加一次;`used` / 主人提及只刷新 `last_touched_at`。
224
+
225
+ **宿主工具注册 API 尚未验证**:`registerTools(...)` 在没有传入真实 `register` 函数时抛 `tool_registration_unverified`,能力状态保持 `unknown`。不要在这之前声称工具已在线可用。
226
+
227
+ ## 十、数据、备份与恢复
228
+
229
+ - 位置:`~/.dsh/memsys/{memsys.db, control.db, dreams.db}`;连接固定 `journal_mode=WAL`、`foreign_keys=ON`、`synchronous=FULL`、`busy_timeout=5000`。
230
+ - 只追加:observations / evidence / relations / origins / operation_attempts / control_events 由触发器拒改拒删;candidates 只许 4 列、memories 只许 3 列事件投影。
231
+ - 备份:`node:sqlite` 没有 `db.backup()`,统一用 `VACUUM INTO` 做一致性快照(包含已提交 WAL 内容),不要直接复制 db/wal/shm。
232
+ - 检查点:`node scripts/checkpoint.mjs --label M5 --data-dir <dir>` 同时写源码副本(`_build/backup/<批次>/`)与三库备份(`<data-dir>/backup/<批次>/`)。
233
+ - 写后读回:业务提交后必须精确读回;`found / missing / unknown` 三态分明,`unknown` 不许当 `missing`。
234
+ - 冻结:同一对象连续三次失败 → `frozen=true`,第四次业务写为 0;重启不归零;冻结后探测到数据存在只记 `reconciliation`,不自动解冻。解冻只能由本地操作者指定 `object_key`、原因与新 generation。
235
+ - 梦库第一批只有 `schema_migrations` 和占位标记,没有任何梦内容表。
236
+
237
+ ## 十一、真机验证结果(2026-11-03)
238
+
239
+ | 项 | 命令 | 结果 |
240
+ |---|---|---|
241
+ | 工具注册 | `node scripts/live-check.mjs --case tools` | `LIVE_CHECK_OK registration=registered registered=10 schemas=35 get=true` |
242
+ | 工具调度 | `node scripts/live-check.mjs --case dispatch`(带 key) | `LIVE_CHECK_OK dispatch=ok`;`tool/result` 里 `direct_hit_count: 2`(命中「葱是绿的」「葱可以吃」),`evidence_mean: 0.14` |
243
+ | 注入段 | `node scripts/live-check.mjs --case inject` | `LIVE_CHECK_OK section=memsys:context order=500 auto_inject=false delivery=unknown` |
244
+
245
+ 证据:`_build/artifacts/live-check-{tools,dispatch,inject}.json`。
246
+ `live-check` 每次会先删 profile 内插件、清空 `%TEMP%` 暂存目录再复制,并记 `stage_hash`;出现 `stale_stage` 说明跑的不是当前代码。
247
+
248
+ ## 十二、已知边界(不许当成已闭合)
249
+
250
+ 写完功能不等于达标。下面 10 条是两轮 Codex 复核后**仍未满足设计稿**的:
251
+
252
+ 1. **恢复不按操作类型核完整预期结果**(L1):due/promote 的完整结果、事件与证据集合没进读回判据
253
+ 2. **批准取得写锁后未重验基版本**(E2):内容可能在验证与写入之间变化
254
+ 3. **编译没走三败控制**(E8):compile 直写控制账,没有 reserve/complete
255
+ 4. **快照不是内容寻址**(C3):靠 section hash 拒绝同 ID 覆盖;未用真实重复编译测混版
256
+ 5. **空分组回执边界**(C1):只有组头、无实际 memory 时的确认语义未测死
257
+ 6. **证据原文无分片续页**(C4):`observation_text` 只截断
258
+ 7. **回退不重放关系内容**(B3):只核行存在与关联,不核 relations 内容/hash
259
+ 8. **合并只保 `due_at`**(B1):候选层没有 `due_state`/`done`
260
+ 9. **门牌回填靠事件自证**(B2):不重读源文件;图只核基本字段
261
+ 10. **写入类工具仍不可用**:`memsys_record`/`dream_record`/`organize` 返回 `runtime_identity_unavailable`
262
+
263
+ 另外:`feed` 生产 provider 未接(`integration_pending`);真实旧库尚未导入,迁移只在夹具上验过。
264
+
265
+
266
+ ## 十二、三十秒自检
267
+
268
+ ```powershell
269
+ node --version # >= 22.13
270
+ node whale-unify/memsys/scripts/build.mjs # BUILD_OK
271
+ node whale-unify/memsys/scripts/check-bom.mjs # UTF8_OK BOM=0
272
+ npm --prefix whale-unify/memsys test # 118/118
273
+ node whale-unify/memsys/scripts/migrate.mjs --data-dir whale-unify/_build/test-data/m5
274
+ node whale-unify/memsys/scripts/check-db.mjs --data-dir whale-unify/_build/test-data/m5
275
+ ```
package/client.js ADDED
@@ -0,0 +1,110 @@
1
+ window.__ModuleLoader__.load({
2
+ id: 'dsh-memsys',
3
+ factory(require) {
4
+ const React = require('react');
5
+ const h = React.createElement;
6
+ const NS = 'dsh-memsys';
7
+ const API = '/dsh-memsys/api/summary';
8
+ const CONFIG = '/dsh-memsys/api/config';
9
+
10
+ function useSummary() {
11
+ const [state, setState] = React.useState(null);
12
+ React.useEffect(() => {
13
+ let alive = true;
14
+ const load = () => {
15
+ fetch(API)
16
+ .then((response) => (response.ok ? response.json() : Promise.reject(new Error(String(response.status)))))
17
+ .then((data) => alive && setState(data))
18
+ .catch(() => alive && setState({ error: true }));
19
+ };
20
+ load();
21
+ const timer = setInterval(load, 15000);
22
+ return () => {
23
+ alive = false;
24
+ clearInterval(timer);
25
+ };
26
+ }, []);
27
+ return [state, setState];
28
+ }
29
+
30
+ function MemoryPanel() {
31
+ const [state] = useSummary();
32
+ if (!state) return h('p', { style: { padding: 16 } }, '读取记忆…');
33
+ if (state.error) return h('p', { style: { padding: 16 } }, '暂时读不到记忆数据。');
34
+ return h(
35
+ 'div',
36
+ { style: { padding: 16, lineHeight: 1.7 } },
37
+ h('h2', { style: { margin: '0 0 8px' } }, '记忆'),
38
+ h('p', null, `记忆 ${state.memories} 条 · 候选 ${state.candidates} 条 · 待确认惦记 ${state.pending_due} 条`),
39
+ h('h3', { style: { margin: '16px 0 6px' } }, '最近 5 条'),
40
+ state.recent.length === 0
41
+ ? h('p', null, '还没有记忆。')
42
+ : h('ul', null, state.recent.map((memory) => h('li', { key: memory.id }, memory.text))),
43
+ );
44
+ }
45
+
46
+ function MemoryIcon() {
47
+ return h('span', { 'aria-hidden': true, role: 'img', title: '记忆' }, '🐳');
48
+ }
49
+
50
+ const SOURCE_LABEL = { 'ui-config': '设置文件', profile: 'profile 配置', default: '默认值' };
51
+
52
+ function MemorySettings() {
53
+ const [state, setState] = React.useState(null);
54
+ const [note, setNote] = React.useState('');
55
+ React.useEffect(() => {
56
+ fetch(API)
57
+ .then((response) => (response.ok ? response.json() : Promise.reject(new Error(String(response.status)))))
58
+ .then(setState)
59
+ .catch(() => setState({ error: true }));
60
+ }, []);
61
+ const save = (value) => {
62
+ const previous = state?.auto_inject === true;
63
+ setState((current) => ({ ...(current ?? {}), auto_inject: value }));
64
+ setNote('保存中…');
65
+ fetch(CONFIG, {
66
+ method: 'POST',
67
+ headers: { 'content-type': 'application/json' },
68
+ body: JSON.stringify({ auto_inject: value }),
69
+ })
70
+ .then((response) => (response.ok ? setNote('已保存') : Promise.reject(new Error(String(response.status)))))
71
+ .catch(() => {
72
+ setState((current) => ({ ...(current ?? {}), auto_inject: previous }));
73
+ setNote('保存失败');
74
+ });
75
+ };
76
+ return h(
77
+ 'div',
78
+ { style: { padding: '8px 0', lineHeight: 1.7 } },
79
+ h('label', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
80
+ h('input', {
81
+ type: 'checkbox',
82
+ checked: state?.auto_inject === true,
83
+ disabled: !state || state.error === true,
84
+ onChange: (event) => save(event.target.checked),
85
+ }),
86
+ '自动注入记忆',
87
+ ),
88
+ h('p', { style: { opacity: 0.75, margin: '6px 0 0' } }, `数据目录:${state?.data_dir ?? '未配置'}`),
89
+ h('p', { style: { opacity: 0.75, margin: '4px 0 0' } },
90
+ `当前生效值来源:${SOURCE_LABEL[state?.auto_inject_source] ?? state?.auto_inject_source ?? '未知'}`),
91
+ note ? h('p', { style: { opacity: 0.75, margin: '4px 0 0' } }, note) : null,
92
+ );
93
+ }
94
+
95
+ return {
96
+ inject: ['slots'],
97
+ apply(ctx) {
98
+ ctx.slots.inject('main', () =>
99
+ ctx.slots.register({ name: 'main', key: NS }, MemoryPanel),
100
+ );
101
+ ctx.slots.inject('sidebar.panellist', () =>
102
+ ctx.slots.register({ name: 'sidebar.panellist', id: NS, order: 30, label: () => '记忆' }, MemoryIcon),
103
+ );
104
+ ctx.slots.inject('settings.section', () =>
105
+ ctx.slots.register({ name: 'settings.section', id: NS, order: 40, label: () => '记忆系统' }, MemorySettings),
106
+ );
107
+ },
108
+ };
109
+ },
110
+ });
@@ -0,0 +1,6 @@
1
+ - insert:
2
+ - id: memsys
3
+ name: dsh-memsys
4
+ config:
5
+ auto_inject: false
6
+ diagnostic_section: false