@dsh-edge/dsh-edge-agent 0.0.0-stage → 0.1.2

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 (238) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/LICENSE +21 -0
  3. package/README.md +181 -2
  4. package/config/edge.example.json +179 -0
  5. package/config/privacy-policy.json +202 -0
  6. package/cordis.desktop-safe.patch.yml +129 -0
  7. package/cordis.patch.yml +382 -0
  8. package/cordis.yml +374 -0
  9. package/lib/attestation.d.ts +2 -0
  10. package/lib/attestation.d.ts.map +1 -0
  11. package/lib/attestation.js +2 -0
  12. package/lib/audit-log.d.ts +2 -0
  13. package/lib/audit-log.d.ts.map +1 -0
  14. package/lib/audit-log.js +2 -0
  15. package/lib/client.js +87 -0
  16. package/lib/cloud-llm-adapter.d.ts +2 -0
  17. package/lib/cloud-llm-adapter.d.ts.map +1 -0
  18. package/lib/cloud-llm-adapter.js +2 -0
  19. package/lib/dsh-adapter/attestation.d.ts +42 -0
  20. package/lib/dsh-adapter/attestation.d.ts.map +1 -0
  21. package/lib/dsh-adapter/attestation.js +40 -0
  22. package/lib/dsh-adapter/audit-log.d.ts +139 -0
  23. package/lib/dsh-adapter/audit-log.d.ts.map +1 -0
  24. package/lib/dsh-adapter/audit-log.js +376 -0
  25. package/lib/dsh-adapter/cloud-egress.d.ts +181 -0
  26. package/lib/dsh-adapter/cloud-egress.d.ts.map +1 -0
  27. package/lib/dsh-adapter/cloud-egress.js +257 -0
  28. package/lib/dsh-adapter/cloud-llm-adapter.d.ts +416 -0
  29. package/lib/dsh-adapter/cloud-llm-adapter.d.ts.map +1 -0
  30. package/lib/dsh-adapter/cloud-llm-adapter.js +1472 -0
  31. package/lib/dsh-adapter/contract.d.ts +80 -0
  32. package/lib/dsh-adapter/contract.d.ts.map +1 -0
  33. package/lib/dsh-adapter/contract.js +194 -0
  34. package/lib/dsh-adapter/desktop-cookie-janitor.d.ts +214 -0
  35. package/lib/dsh-adapter/desktop-cookie-janitor.d.ts.map +1 -0
  36. package/lib/dsh-adapter/desktop-cookie-janitor.js +409 -0
  37. package/lib/dsh-adapter/dsh-edge-agent.d.ts +48 -0
  38. package/lib/dsh-adapter/dsh-edge-agent.d.ts.map +1 -0
  39. package/lib/dsh-adapter/dsh-edge-agent.js +64 -0
  40. package/lib/dsh-adapter/edge-config.d.ts +484 -0
  41. package/lib/dsh-adapter/edge-config.d.ts.map +1 -0
  42. package/lib/dsh-adapter/edge-config.js +898 -0
  43. package/lib/dsh-adapter/edge-context-view.d.ts +184 -0
  44. package/lib/dsh-adapter/edge-context-view.d.ts.map +1 -0
  45. package/lib/dsh-adapter/edge-context-view.js +483 -0
  46. package/lib/dsh-adapter/effective-model.d.ts +85 -0
  47. package/lib/dsh-adapter/effective-model.d.ts.map +1 -0
  48. package/lib/dsh-adapter/effective-model.js +171 -0
  49. package/lib/dsh-adapter/etscli-shell-guard.d.ts +244 -0
  50. package/lib/dsh-adapter/etscli-shell-guard.d.ts.map +1 -0
  51. package/lib/dsh-adapter/etscli-shell-guard.js +420 -0
  52. package/lib/dsh-adapter/etscli-tools.d.ts +112 -0
  53. package/lib/dsh-adapter/etscli-tools.d.ts.map +1 -0
  54. package/lib/dsh-adapter/etscli-tools.js +393 -0
  55. package/lib/dsh-adapter/file-logger.d.ts +294 -0
  56. package/lib/dsh-adapter/file-logger.d.ts.map +1 -0
  57. package/lib/dsh-adapter/file-logger.js +564 -0
  58. package/lib/dsh-adapter/function-call-external.d.ts +357 -0
  59. package/lib/dsh-adapter/function-call-external.d.ts.map +1 -0
  60. package/lib/dsh-adapter/function-call-external.js +1005 -0
  61. package/lib/dsh-adapter/host-egress-guard.d.ts +161 -0
  62. package/lib/dsh-adapter/host-egress-guard.d.ts.map +1 -0
  63. package/lib/dsh-adapter/host-egress-guard.js +213 -0
  64. package/lib/dsh-adapter/host-llm-trace.d.ts +198 -0
  65. package/lib/dsh-adapter/host-llm-trace.d.ts.map +1 -0
  66. package/lib/dsh-adapter/host-llm-trace.js +344 -0
  67. package/lib/dsh-adapter/http-api-routes.d.ts +61 -0
  68. package/lib/dsh-adapter/http-api-routes.d.ts.map +1 -0
  69. package/lib/dsh-adapter/http-api-routes.js +79 -0
  70. package/lib/dsh-adapter/http-api-sessions.d.ts +105 -0
  71. package/lib/dsh-adapter/http-api-sessions.d.ts.map +1 -0
  72. package/lib/dsh-adapter/http-api-sessions.js +173 -0
  73. package/lib/dsh-adapter/http-api.d.ts +221 -0
  74. package/lib/dsh-adapter/http-api.d.ts.map +1 -0
  75. package/lib/dsh-adapter/http-api.js +1811 -0
  76. package/lib/dsh-adapter/index.d.ts +62 -0
  77. package/lib/dsh-adapter/index.d.ts.map +1 -0
  78. package/lib/dsh-adapter/index.js +97 -0
  79. package/lib/dsh-adapter/input-guard-messages.d.ts +56 -0
  80. package/lib/dsh-adapter/input-guard-messages.d.ts.map +1 -0
  81. package/lib/dsh-adapter/input-guard-messages.js +136 -0
  82. package/lib/dsh-adapter/input-guard.d.ts +508 -0
  83. package/lib/dsh-adapter/input-guard.d.ts.map +1 -0
  84. package/lib/dsh-adapter/input-guard.js +2174 -0
  85. package/lib/dsh-adapter/intent-prompts.d.ts +77 -0
  86. package/lib/dsh-adapter/intent-prompts.d.ts.map +1 -0
  87. package/lib/dsh-adapter/intent-prompts.js +97 -0
  88. package/lib/dsh-adapter/intent-router.d.ts +710 -0
  89. package/lib/dsh-adapter/intent-router.d.ts.map +1 -0
  90. package/lib/dsh-adapter/intent-router.js +2802 -0
  91. package/lib/dsh-adapter/intent-signals.d.ts +208 -0
  92. package/lib/dsh-adapter/intent-signals.d.ts.map +1 -0
  93. package/lib/dsh-adapter/intent-signals.js +391 -0
  94. package/lib/dsh-adapter/invariant-install.d.ts +16 -0
  95. package/lib/dsh-adapter/invariant-install.d.ts.map +1 -0
  96. package/lib/dsh-adapter/invariant-install.js +15 -0
  97. package/lib/dsh-adapter/invariant.d.ts +16 -0
  98. package/lib/dsh-adapter/invariant.d.ts.map +1 -0
  99. package/lib/dsh-adapter/invariant.js +22 -0
  100. package/lib/dsh-adapter/llm-adapters.d.ts +19 -0
  101. package/lib/dsh-adapter/llm-adapters.d.ts.map +1 -0
  102. package/lib/dsh-adapter/llm-adapters.js +16 -0
  103. package/lib/dsh-adapter/llm-internal-caller.d.ts +92 -0
  104. package/lib/dsh-adapter/llm-internal-caller.d.ts.map +1 -0
  105. package/lib/dsh-adapter/llm-internal-caller.js +100 -0
  106. package/lib/dsh-adapter/llm-max-turns.d.ts +55 -0
  107. package/lib/dsh-adapter/llm-max-turns.d.ts.map +1 -0
  108. package/lib/dsh-adapter/llm-max-turns.js +127 -0
  109. package/lib/dsh-adapter/llm-trace-store.d.ts +131 -0
  110. package/lib/dsh-adapter/llm-trace-store.d.ts.map +1 -0
  111. package/lib/dsh-adapter/llm-trace-store.js +205 -0
  112. package/lib/dsh-adapter/llm-trace.d.ts +450 -0
  113. package/lib/dsh-adapter/llm-trace.d.ts.map +1 -0
  114. package/lib/dsh-adapter/llm-trace.js +463 -0
  115. package/lib/dsh-adapter/local-llm-adapter.d.ts +141 -0
  116. package/lib/dsh-adapter/local-llm-adapter.d.ts.map +1 -0
  117. package/lib/dsh-adapter/local-llm-adapter.js +787 -0
  118. package/lib/dsh-adapter/local-storage.d.ts +96 -0
  119. package/lib/dsh-adapter/local-storage.d.ts.map +1 -0
  120. package/lib/dsh-adapter/local-storage.js +198 -0
  121. package/lib/dsh-adapter/model-router.d.ts +79 -0
  122. package/lib/dsh-adapter/model-router.d.ts.map +1 -0
  123. package/lib/dsh-adapter/model-router.js +107 -0
  124. package/lib/dsh-adapter/ohttp-gateway.d.ts +49 -0
  125. package/lib/dsh-adapter/ohttp-gateway.d.ts.map +1 -0
  126. package/lib/dsh-adapter/ohttp-gateway.js +32 -0
  127. package/lib/dsh-adapter/openai-sse.d.ts +85 -0
  128. package/lib/dsh-adapter/openai-sse.d.ts.map +1 -0
  129. package/lib/dsh-adapter/openai-sse.js +462 -0
  130. package/lib/dsh-adapter/pii-utils.d.ts +96 -0
  131. package/lib/dsh-adapter/pii-utils.d.ts.map +1 -0
  132. package/lib/dsh-adapter/pii-utils.js +324 -0
  133. package/lib/dsh-adapter/plugin-host.d.ts +38 -0
  134. package/lib/dsh-adapter/plugin-host.d.ts.map +1 -0
  135. package/lib/dsh-adapter/plugin-host.js +38 -0
  136. package/lib/dsh-adapter/plugin.d.ts +46 -0
  137. package/lib/dsh-adapter/plugin.d.ts.map +1 -0
  138. package/lib/dsh-adapter/plugin.js +100 -0
  139. package/lib/dsh-adapter/privacy-policy.d.ts +237 -0
  140. package/lib/dsh-adapter/privacy-policy.d.ts.map +1 -0
  141. package/lib/dsh-adapter/privacy-policy.js +391 -0
  142. package/lib/dsh-adapter/prompt-guard.d.ts +152 -0
  143. package/lib/dsh-adapter/prompt-guard.d.ts.map +1 -0
  144. package/lib/dsh-adapter/prompt-guard.js +872 -0
  145. package/lib/dsh-adapter/realm-skill-bridge.d.ts +365 -0
  146. package/lib/dsh-adapter/realm-skill-bridge.d.ts.map +1 -0
  147. package/lib/dsh-adapter/realm-skill-bridge.js +1180 -0
  148. package/lib/dsh-adapter/sanitizer-pipeline.d.ts +140 -0
  149. package/lib/dsh-adapter/sanitizer-pipeline.d.ts.map +1 -0
  150. package/lib/dsh-adapter/sanitizer-pipeline.js +431 -0
  151. package/lib/dsh-adapter/secure-bridge.d.ts +47 -0
  152. package/lib/dsh-adapter/secure-bridge.d.ts.map +1 -0
  153. package/lib/dsh-adapter/secure-bridge.js +35 -0
  154. package/lib/dsh-adapter/session-attribution.d.ts +70 -0
  155. package/lib/dsh-adapter/session-attribution.d.ts.map +1 -0
  156. package/lib/dsh-adapter/session-attribution.js +76 -0
  157. package/lib/dsh-adapter/sessions.d.ts +86 -0
  158. package/lib/dsh-adapter/sessions.d.ts.map +1 -0
  159. package/lib/dsh-adapter/sessions.js +179 -0
  160. package/lib/dsh-adapter/tcm.d.ts +55 -0
  161. package/lib/dsh-adapter/tcm.d.ts.map +1 -0
  162. package/lib/dsh-adapter/tcm.js +94 -0
  163. package/lib/dsh-adapter/td-bus.d.ts +87 -0
  164. package/lib/dsh-adapter/td-bus.d.ts.map +1 -0
  165. package/lib/dsh-adapter/td-bus.js +196 -0
  166. package/lib/dsh-adapter/tool-runner.d.ts +185 -0
  167. package/lib/dsh-adapter/tool-runner.d.ts.map +1 -0
  168. package/lib/dsh-adapter/tool-runner.js +271 -0
  169. package/lib/dsh-adapter/tools.d.ts +118 -0
  170. package/lib/dsh-adapter/tools.d.ts.map +1 -0
  171. package/lib/dsh-adapter/tools.js +292 -0
  172. package/lib/dsh-adapter/types.d.ts +23 -0
  173. package/lib/dsh-adapter/types.d.ts.map +1 -0
  174. package/lib/dsh-adapter/types.js +16 -0
  175. package/lib/dsh-adapter/voice-input.d.ts +30 -0
  176. package/lib/dsh-adapter/voice-input.d.ts.map +1 -0
  177. package/lib/dsh-adapter/voice-input.js +34 -0
  178. package/lib/dsh-adapter/web-search-tavily-bocha.d.ts +152 -0
  179. package/lib/dsh-adapter/web-search-tavily-bocha.d.ts.map +1 -0
  180. package/lib/dsh-adapter/web-search-tavily-bocha.js +289 -0
  181. package/lib/http-api.d.ts +2 -0
  182. package/lib/http-api.d.ts.map +1 -0
  183. package/lib/http-api.js +2 -0
  184. package/lib/index.d.ts +48 -0
  185. package/lib/index.d.ts.map +1 -0
  186. package/lib/index.js +49 -0
  187. package/lib/intent-router.d.ts +2 -0
  188. package/lib/intent-router.d.ts.map +1 -0
  189. package/lib/intent-router.js +2 -0
  190. package/lib/llm-adapters.d.ts +2 -0
  191. package/lib/llm-adapters.d.ts.map +1 -0
  192. package/lib/llm-adapters.js +2 -0
  193. package/lib/local-llm-adapter.d.ts +2 -0
  194. package/lib/local-llm-adapter.d.ts.map +1 -0
  195. package/lib/local-llm-adapter.js +2 -0
  196. package/lib/local-storage.d.ts +2 -0
  197. package/lib/local-storage.d.ts.map +1 -0
  198. package/lib/local-storage.js +2 -0
  199. package/lib/model-router.d.ts +2 -0
  200. package/lib/model-router.d.ts.map +1 -0
  201. package/lib/model-router.js +2 -0
  202. package/lib/ohttp-gateway.d.ts +2 -0
  203. package/lib/ohttp-gateway.d.ts.map +1 -0
  204. package/lib/ohttp-gateway.js +2 -0
  205. package/lib/plugin-host.d.ts +2 -0
  206. package/lib/plugin-host.d.ts.map +1 -0
  207. package/lib/plugin-host.js +2 -0
  208. package/lib/privacy-policy.d.ts +2 -0
  209. package/lib/privacy-policy.d.ts.map +1 -0
  210. package/lib/privacy-policy.js +2 -0
  211. package/lib/prompt-guard.d.ts +2 -0
  212. package/lib/prompt-guard.d.ts.map +1 -0
  213. package/lib/prompt-guard.js +2 -0
  214. package/lib/sanitizer-pipeline.d.ts +2 -0
  215. package/lib/sanitizer-pipeline.d.ts.map +1 -0
  216. package/lib/sanitizer-pipeline.js +2 -0
  217. package/lib/secure-bridge.d.ts +2 -0
  218. package/lib/secure-bridge.d.ts.map +1 -0
  219. package/lib/secure-bridge.js +2 -0
  220. package/lib/sessions.d.ts +2 -0
  221. package/lib/sessions.d.ts.map +1 -0
  222. package/lib/sessions.js +2 -0
  223. package/lib/tcm.d.ts +2 -0
  224. package/lib/tcm.d.ts.map +1 -0
  225. package/lib/tcm.js +2 -0
  226. package/lib/td-bus.d.ts +2 -0
  227. package/lib/td-bus.d.ts.map +1 -0
  228. package/lib/td-bus.js +2 -0
  229. package/lib/tools.d.ts +2 -0
  230. package/lib/tools.d.ts.map +1 -0
  231. package/lib/tools.js +2 -0
  232. package/lib/voice-input.d.ts +2 -0
  233. package/lib/voice-input.d.ts.map +1 -0
  234. package/lib/voice-input.js +2 -0
  235. package/package.json +253 -3
  236. package/presets/agent-preset-edge.json +50 -0
  237. package/presets/edge.desktop-safe.patch.yml +503 -0
  238. package/presets/edge.patch.yml +557 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,158 @@
