dsh-immune-system 0.9.2 → 1.0.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/README.md +185 -16
- package/dsh-immune-system/client/client.js +375 -159
- package/dsh-immune-system/dist/core/distiller.js +78 -0
- package/dsh-immune-system/dist/core/pairing.js +178 -0
- package/dsh-immune-system/dist/core/prompts.js +88 -2
- package/dsh-immune-system/dist/core/similarity.js +51 -0
- package/dsh-immune-system/dist/core/store/antibodies.js +37 -8
- package/dsh-immune-system/dist/core/store/episodes.js +19 -5
- package/dsh-immune-system/dist/core/store/index.js +58 -35
- package/dsh-immune-system/dist/core/store/jobs.js +17 -0
- package/dsh-immune-system/dist/core/store/open.js +1 -0
- package/dsh-immune-system/dist/core/store/rerank.js +57 -0
- package/dsh-immune-system/dist/core/store/schema.js +9 -0
- package/dsh-immune-system/dist/core/store/turn-state.js +52 -0
- package/dsh-immune-system/dist/core/ui/page.js +481 -132
- package/dsh-immune-system/dist/core/ui/query.js +12 -2
- package/dsh-immune-system/dist/core/ui/server.js +2 -2
- package/dsh-immune-system/dist/dsh/src/dsh-adapter.js +66 -17
- package/dsh-immune-system/dist/dsh/src/index.js +143 -9
- package/dsh-immune-system/dist/dsh/src/ui-route.js +7 -3
- package/dsh-immune-system/dist/zcode/src/doctor.js +51 -3
- package/dsh-immune-system/dist/zcode/src/drain.js +73 -20
- package/dsh-immune-system/dist/zcode/src/hooks/post-tool-use-failure.js +3 -1
- package/dsh-immune-system/dist/zcode/src/hooks/post-tool-use.js +4 -2
- package/dsh-immune-system/dist/zcode/src/hooks/session-start.js +6 -0
- package/dsh-immune-system/dist/zcode/src/hooks/user-prompt-submit.js +3 -0
- package/dsh-immune-system/package.json +7 -7
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -55,8 +55,8 @@ dsh plugin --profile <profile> add dsh-immune-system
|
|
|
55
55
|
**tarball / git**(等价,无需 npm 账号):
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
cd dsh-immune-system && npm pack # → dsh-immune-system-0.9.
|
|
59
|
-
dsh plugin --profile <profile> add ./dsh-immune-system-0.9.
|
|
58
|
+
cd dsh-immune-system && npm pack # → dsh-immune-system-0.9.8.tgz
|
|
59
|
+
dsh plugin --profile <profile> add ./dsh-immune-system-0.9.8.tgz
|
|
60
60
|
dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -90,7 +90,7 @@ node "<zcode.cjs 路径>" plugins install dsh-immune-system@dev-dsh-plugin-immun
|
|
|
90
90
|
|
|
91
91
|
| 命令 | 宿主 | 作用 |
|
|
92
92
|
| --- | --- | --- |
|
|
93
|
-
| `/immune` | DSH | 列出所有抗体(`/immune --all`
|
|
93
|
+
| `/immune` | DSH | 列出所有抗体(`/immune --all` 含已退役);`/immune --budget` 查看蒸馏预算,`--budget <ms>` 本次会话内改(内存态,重启回到环境变量) |
|
|
94
94
|
| `/immune-export` | DSH | 把经过验证的抗体导出成 `SKILL.md` |
|
|
95
95
|
| `/immune-forget <id>` | DSH | 删除指定抗体 |
|
|
96
96
|
| `/immune-forget --last` | DSH | 撤销上一回合刚学的那条 |
|
|
@@ -115,8 +115,9 @@ node "<zcode.cjs 路径>" plugins install dsh-immune-system@dev-dsh-plugin-immun
|
|
|
115
115
|
`/dsh-immune-system:immune-ui`(或 `cli.js ui`)会起一个本地页面:
|
|
116
116
|
|
|
117
117
|
- 搜索触发词/症状/处方,实时过滤;**浏览不计入命中数**,所以晋升和淘汰的统计不会被点鼠标污染
|
|
118
|
-
- 每条可**退役 / 恢复 /
|
|
119
|
-
-
|
|
118
|
+
- 每条可**退役 / 恢复 / 删除**,分「项目库」「共享库」两个页签,可按「最近更新 / 命中最多 / 失效最多」排序
|
|
119
|
+
- 顶部 5 张 KPI:生效数、退役数、累计注入、共享库条数,外加**当前蒸馏预算**
|
|
120
|
+
- 跟随系统深浅色、窄屏自动把表格降级成卡片、骨架屏与空态都是有设计的那种
|
|
120
121
|
|
|
121
122
|
安全边界是刻意收窄的:只绑 `127.0.0.1`、每次启动生成随机 token 且所有 `/api` 都要带它
|
|
122
123
|
(跨站简单 POST 不需要预检,只靠 Origin 判断不够)、严格 CSP、页面不加载任何外部资源。
|
|
@@ -127,20 +128,31 @@ UI 是自建的而非宿主插件 UI——ZCode 插件没有 UI 插槽(只有
|
|
|
127
128
|
#### the agent harness 原生 UI(设置页里的 tab)
|
|
128
129
|
|
|
129
130
|
the agent harness 有 web GUI 和客户端插槽,所以那边**不用另开页面**:
|
|
130
|
-
`设置 → 插件` 里会出现一个「免疫 / Immune」tab
|
|
131
|
+
`设置 → 插件` 里会出现一个「免疫 / Immune」tab。它与独立仪表盘共用同一套设计:
|
|
132
|
+
KPI(含预算)、搜索与清除、页签计数、排序、状态圆点、窄屏卡片、骨架屏与空态。
|
|
131
133
|
|
|
132
134
|
- 客户端是 `dsh-immune-system/client/client.js`,按模块装载器契约手写
|
|
133
135
|
(`window.__ModuleLoader__.load({ id, factory })`),**不需要打包器、不写 JSX**
|
|
136
|
+
- 样式是注入一段静态 `<style>`(选择器全部 scope 在 `.imm` 下),所以 hover、媒体查询、
|
|
137
|
+
跟随宿主的 `--dsh-*` 深浅色变量都能用,而不是几十个 inline style
|
|
134
138
|
- 数据走宿主路由 `/plugins/dsh-immune-system/api`,用
|
|
135
139
|
`ctx.inject(['webServer'], …)` 注册——这是可选依赖,CLI/桌面 profile 没有 web 服务时
|
|
136
140
|
插件照常工作,只是没有这个 tab
|
|
141
|
+
- **manifest 必须同时声明** `dsh.client: { platform: "web" }` 与字符串
|
|
142
|
+
`exports["./client"]`。宿主的 `client-modules` 只按这两条把包认成「浏览器半」,
|
|
143
|
+
再把文件原样挂到 `/plugins/<id>/client.js?rev=<hash>`;少一条,文件在磁盘上存在
|
|
144
|
+
但路由 404、tab 永不出现(而且判定在本进程内缓存,必须重启才生效)。
|
|
145
|
+
`test/manifests.test.mjs` 与 `scripts/verify-installed.mjs` 把声明、导出目标与
|
|
146
|
+
文件内容三件事都钉死了
|
|
137
147
|
- 变更类请求要求 `content-type: application/json` 且同源:JSON 会强制 CORS 预检,
|
|
138
148
|
跨站页面过不去
|
|
139
149
|
|
|
140
150
|
> 诚实说明:客户端与路由的**契约**有测试(模拟 `__ModuleLoader__` 装载、假 ctx 验证路由、
|
|
141
|
-
> 跨源拒绝、无 web
|
|
142
|
-
>
|
|
143
|
-
>
|
|
151
|
+
> 跨源拒绝、无 web 服务时不注册、预算透传),布局则用真实 React 18 做过静态渲染自检
|
|
152
|
+
> (含深色模式、空态、窄屏)。**0.9.8 之前**它还从未被宿主真正当成浏览器半——根因是
|
|
153
|
+
> manifest 漏了 `dsh.client` 声明(实测:`/plugins/dsh-immune-system/api` 返回 200,
|
|
154
|
+
> 说明宿主半在跑;而 `/plugins/dsh-immune-system/client.js` 返回 404)。0.9.8 起已补齐,
|
|
155
|
+
> 但需要**重启 DSH** 后到 `设置 → 插件` 确认 tab 出现。
|
|
144
156
|
|
|
145
157
|
### 同步与体检
|
|
146
158
|
|
|
@@ -170,8 +182,13 @@ node "<plugin>/dist/zcode/src/cli.js" doctor # 安装自检
|
|
|
170
182
|
- DSH:`result.isError` **只覆盖基础设施失败**(spawn 错误、abort、监听器抛错),
|
|
171
183
|
非零退出被明确设计为「reported, not errored」,模型自己决定怎么办。
|
|
172
184
|
|
|
173
|
-
所以两版都自己判结果。DSH
|
|
174
|
-
`isError` →
|
|
185
|
+
所以两版都自己判结果。DSH 侧的判据按权威度排序:
|
|
186
|
+
`isError` → sandbox 拒绝标记(判失败但**不入库学习**)→ kill/timeout(含 `value.timedOut`)→
|
|
187
|
+
结构化 `value.exitCode`(**有则权威:0 成功、非 0 失败,不再看文本**)→
|
|
188
|
+
有结构化 `value` 但无退出状态(**载荷即结论,正文是数据,不扫**)→ 行尾 `[exit code: N]` →
|
|
189
|
+
窄口径失败标记正则(**仅当无任何结构化状态**)。后面两步刻意让位给结构化信号:一条退出 0
|
|
190
|
+
的命令在 `grep` 输出里顺带印出 `command not found` 是常事,而 `read` 一个含 `Error:` 的日志
|
|
191
|
+
文件更是日常——都不该被记成失败。
|
|
175
192
|
|
|
176
193
|
**② 抗体失效会被淘汰(Anti-Rot)**
|
|
177
194
|
|
|
@@ -232,6 +249,32 @@ node "<plugin>/dist/zcode/src/cli.js" doctor # 安装自检
|
|
|
232
249
|
所以它挂在 `agent/turn-stopping`(`@mode serial`,回合边界提交前 await)。
|
|
233
250
|
未清空的队列还会在下次 `session-start` 由 `requeueStuckJobs()` 兜底。
|
|
234
251
|
|
|
252
|
+
### 失败怎么被判出来(四层判定 × 三类失败)
|
|
253
|
+
|
|
254
|
+
`judgeOutcome` 不是「看一个 boolean」,而是按权威顺序逐层走。三类常见失败在四层里的落点并不相同:
|
|
255
|
+
|
|
256
|
+
| 层 | 判据 | bash 非零退出 | 超时 | 沙箱拒绝 |
|
|
257
|
+
| --- | --- | --- | --- | --- |
|
|
258
|
+
| 1 | `result.isError`(宿主**只**对基础设施失败置真) | ✗ 返回 `isError:false` | 可能 ✗ | 偶尔 ✓(file 工具抛异常) |
|
|
259
|
+
| 2 | 沙箱标记 `[sandbox: file access denied under … mode]` | ✗ | ✗ | ✓ 命中 → 打 `sandboxDenied`,**不记录、不惩罚、不蒸馏** |
|
|
260
|
+
| 3 | kill / timed-out 标记 + `value.timedOut` | ✗ | ✓ 命中(即便 `exitCode=0`) | ✗ |
|
|
261
|
+
| 4 | 结构化 `value.exitCode`(shell 工具的权威判据) | ✓ 非 0 即失败 | — | — |
|
|
262
|
+
| 5 | 尾标记 `[exit code: N]` / 特征词(**仅**当没有任何结构化状态) | 兜底 | 兜底 | — |
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
一次工具结果
|
|
266
|
+
├─ isError ──────────────────────────────→ 失败(error.message 原样记入 trace)
|
|
267
|
+
├─ 沙箱拒绝标记 ─────────────────────────→ 拒绝:不记录 / 不惩罚 / 不蒸馏
|
|
268
|
+
├─ killed / timed out ───────────────────→ 失败
|
|
269
|
+
├─ value.exitCode 存在 ──┬─ 0 ───────────→ 成功(此后不再扫文本)
|
|
270
|
+
│ └─ ≠0 ──────────→ 失败
|
|
271
|
+
├─ value 为非空对象(无退出码)──────────→ 成功(载荷即结论)
|
|
272
|
+
├─ [exit code: N] / 特征词 ──────────────→ 失败
|
|
273
|
+
└─ 其余 ─────────────────────────────────→ 成功
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
要点:**结构化 exitCode 一旦存在就压过文本**——`grep` 命中内容、测试打印 `Error:` 都不会被误判成功/失败;而 bash 的非零退出正是靠第 4 层抓到的(宿主本身不会把它置成 `isError`)。这也解释了为什么「读一份含 `Error:` 的日志」不会凭空长出一条抗体。
|
|
277
|
+
|
|
235
278
|
### 存储
|
|
236
279
|
|
|
237
280
|
抗体库是 SQLite 单文件(含 FTS5 trigram 索引,中文按 3 字窗口匹配)。
|
|
@@ -255,12 +298,30 @@ node "<plugin>/dist/zcode/src/cli.js" doctor # 安装自检
|
|
|
255
298
|
| `IMMUNE_LLM_BASE_URL` | OpenAI 兼容端点,如 `https://api.example.com/v1` |
|
|
256
299
|
| `IMMUNE_LLM_MODEL` | 模型名,与上面配对使用 |
|
|
257
300
|
| `IMMUNE_LLM_API_KEY` | Bearer 令牌 |
|
|
301
|
+
| `IMMUNE_DISTILL_BUDGET_MS` | 单次回合末蒸馏的时间预算,默认 `12000`(两版同名同默认,钳制在 1–60 s);超预算的队列留到下次边界 |
|
|
258
302
|
| `IMMUNE_LOCALE` | `zh-CN` / `en` 强制单语;**默认中英双语** |
|
|
259
303
|
| `IMMUNE_ALLOW_PRIVATE_ENDPOINT` | 设为 `1` 才允许环回/私有端点。**默认拒绝**,防 SSRF |
|
|
260
304
|
| `IMMUNE_GLOBAL_LIBRARY` | 设为 `0` 关闭全局共享库。默认开启 |
|
|
261
305
|
| `IMMUNE_GUARD` | `off`(默认)/ `warn` / `deny` —— 高危错招事前检查,见下 |
|
|
262
306
|
| `IMMUNE_DEBUG_LOG` | 指定文件则记录执行细节,用于排障 |
|
|
263
307
|
|
|
308
|
+
### 什么时候值得开 LLM 蒸馏(成本 / 收益)
|
|
309
|
+
|
|
310
|
+
不配 `IMMUNE_LLM_*` 时走离线规则蒸馏器:零成本、结果确定,只从失败/成功的调用串里抽关键词拼出 trigger / prescription。配上模型后,同一段「失败→成功」会被改写成一句更像人话的处方(跨项目复用、导出成 `SKILL.md` 都更好),代价是**每条蒸馏一次模型往返**。
|
|
311
|
+
|
|
312
|
+
实测(`scripts/bench-tool-latency.mjs`,mock 端点 1500 ms/次):
|
|
313
|
+
|
|
314
|
+
| 队列深度 | `agent/turn-stopping` 耗时 | 蒸馏条数 |
|
|
315
|
+
| --- | --- | --- |
|
|
316
|
+
| 1 | ~1.5 s | 1 |
|
|
317
|
+
| 2 | ~3.0 s | 2 |
|
|
318
|
+
| 3 | ~4.6 s | 3 |
|
|
319
|
+
| 5 | ~7.5 s | 5 |
|
|
320
|
+
|
|
321
|
+
- 成本只落在**回合边界**一次(`agent/turn-stopping`),不落在工具调用上——`tools/post-execute` 中位稳定在 ~1 ms。
|
|
322
|
+
- 预算默认 12 s(`IMMUNE_DISTILL_BUDGET_MS`)。队列超过预算时,多出的任务留到下一次边界:实测 3000 ms 预算 + 6 个任务只蒸馏 2 个,其余 4 个顺延。
|
|
323
|
+
- 经验阈值:**每回合最多攒几条**「失败→成功」时才开模型。如果一个回合常常攒十几条,先跑离线规则蒸馏,或把 `IMMUNE_DISTILL_BUDGET_MS` 调小、让它跨多个回合摊平。
|
|
324
|
+
|
|
264
325
|
### 安全
|
|
265
326
|
|
|
266
327
|
- **脱敏**:写入磁盘前擦除 Bearer 令牌、`*_KEY=` 类赋值、URL 内嵌凭证、JWT。
|
|
@@ -274,11 +335,12 @@ node "<plugin>/dist/zcode/src/cli.js" doctor # 安装自检
|
|
|
274
335
|
| 检查 | 结果 |
|
|
275
336
|
| --- | --- |
|
|
276
337
|
| `npm run build` | exit 0 |
|
|
277
|
-
| `npm test` | **
|
|
338
|
+
| `npm test` | **90 条全绿**(34 条 core、7 条 ZCode hook、29 条 DSH 单元、4 条原生 UI(tab 契约)、3 条 MCP、3 条 LLM、5 条 UI 查询层、5 条 manifest 一致性(含 `dsh.client` 浏览器半契约与 shadow root 隔离)) |
|
|
278
339
|
| `scripts/verify-step1.mjs` | 15 断言(真实载荷判失败,含 bash 非零退出) |
|
|
279
340
|
| `scripts/verify-steps23.mjs` | 22 断言(失败→成功在回合结束时产出抗体;新会话注入 `<immune_memory>`) |
|
|
280
341
|
| `scripts/verify-live-host.mjs` | 20 断言(**真 Cordis 宿主**:真事件总线 + 真 SQLite + 跨会话注入 + 冷启动注入) |
|
|
281
|
-
| `scripts/verify-installed.mjs` |
|
|
342
|
+
| `scripts/verify-installed.mjs` | desktop / web 各 22 断言(bundles 登记、裸包名解析、patch 形状,以及浏览器半的 `dsh.client` 声明 + `exports["./client"]` + 装载器注册面) |
|
|
343
|
+
| `scripts/bench-tool-latency.mjs` | PASS,19 断言(慢速 mock 模型 1500 ms/次:`tools/post-execute` 中位 ~1 ms;`agent/turn-stopping` 按队列线性 1.5/3.0/4.6/7.5 s(1/2/3/5 条);3000 ms 预算下 6 条只蒸馏 2 条、其余顺延) |
|
|
282
344
|
|
|
283
345
|
### 已知限制
|
|
284
346
|
|
|
@@ -314,7 +376,7 @@ dsh plugin --profile <profile> add dsh-immune-system
|
|
|
314
376
|
|
|
315
377
|
```bash
|
|
316
378
|
cd dsh-immune-system && npm pack
|
|
317
|
-
dsh plugin --profile <profile> add ./dsh-immune-system-0.9.
|
|
379
|
+
dsh plugin --profile <profile> add ./dsh-immune-system-0.9.8.tgz
|
|
318
380
|
dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune
|
|
319
381
|
```
|
|
320
382
|
|
|
@@ -340,7 +402,10 @@ exiting non-zero is *not* an error result: ZCode's `PostToolUseFailure` only fir
|
|
|
340
402
|
when a handler throws, and DSH documents that "non-zero exits are reported, not
|
|
341
403
|
errored" — `isError` covers infrastructure failures only. Both ports therefore
|
|
342
404
|
classify the result themselves (exit code, trailing `[exit code: N]` /
|
|
343
|
-
`[killed by signal: X]` / timed-out markers, and a narrow marker regex).
|
|
405
|
+
`[killed by signal: X]` / timed-out markers, and a narrow marker regex). A
|
|
406
|
+
structured `value` is authoritative: a non-zero exit code is a failure and a
|
|
407
|
+
zero one is a success even if the output mentions `Error:`; a payload object with
|
|
408
|
+
no exit status is a success and is not text-scanned at all.
|
|
344
409
|
|
|
345
410
|
**Anti-rot, relevance-guarded.** An injected antibody that still did not prevent
|
|
346
411
|
the failure loses credit and is retired after three such episodes. The penalty is
|
|
@@ -352,6 +417,110 @@ blamed for any Bash failure.
|
|
|
352
417
|
never raw tool output; brackets are stripped so a line cannot close its wrapper
|
|
353
418
|
tag; length-capped; labelled advisory.
|
|
354
419
|
|
|
420
|
+
**Distillation cost sits at the turn boundary.** Jobs are queued during tool
|
|
421
|
+
use and drained once at turn end under `IMMUNE_DISTILL_BUDGET_MS` (default
|
|
422
|
+
12 s). Measured with a slow mock model (`scripts/bench-tool-latency.mjs`):
|
|
423
|
+
`tools/post-execute` stays ~1 ms median, while `agent/turn-stopping` pays one
|
|
424
|
+
round trip per queued job (~1.5 s each at 1500 ms/call) and defers whatever the
|
|
425
|
+
budget cannot fit to the next boundary. With a model configured the drain now
|
|
426
|
+
**batches four episodes per request**, so a backlog fits in one budget window
|
|
427
|
+
instead of four.
|
|
428
|
+
|
|
429
|
+
**Failure judgement — four layers.** A tool result is classified in this order,
|
|
430
|
+
and the first layer that yields a verdict wins:
|
|
431
|
+
|
|
432
|
+
| # | Signal | Verdict |
|
|
433
|
+
| --- | --- | --- |
|
|
434
|
+
| 1 | structured `value.exitCode` | non-zero = failure, zero = success — authoritative, output text is not scanned |
|
|
435
|
+
| 2 | `isError` / `error` payload | failure, and its message becomes the symptom |
|
|
436
|
+
| 3 | trailing markers (`[exit code: N]`, `[killed by signal: X]`, timed out) | failure when N≠0 |
|
|
437
|
+
| 4 | narrow marker regex over the body (`ENOENT`, `npm ERR!`, `Cannot find module`, …) | failure; a healthy log that merely mentions "error" does not match |
|
|
438
|
+
|
|
439
|
+
**Repeat suppression.** Asking three questions in a row about the same file
|
|
440
|
+
would otherwise inject the identical block three times and count three hits.
|
|
441
|
+
When the candidate set is unchanged and fewer than four turns have passed, the
|
|
442
|
+
injection is skipped and `hit_count` is left alone — that counter drives
|
|
443
|
+
promotion and anti-rot, so inflating it would corrupt the library's own
|
|
444
|
+
statistics. `resume` / `clear` / `compact` reset the state.
|
|
445
|
+
|
|
446
|
+
**Target-scoped pairing.** A failure only pairs with a success that is the
|
|
447
|
+
*same kind of work*: same file for file tools, same program family for commands
|
|
448
|
+
(`npm`/`npx`/`pnpm` are one family — a build failing under `npm` and passing
|
|
449
|
+
under `npx` is the same task's fix; `npm …` then `git …` is not). Read-only
|
|
450
|
+
probes (`ls`, `pwd`, `cat`, `git status`, `git log`, `git diff`, `which`, …)
|
|
451
|
+
never close an episode: they cannot have fixed anything, and they sit between
|
|
452
|
+
an error and its real fix constantly.
|
|
453
|
+
|
|
454
|
+
**Jaccard near-duplicate merge.** Before inserting, prescriptions within the
|
|
455
|
+
same trigger are compared with character-3-gram Jaccard (blended with a
|
|
456
|
+
character-set Jaccard for short CJK sentences, where inserting one character
|
|
457
|
+
shifts every trigram). At ≥0.70 the rule merges into the existing row and its
|
|
458
|
+
hit count increments; a human correction or a model rewrite then overwrites the
|
|
459
|
+
stored wording, because it outranks whatever was there.
|
|
460
|
+
|
|
461
|
+
**Path-affinity rerank.** Retrieval fetches twice the requested limit, then
|
|
462
|
+
re-ranks in memory: ×1.5 when the trigger names a file or directory the prompt
|
|
463
|
+
mentions, ×0.85 for shared-library rules (project rules always win ties), and a
|
|
464
|
+
time decay of `1 / (1 + 0.02 × days)` so a rule nobody has needed for a year
|
|
465
|
+
does not outrank a fresh one.
|
|
466
|
+
|
|
467
|
+
### Dashboard
|
|
468
|
+
|
|
469
|
+
`cli.js ui` serves a local page at `http://127.0.0.1:7788/`: search, retire /
|
|
470
|
+
restore / delete, project and shared tabs, live counts. Browsing is a
|
|
471
|
+
read-only path — it never counts as an injection.
|
|
472
|
+
|
|
473
|
+
Boundaries are deliberately narrow: binds `127.0.0.1` only, a random per-run
|
|
474
|
+
token is required on every `/api` call (a cross-origin simple POST needs no
|
|
475
|
+
preflight, so Origin checking alone would not do), strict CSP, and the page
|
|
476
|
+
loads nothing external.
|
|
477
|
+
|
|
478
|
+
### Native UI (dsh.client)
|
|
479
|
+
|
|
480
|
+
The DSH web app gets a real settings tab instead of a separate page. The client
|
|
481
|
+
is `dsh-immune-system/client/client.js`, hand-written against the module-loader
|
|
482
|
+
contract (`window.__ModuleLoader__.load({ id, factory })`) — no bundler, no JSX,
|
|
483
|
+
no new dependency. Data comes from a host route registered as an *optional*
|
|
484
|
+
`webServer` dependency, so CLI and desktop profiles still work without it.
|
|
485
|
+
Mutations require same-origin JSON, which forces a CORS preflight.
|
|
486
|
+
|
|
487
|
+
### Sync & Doctor
|
|
488
|
+
|
|
489
|
+
Library snapshots are plain tagged JSON, and merging is conservative:
|
|
490
|
+
prescription is the identity, tallies take the MAX (two machines count
|
|
491
|
+
independently and must not multiply), status is active when either side says
|
|
492
|
+
so, and malformed rows are counted invalid rather than aborting the import.
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
node "<plugin>/dist/zcode/src/cli.js" export-json library.json
|
|
496
|
+
node "<plugin>/dist/zcode/src/cli.js" import-json library.json
|
|
497
|
+
node "<plugin>/dist/zcode/src/cli.js" doctor
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
`doctor` reports node version, an FTS5 trigram probe, database and shared-library
|
|
501
|
+
readability, the plugin marker, the MCP script, the native-UI contract
|
|
502
|
+
(`dsh.client` + `exports["./client"]` + a loader-registered bundle) and counts.
|
|
503
|
+
Any failure exits non-zero, so it can gate a script.
|
|
504
|
+
|
|
505
|
+
### Guard (IMMUNE_GUARD)
|
|
506
|
+
|
|
507
|
+
Advisory text can be dropped by a model in a long turn, so a short curated list
|
|
508
|
+
of calls that are wrong in essentially every environment also gets a
|
|
509
|
+
deterministic pre-execution check: force-push to main, `rm -rf /`,
|
|
510
|
+
`git reset --hard`.
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
IMMUNE_GUARD=off # default: no check
|
|
514
|
+
IMMUNE_GUARD=warn # louder advisory (ZCode only)
|
|
515
|
+
IMMUNE_GUARD=deny # refuse the call (both hosts)
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Patterns live in code, never in the database: a distilled rule is model output,
|
|
519
|
+
and compiling it into a regex that can block tool calls would hand an
|
|
520
|
+
unreviewed string the power to stop work, plus ReDoS risk. `deny` is never the
|
|
521
|
+
default. `warn` is ZCode-only because DSH's `PreToolDecision` carries no
|
|
522
|
+
context channel.
|
|
523
|
+
|
|
355
524
|
### Layout
|
|
356
525
|
|
|
357
526
|
```
|
|
@@ -365,7 +534,7 @@ dsh/ DSH adapter — verified against the real host contract
|
|
|
365
534
|
dsh-immune-system/ installable bundle (manifest, patch, commands, dist)
|
|
366
535
|
zcode/ ZCode adapter (hooks, CLI)
|
|
367
536
|
docs/ dsh-host-contract.md — every contract claim with file:line
|
|
368
|
-
scripts/ the
|
|
537
|
+
scripts/ the five verification & benchmark scripts above
|
|
369
538
|
test/ node:test
|
|
370
539
|
```
|
|
371
540
|
|