dsh-immune-system 0.0.0-stage → 0.9.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +382 -2
  3. package/dsh-immune-system/README.md +48 -0
  4. package/dsh-immune-system/client/client.js +307 -0
  5. package/dsh-immune-system/cordis.patch.yml +7 -0
  6. package/dsh-immune-system/dist/core/distiller.js +151 -0
  7. package/dsh-immune-system/dist/core/export.js +42 -0
  8. package/dsh-immune-system/dist/core/guards.js +67 -0
  9. package/dsh-immune-system/dist/core/i18n.js +85 -0
  10. package/dsh-immune-system/dist/core/index.js +8 -0
  11. package/dsh-immune-system/dist/core/keywords.js +59 -0
  12. package/dsh-immune-system/dist/core/lessons.js +31 -0
  13. package/dsh-immune-system/dist/core/llm.js +151 -0
  14. package/dsh-immune-system/dist/core/prompts.js +94 -0
  15. package/dsh-immune-system/dist/core/redact.js +50 -0
  16. package/dsh-immune-system/dist/core/relevance.js +51 -0
  17. package/dsh-immune-system/dist/core/selfnoise.js +135 -0
  18. package/dsh-immune-system/dist/core/store/antibodies.js +210 -0
  19. package/dsh-immune-system/dist/core/store/episodes.js +127 -0
  20. package/dsh-immune-system/dist/core/store/global.js +106 -0
  21. package/dsh-immune-system/dist/core/store/index.js +263 -0
  22. package/dsh-immune-system/dist/core/store/jobs.js +41 -0
  23. package/dsh-immune-system/dist/core/store/open.js +56 -0
  24. package/dsh-immune-system/dist/core/store/path.js +32 -0
  25. package/dsh-immune-system/dist/core/store/schema.js +76 -0
  26. package/dsh-immune-system/dist/core/store/tokenize.js +106 -0
  27. package/dsh-immune-system/dist/core/sync.js +119 -0
  28. package/dsh-immune-system/dist/core/trace.js +87 -0
  29. package/dsh-immune-system/dist/core/types.js +7 -0
  30. package/dsh-immune-system/dist/core/ui/page.js +291 -0
  31. package/dsh-immune-system/dist/core/ui/query.js +28 -0
  32. package/dsh-immune-system/dist/core/ui/server.js +153 -0
  33. package/dsh-immune-system/dist/dsh/src/dsh-adapter.js +180 -0
  34. package/dsh-immune-system/dist/dsh/src/index.js +424 -0
  35. package/dsh-immune-system/dist/dsh/src/ui-route.js +99 -0
  36. package/dsh-immune-system/dist/zcode/src/cli.js +263 -0
  37. package/dsh-immune-system/dist/zcode/src/doctor.js +124 -0
  38. package/dsh-immune-system/dist/zcode/src/drain.js +57 -0
  39. package/dsh-immune-system/dist/zcode/src/format.js +103 -0
  40. package/dsh-immune-system/dist/zcode/src/hook-io.js +115 -0
  41. package/dsh-immune-system/dist/zcode/src/hooks/post-tool-use-failure.js +44 -0
  42. package/dsh-immune-system/dist/zcode/src/hooks/post-tool-use.js +55 -0
  43. package/dsh-immune-system/dist/zcode/src/hooks/pre-tool-use.js +67 -0
  44. package/dsh-immune-system/dist/zcode/src/hooks/session-start.js +34 -0
  45. package/dsh-immune-system/dist/zcode/src/hooks/stop.js +23 -0
  46. package/dsh-immune-system/dist/zcode/src/hooks/user-prompt-submit.js +40 -0
  47. package/dsh-immune-system/dist/zcode/src/mcp-server.js +244 -0
  48. package/dsh-immune-system/dist/zcode/src/outcome.js +60 -0
  49. package/dsh-immune-system/dist/zcode/src/root-marker.js +69 -0
  50. package/dsh-immune-system/package.json +89 -0
  51. package/package.json +76 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 meat-daemon-404
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,383 @@
1
- # Temporary Holding Version
1
+ # dsh-plugin-immune
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
+ **自进化避坑免疫系统** — 让 Agent 踩一次坑,就自己长记性。
4
+
5
+ Agent 踩了坑、修好了,却没人写文档——这次的经验被自动提炼成一条**抗体**。
6
+ 下次在同一个坑面前,相关抗体先一步进入它的上下文。
7
+
8
+ > Agent steps on a rake, fixes it, and the lesson is captured as an *antibody* —
9
+ > no docs required. Next session the relevant antibodies are injected before the
10
+ > mistake can repeat.
11
+
12
+ 支持两个宿主:**DSH**(DeepSeek Harness,已按真实宿主契约实测)与 **ZCode**(按实测 hook 契约)。
13
+ 两者共用同一份 `core/`。
14
+
15
+ [中文](#中文) · [English](#english)
16
+
17
+ ---
18
+
19
+ ## 中文
20
+
21
+ ### 它解决什么问题
22
+
23
+ 同一个坑反复踩,是 Agent 最浪费时间的模式:
24
+
25
+ - 改了 `vite.config.ts`,`process.env` 读不到客户端变量,报错,换 `loadEnv` 才好;
26
+ - 下次换个会话,**又这么写一遍**。
27
+
28
+ 静态文档解决不了——没人维护,很快过期。这个插件让**踩坑现场自己变成规则**:
29
+
30
+ ```
31
+ 工具失败 ─┐
32
+ ├─► 配对 ─► 蒸馏 ─► 抗体(SQLite + FTS5,按项目隔离)
33
+ 工具成功 ─┘ │
34
+ ▼
35
+ 下一次会话提问 ─► <immune_memory> 注入模型上下文
36
+ ```
37
+
38
+ ### 安装
39
+
40
+ #### DSH
41
+
42
+ 插件包就是 `dsh-immune-system/` 目录本身(`package.json` 带 `main`、`dsh.bundle.patch`
43
+ 与 `permissions` 声明,`dist/` 随包提交,安装时无需构建)。
44
+
45
+ **方式一:tarball(不需要 npm 账号,已验证)**
46
+
47
+ ```bash
48
+ cd dsh-immune-system && npm pack # → dsh-immune-system-0.3.8.tgz
49
+ dsh plugin add ./dsh-immune-system-0.3.8.tgz
50
+ ```
51
+
52
+ 冒烟验证过:装进空项目后 `import('dsh-immune-system')` 能解析到 `apply()`,
53
+ `cordis.patch.yml` 随包到位。
54
+
55
+ **方式二:npm(需先发布)**
56
+
57
+ ```bash
58
+ cd dsh-immune-system && npm publish # 需要你自己的 npm 账号
59
+ dsh plugin add dsh-immune-system
60
+ ```
61
+
62
+ **方式三:git** —— `dsh plugin add github:owner/repo` 会在安装时用 `prepare` 构建,
63
+ 而 pnpm 默认阻止依赖的构建脚本,需要把 pnpm 打印的 key 加进 profile 的
64
+ `pnpm-workspace.yaml → allowBuilds` 才能装。除非你就要这个形态,否则用前两种。
65
+
66
+ 装完**重启 DSH**(bundle 是启动时装配的,运行中的进程不会自己加载)。
67
+ `<profile>` 就是 `~/.dsh/profiles/` 下你实际在用的那个(桌面端通常是 `desktop`)。
68
+
69
+ 装完可用 `/immune` 自检:能返回「暂无抗体」就说明挂上了。
70
+
71
+ **上架 DSH Plugin Hub**:仓库已打 `dsh-plugin` topic,Hub 会周期性自动抓取;
72
+ 也可在 `dsh-plugin.org/submit` 主动提交。注意 Hub 索引的是仓库条目,**安装仍走
73
+ 上面的 tarball / npm**。
74
+
75
+ #### ZCode
76
+
77
+ 本仓库本身就是插件市场源:
78
+
79
+ ```bash
80
+ node "<zcode.cjs 路径>" plugins marketplace add meat-daemon-404/dsh-plugin-immune
81
+ node "<zcode.cjs 路径>" plugins install dsh-immune-system@dev-dsh-plugin-immune
82
+ ```
83
+
84
+ 装完**新建一个任务**才生效(旧任务保留的是旧能力清单)。
85
+
86
+ ### 怎么用
87
+
88
+ 正常干活就行。踩坑 → 修好 → 下一回合自动提炼。
89
+ 之后问到含相同文件名/命令的话,上下文里会出现 `<immune_memory>…</immune_memory>`。
90
+
91
+ | 命令 | 宿主 | 作用 |
92
+ | --- | --- | --- |
93
+ | `/immune` | DSH | 列出所有抗体(`/immune --all` 含已退役) |
94
+ | `/immune-export` | DSH | 把经过验证的抗体导出成 `SKILL.md` |
95
+ | `/immune-forget <id>` | DSH | 删除指定抗体 |
96
+ | `/immune-forget --last` | DSH | 撤销上一回合刚学的那条 |
97
+ | `/immune-forget --prune` | DSH | **退役**从未命中且超过 14 天的抗体(可恢复,不再注入) |
98
+ | `/immune-forget --purge` | DSH | **物理删除**上述抗体(不可恢复) |
99
+ | `/dsh-immune-system:immune` | ZCode | 同上(加 `--all`) |
100
+ | `/dsh-immune-system:immune-export` | ZCode | 同上 |
101
+ | `/dsh-immune-system:immune-forget` | ZCode | 同上(`<id>` / `--last` / `--prune` / `--purge`) |
102
+ | `/dsh-immune-system:immune-ui` | ZCode | 启动本地仪表盘,给出地址 |
103
+ | `cli.js ui [--port N]` | 两者 | 同上(前台运行,默认 7788,端口占用自动顺延) |
104
+ | `cli.js learn <trigger> [symptom] <prescription>` | 两者 | 手动教一条规则(借鉴 pro-workflow 的 `/learn-rule`) |
105
+ | `cli.js export-json <path>` / `import-json <path>` | 两者 | 库快照导出/导入——跨机器同步、git 提交、团队共享 |
106
+ | `cli.js doctor` | 两者 | 安装健康自检:node 版本、FTS5、数据库、marker、MCP 脚本 |
107
+
108
+ > 检索层同时跑两条通道:3 字以上走 FTS5 trigram,**2 字中文词(跨域/端口/权限/死锁)
109
+ > 走参数化 LIKE 兜底**——trigram 压根无法索引 2 字词,只靠 MATCH 会把这类词整段漏掉。
110
+
111
+ ### 仪表盘
112
+
113
+ `/dsh-immune-system:immune-ui`(或 `cli.js ui`)会起一个本地页面:
114
+
115
+ - 搜索触发词/症状/处方,实时过滤;**浏览不计入命中数**,所以晋升和淘汰的统计不会被点鼠标污染
116
+ - 每条可**退役 / 恢复 / 删除**,分「项目库」「共享库」两个页签
117
+ - 顶部显示生效数、退役数、累计注入、共享库条数
118
+
119
+ 安全边界是刻意收窄的:只绑 `127.0.0.1`、每次启动生成随机 token 且所有 `/api` 都要带它
120
+ (跨站简单 POST 不需要预检,只靠 Origin 判断不够)、严格 CSP、页面不加载任何外部资源。
121
+
122
+ UI 是自建的而非宿主插件 UI——ZCode 插件没有 UI 插槽(只有 hooks/commands/skills/MCP),
123
+ 所以走本地 HTTP,两个宿主通用。
124
+
125
+ #### the agent harness 原生 UI(设置页里的 tab)
126
+
127
+ the agent harness 有 web GUI 和客户端插槽,所以那边**不用另开页面**:
128
+ `设置 → 插件` 里会出现一个「免疫 / Immune」tab。
129
+
130
+ - 客户端是 `dsh-immune-system/client/client.js`,按模块装载器契约手写
131
+ (`window.__ModuleLoader__.load({ id, factory })`),**不需要打包器、不写 JSX**
132
+ - 数据走宿主路由 `/plugins/dsh-immune-system/api`,用
133
+ `ctx.inject(['webServer'], …)` 注册——这是可选依赖,CLI/桌面 profile 没有 web 服务时
134
+ 插件照常工作,只是没有这个 tab
135
+ - 变更类请求要求 `content-type: application/json` 且同源:JSON 会强制 CORS 预检,
136
+ 跨站页面过不去
137
+
138
+ > 诚实说明:客户端与路由的**契约**有测试(模拟 `__ModuleLoader__` 装载、假 ctx 验证路由、
139
+ > 跨源拒绝、无 web 服务时不注册),但**没有在真实 web 应用里端到端跑通**——
140
+ > 本机 web profile 装了十几个第三方插件和 MCP 服务,启动日志淹没在它们的输出里,
141
+ > 我没能把启动稳定下来。tab 是否如期出现,需要你重启 DSH 后看一眼。
142
+
143
+ ### 同步与体检
144
+
145
+ 库快照是纯 JSON(带格式标签),合并语义刻意保守:
146
+
147
+ - **处方是身份**——与库自身的去重规则一致
148
+ - 两侧都认识的规则,命中/失效计数取 **max**(两台机器各自独立计数,不该相乘)
149
+ - 状态任一侧 active 即 active——「退役」是一台机器的判断,不是共识
150
+
151
+ ```bash
152
+ node "<plugin>/dist/zcode/src/cli.js" export-json library.json # 快照
153
+ node "<plugin>/dist/zcode/src/cli.js" import-json library.json # 合并进当前库
154
+ node "<plugin>/dist/zcode/src/cli.js" doctor # 安装自检
155
+ ```
156
+
157
+ 把 `library.json` 提交进仓库(或放进共享库 `global.sqlite` 所在目录的 git),团队每个
158
+ 成员 `import-json` 一次就继承全部教训。`doctor` 逐项报告 node 版本、FTS5 trigram
159
+ 探针、库可写性、marker、MCP 脚本,任何一项失败退出码非零,可以接进 CI。
160
+
161
+ ### 四个关键设计
162
+
163
+ **① 失败感知不赌单一事件(这是最要命的一条)**
164
+
165
+ 两个宿主上「bash 退出码非 0」都**不是**错误结果:
166
+
167
+ - ZCode:`PostToolUseFailure` 只在工具处理器**抛异常**时触发,命令非零退出是正常返回;
168
+ - DSH:`result.isError` **只覆盖基础设施失败**(spawn 错误、abort、监听器抛错),
169
+ 非零退出被明确设计为「reported, not errored」,模型自己决定怎么办。
170
+
171
+ 所以两版都自己判结果。DSH 侧的判据顺序是
172
+ `isError` → `value.exitCode` → 行尾 `[exit code: N]` / `[killed by signal: X]` / `[timed out after Nms]` / sandbox 拒绝标记 → 窄口径失败标记正则。
173
+
174
+ **② 抗体失效会被淘汰(Anti-Rot)**
175
+
176
+ 一条抗体注入后工具**仍然**失败,说明它没起作用,扣分;满 3 次自动退役并联合新证据重新蒸馏。
177
+ 扣分**带相关性判断**——失败内容必须与该抗体的触发词有共同词才算数,且**排除工具名本身**
178
+ (否则每条 `Bash …` 抗体都会被任何一次 Bash 失败冤枉)。
179
+
180
+ **②⁺ 泛型坑只学一条(Generic lessons)**
181
+
182
+ `File has been modified since read` 这类坑与具体文件无关——实测它在三个不同文件上
183
+ 各学了一条重复规则。精选模式表(`core/lessons.ts`)把这类症状归一成泛型触发词和处方,
184
+ 规则的处方级去重随后把所有实例合并成一条,教训靠命中数累积而不是繁殖。
185
+ 规则蒸馏与模型蒸馏两条路径共用同一张表。
186
+
187
+ **②⁺⁺ 人类纠偏优先于模型猜测(Cross-turn hint)**
188
+
189
+ 真实会话经常是:工具失败 → Agent 问人 → 人类说"这个仓库用 pnpm" → 重试成功。
190
+ 那句人话是最好的蒸馏输入。`UserPromptSubmit` 会把**新回合的人类消息**挂到上一回合
191
+ 未闭合的失败上,蒸馏时作为处方的最优来源(人类陈述规则,模型只能从 diff 里猜)。
192
+ "继续""ok"这类空话会被 `isSubstantiveHint` 挡掉——否则会变成 `do: 继续` 这种废处方。
193
+
194
+ **②⁺⁺⁺ 全局共享库(Shared library)**
195
+
196
+ 按项目隔离防串味,代价是新项目从零开踩。泛型抗体(`core/lessons.ts` 判定的,
197
+ 或触发词不含具体文件路径且命中 ≥5 次的)会同步到同目录的 `global.sqlite`,
198
+ 新项目召回不足时从它补齐。
199
+
200
+ **没有用 `ATTACH DATABASE`**:SQLite 明确说明 WAL 模式下跨附加库的事务不原子,
201
+ 而本插件每进程短连接、多 hook 并发。改用两条独立连接——每次写入仍是单文件事务。
202
+ 代价只是召回变成两次查询,这个量级无所谓。项目库与共享库的 id 在合并时**从不按 id 混用**
203
+ (两个库的 AUTOINCREMENT 各自独立,会撞)。
204
+
205
+ **③ 事前检查(Guard,默认关闭)**
206
+
207
+ 提示词是软建议,长上下文里模型偶尔照样照错发。`IMMUNE_GUARD` 提供一层确定性检查:
208
+ `off`(默认)/ `warn`(更醒目的提示)/ `deny`(拒绝执行该工具调用)。
209
+
210
+ 三条只在任何环境都错的规则:强推 main、`rm -rf /`、`git reset --hard`。
211
+ **规则写在代码里,不进数据库**——蒸馏产物是模型输出,把它编译成正则去拦截工具调用
212
+ 等于让未经审阅的字符串获得停止工作的权力,还有 ReDoS 风险。
213
+ `deny` 是唯一有破坏性的模式,所以它从不是默认值。
214
+
215
+ 两侧都接上了:ZCode 用 `permissionDecision: deny`,the agent harness 用
216
+ `tools/pre-execute` 返回 `{ kind: 'deny', reason }`(`PreToolDecision` 的三种形态
217
+ 查自 `packages/core/tools/src/index.ts:588`)。**`warn` 仅 ZCode 有效**——
218
+ `PreToolDecision` 没有携带上下文的字段,the agent harness 侧只能"拦或不拦"。
219
+
220
+ **③ 注入卫生**
221
+ 注入内容只来自我们自己提炼的字段,从不放原始工具输出;抗体文本里的尖括号被剥离,
222
+ 无法闭合包裹标签;有长度上限;明确标注「仅供参考」,避免模型把它当成用户指令。
223
+ 一个回合最多注入一次、且只在回合第一步匹配(工具循环里每步都匹配既会重复灌入,
224
+ 也会把抗体的命中计数灌成「这回合跑了几步」的噪声)。
225
+
226
+ **④ 蒸馏在回合边界跑,不在工具结果路径里**
227
+
228
+ `drain()` 可能向配置的 LLM 端点发一次 HTTP 请求。DSH 的 `tools/post-execute` 监听器被 await
229
+ 在工具结果路径上,把蒸馏放进去会让每次「失败后成功」的工具调用都卡到 HTTP 返回;
230
+ 所以它挂在 `agent/turn-stopping`(`@mode serial`,回合边界提交前 await)。
231
+ 未清空的队列还会在下次 `session-start` 由 `requeueStuckJobs()` 兜底。
232
+
233
+ ### 存储
234
+
235
+ 抗体库是 SQLite 单文件(含 FTS5 trigram 索引,中文按 3 字窗口匹配)。
236
+
237
+ - `IMMUNE_DB_PATH` 显式指定时最优先;两版指向同一个文件就共享抗体库。
238
+ - DSH 默认:`<DSH_HOME>/storages/dsh-immune-system/<sha1(当前目录)[0..16]>.sqlite`
239
+ —— **按项目隔离**,不同工作区互不串味,也不往项目目录里丢文件。
240
+ - ZCode 默认:插件数据目录(`ZCODE_PLUGIN_DATA`),无则 `<项目>/.dsh/immune.sqlite`。
241
+
242
+ **冷启动**:`agent/pre-step` 在 `resume`/`clear`/`compact` 时拿到的是「seed 之后新增」的批次,
243
+ 不含人类提问。DSH 版订阅 `session/event` 的 `user/message` 记住每个会话最近的提问,
244
+ 作为匹配依据的兜底,所以冷启动后的第一句也能召回。
245
+
246
+ ### 配置(全部可选)
247
+
248
+ 不配也能跑——默认走确定性规则蒸馏器,离线可用。
249
+
250
+ | 环境变量 | 作用 |
251
+ | --- | --- |
252
+ | `IMMUNE_DB_PATH` | 抗体库路径;两版指向同一个即可共享 |
253
+ | `IMMUNE_LLM_BASE_URL` | OpenAI 兼容端点,如 `https://api.example.com/v1` |
254
+ | `IMMUNE_LLM_MODEL` | 模型名,与上面配对使用 |
255
+ | `IMMUNE_LLM_API_KEY` | Bearer 令牌 |
256
+ | `IMMUNE_LOCALE` | `zh-CN` / `en` 强制单语;**默认中英双语** |
257
+ | `IMMUNE_ALLOW_PRIVATE_ENDPOINT` | 设为 `1` 才允许环回/私有端点。**默认拒绝**,防 SSRF |
258
+ | `IMMUNE_GLOBAL_LIBRARY` | 设为 `0` 关闭全局共享库。默认开启 |
259
+ | `IMMUNE_GUARD` | `off`(默认)/ `warn` / `deny` —— 高危错招事前检查,见下 |
260
+ | `IMMUNE_DEBUG_LOG` | 指定文件则记录执行细节,用于排障 |
261
+
262
+ ### 安全
263
+
264
+ - **脱敏**:写入磁盘前擦除 Bearer 令牌、`*_KEY=` 类赋值、URL 内嵌凭证、JWT。
265
+ 这些记录之后可能被导出成团队共享的 `SKILL.md`,泄漏在那里比泄漏在日志更糟。
266
+ - **失败不致命**:DSH 版 `apply()` 把启动包在 try/catch 里,抗体库打不开只写一条
267
+ `warn` 并把本会话降级为「无免疫」——插件坏了不能拖垮会话。ZCode 版 hook 永远 `exit 0`。
268
+ - **SQL 只走参数绑定**,不拼接;FTS 查询词逐条加引号并转义,用户文本无法注入 FTS 语法。
269
+
270
+ ### 验证状态
271
+
272
+ | 检查 | 结果 |
273
+ | --- | --- |
274
+ | `npm run build` | exit 0 |
275
+ | `npm test` | **66 条全绿**(41 条 core + ZCode,22 条 DSH 单元,3 条原生 UI 契约) |
276
+ | `scripts/verify-step1.mjs` | 15 断言(真实载荷判失败,含 bash 非零退出) |
277
+ | `scripts/verify-steps23.mjs` | 22 断言(失败→成功在回合结束时产出抗体;新会话注入 `<immune_memory>`) |
278
+ | `scripts/verify-live-host.mjs` | 20 断言(**真 Cordis 宿主**:真事件总线 + 真 SQLite + 跨会话注入 + 冷启动注入) |
279
+ | `scripts/verify-installed.mjs` | 16 断言(本机 profile 安装校验:bundles 登记、裸包名解析、patch 形状) |
280
+
281
+ ### 已知限制
282
+
283
+ - **模型端点默认拒绝环回**:本地 Ollama 类服务需显式设 `IMMUNE_ALLOW_PRIVATE_ENDPOINT=1`。
284
+ 这是防 SSRF 的代价,不是疏漏。
285
+ - **外部 LLM 蒸馏未实测**:规则蒸馏器全流程有测试;接真实模型端点的路径只有 mock 覆盖。
286
+ - **真实 agent 端到端未验**:验证期间模型额度返回 402,turn 到不了工具阶段。同一条链路改为
287
+ 向安装副本投喂真实事件载荷验证(含真 Cordis 宿主的 20 条断言)。
288
+
289
+ ---
290
+
291
+ ## English
292
+
293
+ ### What it does
294
+
295
+ Repeating the same mistake is the most expensive pattern an agent has. Static docs
296
+ do not fix it — nobody maintains them, and they rot. This plugin turns the failure
297
+ scene itself into a rule.
298
+
299
+ Works on two hosts that share one `core/`: **DSH** (DeepSeek Harness, verified
300
+ against the real host contract) and **ZCode** (verified against its hooks).
301
+
302
+ ### Install
303
+
304
+ **DSH** — the plugin is a bundle: a directory carrying `package.json` (with
305
+ `dsh.bundle.patch`) plus `cordis.patch.yml`, listed in the profile's
306
+ `dsh.profile.bundles`. From a local checkout (verified path):
307
+
308
+ ```powershell
309
+ New-Item -ItemType Junction `
310
+ -Path "$env:USERPROFILE\.dsh\profiles\<profile>\node_modules\dsh-immune-system" `
311
+ -Target "<repo>\dsh-immune-system"
312
+ # then in that profile's package.json:
313
+ # dependencies["dsh-immune-system"] = "link:<repo>/dsh-immune-system"
314
+ # append "dsh-immune-system" to dsh.profile.bundles
315
+ ```
316
+
317
+ **Restart DSH** afterwards — bundles are composed at startup.
318
+
319
+ **ZCode** — this repository *is* a marketplace source:
320
+
321
+ ```bash
322
+ node "<zcode.cjs>" plugins marketplace add meat-daemon-404/dsh-plugin-immune
323
+ node "<zcode.cjs>" plugins install dsh-immune-system@dev-dsh-plugin-immune
324
+ ```
325
+
326
+ Start a **new task** afterwards; old tasks keep their earlier capability catalog.
327
+
328
+ ### Design notes
329
+
330
+ **Sensing outcomes, not trusting one event.** On both hosts a shell command
331
+ exiting non-zero is *not* an error result: ZCode's `PostToolUseFailure` only fires
332
+ when a handler throws, and DSH documents that "non-zero exits are reported, not
333
+ errored" — `isError` covers infrastructure failures only. Both ports therefore
334
+ classify the result themselves (exit code, trailing `[exit code: N]` /
335
+ `[killed by signal: X]` / timed-out markers, and a narrow marker regex).
336
+
337
+ **Anti-rot, relevance-guarded.** An injected antibody that still did not prevent
338
+ the failure loses credit and is retired after three such episodes. The penalty is
339
+ relevance-guarded: the failure must share a meaningful term with the antibody's
340
+ trigger, and the tool name is excluded — otherwise every `Bash …` rule would be
341
+ blamed for any Bash failure.
342
+
343
+ **Injection hygiene.** Injected text comes only from our own distilled fields,
344
+ never raw tool output; brackets are stripped so a line cannot close its wrapper
345
+ tag; length-capped; labelled advisory.
346
+
347
+ ### Layout
348
+
349
+ ```
350
+ core/ shared, host-agnostic, zero runtime deps
351
+ ├── store/ SQLite + FTS5 trigram: antibodies, episodes, job queue
352
+ ├── distiller.ts episode -> antibody (LLM, rule-based fallback)
353
+ ├── i18n.ts zh/en strings
354
+ ├── redact.ts credential scrubbing before disk
355
+ └── export.ts render SKILL.md
356
+ dsh/ DSH adapter — verified against the real host contract
357
+ dsh-immune-system/ installable bundle (manifest, patch, commands, dist)
358
+ zcode/ ZCode adapter (hooks, CLI)
359
+ docs/ dsh-host-contract.md — every contract claim with file:line
360
+ scripts/ the four verification scripts above
361
+ test/ node:test
362
+ ```
363
+
364
+ Point both hosts at the same `IMMUNE_DB_PATH` and they share one antibody store.
365
+
366
+ ### The first sketch vs. what the host actually does
367
+
368
+ The original design guessed a Cordis-style API. Reading the DSH source disproved
369
+ every one of those guesses:
370
+
371
+ | First sketch | What DSH actually has |
372
+ | --- | --- |
373
+ | `dsh/before-llm-request` event | no such event; recall rides `agent/pre-step` (waterfall) and `agent.inject()` |
374
+ | `dsh/after-tool-call` event | no such event; results arrive on `tools/post-execute` (waterfall) |
375
+ | success / failure as two events | one event plus `isError`, and `isError` is infrastructure-only |
376
+ | `session.appendSystemReminder()` | does not exist; use `agent.inject(createUserMessage(...))` |
377
+ | `ctx.command(name, desc, handler)` | `ctx.commands.register({ name, description, handler })`, needs `inject=['commands']`, returns a `CommandResult` |
378
+ | `agent/request` to add context | explicitly forbidden — "this waterfall cannot mutate messages" |
379
+
380
+ Every row above is sourced to a file and line in
381
+ [docs/dsh-host-contract.md](docs/dsh-host-contract.md).
382
+
383
+ See also [dsh/README.md](dsh/README.md) for the DSH-side operational details.
@@ -0,0 +1,48 @@
1
+ # dsh-immune-system
2
+
3
+ 自进化避坑免疫:捕获每次「失败 → 修复」的工具调用,蒸馏成可复用的抗体,并在下次遇到同样场景前注入。
4
+
5
+ - **宿主**:DeepSeek Harness(DSH)0.2.x,Cordis 插件形态
6
+ - **同源**:与本仓库的 ZCode 版本共用 `core/`(SQLite + FTS5 存储、蒸馏、脱敏、导出),行为与存储格式一致
7
+ - **入口**:`dist/dsh/src/index.js`(`export const name` / `export const inject = ['commands']` / `export function apply(ctx)`)
8
+
9
+ ## 它做什么
10
+
11
+ 1. **感知**:挂 `tools/post-execute`(waterfall)。DSH 里 bash 非零退出**不会**置 `isError`,所以判定同时读三路信号——`isError` → `result.value.exitCode` → 输出末尾的 `[exit code: N]` / `[killed by signal: X]` 标记。
12
+ 2. **沉淀**:同一会话同一工具「先失败后成功」时,把失败轨迹与成功轨迹交给 `core/distiller.ts` 蒸馏;配了 `IMMUNE_LLM_*` 走模型,否则退回确定性规则蒸馏。结果写入 SQLite `antibodies` 表。
13
+ 3. **注入**:`agent/session-start` 预热最常用的三条;`agent/pre-step` 按当前人类提问做 FTS 匹配,把命中项包成 `<immune_memory>…</immune_memory>` 拼进模型上下文。冷启动 `resume`/`compact` 的首个批次不含人类提问,插件会从 `session/event` 的 `user/message` 流里补取。
14
+
15
+ ## 命令
16
+
17
+ | 命令 | 作用 |
18
+ | --- | --- |
19
+ | `/immune` | 列出本项目学到的抗体(含命中/失败计数与状态) |
20
+ | `/immune` `--all` | 连同已退役抗体一起列出 |
21
+ | `/immune-export` | 把验证过的抗体(命中 ≥ 3)导出成 `project-pitfalls` 技能 Markdown |
22
+
23
+ ## 存储
24
+
25
+ 优先级:
26
+
27
+ 1. `IMMUNE_DB_PATH`(显式指定,测试与 ZCode 版都走这条)
28
+ 2. `<DSH_HOME>/storages/dsh-immune-system/<sha1(当前工作目录)[0..16]>.sqlite`
29
+
30
+ 即默认按项目隔离,互不串味,也不会往项目目录里写文件。
31
+
32
+ ## 环境变量
33
+
34
+ | 变量 | 说明 |
35
+ | --- | --- |
36
+ | `IMMUNE_DB_PATH` | 覆盖抗体库路径 |
37
+ | `IMMUNE_LLM_BASE_URL` / `IMMUNE_LLM_API_KEY` / `IMMUNE_LLM_MODEL` | 可选,启用模型蒸馏;缺省用规则蒸馏 |
38
+ | `IMMUNE_LOCALE` | `zh-CN` / `en` / `both`(默认 `both`) |
39
+
40
+ ## 安装
41
+
42
+ 本目录既是 ZCode 插件目录(`.zcode-plugin/plugin.json`),也是 DSH 可安装 bundle 根(`package.json` 的 `dsh.bundle.patch` + `cordis.patch.yml`)。
43
+
44
+ 把本目录作为 DSH profile 的依赖包安装,并把包名 `dsh-immune-system` 加进该 profile `package.json` 的 `dsh.profile.bundles`,重启 DSH 即生效。
45
+
46
+ ## 验证
47
+
48
+ 见仓库 `dsh/README.md` 的「验证状态」一节:`npm run build` 0 错误、`npm test` 31 条全绿、四个 `scripts/verify-*.mjs` 断言全过(含真实 Cordis 宿主下的跨会话注入与冷启动注入、本机 profile 已安装校验)。