1
+ # Changelog
2
+
3
+ 本文件的**唯一权威**是 [`docs/PROGRESS.md`](docs/PROGRESS.md) —— 那里逐条记着
4
+ 每一次改动的根因、实证与裁决(§7.x 全量历史)。本文件是给**使用者**看的:装上之后
5
+ 能得到什么、有什么边界、怎么排障。
6
+
7
+ 格式:[Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/);
8
+ 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
9
+
10
+ ---
11
+
12
+ ## [0.1.2] — 2026-11-11
13
+
14
+ **修三处「写的东西与实际行为不一致」,并把 README 收瘦。** 2026-08-18 → 2026-11-11。
15
+
16
+ - **包内版本头的日期与提交数**:0.1.1 那行写的是「2026-11-10 / 506 次提交」——
17
+ 两个都不是发布时的真实值(真实发布时刻是 2026-11-11 19:33)。现在只写日期区间,
18
+ 把「次提交」整个去掉:它是**移动靶**(写完没几轮就又不对了)。
19
+ - **发布出去的 `package.json` 不再声明不在包里的东西**:0.1.1 的清单带着 50 余条
20
+ `scripts.*` 和一整套 `devDependencies`,而它们指向的 `scripts/`、`src/`、`tests/`
21
+ 根本不在 tarball 里。现在发布走一个**消毒过的暂存目录**(`dist/.npm-stage/`):
22
+ 内容 = safe 变体 + README + LICENSE,清单 = 去掉 `scripts` / `devDependencies` /
23
+ `packageManager` 的那份。**出货文件逐个不变**(仍是 238 个),只有清单瘦了。
24
+ - **README 重写**:只讲 `desktop-safe` 一种变体,重点放在**「不配 edge.json 时会怎样」**
25
+ 与**「edge.json 怎么配」**上 —— npm 包页就是这份 README,访客第一眼要看到的正是
26
+ 「缺省下『本地』并不在本地」这件事。
27
+
28
+ ## [0.1.1] — 2026-11-11
29
+
30
+ **首次发布到 npm registry。** 2026-08-18 → 2026-11-11。
31
+
32
+ ### 加载通道
33
+
34
+ 桌面版 DSH App 的 **Plugins → Add plugin** 支持四种输入,本包四条都能装:
35
+
36
+ | 通道 | 输入 | 要构建授权 | PATH 要有 pnpm |
37
+ |---|---|---|---|
38
+ | A 本地目录 | 绝对路径 | 否 | 否 |
39
+ | B GitHub 地址 | `git+ssh://git@github.com/<owner>/<repo>` | 是 | 是 |
40
+ | C npm 包名 | `@dsh-edge/dsh-edge-agent` | 否 | 否 |
41
+ | D tarball 路径 | `…/x.tar.gz` 绝对路径 | 否 | 否 |
42
+
43
+ 装完**必须点「立即启用」** —— GUI 不会自动启用,否则启动时不加载、右栏不出现
44
+ Edge tab。装法细节见 [`docs/deploy-github.md`](docs/deploy-github.md) /
45
+ [`docs/deploy-npm.md`](docs/deploy-npm.md) / [`docs/deploy-tarball.md`](docs/deploy-tarball.md)。
46
+
47
+ ### 隐私管线(边缘模式)
48
+
49
+ 宿主自带四种工作方式之外,本包注册了第五种 —— **「边缘模式」= 标准模式 + 本项目的
50
+ 全部功能**。标准模式下本项目的插件**完全不生效**(护栏 hook 挂在宿主的 scope 链上);
51
+ 判一个会话有没有走本项目链路,**唯一判据是审计链**。
52
+
53
+ - **输入防御**(prompt-guard)—— 正则 + 频率 + PII 分级 + 可选模型辅助检测;
54
+ - **隐私策略**(privacy-policy)—— 业务危险操作与 PII 护栏,REJECT 永不出网;
55
+ - **意图识别**(intent-router)—— 设备正则 → 决策模型 → LLM 分类器 → 规则层
56
+ 四级阶梯(**决策模型那档出厂是关的**,§7.182),实体护栏与云闸两道后处理闸收口;
57
+ - **脱敏**(sanitizer + pii-utils)—— 10 类 PII,可逆占位符,出网前替换、回程还原;
58
+ - **编排层**(input-guard)—— 挂 `agent/pre-step` / `agent/request` / `llm/stream`
59
+ 三个 hook,谁都绕不过;
60
+ - **Realm 闸门**—— LOCAL/CLOUD realm 分离,skill 与工具按 realm 放行;
61
+ - **威胁总线**(td-bus)—— 云请求速率 / 连续脱敏失败 / 权限拒绝 / 端点失联
62
+ 四档阈值,逐级升到 block。
63
+
64
+ ### LLM 路由
65
+
66
+ 两个池(`llm.local` / `llm.cloud`)**完全对称**,各三种模式:
67
+
68
+ | `baseUrl` | `delegate` | 行为 |
69
+ |---|---|---|
70
+ | 填了 | 任意 | **直连** —— edge 自有池,密钥走 `keyRef` |
71
+ | 留空 | `true`(默认) | **DELEGATE** —— 转发宿主 GUI 里选中的模型,凭据归宿主 |
72
+ | 留空 | `false` | **不注册路由** —— LOCAL 意图 fail-closed |
73
+
74
+ 出网口径**不对称**(这是裁决,不是 bug):云池只带「之前上过云的历史」;本地池带
75
+ **全部**历史。出网那一跳由 `host-egress-guard` 兜底裁剪(挂在 `llm/stream`
76
+ waterfall 上)。
77
+
78
+ ### 边缘模式的模型路由(fail-closed)
79
+
80
+ 宿主 0.2.2-rc.1 起,**路由只由会话级 `modelSelection` 决定** —— 在
81
+ `agent/request` 里改写 provider/model 会被宿主的 `prepareRequest()` 判非法并
82
+ 抛错。因此本包:
83
+
84
+ - **边缘模式不改写会话选型**(§7.184):`agent/created` / `agent-preset/selected`
85
+ 的钉选与「客户端回写后回钉」全部退役 ⇒ 宿主模型选择器保持主人选的那个;
86
+ - **出网那一跳改道**:本轮判定为 LOCAL、而实际请求的 provider 不是本地 provider
87
+ ⇒ 在 `llm/stream` 把它换成 provider = 本地池(**不再拒绝这一轮**);判定为
88
+ CLOUD ⇒ 不改道(用哪个模型是用户自己的行为);改不动 ⇒ fail-closed 抛错;
89
+ - 面板上的「模型」一行显示的是**这一轮真正会跑**的模型与它的真实落点
90
+ (DELEGATE 下 = 委托解析出来的宿主 provider/model),不是配置里的默认值;
91
+ - **宿主模型选择器(输入框下方)里能选到本地池**:本地池在宿主的模型目录里
92
+ **如实声明**它真正会跑的那**一个**模型(直连池 = 池自己的 model id;
93
+ DELEGATE = 委托目标的 model id)—— 宿主会把「空目录的 provider」整组剔出
94
+ 目录,不声明就只剩「请选择模型」(§7.180)。**wire 上跑的模型、目录里声明的
95
+ 那一项、会话被补钉的 model id 三者是同一个 id**;云池仍然在目录里隐身;
96
+ - **选型不动,实际请求改道**(§7.184):进入边缘模式时插件**一个字都不改**你的
97
+ 模型选择(宿主选择器里显示什么就一直是什么);但「本轮必须留在设备上」这条不变式
98
+ 由**出网那一跳**保证 —— 本轮判定为 LOCAL 时,请求在 `llm/stream` 被换成
99
+ provider = 本地池(model id 与 wire 同源)。改道改不动(本地路由不存在等)就
100
+ **fail-closed 抛错**,绝不放行到云端。面板的「答案出口」会写明
101
+ **「会话选型 → 实际落点」**,两边都摆出来。
102
+
103
+ npm 包**不带** `config/edge.json` ⇒ 没配本地端点时 `local-llm` 走 DELEGATE
104
+ (转发宿主提供商),但 provider 身份仍是 `local-llm` ⇒ 闸门照常放行、装上即用。
105
+
106
+ ### 配置
107
+
108
+ 只有**一个文件**:`edge.json`,两个合法落点(§7.182):
109
+
110
+ | 顺位 | 路径 | 适合 |
111
+ |---|---|---|
112
+ | 1 | `<插件目录>/config/edge.json` | 「插件装在哪,配置就在哪」—— 打开插件目录就能看到能改的那个 |
113
+ | 2 | `~/.dsh-edge-agent/edge.json` | **推荐** —— 重装 / 删 profile 都活着,npm / git / tarball 三条通道通用 |
114
+
115
+ 仓库的 `config/` 目录**不再放活配置**(明文 API key 不该待在 git checkout 里),
116
+ 三条通道的产物里也本来就没有这个文件。`config/edge.example.json` 仍随产物发布,
117
+ 但它只是**给你抄的模板**(模板里的部署值已并入代码默认值 ⇒ 「没配置文件」与
118
+ 「照模板抄」行为一致)。
119
+
120
+ **出厂默认(§7.182 主人裁决)**:三个可选开关默认**关闭** ——
121
+ `jev.enabled`(决策模型是需要本地 `:8001` 服务的可选加速器)、
122
+ `webSearch.tavilyEnabled` / `webSearch.bochaEnabled`
123
+ (⇒ 出厂 web-search 链上只有 DeepSeek 档)。要开就显式写 `true`。
124
+ 这三个开关在插件 `apply()` 时读取 ⇒ **改完要重启 App**。
125
+
126
+ `secrets` 段存明文 key,其他段按 `keyRef` 引用它。**`secrets` 与 `llm` 都不是必填**
127
+ —— 桌面版装法下空对象也能跑。
128
+
129
+ ### 观测
130
+
131
+ - **审计链** —— 决策级事件,JSONL 落盘,Edge 面板按「意图 / 判定 / 路由决策 /
132
+ LLM 调用」分栏;
133
+ - **ops log** —— 启动与运行期诊断;
134
+ - **Edge 标签页** —— 桌面右栏 4 个面板 + 会话摘要实时可视化;
135
+ - 面板「答案出口」一栏**认得 DELEGATE 出口行**(`llm.*.delegate.*`):直连池与
136
+ DELEGATE 池都会显示「池 + 模型」,不会在明明作答时写「路由未定」(§7.181)。
137
+ LOCAL / CLOUD 的判据仍是**哪个池**(§7.130),DELEGATE 下额外多一行「真实
138
+ 落点」,如实说明这一轮转发去了哪儿;
139
+ - **LLM trace** —— 每次往返的请求/响应成对可查(凭据与 PII 脱敏)。
140
+
141
+ ### 工程门禁
142
+
143
+ 28 道门禁,验的是**产物**不是源码(`npm run build` 一条链跑完):
144
+ 构建链完整性、adapter 边界、上游契约、patch 面、preset 隔离、发布面、文档可达性、
145
+ 安装包内容、tarball、CI 链不漂移、边缘模式选型回钉(§7.183)……
146
+
147
+ ### 已知边界
148
+
149
+ 见 [`docs/privacy-policy.md`](docs/privacy-policy.md#已知边界) —— 那里逐条记着
150
+ 「当前**未实现或未接线**」的部分(例如标准模式会话不做出网裁剪、本地池 delegate
151
+ 模式下内部阶段会跑在宿主模型上)。
152
+
153
+ ---
154
+
155
+ ## 更早的版本
156
+
157
+ `0.1.0` 及更早只存在于 git 历史里,**从未发布到 registry**。
158
+ 逐条实施记录见 [`docs/PROGRESS.md`](docs/PROGRESS.md)。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chschytzmcy
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,182 @@
1
- # Temporary Holding Version
1
+ # dsh-edge-agent
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
+ > 边缘侧私密智能体 · 基于 DSH (DeepSeek Harness) Cordis 插件机制
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-blue)](package.json)
7
+
8
+ 给 DSH 桌面版 / CLI 装上一个新的 **Agent Preset —— 边缘模式**:宿主自带的
9
+ 标准 / 创造 / PTC / 极简之外,**边缘模式 = 标准模式 + 本项目的全部能力**
10
+ (隐私护栏 · 意图判定 · 路由决策 · PII 脱敏 · Realm 闸门 · 审计链 · Edge 面板)。
11
+ 默认仍是标准模式,在设置页选中「边缘模式」才生效。
12
+
13
+ > 这是**一种工作方式**,不是替代品:标准模式下本插件**完全不生效**(Edge tab 常驻可见)。
14
+ > 判一个会话有没有走本链路,**唯一判据是审计链**。
15
+
16
+ 本包启用的是 `desktop-safe` 变体 —— 只 insert 插件行,不改写宿主的 persona /
17
+ approval / sandbox 等全局设置。
18
+
19
+ ---
20
+
21
+ ## 📥 装
22
+
23
+ 桌面版 DSH App:**Plugins → Add plugin** → 填包名 `@dsh-edge/dsh-edge-agent`
24
+ (装预构建包,不需要构建授权、不需要 PATH 上有 pnpm)。
25
+
26
+ > ⚠️ **装完必做两步**:① 点「**立即启用**」(GUI 不会自动启用)② **重启桌面 App**。
27
+ > 否则包在 `dependencies` 里却不在 `dsh.profile.bundles` 里 ⇒ 启动不加载 ⇒ 右栏没有 Edge tab。
28
+
29
+ 另有三条通道(本地目录 / GitHub 地址 / tarball 路径)与独立 headless profile,
30
+ 见 [`docs/load-into-local-dsh.md`](docs/load-into-local-dsh.md)。
31
+
32
+ ---
33
+
34
+ ## ⚙️ 配置:`edge.json`
35
+
36
+ **只有一个文件**:`edge.json`,所有 profile 读同一份,不依赖环境变量。
37
+
38
+ ### 缺省是什么样(不写配置文件会怎样)
39
+
40
+ **一句话**:缺省下两个池都**转发宿主 GUI 里选中的模型**(DELEGATE)——
41
+ 所以「本地」这一栏**默认并不在本地**。
42
+
43
+ | 缺省项 | 缺省值 | 后果 |
44
+ |---|---|---|
45
+ | `llm.local.baseUrl` | 空 ⇒ **DELEGATE** | `local-llm` 路由**照样注册**,但它转发宿主模型 —— 不是「没模型」|
46
+ | `llm.cloud.baseUrl` | 空 ⇒ **DELEGATE** | 云池同样转发宿主模型 |
47
+ | `stages.intent.provider` | `local-llm` | 意图判定钉在本地池上 ⇒ 端点留空时它**跑宿主模型** ⇒ **待检测的用户原文也会上行**(§7.171 的已知边界)|
48
+ | `stages.intent.timeoutMs` | `60000` | 意图判定那一次调用的超时窗口是 **60 秒** |
49
+ | `stages.guard.timeoutMs` | `60000` | 同上 |
50
+ | `llm.local.delegate` | `true` | 要「不注册本地路由」必须显式写 `false`(kill switch)|
51
+
52
+ ⇒ **想让「本地」真的是本地,必须填 `llm.local.baseUrl`**。
53
+
54
+ ### 落点(两个,没有第三个)
55
+
56
+ | 顺位 | 路径 | 说明 |
57
+ |---|---|---|
58
+ | 1 | `<插件目录>/config/edge.json` | 装好后在 App 里就能找到的那份 |
59
+ | 2 | `~/.dsh-edge-agent/edge.json` | 跨通道、跨重装、删 profile 都活着 ★ **推荐** |
60
+
61
+ > 三条发布通道的产物里**本来就没有** `config/edge.json`(打包时按设计排除);
62
+ > 插件目录那份 `config/edge.example.json` 只是**给你抄的模板**。
63
+
64
+ ### 怎么配(三步)
65
+
66
+ 1. 把插件目录里的模板抄到 home:
67
+ ```bash
68
+ mkdir -p ~/.dsh-edge-agent
69
+ cp <插件目录>/config/edge.example.json ~/.dsh-edge-agent/edge.json
70
+ ```
71
+ 2. 填三样东西:
72
+ ```jsonc
73
+ {
74
+ "secrets": { "myEdgeKey": "<你的 key>" },
75
+ "llm": {
76
+ "local": {
77
+ "baseUrl": "https://your-endpoint/v1", // 必须含 /v1
78
+ "model": "your-model",
79
+ "keyRef": "myEdgeKey" // 端点不鉴权(本地 llama.cpp / vLLM)时整行可省
80
+ }
81
+ }
82
+ }
83
+ ```
84
+ - `baseUrl` 留空 = DELEGATE(转发宿主模型);填了 = **直连自有池**,凭据只认 `keyRef` 指向的槽位;
85
+ - `llm.cloud` 与 `llm.local` **完全对称**,配法一模一样;
86
+ - `delegate: false` = kill switch(不注册路由 ⇒ LOCAL 轮次 fail-closed,不放行到可能上云的 provider)。
87
+ 3. **重启桌面 App** —— `llm` / `stages` / `jev` 在插件启动时定死,不重启不生效。
88
+
89
+ ### 不配会怎样
90
+
91
+ | 你没配的东西 | 结果 |
92
+ |---|---|
93
+ | 整个 `edge.json` | 两池都 DELEGATE ⇒ 实际是**同一个模型**(宿主选的那个)|
94
+ | `llm.local.baseUrl` | `local-llm` 路由照样注册(转发宿主模型)|
95
+ | `llm.local.keyRef` | 端点不鉴权 ⇒ 完全正常;端点要鉴权 ⇒ 401,报错会指名让你配 `keyRef` |
96
+ | `secrets` 整段(**桌面版**)| **完全正常** —— 桌面版不挂 http-api,一个 secret 都不需要 |
97
+ | `secrets.edgeApiKey`(**独立 HTTP profile**)| **必填** —— 没凭据**拒绝启动**(fail-closed)|
98
+
99
+ > ⚠️ 两池的**出网口径不同**:云池只带「之前上过云的历史」;本地池带**全部**历史。
100
+ > ⚠️ `edge.json` 含**明文 API key**,绝不能提交到 git。
101
+ > 完整配置清单见 [`docs/privacy-policy.md`](docs/privacy-policy.md#配置清单)。
102
+
103
+ ---
104
+
105
+ ## ✅ 装完自检
106
+
107
+ 「装上了」分三层,**boot 不报错只证明前两层**:
108
+
109
+ | 层 | 含义 | 怎么验 |
110
+ |---|---|---|
111
+ | **mount** | 插件被挂上 | boot 不报错 |
112
+ | **active** | cordis 把插件置为 ACTIVE | `--dump-config` 里能看到插件行 |
113
+ | **enforced** | 真拦得住 | **审计链里出现执法行**(`blocked.attack` / `blocked.policy` …)|
114
+
115
+ ```bash
116
+ grep '@dsh-edge/dsh-edge-agent' ~/.dsh/profiles/<profile>/package.json
117
+ dsh --profile <profile> --dump-config | grep -cE 'input-guard|file-logger|intent-router|sanitizer'
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 🔀 边缘模式的模型从哪来
123
+
124
+ 宿主 0.2.2-rc.1 起**路由只由会话级 `modelSelection` 决定**,本包**不改写会话选型**:
125
+
126
+ - 进入边缘模式 ⇒ **选型一个字都不改**;
127
+ - 本轮判定为 LOCAL、但会话选的 provider 不是本地 provider ⇒ 这一轮在**出网那一跳**
128
+ 被**改道**到本地池(provider 与 model 都换)⇒ 这一轮仍然留在设备上,**不再被拒收**;
129
+ - 改道改不动(本地路由不存在 / 解析不到本地模型 / 宿主 LLM runtime 不可用)⇒
130
+ **fail-closed 抛错**,绝不放行到可能上云的 provider。
131
+
132
+ 判一次会话到底用了哪个 provider,**唯一权威是审计链**:`input-guard.reroute.local`
133
+ (改道发生,带 `from` / `to`)/ `input-guard.reroute.blocked`(改不动)。
134
+ 详见 [`docs/privacy-policy.md`](docs/privacy-policy.md)。
135
+
136
+ ---
137
+
138
+ ## ✨ 能力
139
+
140
+ | 能力 | 状态 | 说明 |
141
+ |---|---|---|
142
+ | **🔒 端侧优先** | ✅ | 默认本地 LLM 处理;未决轮次一律留在设备上 |
143
+ | **🛡️ 隐私保护** | ✅ | PII 可逆脱敏 + 日志掩码 + 隐私策略热重载 |
144
+ | **🚦 Realm 三级闸门** | ✅ | LOCAL → SANITIZED → CLOUD;缺件即降级本地 |
145
+ | **🚨 威胁检测** | ✅ | `td-bus` 滑动窗口 + 分级 + 隔离;超阈值返回 503 |
146
+ | **📜 审计与可追溯** | ✅ | 追加式 JSONL 审计链 + ops log(PII + 密钥双重脱敏)|
147
+ | **🔌 插件化** | ✅ | 按 subpath 导出(**37 条**),`verify:*` 门禁锁死装配面 |
148
+ | **🌐 零信任通信** | 🚧 | OHTTP 网关为**桩实现**;当前云端通道走标准 TLS |
149
+ | **🔑 TEE / 远程证明** | 🚧 | `tcm` / `attestation` 为**桩实现** |
150
+
151
+ ---
152
+
153
+ ## 📚 文档
154
+
155
+ | 文档 | 内容 |
156
+ |---|---|
157
+ | [`docs/README.md`](docs/README.md) | **文档总索引**(每份一句说明 + 状态)|
158
+ | [`docs/architecture.md`](docs/architecture.md) | 架构主文档:四层模型 / Realm / 插件开发 / 威胁模型 / patch 树 |
159
+ | [`docs/privacy-policy.md`](docs/privacy-policy.md) | 隐私链路单一权威:策略 + 判定表 + 配置清单 + 审计 + 行为契约 |
160
+ | [`docs/deploy-npm.md`](docs/deploy-npm.md) | npm 包名安装 + 发布链排障 |
161
+ | [`docs/desktop-troubleshooting.md`](docs/desktop-troubleshooting.md) | 桌面版排障手册 |
162
+ | [`docs/PROGRESS.md`](docs/PROGRESS.md) | 实施进度档案(§7.x 全量历史)|
163
+
164
+ ### 开发与发布
165
+
166
+ ```bash
167
+ pnpm install
168
+ pnpm run build # compile → typecheck:client → build:client → build:installable → pack:tarball → verify:build
169
+ pnpm test
170
+
171
+ npm run publish:npm # 构建 + 门禁 + dry-run(**默认不发**)
172
+ npm run publish:npm -- --yes # 真发
173
+ npm run publish:npm -- --yes --tag # 发完打 v<version> tag
174
+ ```
175
+
176
+ 前置:`npm login` 一次(凭据写在 `~/.npmrc`;PATH 上要有 `pnpm`)。
177
+ 不带 `--yes` **永远不会碰 registry**。
178
+
179
+ > ⚠️ **「publish 报成功」≠「包已经能装」**:分级发布(staged publishing)下包名先被
180
+ > 占位版 `0.0.0-stage` 占住。**判据**:`npm view <pkg> version` 返回 `0.0.0-stage` = 还卡着。
181
+
182
+ MIT License. 详见 [LICENSE](LICENSE)。
@@ -0,0 +1,179 @@
1
+ {
2
+ "_comment": "dsh-edge-agent 配置模板(tracked)。§7.182(2026-11-11)起,运行时只认两个落点:①装好后的插件目录里的 config/edge.json(顺位 1 —— 打开插件目录就能看到能改的那个);②~/.dsh-edge-agent/edge.json(顺位 2 —— 重装/删 profile 都活着,跨 npm/git/tarball 三条通道通用)。仓库的 config/ 目录不再放活配置(明文 API key 不该待在 git checkout 里)。本文件本身不再是运行时候选(模板里的部署值已并入代码默认值),它只是给你抄的那份模板:cp config/edge.example.json ~/.dsh-edge-agent/edge.json,然后填真实端点与 key。",
3
+ "secrets": {
4
+ "_comment": "HTTP API 鉴权 + 各 LLM 池的直连凭据。直连模式下写入 Authorization 头。",
5
+ "edgeApiKey": "",
6
+ "deepseekApiKey": "",
7
+ "dashscopeApiKey": "",
8
+ "minimaxApiKey": "",
9
+ "aliyuntokenplanApiKey": "",
10
+ "tavilyApiKey": "",
11
+ "bochaApiKey": "",
12
+ "bailianApiKey": ""
13
+ },
14
+ "http": {
15
+ "_comment": "http-api 监听入口。",
16
+ "host": "127.0.0.1",
17
+ "port": 21017
18
+ },
19
+ "llm": {
20
+ "_comment": "§7.100 / §7.171:两个池的 baseUrl 都留空 = DELEGATE 到宿主 profile 已配置的模型提供商(桌面 GUI 选的模型 / dsh-base 默认 deepseek-official),凭据全归宿主 adapter,edge 侧不碰任何 secret。",
21
+ "local": {
22
+ "_comment": "留空 = DELEGATE 模式(转发宿主提供商,与 llm.cloud 对称)。要 edge 自有直连池时填 baseUrl + model + keyRef。baseUrl 必须含 /v1 —— adapter 只追加 /chat/completions(local-llm-adapter.ts)。delegate=false = kill switch,回到 opt-in 语义(不注册 local-llm 路由,意图分类 / guard 检测自动降级到关键词 / 正则规则档)。reasoningEffortNone:ollama 池必开 —— ollama 的 /v1 层静默丢弃 chat_template_kwargs 和顶层 think:false,只有 reasoning_effort:\"none\" 生效(实测 <你的内网主机>:11434)。不加它,thinking 吃满 max_tokens → content 空 → json-parse-failed → 攻击检测静默落回正则档(24.6s → 6.5s)。严格网关(OpenAI 官方风格)不认这个参数会 400,保持 false。",
23
+ "baseUrl": "",
24
+ "model": "",
25
+ "keyRef": "",
26
+ "stripTools": false,
27
+ "enableThinking": false,
28
+ "reasoningEffortNone": false,
29
+ "delegate": true,
30
+ "delegateProvider": "",
31
+ "delegateModel": ""
32
+ },
33
+ "cloud": {
34
+ "_comment": "留空 = DELEGATE 模式(转发宿主提供商)。要 edge 自有直连云池时填 baseUrl + model + keyRef。",
35
+ "baseUrl": "",
36
+ "model": "",
37
+ "keyRef": "",
38
+ "delegate": true,
39
+ "delegateProvider": "",
40
+ "delegateModel": ""
41
+ }
42
+ },
43
+ "stages": {
44
+ "_comment": "★ 隐私不变式(用户仲裁 2026-10-18):分类 / guard 检测只吃本地模型 —— 送进云池等于把「待检测的用户原文」上行,违背端侧私密定位。留空 = 用 plugin Schema 默认 local-llm;本地端点不可达时 intent-router / prompt-guard 自动降级到关键词 / 正则规则档,绝不借用宿主云模型。",
45
+ "guard": {
46
+ "provider": "local-llm",
47
+ "model": "",
48
+ "enabled": true,
49
+ "timeoutMs": 60000,
50
+ "maxInput": 2000
51
+ },
52
+ "intent": {
53
+ "provider": "local-llm",
54
+ "model": "",
55
+ "defaultCategory": "LOCAL",
56
+ "timeoutMs": 60000,
57
+ "confidenceThreshold": 0.6,
58
+ "maxInput": 2000,
59
+ "recentLimit": 3,
60
+ "cloudKeywords": [],
61
+ "localEntities": [],
62
+ "fallbackKeywords": [],
63
+ "deviceCommands": {
64
+ "_comment": "§7.124 家庭设备操作正则层(Tier-0)的扩展词表 —— 追加到内置词表(灯/空调/电视/窗帘…)。加一个新电器名只改这里一行,不用改代码。devices = 设备名词,actions = 动作词。",
65
+ "devices": [],
66
+ "actions": []
67
+ },
68
+ "llmFallback": true,
69
+ "_comment": " §7.115:llmFallback=false = 决策模型 only 模式(jev skip 直接落规则层,LLM 分类器不再运行)。"
70
+ }
71
+ },
72
+ "jev": {
73
+ "_comment": "§7.92.3 jev 意图识别(toggle,§7.182 主人裁决:**默认 OFF** —— 它是可选加速器,需要本地 :8001 的决策模型服务;没有它时 classify() 完全跳过 jev 路径;开 → 在 LLM classifier 之前先跑)。Toggle=false 时 classify() 完全跳过 jev 路径(无 network call)。高置信 (>=confidenceThreshold) + 低风险 (<riskThreshold) → 直接采纳;否则 fall through 到 LLM。 §7.112(2026-10-25):provider 可选 local(默认,ONNX /predict)| bailian-decision(阿里 decision-model /systemone——questions 从 categories 自动生成,model 填 decision-model-preview,apiKeyRef 指向 secrets 里的百炼 MaaS key,authScheme raw=Authorization: <key> / bearer=Bearer 前缀)。§7.114:决策模型是配置文件绑定的外部服务——path 默认 /systemone(MaaS 部署);本地 MLX serve 同一契约时 url 指 http://127.0.0.1:11017/v1、path 改 /classify(它有 /health,探针自动恢复)。 §7.115:localAdoptFloor=方向分闸——LOCAL 倾向判决 ≥ 该值直接采纳(默认本地,不落 LLM),非 LOCAL 仍走 confidenceThreshold。",
74
+ "enabled": false,
75
+ "url": "http://127.0.0.1:8001",
76
+ "timeoutMs": 2500,
77
+ "confidenceThreshold": 0.85,
78
+ "riskThreshold": 0.7,
79
+ "breakerTtlMs": 30000,
80
+ "bootProbeEnabled": true,
81
+ "bootProbeTimeoutMs": 2000,
82
+ "breakerProbeMs": 5000,
83
+ "probeTimeoutMs": 1000,
84
+ "provider": "local",
85
+ "model": "decision-model-preview",
86
+ "_apiKeyRef": "§7.182:默认档(provider=local,ONNX 决策模型)不需要 key —— 故意不写 apiKeyRef。写了就会在启动时报「配置里的密钥引用指向不存在的槽位」(槽位为空也算不存在,edge-config.ts danglingSecretRefs())。要换百炼决策模型才把这一行加回来:\"apiKeyRef\": \"bailianApiKey\",同时在 secrets 里填上那个槽位的真实 key。",
87
+ "authScheme": "raw",
88
+ "path": "/systemone",
89
+ "localAdoptFloor": 0.5
90
+ },
91
+ "functionCallExternal": {
92
+ "_comment": "§7.93.1 ExternalToolCaller(纯 client,外部维护)。契约 GET /health + POST /extract {query,tools,system?,max_new_tokens?}。CLOUD intent 不走 caller;caller 不可达 / 低置信 fall through 到 LLM 自提取。",
93
+ "enabled": false,
94
+ "url": "http://127.0.0.1:8081",
95
+ "timeoutMs": 15000,
96
+ "confidenceThreshold": 0.5,
97
+ "breakerTtlMs": 30000,
98
+ "bootProbeEnabled": true,
99
+ "bootProbeTimeoutMs": 2000,
100
+ "breakerProbeMs": 5000,
101
+ "probeTimeoutMs": 1000
102
+ },
103
+ "privacy": {
104
+ "_comment": "Input-Guard / Privacy-Policy / Realm-Bridge 开关。policyPath/realmConfigPath 留空 = ~/.dsh-edge-agent/ 下同名文件。",
105
+ "inputGuardEnabled": true,
106
+ "rejectPolicyEnabled": true,
107
+ "sanitizeDowngradeEnabled": true,
108
+ "realmHotReload": true,
109
+ "realmWatchMs": 5000,
110
+ "policyPath": "",
111
+ "realmConfigPath": "",
112
+ "policyHotReload": true,
113
+ "policyWatchMs": 5000,
114
+ "sanTtlMs": 900000,
115
+ "sanMaxSessions": 1000,
116
+ "sanSweepMs": 60000
117
+ },
118
+ "resources": {
119
+ "_comment": "Session / Sanitizer / body 限制。",
120
+ "maxSessions": 100,
121
+ "maxBodyBytes": 1048576,
122
+ "sessionIdleMs": 1800000,
123
+ "sanMaxSessions": 1000,
124
+ "sanSweepMs": 60000,
125
+ "sanTtlMs": 900000
126
+ },
127
+ "audit": {
128
+ "_comment": "Audit / Ops Log 落盘。root/logRoot 留空 = ~/.dsh-edge-agent/audit 与 ~/.dsh-edge-agent/log。",
129
+ "minLevel": "info",
130
+ "flushMs": 1000,
131
+ "logLevel": "info",
132
+ "logMaxAgeDays": 1,
133
+ "logMaxRotated": 7,
134
+ "root": "",
135
+ "logRoot": "",
136
+ "llmTrace": {
137
+ "_comment": "§7.118 详细 LLM request/response 追踪(写进 ops log,info 级别默认可见)。enabled=false 全关;includeRequest/includeResponse 可各自单关;maxChars 是单字段字符上限(截断会以 …[+N chars] 显式标注,绝不静默);maxMessages 只保留最近 N 条 message;redactPii 走 pii-utils 10 类规则;redactSecrets 盖住厂商前缀密钥(sk-cp- / sk-ws- / tvly- …,pii-utils 的 api-key 规则盖不住)。",
138
+ "enabled": true,
139
+ "includeRequest": true,
140
+ "includeResponse": true,
141
+ "maxChars": 2000,
142
+ "maxMessages": 12,
143
+ "redactPii": true,
144
+ "redactSecrets": true
145
+ }
146
+ },
147
+ "tee": {
148
+ "_comment": "TEE 后端选择(simulated / trustzone / keystone / sgx);平台自动探测时可留空。storageRoot 留空 = ~/.dsh-edge-agent。",
149
+ "type": "simulated",
150
+ "storageRoot": "",
151
+ "keyStorePath": ""
152
+ },
153
+ "storage": {
154
+ "_comment": "local-storage 明文 JSONL KV 根目录;留空 = ~/.dsh-edge-agent/storage。",
155
+ "root": ""
156
+ },
157
+ "etscli": {
158
+ "_comment": "etscli 只读工具 wrapper(§7.98)。bin 留空 = ~/.local/bin/etscli。",
159
+ "bin": "",
160
+ "timeoutMs": 30000
161
+ },
162
+ "threat": {
163
+ "_comment": "td-bus 阈值。cloudRate = 每分钟云请求数上限(0 = 不限)。",
164
+ "cloudRate": 20,
165
+ "blockLevel": "critical"
166
+ },
167
+ "webSearch": {
168
+ "_comment": "Phase 5 web-search providers(§7.82)三级链:Tavily → 博查 AI → DeepSeek。任一可用即可,任一失败自动 fallback;不写 key 就跳过该 tier。§7.182 主人裁决:Tavily / 博查两档**默认关闭** ⇒ 出厂只有 DeepSeek 档在链上(secrets.deepseekApiKey);要开就把对应开关写 true。",
169
+ "tavilyEnabled": false,
170
+ "bochaEnabled": false
171
+ },
172
+ "agent": {
173
+ "_comment": "agent persona + 工具权限。",
174
+ "persona": "You are edge-agent, a privacy-first AI assistant running on the user's device.",
175
+ "localToolsWritable": true,
176
+ "localToolsAllowlist": "/tmp",
177
+ "permissionMode": "workspace-write"
178
+ }
179
+ }