dsh-prime-memory 0.11.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.
- package/CHANGELOG.en.md +28 -0
- package/CHANGELOG.ja.md +30 -0
- package/CHANGELOG.ko.md +30 -0
- package/CHANGELOG.md +1220 -0
- package/ENGINEERING-NOTES.md +452 -0
- package/INSTALL.en.md +92 -0
- package/INSTALL.ja.md +92 -0
- package/INSTALL.ko.md +92 -0
- package/INSTALL.md +92 -0
- package/LICENSE +21 -0
- package/README.en.md +458 -0
- package/README.ja.md +306 -0
- package/README.ko.md +306 -0
- package/README.md +425 -0
- package/assets/changelog/0.8.10/01-write-only-pill.png +0 -0
- package/assets/changelog/0.8.9/01-panel.png +0 -0
- package/assets/changelog/0.8.9/02-halo.png +0 -0
- package/assets/changelog/0.8.9/03-layer-segmented-panel.png +0 -0
- package/assets/changelog/0.8.9/04-layer-l1-panel.png +0 -0
- package/assets/img/EmbeddingSource.png +0 -0
- package/assets/img/Hero.png +0 -0
- package/assets/img/Layers.png +0 -0
- package/assets/img/MemoryTools.png +0 -0
- package/assets/img/Modes.png +0 -0
- package/assets/img/ToolTrajectory.png +0 -0
- package/assets/img/ui-dark.jpg +0 -0
- package/assets/img/ui-light.jpg +0 -0
- package/assets/readme/bench-dialog.svg +70 -0
- package/assets/readme/bench-workflow.svg +79 -0
- package/assets/readme/flow.svg +189 -0
- package/assets/readme/storage.svg +115 -0
- package/cordis.patch.yml +16 -0
- package/dist/bench-control.d.ts +34 -0
- package/dist/bench-control.js +16 -0
- package/dist/client.js +4293 -0
- package/dist/config.d.ts +683 -0
- package/dist/config.js +129 -0
- package/dist/contract.d.ts +820 -0
- package/dist/contract.js +1 -0
- package/dist/embedding-worker.cjs +176 -0
- package/dist/graph/apply.d.ts +37 -0
- package/dist/graph/apply.js +270 -0
- package/dist/graph/constraints.d.ts +47 -0
- package/dist/graph/constraints.js +38 -0
- package/dist/graph/search.d.ts +16 -0
- package/dist/graph/search.js +115 -0
- package/dist/graph/types.d.ts +142 -0
- package/dist/graph/types.js +14 -0
- package/dist/hooks/capture.d.ts +32 -0
- package/dist/hooks/capture.js +194 -0
- package/dist/hooks/recall.d.ts +63 -0
- package/dist/hooks/recall.js +429 -0
- package/dist/index.d.ts +534 -0
- package/dist/index.js +344 -0
- package/dist/llm-usage.d.ts +26 -0
- package/dist/llm-usage.js +37 -0
- package/dist/llm.d.ts +153 -0
- package/dist/llm.js +530 -0
- package/dist/pipeline/graph.d.ts +35 -0
- package/dist/pipeline/graph.js +104 -0
- package/dist/pipeline/l1.d.ts +19 -0
- package/dist/pipeline/l1.js +271 -0
- package/dist/pipeline/l2.d.ts +13 -0
- package/dist/pipeline/l2.js +83 -0
- package/dist/pipeline/l3.d.ts +15 -0
- package/dist/pipeline/l3.js +78 -0
- package/dist/pipeline/rebuild.d.ts +61 -0
- package/dist/pipeline/rebuild.js +307 -0
- package/dist/pipeline/ruminate.d.ts +89 -0
- package/dist/pipeline/ruminate.js +298 -0
- package/dist/pipeline/runner.d.ts +167 -0
- package/dist/pipeline/runner.js +638 -0
- package/dist/pipeline/trigger.d.ts +40 -0
- package/dist/pipeline/trigger.js +75 -0
- package/dist/prompts/graph-projection.d.ts +70 -0
- package/dist/prompts/graph-projection.js +167 -0
- package/dist/prompts/l1-dedup.d.ts +22 -0
- package/dist/prompts/l1-dedup.js +251 -0
- package/dist/prompts/l1-extraction.d.ts +22 -0
- package/dist/prompts/l1-extraction.js +457 -0
- package/dist/prompts/persona.d.ts +23 -0
- package/dist/prompts/persona.js +240 -0
- package/dist/prompts/scene.d.ts +32 -0
- package/dist/prompts/scene.js +414 -0
- package/dist/runtime-package-lock.json +982 -0
- package/dist/settings.d.ts +50 -0
- package/dist/settings.js +355 -0
- package/dist/stats.d.ts +109 -0
- package/dist/stats.js +929 -0
- package/dist/store/bm25.d.ts +19 -0
- package/dist/store/bm25.js +63 -0
- package/dist/store/cost-ledger.d.ts +75 -0
- package/dist/store/cost-ledger.js +171 -0
- package/dist/store/download-queue.d.ts +79 -0
- package/dist/store/download-queue.js +424 -0
- package/dist/store/embedding-source.d.ts +118 -0
- package/dist/store/embedding-source.js +443 -0
- package/dist/store/embedding.d.ts +90 -0
- package/dist/store/embedding.js +206 -0
- package/dist/store/graph-store.d.ts +94 -0
- package/dist/store/graph-store.js +641 -0
- package/dist/store/l0.d.ts +40 -0
- package/dist/store/l0.js +197 -0
- package/dist/store/l1.d.ts +93 -0
- package/dist/store/l1.js +297 -0
- package/dist/store/local-embedding.d.ts +89 -0
- package/dist/store/local-embedding.js +227 -0
- package/dist/store/model-catalog.d.ts +48 -0
- package/dist/store/model-catalog.js +81 -0
- package/dist/store/occupancy.d.ts +30 -0
- package/dist/store/occupancy.js +134 -0
- package/dist/store/pending.d.ts +36 -0
- package/dist/store/pending.js +103 -0
- package/dist/store/persona.d.ts +15 -0
- package/dist/store/persona.js +60 -0
- package/dist/store/recall-dedupe.d.ts +26 -0
- package/dist/store/recall-dedupe.js +138 -0
- package/dist/store/runtime-installer.d.ts +59 -0
- package/dist/store/runtime-installer.js +243 -0
- package/dist/store/scenes.d.ts +24 -0
- package/dist/store/scenes.js +160 -0
- package/dist/store/search-utils.d.ts +38 -0
- package/dist/store/search-utils.js +100 -0
- package/dist/store/session-modes.d.ts +35 -0
- package/dist/store/session-modes.js +144 -0
- package/dist/store/sqlite.d.ts +246 -0
- package/dist/store/sqlite.js +1491 -0
- package/dist/store/state.d.ts +41 -0
- package/dist/store/state.js +72 -0
- package/dist/token-cost.d.ts +23 -0
- package/dist/token-cost.js +185 -0
- package/dist/tools/index.d.ts +34 -0
- package/dist/tools/index.js +758 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +38 -0
- package/dist/util/context-occupancy.d.ts +68 -0
- package/dist/util/context-occupancy.js +92 -0
- package/dist/util/filelog.d.ts +6 -0
- package/dist/util/filelog.js +108 -0
- package/dist/util/io.d.ts +18 -0
- package/dist/util/io.js +97 -0
- package/dist/util/recall-budget.d.ts +31 -0
- package/dist/util/recall-budget.js +84 -0
- package/dist/util/sanitize.d.ts +11 -0
- package/dist/util/sanitize.js +67 -0
- package/dist/util/text.d.ts +16 -0
- package/dist/util/text.js +61 -0
- package/dist/util/tokenizer.d.ts +9 -0
- package/dist/util/tokenizer.js +50 -0
- package/dsh.plugin.json +22 -0
- package/package.json +118 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# ENGINEERING NOTES — 踩坑与修复方向手册
|
|
2
|
+
|
|
3
|
+
> 本文件沉淀 `dsh-prime-memory` 开发中**实际踩过的坑**与**走错过的修复方向**,目的不是记录"什么是对的",
|
|
4
|
+
> 而是让后来者**不再重复付出同样的调试代价**。
|
|
5
|
+
>
|
|
6
|
+
> 收录标准:① 曾实际导致失败、误判或返工;② 有非显然的根因;③ 有可复用的规避动作。
|
|
7
|
+
> 每条都给出**现象 / 根因 / 正确做法 / 如何验证**四段。
|
|
8
|
+
>
|
|
9
|
+
> 适用版本:`dsh-prime-memory@0.10.0`,DSH value schema DSL `@deepseek-ai/dsh-tools@0.1.1-rc.2`。
|
|
10
|
+
> 最近更新:2026-09-12(反刍功能三轮修复期间)。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 0. 一页速查
|
|
15
|
+
|
|
16
|
+
| 现象 | 跳转 |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `unsupported JSON schema: … nullable is not supported` → 插件树整棵加载失败 | [A1](#a1-nullable-让整棵插件树崩溃) |
|
|
19
|
+
| 端点恒返 `{supported:false}` / 「控制器未初始化」 | [A2](#a2-声明了字段却忘了注入--端点被永久钉在降级分支) |
|
|
20
|
+
| `TypeError: messages is not iterable` | [A3](#a3-同一份文件两个解析器--其中一个从未成功过) |
|
|
21
|
+
| 「反刍已在进行中」卡住不动、界面无进度 | [B1](#b1-守卫标志位放在-finally-等于没放) / [B2](#b2-长任务不置-running-界面只能显示运行中) |
|
|
22
|
+
| `ReferenceError: xxx is not defined` 而 `tsc` 不报错 | [B3](#b3-可选字段让-tsc-抓不到未定义标识符) |
|
|
23
|
+
| 测试用挂起 promise 挡住某一步却挡不住 | [B4](#b4-await-不能出现在非-async-作用域) |
|
|
24
|
+
| 测试全绿但真实环境必崩 | [C1](#c1-只用空数据写测试) |
|
|
25
|
+
| `tsc` 报 0 个错误(其实是假的) | [D2](#d2-powershell-管道捕获让-tsc-错误数变-0) |
|
|
26
|
+
| 中文乱码 / 行号对不上 / 文件开头多个不可见字符 | [D3](#d3-编码三连bom-乱码-行号漂移) |
|
|
27
|
+
| `git commit -F` 后正文变 `?` 或整条被覆盖 | [E1](#e1-amend--m-会整条替换消息) |
|
|
28
|
+
| 提权后仍 `spawn EPERM` | [D1](#d1-沙箱-spawn-eperm不是权限不够的意思) |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# A. 导致线上/启动失败的真实缺陷
|
|
33
|
+
|
|
34
|
+
## A1. `nullable` 让整棵插件树崩溃
|
|
35
|
+
|
|
36
|
+
**现象**
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Error: dsh: plugin tree failed to load: 应用 loader 条目 dsh-memory (dsh-prime-memory) 失败:
|
|
40
|
+
unsupported JSON schema: schema.properties.startedAt.nullable is not supported by the value schema DSL
|
|
41
|
+
```
|
|
42
|
+
DSH **完全无法启动**(不是功能降级,是进程退出)。
|
|
43
|
+
|
|
44
|
+
**根因**:DSH 的 value schema DSL **只接受一组白名单作者键**:
|
|
45
|
+
`description` / `title` / `default` / `examples` / `required`(仅 properties)/ `type` / `enum` /
|
|
46
|
+
`const` / `properties` / `additionalProperties` / `items` / `oneOf`。
|
|
47
|
+
`nullable` **不在其中**——它是 OpenAPI/JSON-Schema 方言的写法,写进去会在 `defineTool()` 编译 schema 时抛错。
|
|
48
|
+
|
|
49
|
+
**正确做法**:删掉 `nullable: true`。该 DSL 中**属性默认即可选**,只有显式 `required: true` 才必填;
|
|
50
|
+
且运行时校验对 `undefined` 取值**跳过**,所以 `execute()` 返回 `startedAt: undefined` 依然合法。
|
|
51
|
+
语义完全不变。
|
|
52
|
+
|
|
53
|
+
**如何验证**:`tsc` 通过 + `grep -r nullable src/` 无命中 + 启动后工具注册日志出现
|
|
54
|
+
`工具已注册: …`。**光看 `tsc` 不够**——这是 schema 编译期(运行时)错误,类型检查发现不了。
|
|
55
|
+
|
|
56
|
+
> 🔴 **方向性教训**:插件树是**全有或全无**的。任何单个 loader 条目 apply 失败都会带走整个进程。
|
|
57
|
+
> 因此新增工具/schema 后,**必须做一次真实启动验证**,"类型过了"远不足以交付。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## A2. 声明了字段却忘了注入 → 端点被永久钉在降级分支
|
|
62
|
+
|
|
63
|
+
**现象**:`dsh-memory/ruminate-status` 恒返 `{supported:false,running:false,phase:'idle'}`,
|
|
64
|
+
`start`/`cancel` 恒抛「反刍控制器未初始化」。而**界面把 `supported===false` 解释为"功能不存在"整块 `return null`**,
|
|
65
|
+
于是故障表现为**功能凭空消失**而非报错——这就是它能潜伏很久的原因。
|
|
66
|
+
|
|
67
|
+
**根因**:`EndpointDeps.ruminate` 字段已声明、三个端点实现也写好了,但组装 deps 实参时**没把控制器传进去**,
|
|
68
|
+
`deps.ruminate` 恒为 `undefined`。审计发现:`EndpointDeps` 的 13 个字段里,`ruminate` 是**唯一"已声明但未注入"**的那个——
|
|
69
|
+
`rebuild`/`embedManager`/`sessionInfo` 都是对的,所以**没有对照组、看不出异常**。
|
|
70
|
+
|
|
71
|
+
**正确做法**:把 deps 组装抽成**可测的单一接缝** `buildEndpointDeps()`,并用
|
|
72
|
+
`EndpointDepsInput`(`Omit<EndpointDeps,'ctx'|'cfg'|'stores'|'logger'>`)约束注入面——
|
|
73
|
+
这样"漏注入"会在**编译期**暴露,而不是运行时静默降级。
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// ✅ 接缝形态:注入字段由类型检查保证齐全
|
|
77
|
+
export function buildEndpointDeps(base, sources, controller): EndpointDeps {
|
|
78
|
+
const injected: EndpointDepsInput = { ...sources, ruminate: controller };
|
|
79
|
+
return { ...base, ...injected };
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**如何验证**:控制器级测试断言注入后 `status` 返回 `supported !== false`;未注入时走降级分支。
|
|
84
|
+
|
|
85
|
+
> 🔴 **方向性教训**:**降级分支会掩盖故障**。任何 `?.` / `if (!dep) return DEGRADED` 的设计,
|
|
86
|
+
> 都必须配一条"已装配"的正向用例,否则你永远只测到了降级那条路。
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## A3. 同一份文件两个解析器 —— 其中一个从未成功过
|
|
91
|
+
|
|
92
|
+
**现象**
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
TypeError: messages is not iterable
|
|
96
|
+
❯ groupPendingBySession src/store/pending.ts:104
|
|
97
|
+
❯ groupSessions src/pipeline/ruminate.ts:75
|
|
98
|
+
❯ RuminateController.start src/pipeline/ruminate.ts:139
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**根因**:`ruminate.ts` 自带一个 `readPendingBuckets()`,把 `JSON.parse(readFileSync(file))`
|
|
102
|
+
**直接断言**成 `PendingBuckets`(`{auto,chat,work}`)。但磁盘真实形状是 `PendingFile`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{ "version": 1, "buckets": { "auto": [], "chat": [], "work": [] }, "warmup": {...} }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**漏了一层 `buckets` 解包** → `buckets[mode]` 恒为 `undefined` → `for (const m of messages)` 抛错。
|
|
109
|
+
|
|
110
|
+
**关键反直觉点(我曾判断错)**:抛错**与桶里有没有数据无关**。桶空也抛,因为
|
|
111
|
+
`undefined` 本身不可迭代。`catch` 只在**文件不存在(ENOENT)**时才兜出合法空桶;
|
|
112
|
+
而 `persistPending` 每轮都会写这个文件,所以**在任何有缓冲的真实部署里,点反刍必然失败**。
|
|
113
|
+
真实潜伏原因是**该功能从未被触发过**(日志里没有任何「反刍开始」记录),不是"数据恰好为空"。
|
|
114
|
+
|
|
115
|
+
**正确做法**:`store/pending.ts` 的 `loadPending()` 已经是该文件形状的**唯一权威**
|
|
116
|
+
(含形状校验、逐桶 `Array.isArray`、`isMessage` 坏行丢弃计数、旧格式 `LEGACY_SESSION` 归组、`warmup` 校验)。
|
|
117
|
+
**删掉第二个解析器**,改为:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const { buckets } = await loadPending(this.pendingFile, this.logger);
|
|
121
|
+
this.sessions = groupSessions(buckets);
|
|
122
|
+
```
|
|
123
|
+
**绝不要**在 `pending.ts` 再开一个"XX 专用读取入口"——那正是本缺陷的成因。
|
|
124
|
+
|
|
125
|
+
**如何验证**:控制器级测试(见 [C1](#c1-只用空数据写测试))先红后绿;`grep -r readPendingBuckets dist/` 应无命中。
|
|
126
|
+
|
|
127
|
+
> 🔴 **方向性教训(Anti-Entropy)**:**同一语义存在第二条实现路径时,其中一条必然腐烂**,
|
|
128
|
+
> 而且因为它是"没人走的那条",腐烂不会变红。发现重复解析/重复契约时,第一反应应该是**删掉一条**,
|
|
129
|
+
> 而不是"把两条都改对"。
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
# B. 代码级陷阱
|
|
134
|
+
|
|
135
|
+
## B1. 守卫标志位放在 `finally` 等于没放
|
|
136
|
+
|
|
137
|
+
**背景**:把 `readPendingBuckets` 改成 `await loadPending(...)` 后,`start()` 在
|
|
138
|
+
"守卫检查"与"`status.running` 置位"之间**多出一个事件循环让出点**,双击/连发 RPC 可双双穿过守卫。
|
|
139
|
+
|
|
140
|
+
**我第一版的错误改法**:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
this.starting = true;
|
|
144
|
+
try {
|
|
145
|
+
...
|
|
146
|
+
this.doEnqueue(0); // ← 异步入队,立刻返回
|
|
147
|
+
return { ...this.status };
|
|
148
|
+
} finally {
|
|
149
|
+
this.starting = false; // ← 错!start() 一返回就清旗,守卫形同虚设
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**根因**:`doEnqueue` 用 `setImmediate` **异步**递归入队,`start()` **不等蒸馏完成就返回**。
|
|
154
|
+
所以 `finally` 在"任务还在跑"时就把旗清了 → 第二次 `start()` 顺利通过 → 两套 `this.sessions` 互相覆盖。
|
|
155
|
+
|
|
156
|
+
**正确做法**:把"跨 `await` 的持久守卫"交给 `status.running`(它在 `await` **之前**就置位),
|
|
157
|
+
`starting` 只封住"读取期间"这个**同步→异步过渡窗口**,并在成功分支末尾显式复位:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
this.status = { ...IDLE_STATUS, running: true, phase: 'distilling', ... };
|
|
161
|
+
this.doEnqueue(0);
|
|
162
|
+
this.starting = false; // 此刻 status.running 已置位,后续并发由它拦
|
|
163
|
+
return { ...this.status };
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**如何验证**:并发用例 `Promise.allSettled([ctl.start(), ctl.start()])`
|
|
167
|
+
断言 **恰好 1 个 fulfilled / 1 个 rejected**。
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## B2. 长任务不置 `running`,界面只能显示"运行中"
|
|
172
|
+
|
|
173
|
+
**现象**:点反刍后界面只有一句静态说明,**无进度条、无取消按钮、无阶段、无耗时**;
|
|
174
|
+
此时再点一次得到「反刍已在进行中」——提示正确但毫无信息量,用户无法判断"在跑"还是"卡死"。
|
|
175
|
+
|
|
176
|
+
**根因**:`start()` 在无 pending 切片时 `return await this.doLightRefresh()`,
|
|
177
|
+
而该分支**从不置 `running`** → `ruminate-status` 期间仍返回 `phase:'idle'`/`running:false`。
|
|
178
|
+
**但这里的 L2/L3 是真实 LLM 调用,实测单次 70 秒以上**,且 `start()` 一直挂在 `await` 上占着守卫。
|
|
179
|
+
|
|
180
|
+
**正确做法**:长任务开始前就把状态置为"运行中",并给出**可判定的进度**:
|
|
181
|
+
|
|
182
|
+
1. 先算出待执行步骤数 → `total`;
|
|
183
|
+
2. 每完成一步 `done++`;
|
|
184
|
+
3. 新增 `phase='refreshing'` 让 UI 有阶段名;
|
|
185
|
+
4. 新增 `detail` 描述**当前动作**(如 `L2 场景整合(chat)`);
|
|
186
|
+
5. 面板显示 `已完成/总步数(百分比)` + **实时「已用 X分Y秒」**。
|
|
187
|
+
|
|
188
|
+
**如何验证**:用挂起的依赖把某一步钉住,在**运行期间**取样断言
|
|
189
|
+
`running===true` / `total>0` / `detail` 含预期关键字(见 [B4](#b4-await-不能出现在非-async-作用域))。
|
|
190
|
+
|
|
191
|
+
> 🔴 **方向性教训**:**"运行中"不是一个状态,三个信息才构成一个状态:在做什么 + 做到哪 + 花了多久。**
|
|
192
|
+
> 只报 `running:true` 等于没报。凡是单步可能超过 ~10 秒的操作,都必须有 `detail` 级别的描述。
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## B3. 可选字段让 `tsc` 抓不到"未定义标识符"
|
|
197
|
+
|
|
198
|
+
**现象**:删掉一个形参后,deps 字面量里仍写着 `ruminate,` —— 该标识符已不存在,
|
|
199
|
+
但 **`tsc` 不报错**,直到运行时抛 `ReferenceError: ruminate is not defined`,**一次带走 12 个无关测试**。
|
|
200
|
+
|
|
201
|
+
**根因**:`EndpointDeps.ruminate` 是**可选**属性,`{ ..., ruminate }` 这种简写上,
|
|
202
|
+
类型检查器不会把"标识符是否存在"与"属性是否可选"联系起来(简写属性走的是赋值兼容性)。
|
|
203
|
+
|
|
204
|
+
**正确做法**:不要把裸标识符写进对象字面量,而是**经过显式接缝**:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const injected: EndpointDepsInput = { status, live, modes, dataDir, rebuild, ruminate };
|
|
208
|
+
```
|
|
209
|
+
`EndpointDepsInput` 里 `ruminate` 是**必填**,于是漏传/拼错会在**编译期**失败。
|
|
210
|
+
(这也是 [A2](#a2-声明了字段却忘了注入--端点被永久钉在降级分支) 的同一个修法。)
|
|
211
|
+
|
|
212
|
+
**如何验证**:故意删掉一个注入字段,`tsc` 应当报错。
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## B4. `await` 不能出现在非 async 作用域
|
|
217
|
+
|
|
218
|
+
**我的错误尝试**:在 `describe('...', () => { ... })` 回调里写
|
|
219
|
+
`const { createHash } = await import('node:crypto');`
|
|
220
|
+
—— 这不是 async 函数,ESM 顶层 await **只允许在模块顶层**。
|
|
221
|
+
|
|
222
|
+
**正确做法**:需要模块级依赖就写**顶层 `import`**:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { createHash } from 'node:crypto';
|
|
226
|
+
```
|
|
227
|
+
(改完记得删掉原地的局部 `const`,否则会留下一条夹在函数中间的 import。)
|
|
228
|
+
|
|
229
|
+
**同理**:测试里想"挡住某一步"时,**不要**依赖 `vi.doMock` —— 如果被测模块已在本文件顶部 import,
|
|
230
|
+
再 `doMock` 不会生效。**更稳的做法是让依赖真的挂住**:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
let release: () => void = () => {};
|
|
234
|
+
const gate = new Promise<void>((r) => { release = r; });
|
|
235
|
+
// runSceneConsolidation 第一步就是 scenes.list(),用挂起的 list() 把它钉住
|
|
236
|
+
const stores = { scenes: { chat: { list: () => gate }, work: { list: () => gate } }, ... };
|
|
237
|
+
```
|
|
238
|
+
这样无需 mock 任何模块,且能精确在"运行中"取样。
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
# C. 测试与类型门禁
|
|
243
|
+
|
|
244
|
+
## C1. 只用空数据写测试
|
|
245
|
+
|
|
246
|
+
**现象**:**17 个测试文件、199 个用例全绿**,而 [A3](#a3-同一份文件两个解析器--其中一个从未成功过) 的崩溃
|
|
247
|
+
在真实环境**必现**。
|
|
248
|
+
|
|
249
|
+
**根因(两重)**:
|
|
250
|
+
1. **`tests/` 对 `RuminateController` 的实例化引用为 0**——唯一命中是注释里的 stub;
|
|
251
|
+
2. 形状契约被**测在了另一个函数上**:`stores.test.ts` 覆盖的是**正确实现 `loadPending`**,
|
|
252
|
+
而真正腐烂的重复解析器**零覆盖**。
|
|
253
|
+
准确表述不是"只覆盖了空数据",而是**"契约被测在 A 上,而 B 才是生产路径"**。
|
|
254
|
+
|
|
255
|
+
**正确做法**:
|
|
256
|
+
- 新增**控制器级**用例(解析层被删除后,这是**唯一**可行路径,不是"更划算"的取舍);
|
|
257
|
+
- **必须含非空载荷**——三桶里放真实消息,断言 `total` == 会话组数、`mode` 由**桶键推导**;
|
|
258
|
+
- 另加**契约用例**:手写磁盘形状 JSON 走过真实读取路径。**不要**只依赖 `savePending → loadPending` 往返——
|
|
259
|
+
那种往返测试在"读写两侧同时改错"时**依然会通过**。
|
|
260
|
+
|
|
261
|
+
> 🔴 **方向性教训**:**测试要钉在"生产真实调用路径"上,而不是钉在"最容易测的函数"上。**
|
|
262
|
+
> 写测试前先问:这段代码在真实调用链里是谁在读它?
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## C2. `vitest` 只转译不检查 → 测试与类型契约悄悄漂移
|
|
267
|
+
|
|
268
|
+
**现象**:把 `tests/**` 纳入 `tsconfig.test.json` 后,暴露 **8 个文件约 60 个类型错误**:
|
|
269
|
+
`MemoryConfig` 从错误模块导入(`contract.js`/`types.js`,实际在 `config.js`)、
|
|
270
|
+
`DistillBudgets` 缺必填 `graph`、`UserMessage.turn` 不存在、`readonly string[]` 赋给可变 `string[]`、
|
|
271
|
+
`rpc.test.ts` 约 33 处裸 `as` 不重叠断言、隐式 `any` 参数……
|
|
272
|
+
|
|
273
|
+
**根因**:`tsconfig.json` 的 `include` 只有 `src/**`,vitest 只**转译**不类型检查,
|
|
274
|
+
于是**契约变了、测试没跟着变,运行时却照旧全绿**。
|
|
275
|
+
|
|
276
|
+
**正确做法(ratchet,不要一把梭)**:
|
|
277
|
+
1. `tsconfig.json` 有 `rootDir:"src"`,**不能**直接 include `tests/**`(会报 **TS6059**),
|
|
278
|
+
必须新建 `tsconfig.test.json`(`extends` 主配置 + `rootDir:"."` + `noEmit` + `declaration:false`);
|
|
279
|
+
2. **先测量再决定**:错误少就修,错误多就先 ratchet——**只纳入干净的测试文件**,
|
|
280
|
+
其余逐个修完再放开;
|
|
281
|
+
3. 绝不使用 `@ts-nocheck` 或批量 `as unknown as` 掩盖——那只是把漂移藏得更深。
|
|
282
|
+
|
|
283
|
+
**如何验证**:`tsc -p tsconfig.test.json` exit 0;新加入的测试文件必须落在 include 里。
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
# D. 环境与工具链陷阱
|
|
288
|
+
|
|
289
|
+
## D1. 沙箱 `spawn EPERM` 不是"权限不够"的意思
|
|
290
|
+
|
|
291
|
+
**现象**:`node node_modules/vitest/vitest.mjs run` 与 `esbuild` 构建全部失败:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
Error: spawn EPERM
|
|
295
|
+
at ensureServiceIsRunning (node_modules/esbuild/lib/main.js:2272)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**根因**:受限文件沙箱**禁止子进程用管道 stdio**(`child_process.spawn` 默认 `stdio:'pipe'`)。
|
|
299
|
+
这不是"文件不可写",是**进程创建被拦**。**任何**会 spawn 的工具都会中招(vitest worker、esbuild 服务、`Start-Process`)。
|
|
300
|
+
|
|
301
|
+
**我试过且无效的路子(记录下来省得重试)**:
|
|
302
|
+
| 尝试 | 结果 |
|
|
303
|
+
|---|---|
|
|
304
|
+
| 直接执行 | `spawn EPERM` |
|
|
305
|
+
| 提权重试 | 策略判 `risky:system → auto-deny`(ask 模式下无审批者可应答 → fail closed) |
|
|
306
|
+
| `ESBUILD_BINARY_PATH` 指向 `@esbuild/win32-x64/esbuild.exe` | **仍然 EPERM** —— 该变量只改二进制**路径**,不改"必须 spawn"这件事 |
|
|
307
|
+
|
|
308
|
+
**正确做法**:需要真实子进程时**提升文件策略**(`danger-full-access`)。
|
|
309
|
+
一旦放开,此前被判 `risky:remote` 而误拒的 `npm run smoke` / `verify-catalog` 也一并恢复——
|
|
310
|
+
**它们其实纯本地,属启发式误判**,不需要加白名单。
|
|
311
|
+
|
|
312
|
+
**如何验证**:`node scripts/build-client.mjs` → `client bundle built → dist/client.js (N bytes)`。
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## D2. PowerShell 管道捕获让 `tsc` 错误数变 0
|
|
317
|
+
|
|
318
|
+
**现象**:我用下面这行"测量"测试目录的类型错误,得到 **0 个**:
|
|
319
|
+
|
|
320
|
+
```powershell
|
|
321
|
+
$out = node node_modules/typescript/bin/tsc -p tsconfig.test.json 2>&1
|
|
322
|
+
($out | Select-String -Pattern "error TS").Count # → 0 ❌ 假的
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**根因**:**PS 在输出被重定向/捕获时会跳过逐行管道处理**,`$out` 实际为空 → 数了个寂寞。
|
|
326
|
+
"0 个错误"是假象,真实是**约 60 个**。我据此做出了"可直接全量纳入"的错误判断,被真实测量推翻。
|
|
327
|
+
|
|
328
|
+
**正确做法**:测量类命令**直接让它打到控制台**,人眼/直接读取:
|
|
329
|
+
|
|
330
|
+
```powershell
|
|
331
|
+
node node_modules/typescript/bin/tsc -p tsconfig.test.json; Write-Host "exit: $LASTEXITCODE"
|
|
332
|
+
```
|
|
333
|
+
或**重定向到文件再读文件**,不要依赖内存变量:
|
|
334
|
+
|
|
335
|
+
```powershell
|
|
336
|
+
node ... > out.txt 2>&1; Get-Content out.txt | Select-String "error TS"
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**同类陷阱**:`2>$null` 与 `Start-Process` 也会触发沙箱拒绝。
|
|
340
|
+
**判断命令真伪的唯一可靠依据是 `$LASTEXITCODE`**,不要用"输出看起来对"来推断成功。
|
|
341
|
+
|
|
342
|
+
> 🔴 **方向性教训**:**"没有输出"和"没有错误"是两件事。**
|
|
343
|
+
> 任何"测量得到 0/空"的结论,都要用第二种方式复核一次再采信。
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## D3. 编码三连:BOM、乱码、行号漂移
|
|
348
|
+
|
|
349
|
+
**现象**:
|
|
350
|
+
1. `git commit` 后 commit subject 变成 `<BOM>fix(ruminate): …`(开头一个不可见字符);
|
|
351
|
+
2. `Get-Content` 打印中文注释变 `锟斤拷` 式乱码,且**报错行号与真实文件对不上**;
|
|
352
|
+
3. `Out-File -Encoding ascii` 写出的提交消息,中文全变 `?`。
|
|
353
|
+
|
|
354
|
+
**根因**:
|
|
355
|
+
- **BOM**:PowerShell `Out-File -Encoding UTF8` 在 Windows PowerShell 上会**带 BOM**;
|
|
356
|
+
- **乱码**:控制台代码页与文件编码不一致;
|
|
357
|
+
- **行号漂移**:乱码渲染让输出与真实行号错位——**我因此一度追错文件**。
|
|
358
|
+
|
|
359
|
+
**正确做法**:
|
|
360
|
+
| 场景 | 做法 |
|
|
361
|
+
|---|---|
|
|
362
|
+
| 写 UTF-8 无 BOM 文件 | `[System.IO.File]::WriteAllText($p, $s, [System.Text.UTF8Encoding]::new($false))` |
|
|
363
|
+
| 读文件核对内容 | 用 `read` 工具(带行号),**不要**用 `Get-Content` 判断中文 |
|
|
364
|
+
| 定位报错行 | 以工具给出的**文件:行号**为准,别信控制台渲染的行号 |
|
|
365
|
+
| 行尾 | 给 shell 脚本写文件要确保 **LF**,CRLF 会让 `sh` 解析出错 |
|
|
366
|
+
|
|
367
|
+
**如何验证**:写完检查首字节不是 `EF BB BF`;`ReadAllBytes` 前 3 字节应为内容字节。
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
# E. Git 与提交纪律
|
|
372
|
+
|
|
373
|
+
## E1. `amend` + `-m` 会整条替换消息
|
|
374
|
+
|
|
375
|
+
**我的错误**:为去掉 BOM,执行 `git commit --amend -m "…subject…"` ——
|
|
376
|
+
`-m` 是**整条替换**,把原本的**详细正文全部清空**了(正文长度变成 0)。
|
|
377
|
+
|
|
378
|
+
**正确做法**:要改消息就**完整重写**,用文件传入并确保编码正确:
|
|
379
|
+
|
|
380
|
+
```powershell
|
|
381
|
+
[System.IO.File]::WriteAllText($p, $fullMsg, [System.Text.UTF8Encoding]::new($false))
|
|
382
|
+
git commit --amend -F $p -q
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**同类踩坑**:
|
|
386
|
+
- `git rebase -i --exec "…"` 在 PowerShell 下**引用极易被破坏**(反斜杠被 shell 吃掉、引号被吞),
|
|
387
|
+
中途还会停在 detached HEAD。**能不用就不用**。
|
|
388
|
+
- `git filter-branch` 的 `--msg-filter` 里写路径要用 **`/` 正斜杠**(反斜杠会被当转义),
|
|
389
|
+
且脚本内容最好**放进独立 `.sh` 文件**由 `sh <file>` 调用,避开一切内联引用问题;
|
|
390
|
+
不加范围时 `-f` 会被拒绝,要显式给 `<base>..HEAD`。
|
|
391
|
+
|
|
392
|
+
## E2. "内容没变"必须用 tree 哈希证明,不能靠感觉
|
|
393
|
+
|
|
394
|
+
重写历史(`amend`/`rebase`/`filter-branch`)后,**唯一可信的等价性证明是 tree 哈希**:
|
|
395
|
+
|
|
396
|
+
```powershell
|
|
397
|
+
git rev-parse "NEW^{tree}"; git rev-parse "OLD^{tree}" # 相同 → 只改了消息,内容零变化
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**兜底习惯**:动手前先开个备份分支 + 记住 reflog:
|
|
401
|
+
|
|
402
|
+
```powershell
|
|
403
|
+
git branch backup/pre-rewrite
|
|
404
|
+
git reflog # 任何误操作都能回到 aaa HEAD@{n}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
> 我这次因 `reset --soft` 目标判断失误把状态搞乱过一次,**是靠 reflog 完整恢复的**。
|
|
408
|
+
|
|
409
|
+
## E3. 提交纪律(本项目约定)
|
|
410
|
+
|
|
411
|
+
1. **超出既有约定的改动(尤其"契约零改动"这类)动手前后都要有 commit 固定**,
|
|
412
|
+
便于回溯与回滚;
|
|
413
|
+
2. **修复必须记入 `CHANGELOG.md` 的 `[未发布]` 块**,且要写**用户可见影响**,不能只写技术根因;
|
|
414
|
+
3. 提交信息用**完整正文**说明:根因 → 修复 → 验证 → 前置 checkpoint;
|
|
415
|
+
4. **不要**顺手提交无关的大文件(本项目 `registry-save.json` 2.7MB 仍未决策,见 `.agents/plans/pending-issues.md` P6);
|
|
416
|
+
5. 规划与论证产物统一落在 `.agents/plans/<任务名>/`,固定 4 个文件名
|
|
417
|
+
(`spec.md` / `findings.md` / `checklist.md` / `tasks.md`),超限时归并归档。
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
# F. 验证清单(交付前逐条过)
|
|
422
|
+
|
|
423
|
+
以下每条都对应上面一个真实踩过的坑,**建议直接抄进 PR 描述**:
|
|
424
|
+
|
|
425
|
+
- [ ] `tsc -p tsconfig.json` exit 0
|
|
426
|
+
- [ ] `tsc -p tsconfig.client.json` exit 0(**契约改动后必跑**,见 [A2](#a2-声明了字段却忘了注入--端点被永久钉在降级分支)/[E](#e-git-与提交纪律))
|
|
427
|
+
- [ ] `tsc -p tsconfig.test.json` exit 0(新测试文件必须在 include 内)
|
|
428
|
+
- [ ] `vitest run` 全绿,**且 `$LASTEXITCODE` 为 0**(不要只看输出,见 [D2](#d2-powershell-管道捕获让-tsc-错误数变-0))
|
|
429
|
+
- [ ] `eslint` 0 error
|
|
430
|
+
- [ ] `npm run build` 三步全过(`build-client.mjs` 需能 spawn,见 [D1](#d1-沙箱-spawn-eperm不是权限不够的意思))
|
|
431
|
+
- [ ] `node dist-smoke/smoke.js` 全过
|
|
432
|
+
- [ ] **`dist/` 已同步**(改了 `src/` 或 `client/src/` 后必须重建;profile 是符号链接,**重启 DSH 才生效**)
|
|
433
|
+
- [ ] 新增/修改的 schema:**做一次真实启动**([A1](#a1-nullable-让整棵插件树崩溃) 是运行时错误,类型检查抓不到)
|
|
434
|
+
- [ ] 新增端点/控制器:**有"已装配"的正向用例**,不能只测降级分支([A2](#a2-声明了字段却忘了注入--端点被永久钉在降级分支))
|
|
435
|
+
- [ ] 新增读取器:**确认没有第二条解析路径**([A3](#a3-同一份文件两个解析器--其中一个从未成功过))
|
|
436
|
+
- [ ] 长任务(单步 >10s):有 `phase` + `done/total` + `detail` + 耗时([B2](#b2-长任务不置-running-界面只能显示运行中))
|
|
437
|
+
- [ ] 测试用例含**非空真实载荷**,且钉在**生产调用路径**上([C1](#c1-只用空数据写测试))
|
|
438
|
+
- [ ] `CHANGELOG.md` 已记录**用户可见影响**
|
|
439
|
+
- [ ] 提交信息完整;如需重写历史,**已用 tree 哈希证明内容未变**([E2](#e2-内容没变必须用-tree-哈希证明不能靠感觉))
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## 附:本手册对应的三轮修复记录
|
|
444
|
+
|
|
445
|
+
| 轮次 | 主题 | 关键教训 |
|
|
446
|
+
|---|---|---|
|
|
447
|
+
| 一 | 工具 schema `nullable` 导致插件树崩溃 | 插件树全有或全无;schema 错误类型检查抓不到([A1](#a1-nullable-让整棵插件树崩溃)) |
|
|
448
|
+
| 二 | 反刍 RPC 端点未接通(deps 漏注入) | 降级分支会掩盖故障;注入面要可测([A2](#a2-声明了字段却忘了注入--端点被永久钉在降级分支)) |
|
|
449
|
+
| 三 | `pending.json` 漏解包 `buckets` + 进度不可观测 | 重复解析器必然腐烂;"运行中"需要三个信息([A3](#a3-同一份文件两个解析器--其中一个从未成功过) / [B2](#b2-长任务不置-running-界面只能显示运行中)) |
|
|
450
|
+
|
|
451
|
+
更细的论证、行号证据与未决项见 `.agents/plans/`:
|
|
452
|
+
`ruminate-rpc-wiring/`(第二轮)、`ruminate-pending-fix/`(第三轮)、`pending-issues.md`(未决台账)。
|
package/INSTALL.en.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Installation Guide (dsh-prime-memory)
|
|
2
|
+
|
|
3
|
+
This plugin ships as a **DSH official bundle package**: after install, the `dsh.bundle` layer in `cordis.patch.yml` auto-mounts the plugin entry — no manual profile edits needed.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js ≥ 22.16 (DSH 0.1.1-rc.2 and above)
|
|
8
|
+
- DeepSeek Harness (DSH) installed, with `--profile web` available
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Pick any invocation style (the `npx` prefix can replace `dsh` in any command below):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Option 1: run the official CLI via npx (no pre-installed dsh; version can be pinned, e.g. dsh-prime-memory@0.8.4)
|
|
16
|
+
npx -y @deepseek-ai/dsh plugin --profile web add dsh-prime-memory
|
|
17
|
+
|
|
18
|
+
# Option 2: with the dsh CLI installed (dsh is a pnpm forwarder; npm i -g pnpm first if missing)
|
|
19
|
+
dsh plugin --profile web add dsh-prime-memory
|
|
20
|
+
|
|
21
|
+
# Alternative sources: GitHub repo / local path (dev & debugging, link: points at the repo; npm run build + restart dsh to apply)
|
|
22
|
+
dsh plugin --profile web add https://github.com/drscrewdriver/dsh-prime-memory
|
|
23
|
+
dsh plugin --profile web add /path/to/dsh-prime-memory
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Install via an AI Agent (Recommended)
|
|
27
|
+
|
|
28
|
+
Send this message as-is to your current agent (if it can run terminal commands):
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
Please install the dsh-prime-memory plugin for the web profile of DeepSeek Harness.
|
|
32
|
+
|
|
33
|
+
Run only the two commands below and do not modify any other profile:
|
|
34
|
+
dsh plugin --profile web add dsh-prime-memory
|
|
35
|
+
dsh --profile web --dump-config
|
|
36
|
+
|
|
37
|
+
Confirm that dsh-prime-memory appears in the output, then report the result to me.
|
|
38
|
+
Do not close or restart my running DSH yourself; after installation, remind me to manually restart the DSH Web Host.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Upgrade
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Upgrade to the latest
|
|
45
|
+
dsh plugin --profile web update dsh-prime-memory
|
|
46
|
+
|
|
47
|
+
# Upgrade to a specific version
|
|
48
|
+
dsh plugin --profile web update dsh-prime-memory@0.8.11
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Upgrade only replaces plugin code and the `dist/` build; the data directory `~/.dsh/memory/` is untouched.
|
|
52
|
+
|
|
53
|
+
## Verify
|
|
54
|
+
|
|
55
|
+
After installing and restarting the DSH Web Host, check:
|
|
56
|
+
|
|
57
|
+
1. **Data directory appears** → plugin applied: `~/.dsh/memory/` contains `conversations/` `records/` `scenes/` and `memory.db`;
|
|
58
|
+
2. **Settings shows a "Memory" page** and the input bar shows the mode pill → client half is ready;
|
|
59
|
+
3. Send a message with personal info; after distillation completes, ask about it in another turn — you should see a "Context injection · memory" row in the context.
|
|
60
|
+
|
|
61
|
+
Optional smoke test (dev / troubleshooting):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run build
|
|
65
|
+
npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution nodenext --target es2022 --strict --skipLibCheck --esModuleInterop
|
|
66
|
+
node dist-smoke/smoke.js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Migrate / Downgrade
|
|
70
|
+
|
|
71
|
+
- **Migrate from old versions (named `dsh-memory-plugin` before 0.5.0)**: old data dir is incompatible with the new package. Back it up, delete `~/.dsh/memory/`, and let the new plugin rebuild on first run; history cannot be upgraded in place — re-distillation is required.
|
|
72
|
+
- **Roll back to an old version**: `dsh plugin --profile web remove dsh-prime-memory`, then reinstall per the old docs. The data dir is preserved, but old versions won't read the new layout — clean it too.
|
|
73
|
+
|
|
74
|
+
## Uninstall
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
dsh plugin --profile web remove dsh-prime-memory
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Data stays in `~/.dsh/memory/`; delete the whole directory manually if you don't need it.
|
|
81
|
+
|
|
82
|
+
## Troubleshooting
|
|
83
|
+
|
|
84
|
+
| Symptom | Likely cause | Fix |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| No "Memory" page after install | DSH not restarted / bundle not mounted | Restart DSH Web Host; `dsh --profile web --dump-config` should list `dsh-prime-memory` |
|
|
87
|
+
| Startup error `duplicate loader entry id` | patch uses `insert:` with the same id as the bundle layer | Remove your manual `insert:` entry — the bundle layer already ships with the package |
|
|
88
|
+
| No "Context injection · memory" row | Distillation didn't run / recall off | Ensure mode ≠ off and `recall.enabled=true`; check `L1 阶段完成` in `memory.log` |
|
|
89
|
+
| Local embedding download stuck | Mirror unreachable directly | Set `embedding.proxy` to a proxy, or switch `embedding.mirror` to official `huggingface.co` |
|
|
90
|
+
| Remote embedding 401 | Wrong apiKey / key-less service shouldn't get a key | Check `embedding.apiKey`; for a key-less self-hosted service, leave apiKey empty |
|
|
91
|
+
|
|
92
|
+
See also [README.en.md](./README.en.md) and [CHANGELOG.en.md](./CHANGELOG.en.md).
|
package/INSTALL.ja.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# インストールガイド(dsh-prime-memory)
|
|
2
|
+
|
|
3
|
+
本プラグインは **DSH 公式 bundle 合成パッケージ**として配布されます。インストール後、`cordis.patch.yml` の `dsh.bundle` 層がプラグイン行を自動マウントするため、profile 設定を手修正する必要はありません。
|
|
4
|
+
|
|
5
|
+
## 環境要件
|
|
6
|
+
|
|
7
|
+
- Node.js ≥ 22.16(DSH 0.1.1-rc.2 以上)
|
|
8
|
+
- DeepSeek Harness(以下 DSH)が導入済みで、`--profile web` が利用可能
|
|
9
|
+
|
|
10
|
+
## インストール
|
|
11
|
+
|
|
12
|
+
2 通りの呼び出し方式から選べます(`npx` 接頭辞は以下のどの `dsh` コマンドも置き換え可能):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 方法1:npx で公式 CLI を直接実行(dsh の事前導入不要。バージョン固定可、例: dsh-prime-memory@0.8.4)
|
|
16
|
+
npx -y @deepseek-ai/dsh plugin --profile web add dsh-prime-memory
|
|
17
|
+
|
|
18
|
+
# 方法2:dsh CLI 導入済みの場合(dsh は pnpm フォワーダ。未導入なら先に npm i -g pnpm)
|
|
19
|
+
dsh plugin --profile web add dsh-prime-memory
|
|
20
|
+
|
|
21
|
+
# その他のソース:GitHub リポジトリ / ローカルパス(開発・デバッグ用。link: はリポジトリを指し、npm run build + dsh 再起動で反映)
|
|
22
|
+
dsh plugin --profile web add https://github.com/drscrewdriver/dsh-prime-memory
|
|
23
|
+
dsh plugin --profile web add /path/to/dsh-prime-memory
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Agent にインストールさせる(推奨)
|
|
27
|
+
|
|
28
|
+
現在の Agent がターミナルコマンドを実行できるなら、以下の文をそのまま送ってください:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
DeepSeek Harness の web プロファイルに dsh-prime-memory プラグインをインストールしてください。
|
|
32
|
+
|
|
33
|
+
他のプロファイルは変更せず、以下の2コマンドのみを実行してください:
|
|
34
|
+
dsh plugin --profile web add dsh-prime-memory
|
|
35
|
+
dsh --profile web --dump-config
|
|
36
|
+
|
|
37
|
+
出力に dsh-prime-memory が表示されたらインストール結果を教えてください。
|
|
38
|
+
稼働中の DSH を勝手に閉じたり再起動しないでください。インストール後、DSH Web Host の手動再起動を促してください。
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## アップグレード
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 最新版へ
|
|
45
|
+
dsh plugin --profile web update dsh-prime-memory
|
|
46
|
+
|
|
47
|
+
# 特定バージョンへ
|
|
48
|
+
dsh plugin --profile web update dsh-prime-memory@0.8.11
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
アップグレードはプラグインコードと `dist/` 成果物のみを置換し、データディレクトリ `~/.dsh/memory/` には影響しません。
|
|
52
|
+
|
|
53
|
+
## 検証
|
|
54
|
+
|
|
55
|
+
DSH Web Host を再起動後、以下を確認:
|
|
56
|
+
|
|
57
|
+
1. **データディレクトリが現れる**=プラグイン適用成功:`~/.dsh/memory/` 配下に `conversations/` `records/` `scenes/` と `memory.db` が現れる;
|
|
58
|
+
2. **設定に「記憶」ページ**、入力バーにモードピルが現れる=クライアント側準備完了;
|
|
59
|
+
3. 個人情報を含むメッセージを送り、蒸留完了後、別のターンで関連を尋ねると、コンテキストに「コンテキスト注入 · memory」行が見えるはず。
|
|
60
|
+
|
|
61
|
+
任意のスモークテスト(開発・障害対応用):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run build
|
|
65
|
+
npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution nodenext --target es2022 --strict --skipLibCheck --esModuleInterop
|
|
66
|
+
node dist-smoke/smoke.js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 移行 / ダウングレード
|
|
70
|
+
|
|
71
|
+
- **旧版(0.5.0 以前は `dsh-memory-plugin`)からの移行**:旧データディレクトリは新パッケージと互換性がありません。バックアップ後 `~/.dsh/memory/` を削除し、新プラグインの初回実行で再構築してください。履歴はそのまま升格不可で、再蒸留が必要です。
|
|
72
|
+
- **旧版へのロールバック**:`dsh plugin --profile web remove dsh-prime-memory` 後、旧版ドキュメントで再インストール。データディレクトリは残りますが、旧版は新レイアウトを読めないため、併せて削除を推奨。
|
|
73
|
+
|
|
74
|
+
## アンインストール
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
dsh plugin --profile web remove dsh-prime-memory
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
データは `~/.dsh/memory/` に残ります。不要ならディレクトリごと手動削除してください。
|
|
81
|
+
|
|
82
|
+
## トラブルシューティング
|
|
83
|
+
|
|
84
|
+
| 現象 | 考えられる原因 | 対処 |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| インストール後「記憶」ページがない | DSH 未再起動 / bundle 未マウント | DSH Web Host を再起動。`dsh --profile web --dump-config` で `dsh-prime-memory` を確認 |
|
|
87
|
+
| 起動時 `duplicate loader entry id` | patch が `insert:` と bundle 同 id を重複追加 | 手動の `insert:` を削除(本パッケージは bundle 層を同梱) |
|
|
88
|
+
| 「コンテキスト注入 · memory」行がない | 蒸留未実行 / 想起オフ | モードが off でなく `recall.enabled=true` を確認。`memory.log` の `L1 段完了` を確認 |
|
|
89
|
+
| ローカル埋め込みダウンロードが止まる | ミラー直結が不可 | `embedding.proxy` でプロキシを設定、または `embedding.mirror` を公式 `huggingface.co` へ |
|
|
90
|
+
| リモート埋め込み 401 エラー | apiKey 誤り / 免キー服務に key 不要 | `embedding.apiKey` を確認。自己ホスト免キー服務は apiKey を空に |
|
|
91
|
+
|
|
92
|
+
詳細は [README.ja.md](./README.ja.md) と [CHANGELOG.ja.md](./CHANGELOG.ja.md) を参照。
|