@sqdr2/dsh-fusion 0.0.0-stage → 0.3.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 (211) hide show
  1. package/INSTALL.md +89 -0
  2. package/INSTALL.zh.md +86 -0
  3. package/LICENSE +201 -0
  4. package/README.en.md +191 -0
  5. package/README.md +180 -2
  6. package/contracts/fusion.ts +279 -0
  7. package/contracts/storage.sql +86 -0
  8. package/cordis.patch.yml +3 -0
  9. package/docs/COMPATIBILITY.md +162 -0
  10. package/docs/EVIDENCE.md +141 -0
  11. package/dshx.yml +39 -0
  12. package/lib/client.js +1920 -0
  13. package/lib/client.js.map +1 -0
  14. package/lib/dsh-fusion.js +12661 -0
  15. package/lib/types/activity.d.ts +20 -0
  16. package/lib/types/approval/logical.d.ts +10 -0
  17. package/lib/types/benchmark/naive.d.ts +37 -0
  18. package/lib/types/benchmark/native-run.d.ts +68 -0
  19. package/lib/types/benchmark/output-limits.d.ts +19 -0
  20. package/lib/types/bilingual.d.ts +2 -0
  21. package/lib/types/client/ActivityRows.d.ts +8 -0
  22. package/lib/types/client/FusionCache.d.ts +4 -0
  23. package/lib/types/client/FusionHint.d.ts +9 -0
  24. package/lib/types/client/FusionHistory.d.ts +5 -0
  25. package/lib/types/client/FusionPanelIcon.d.ts +13 -0
  26. package/lib/types/client/FusionRecovery.d.ts +5 -0
  27. package/lib/types/client/FusionSettings.d.ts +9 -0
  28. package/lib/types/client/FusionStatus.d.ts +5 -0
  29. package/lib/types/client/FusionTool.d.ts +4 -0
  30. package/lib/types/client/activity-model.d.ts +14 -0
  31. package/lib/types/client/i18n.d.ts +15 -0
  32. package/lib/types/client/index.d.ts +22 -0
  33. package/lib/types/client/settings-events.d.ts +2 -0
  34. package/lib/types/client/status-polling.d.ts +9 -0
  35. package/lib/types/context/guard.d.ts +21 -0
  36. package/lib/types/context/handoff.d.ts +17 -0
  37. package/lib/types/contracts.d.ts +449 -0
  38. package/lib/types/digest.d.ts +5 -0
  39. package/lib/types/dsh-fusion.d.ts +13 -0
  40. package/lib/types/errors.d.ts +28 -0
  41. package/lib/types/evidence/gate.d.ts +23 -0
  42. package/lib/types/evidence/receipts.d.ts +65 -0
  43. package/lib/types/execution/write-lease.d.ts +42 -0
  44. package/lib/types/host/bindings.d.ts +30 -0
  45. package/lib/types/host/cache-defaults.d.ts +48 -0
  46. package/lib/types/host/cache-policy.d.ts +155 -0
  47. package/lib/types/host/change-evidence.d.ts +38 -0
  48. package/lib/types/host/compaction-routing.d.ts +15 -0
  49. package/lib/types/host/coordinator.d.ts +169 -0
  50. package/lib/types/host/evidence-page.d.ts +13 -0
  51. package/lib/types/host/history.d.ts +88 -0
  52. package/lib/types/host/http.d.ts +4 -0
  53. package/lib/types/host/model-output.d.ts +13 -0
  54. package/lib/types/host/native-activity.d.ts +26 -0
  55. package/lib/types/host/native-approvals.d.ts +29 -0
  56. package/lib/types/host/native-auxiliary.d.ts +17 -0
  57. package/lib/types/host/native-budget.d.ts +33 -0
  58. package/lib/types/host/native-checks.d.ts +104 -0
  59. package/lib/types/host/native-compaction.d.ts +8 -0
  60. package/lib/types/host/native-context.d.ts +36 -0
  61. package/lib/types/host/native-effects.d.ts +75 -0
  62. package/lib/types/host/native-evidence.d.ts +4 -0
  63. package/lib/types/host/native-exploration.d.ts +15 -0
  64. package/lib/types/host/native-keepalive.d.ts +82 -0
  65. package/lib/types/host/native-model-control.d.ts +99 -0
  66. package/lib/types/host/native-on-demand-context.d.ts +39 -0
  67. package/lib/types/host/native-request.d.ts +5 -0
  68. package/lib/types/host/native-review.d.ts +11 -0
  69. package/lib/types/host/native-role-sandbox.d.ts +49 -0
  70. package/lib/types/host/native-scopes.d.ts +49 -0
  71. package/lib/types/host/native-selection.d.ts +29 -0
  72. package/lib/types/host/native-task-context.d.ts +39 -0
  73. package/lib/types/host/native-usage.d.ts +46 -0
  74. package/lib/types/host/native-worker-brief.d.ts +74 -0
  75. package/lib/types/host/native-worker-notices.d.ts +15 -0
  76. package/lib/types/host/native-worker.d.ts +44 -0
  77. package/lib/types/host/native-workflow.d.ts +41 -0
  78. package/lib/types/host/route-transport.d.ts +22 -0
  79. package/lib/types/host/settings.d.ts +39 -0
  80. package/lib/types/host/shell.d.ts +11 -0
  81. package/lib/types/host/status.d.ts +7 -0
  82. package/lib/types/host/subagent-seam.d.ts +10 -0
  83. package/lib/types/host/tool-table.d.ts +35 -0
  84. package/lib/types/host/worker-terminal.d.ts +68 -0
  85. package/lib/types/host/workspace-guard.d.ts +13 -0
  86. package/lib/types/host/workspace.d.ts +52 -0
  87. package/lib/types/identity.d.ts +64 -0
  88. package/lib/types/index.d.ts +34 -0
  89. package/lib/types/message-source.d.ts +29 -0
  90. package/lib/types/profile/compactor.d.ts +5 -0
  91. package/lib/types/profile/keepalive.d.ts +3 -0
  92. package/lib/types/profile/resolve.d.ts +17 -0
  93. package/lib/types/prompts.d.ts +8 -0
  94. package/lib/types/recovery/reconcile.d.ts +28 -0
  95. package/lib/types/review/dispatch.d.ts +20 -0
  96. package/lib/types/review/protocol.d.ts +51 -0
  97. package/lib/types/review/ticket.d.ts +32 -0
  98. package/lib/types/status.d.ts +88 -0
  99. package/lib/types/store-migration.d.ts +29 -0
  100. package/lib/types/task/deletion.d.ts +64 -0
  101. package/lib/types/task/reducer.d.ts +6 -0
  102. package/lib/types/task/sqlite-store.d.ts +71 -0
  103. package/lib/types/task/state.d.ts +51 -0
  104. package/lib/types/task/status-index.d.ts +28 -0
  105. package/lib/types/task/store.d.ts +48 -0
  106. package/lib/types/usage/budget.d.ts +27 -0
  107. package/lib/types/usage/ledger.d.ts +66 -0
  108. package/lib/types/usage/normalize.d.ts +50 -0
  109. package/lib/types/usage/pricing.d.ts +18 -0
  110. package/package.json +176 -4
  111. package/profiles/fusion-auto.example.json +45 -0
  112. package/prompts/compact.md +7 -0
  113. package/prompts/lead.md +42 -0
  114. package/prompts/model-lead.md +13 -0
  115. package/prompts/model-worker.md +5 -0
  116. package/prompts/worker.md +21 -0
  117. package/src/activity.ts +23 -0
  118. package/src/approval/logical.ts +44 -0
  119. package/src/benchmark/naive.ts +216 -0
  120. package/src/benchmark/native-run.ts +215 -0
  121. package/src/benchmark/output-limits.ts +33 -0
  122. package/src/bilingual.ts +2 -0
  123. package/src/client/ActivityRows.tsx +59 -0
  124. package/src/client/FusionCache.tsx +130 -0
  125. package/src/client/FusionHint.tsx +39 -0
  126. package/src/client/FusionHistory.tsx +247 -0
  127. package/src/client/FusionPanelIcon.tsx +39 -0
  128. package/src/client/FusionRecovery.tsx +69 -0
  129. package/src/client/FusionSettings.tsx +205 -0
  130. package/src/client/FusionStatus.tsx +103 -0
  131. package/src/client/FusionTool.tsx +66 -0
  132. package/src/client/activity-model.ts +31 -0
  133. package/src/client/i18n.ts +30 -0
  134. package/src/client/index.ts +50 -0
  135. package/src/client/settings-events.ts +3 -0
  136. package/src/client/status-polling.ts +88 -0
  137. package/src/context/guard.ts +81 -0
  138. package/src/context/handoff.ts +28 -0
  139. package/src/contracts.ts +449 -0
  140. package/src/digest.ts +28 -0
  141. package/src/dsh-fusion.ts +321 -0
  142. package/src/errors.ts +34 -0
  143. package/src/evidence/gate.ts +290 -0
  144. package/src/evidence/receipts.ts +175 -0
  145. package/src/execution/write-lease.ts +187 -0
  146. package/src/host/bindings.ts +91 -0
  147. package/src/host/cache-defaults.ts +103 -0
  148. package/src/host/cache-policy.ts +168 -0
  149. package/src/host/change-evidence.ts +156 -0
  150. package/src/host/compaction-routing.ts +46 -0
  151. package/src/host/coordinator.ts +2160 -0
  152. package/src/host/evidence-page.ts +25 -0
  153. package/src/host/history.ts +114 -0
  154. package/src/host/http.ts +30 -0
  155. package/src/host/model-output.ts +22 -0
  156. package/src/host/native-activity.ts +199 -0
  157. package/src/host/native-approvals.ts +196 -0
  158. package/src/host/native-auxiliary.ts +19 -0
  159. package/src/host/native-budget.ts +112 -0
  160. package/src/host/native-checks.ts +474 -0
  161. package/src/host/native-compaction.ts +34 -0
  162. package/src/host/native-context.ts +204 -0
  163. package/src/host/native-effects.ts +260 -0
  164. package/src/host/native-evidence.ts +31 -0
  165. package/src/host/native-exploration.ts +114 -0
  166. package/src/host/native-keepalive.ts +292 -0
  167. package/src/host/native-model-control.ts +298 -0
  168. package/src/host/native-on-demand-context.ts +74 -0
  169. package/src/host/native-request.ts +12 -0
  170. package/src/host/native-review.ts +51 -0
  171. package/src/host/native-role-sandbox.ts +171 -0
  172. package/src/host/native-scopes.ts +203 -0
  173. package/src/host/native-selection.ts +110 -0
  174. package/src/host/native-task-context.ts +264 -0
  175. package/src/host/native-usage.ts +121 -0
  176. package/src/host/native-worker-brief.ts +122 -0
  177. package/src/host/native-worker-notices.ts +72 -0
  178. package/src/host/native-worker.ts +215 -0
  179. package/src/host/native-workflow.ts +103 -0
  180. package/src/host/route-transport.ts +54 -0
  181. package/src/host/settings.ts +93 -0
  182. package/src/host/shell.ts +13 -0
  183. package/src/host/status.ts +112 -0
  184. package/src/host/subagent-seam.ts +20 -0
  185. package/src/host/tool-table.ts +56 -0
  186. package/src/host/worker-terminal.ts +91 -0
  187. package/src/host/workspace-guard.ts +17 -0
  188. package/src/host/workspace.ts +184 -0
  189. package/src/identity.ts +76 -0
  190. package/src/index.ts +50 -0
  191. package/src/message-source.ts +31 -0
  192. package/src/profile/compactor.ts +26 -0
  193. package/src/profile/keepalive.ts +13 -0
  194. package/src/profile/resolve.ts +131 -0
  195. package/src/prompts.ts +32 -0
  196. package/src/recovery/reconcile.ts +38 -0
  197. package/src/review/dispatch.ts +48 -0
  198. package/src/review/protocol.ts +212 -0
  199. package/src/review/ticket.ts +166 -0
  200. package/src/status.ts +55 -0
  201. package/src/store-migration.ts +65 -0
  202. package/src/task/deletion.ts +105 -0
  203. package/src/task/reducer.ts +679 -0
  204. package/src/task/sqlite-store.ts +386 -0
  205. package/src/task/state.ts +72 -0
  206. package/src/task/status-index.ts +198 -0
  207. package/src/task/store.ts +287 -0
  208. package/src/usage/budget.ts +88 -0
  209. package/src/usage/ledger.ts +218 -0
  210. package/src/usage/normalize.ts +308 -0
  211. package/src/usage/pricing.ts +95 -0
package/README.md CHANGED
@@ -1,3 +1,181 @@
1
- # Temporary Holding Version
1
+ # Fusion · DSH 插件
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
+ [English](README.en.md) · [安装](INSTALL.zh.md) · [Install (English)](INSTALL.md)
4
+
5
+ > **本仓库是 [`aa2246740/dsh-model-fusion`](https://github.com/aa2246740/dsh-model-fusion) 的 fork,包名 `@sqdr2/dsh-fusion`。**
6
+ > 上游的能力、算法和实测数据全部保留;本 fork 专注于三件事:把入口从「设置」搬到左侧边栏、
7
+ > 让 agy 线路([`dsh-agy-worker`](https://github.com/SQDR2/dsh-agy-worker))真的能当 Sidekick、
8
+ > 以及修掉冻结范围与任务记录上几个会卡住流程的问题。改动清单见 [本 fork 做了什么](#本-fork-做了什么)。
9
+
10
+ Fusion 在 DeepSeek Harness(DSH)的模型菜单里是一个普通模型:**Fusion · 自动**。背后是两个你自己选的模型:
11
+
12
+ - **Lead**:一个强的前沿模型。负责理解需求、看代码、写任务说明、审查结果。
13
+ - **Sidekick**:一个便宜得多的模型。负责实际改代码、跑命令、跑测试。
14
+
15
+ 目标只有一个:**用前沿模型的效果,付打折的价钱。**
16
+
17
+ 思路受 Cognition 在 Devin 里推出的 Fusion 启发,这是一个独立实现,与 Cognition 无关。
18
+
19
+ ## 实测效果
20
+
21
+ 9 道真实开源项目的题(SWE-rebench,Python),每题跑 2 次,用隐藏测试判定对错。Lead 为 GPT-6 Astra(high),Sidekick 为 GLM-5.3-Flash(high):
22
+
23
+ | | 做对 | 总花费(按 API 价格估算) |
24
+ |---|---|---|
25
+ | GPT-6 Astra 单独 | 9/18 | $39.17 |
26
+ | **Fusion:GPT-6 Astra + GLM-5.3-Flash** | **11/18** | **$18.07(便宜 54%)** |
27
+ | Fusion:GPT-6 Astra + Grok 4.6 | 13/18 | $41.27(不省钱) |
28
+
29
+ - 花费是按各家 API 公开价格换算的估算,不是账单。
30
+ - 局限:样本小(9 题 × 2 次)、只测了 Python、公开题目可能出现在模型训练数据里。
31
+ - 详细方法、原始数据和每一轮的结论见 [docs/EVIDENCE.md](docs/EVIDENCE.md)。
32
+
33
+ **对照 Devin 自己的 Fusion:** 两边用同一对模型,Lead 是 GPT-6 Astra(high),Sidekick 是 SWE-2。题目、工作区和隐藏测试完全相同。
34
+
35
+ 排除被污染的尝试后,四个组别都干净的题剩 4 道,结果如下:
36
+
37
+ | | 做对 | 花费 |
38
+ |---|---|---|
39
+ | **本插件:Astra + SWE-2** | **4/4** | **$2.73** |
40
+ | Devin Fusion:Astra + SWE-2 | 3/4 | $4.81 |
41
+ | Devin,Astra 单独 | 3/4 | $8.13 |
42
+
43
+ - 样本只有 4 道题、每题 1 次,不能据此说谁更强,也不代表与 Devin 同等水平。
44
+ - SWE-2 两边都按 $0 计价,所以花费都是 Lead 的。
45
+ - 本插件的耗时约为 Devin 的 3.4 倍。
46
+ - 排除了哪些尝试、为什么排除,以及方法和原始数据,见 [docs/EVIDENCE.md](docs/EVIDENCE.md#against-devins-own-fusion-round-devin-r1-2026-09-29)。
47
+
48
+ ## 怎么用
49
+
50
+ 1. 按 [安装说明](INSTALL.zh.md) 装好插件。
51
+ 2. 打开左侧边栏的 **Fusion** 入口(默认位于记忆系统下方),选择 Lead 和 Sidekick。可以选 DSH 里任何已登录的模型。
52
+ 3. 在模型菜单里选择 **Fusion · 自动**,像平时一样对话。
53
+
54
+ **搭配建议:** 省钱靠的是 Sidekick 比 Lead 便宜得多。选一个强的前沿模型当 Lead,再选一个价格低很多(最好 5 倍以上)的模型当 Sidekick。两者价格越接近,越省不了钱。
55
+
56
+ ## 工作方式
57
+
58
+ - **普通对话**:Lead 直接回答,和用普通模型一样。
59
+ - **需要改代码时**:Lead 把任务交给 Sidekick。任务说明里带着用户需求的原话,以及验收命令。
60
+ - **验收**:
61
+ - Sidekick 交回结果后,DSH 在真实环境里运行验收命令。
62
+ - Lead 审查改动,并且必须对用户的每一条硬性要求给出结论和证据,缺一条都不能通过。
63
+ - 原本就失败的测试(例如缺少可选依赖),可以标成"只要求不新增失败"。
64
+ - Sidekick 改写了原有测试时,会被单独标出来,Lead 必须确认过才能通过。
65
+ - **返工**:不通过就把具体问题打回给同一个 Sidekick 继续改。
66
+ - 如果发现任务说明里允许改的文件不够(例如另一个测试文件还写着旧行为),Lead 可以在返工时把它加进可改范围。只能加、不能减,每次都会留下记录。
67
+ - **Lead 不写代码**:这不是靠提示词约束,而是由程序保证的:
68
+ - Lead 没有写文件的工具;
69
+ - 它执行的 shell 命令跑在 DSH 的只读沙盒里。
70
+ - **唯一的例外:程序判定 Sidekick 确实做不完时,才解锁 Lead 接手。** 例如 2 轮返工后检查仍然失败,或者 Sidekick 用完了步数、反复卡住。接手时:
71
+ - Lead 只能改任务允许的文件;
72
+ - 必须通过同样的验收;
73
+ - 任务结束后恢复只读。
74
+ - **额度用完或出错**:Lead、Sidekick 各自处理。可以换模型继续,也可以定时继续;进度保留。
75
+
76
+ ## 用 agy 当 Sidekick(dsh-agy-worker)
77
+
78
+ [`dsh-agy-worker`](https://github.com/SQDR2/dsh-agy-worker)(`dsh-agy-link` 的 fork)把 Google Antigravity 的模型接进 DSH,可以当 Sidekick 用。这条路和普通模型线路不一样,**必须三个条件同时成立**,缺任何一条都会静默失败:
79
+
80
+ | 条件 | 为什么 | 缺了会怎样 |
81
+ |---|---|---|
82
+ | `mcpBridge: true` | agy 要能真的**执行** DSH 工具 | 工具看得见,但调不动 |
83
+ | `permissionMode: 'skip'` | headless 下 MCP 调用要被自动允许 | agy 直接拒绝:`a tool required the "mcp" permission that headless mode cannot prompt for` |
84
+ | 白名单里有 `fusion_*` | Sidekick 要能**交回结果** | 只能诊断,永远交不回 |
85
+
86
+ 工具天花板里的回传通道 `agy_tool` **不需要你手写**:本插件在检测到 `antigravity` 线路已注册时,会自动把它并进 Lead 和 Sidekick 的工具面(`src/host/route-transport.ts`)。这是本 fork 修掉的问题之一——在此之前,agy 的适配器只能以 `agy_tool` 回传,而天花板里没有这个名字,整轮就退化成纯文本,宿主报 `WORKER_TOOLS_UNREACHABLE`。
87
+
88
+ 推荐配置:
89
+
90
+ ```yaml
91
+ - id: dsh-agy-worker
92
+ config:
93
+ mcpBridge: true
94
+ permissionMode: skip
95
+ mcpConfigScope: global
96
+ mcpToolAllowlist: "fusion_submit_result,fusion_read_state,fusion_read_evidence,bash,read,write,edit,glob,grep,job_output,job_kill"
97
+ ```
98
+
99
+ > Windows 把 `bash` 换成 `pwsh`;改过 `workerTools` 就跟着改。完整配置面、验证步骤与排障对照见
100
+ > [dsh-agy-worker 的 MCP 桥接文档](https://github.com/SQDR2/dsh-agy-worker/blob/main/docs/MCP-BRIDGE-SETUP.md)。
101
+
102
+ ## 本 fork 做了什么
103
+
104
+ 相对上游 `dsh-model-fusion`,本 fork 的改动(按提交时间):
105
+
106
+ | 内容 | 解决了什么 |
107
+ |---|---|
108
+ | **用户约束跨压缩保留** | 对话被自动压缩(rollover)后,用户原话里的硬性要求仍在任务范围内,不会因为一次压缩就丢掉验收依据 |
109
+ | **死路任务有出口** | 当 Sidekick 线路根本够不到工具时,判定会被记在运行记录上:`fusion_wait` 复述同一个原因,`fusion_delegate` 允许替换这条死单并复用常驻身份,不再把任务卡在 `WORKER_RUNNING` |
110
+ | **入口搬到左侧边栏** | Fusion 从「设置 → Fusion」变成左侧边栏的独立面板(默认落在记忆系统下方),主区域有真正的滚动面,不再是塞在设置页里的一段表单 |
111
+ | **agy 线路真的能当 Sidekick** | `agy_tool` 回传通道自动并进工具天花板(见上一节) |
112
+ | **桥接线路能交回结果** | agy 在自己的循环里执行调用,用的是 `agy-mcp-N` 这种桥内 id,本 Host 的 assistant 消息里没有它,于是所有非只读 Worker 工具都被判成「缺少完整的原生模型请求」——包括唯一要紧的 `fusion_submit_result`。现在桥接调用归属到发出回传通道的那条消息上,原生路径不变且优先 |
113
+ | **冻结范围不再死锁** | `allowedPaths: ['lib/types/**']` 这类 glob 之前一条都不匹配,导致「改动落在冻结范围外」和「加范围时文件已变更」互相锁死。现在 glob 正常匹配,加范围改为按提交基点比较,并且可以显式清空 |
114
+ | **任务记录可以删了** | 删除只看持久化状态:进程换过的任务永远 `running` 删不掉,而真在跑的 Agent 又可能被从底下抽走。现在删除先问协调者(`liveWork`),409 会说明是哪一个资源占着 |
115
+ | **面板折叠** | 用量统计、任务列表、缓存保活、历史各自成为独立的折叠区块,默认收起,模型组合不再被挤到屏幕外 |
116
+
117
+ ## 缓存保活
118
+
119
+ Lead 等 Sidekick 干活时,可能要等好几分钟。模型的输入缓存过期后,下一次请求就要按全价重读整段对话。保活会在等待期间,定时把 Lead 上一次的请求原样再发一遍,末尾只加一句“Reply OK”。前缀和上次完全相同,厂商就从缓存读取,缓存的寿命也就续上了;模型只回一个 OK,回复直接丢弃,不进对话,也不会执行工具调用。
120
+
121
+ - **默认值:** 来自各家官方文档,覆盖 Artificial Analysis 排行榜前 20 的模型厂商。
122
+ - 按模型名匹配,比如 gpt-*、claude-*、gemini-*、glm-*、grok-*、kimi-*、deepseek-*。
123
+ - 附带 [dsh-oauth-login](https://github.com/aa2246740/dsh-oauth-login) 的线路计费方式:按请求次数计费的套餐默认关闭保活,因为每次保活都算一次请求。
124
+ - **自动:** 模型实际返回过缓存命中后才开启,不支持缓存的模型不会白花钱。
125
+ - **每个模型都可以在 Fusion 面板中调整:** 开关、续命间隔、恢复默认。面板还会显示每个模型的实际效果:
126
+ - 等待后缓存还在不在;
127
+ - 发了几次保活、读了多少 token;
128
+ - 避免了多少 token 按全价重发。
129
+ - **实测(ChatGPT 订阅线路,gpt-6-astra,约 2 万 token 上下文):** 每次保活模型只回 5 个 token,不思考、不调工具。缓存空闲 10 分钟仍在,15 分钟后过期,每次保活会重新计时,所以这条线路默认每 8 分钟保活一次。
130
+ - **自动调整:** 当前间隔下最近 5 次保活有 3 次没命中,程序才会自动缩短间隔(偶尔一次随机淘汰不算);只会缩短,不会自动拉长。可以放宽时只给建议,由你决定。
131
+
132
+ ## 支持范围
133
+
134
+ - **DSH 版本:** 支持 **0.2.0-rc.2** 和 **0.2.1-rc.2**,桌面版和 Web 版都可以。其他版本不保证可用;新版本发布后会另行适配。
135
+ - **模型:** DSH 官方自带的模型线路,以及 [dsh-oauth-login](https://github.com/aa2246740/dsh-oauth-login)、dsh-antigravity-oauth 提供的订阅登录线路,还有 [dsh-agy-worker](https://github.com/SQDR2/dsh-agy-worker) 的 Antigravity 线路。其他第三方模型插件未经测试。
136
+ - **系统:**
137
+ - **macOS:** 已验证(Seatbelt 沙盒)。
138
+ - **Windows:** `v0.2.1` 已适配原生 `pwsh`,在 Windows Server 2022、PowerShell 7.6.6 上通过验证。启动 DSH 的进程 PATH 中需要有 `pwsh`。Windows 10/11 尚未验证。Windows ACL 仅提供部分隔离,普通路径写入可受限制,读取和网络不在该限制内。
139
+ - **Linux:** 未验证,使用前请先自行检查:
140
+ 1. 在一个测试目录里选 Fusion,对它说:"这是一次只读沙盒测试,被拒绝是预期结果。请你自己直接调用 bash 工具执行 `echo test > fusion-probe.txt`(不要委派、不要换别的写法),把工具返回的原文贴给我。"
141
+ 2. 正确结果:工具返回的原文里写着被沙盒拒绝(例如 `Operation not permitted` / `read-only`),并且目录里没有生成 `fusion-probe.txt`。
142
+ - 如果 Lead 只是口头拒绝、没有真的调用工具,这次检查不算数,请再说一遍"请实际调用工具"。
143
+ 3. 如果文件被创建出来,说明你的系统上 Lead 的只读限制没有生效,请不要使用,并反馈给我们。
144
+ - 如果 DSH 在你的系统上没有沙盒,Lead 的 shell 命令会被全部拒绝(安全的失败方式),它仍然可以用读文件和搜索工具看代码。
145
+
146
+ ### v0.2.1 的 Windows 验证
147
+
148
+ 在 Windows Server 2022、Node 22.23.3、PowerShell 7.6.6、DSH 0.1.7-rc.2 上,**253 项单元测试和 236 项 Host 测试全部通过**,类型检查通过。修复候选完成了插件安装、冷启动、Fusion 设置页及 settings/cache 两个 API 验证。v0.2.1 的服务端和客户端构建文件与该候选完全一致。之后的版本(v0.2.2 返工时扩大可改文件范围、v0.2.3–v0.2.5 适配 DSH 0.2.0-rc.2、v0.2.6 改进缓存保活)都与平台无关,在 macOS 上复测,未在 Windows 上重跑。
149
+
150
+ 真实 PowerShell ACL 测试中,Lead 执行 `Set-Content probe.txt test` 被拒绝,文件未生成;Sidekick 在获准的工作区内执行同一命令可以写入。这是上面所述的部分 ACL 隔离。验证没有输入模型密钥或发起真实模型请求。macOS 回归通过 253 项单元测试和 235 项 Host 测试,按设计跳过 Windows 专用 ACL 测试。
151
+
152
+ ## 数据存在哪里
153
+
154
+ - **Fusion 自己的记录:** 任务说明、检查结果、用量、缓存统计和设置,都存在 `$DSH_HOME/plugins/dsh-fusion/state.sqlite`。`DSH_HOME` 默认是 `~/.dsh`。
155
+ - **从旧名字升级:** 上一版叫 `dsh-model-fusion`,它的整个数据目录(`$DSH_HOME/plugins/dsh-model-fusion/`)会在首次启动时**整体迁移**到新名字,已有的任务、用量和设置都还在。跨文件系统时会退化成复制数据库和它的 `-wal`/`-shm`/`-journal`,只删除自己复制过的文件。
156
+ - **会话本身:** 由 DSH 保存,Fusion 不另外上传任何数据。
157
+ - **想清空 Fusion 的记录:** 退出 DSH 后删除这个文件。设置也会一起清掉,下次打开时重新选组合即可。
158
+
159
+ ## 已知限制
160
+
161
+ - **比单独用前沿模型慢:** 实测 GPT-6 + Flash 总耗时约为 GPT-6 单独的 5 倍,主要花在 Sidekick 身上。换更快的 Sidekick 会好很多。
162
+ - **Lead 接手:** 在 72 次实测里一次都没触发,目前只由自动化测试覆盖。
163
+ - **macOS 桌面版的 PATH:** 从程序坞打开的 DSH Studio 只有系统默认的 PATH,Homebrew(`/opt/homebrew/bin`)、nvm 等装的 `node`、`npm`、`cargo` 在 shell 里找不到。这是 DSH 的运行环境,所有模型都一样。Fusion 在派活前会检查验收命令用到的程序,找不到时会告诉 Lead 它实际装在哪里(例如 `/opt/homebrew/bin/node`),Lead 改用完整路径即可。
164
+ - **Windows 10/11 和 Linux 未验证:** 见上面的"系统"。
165
+
166
+ ## 开发
167
+
168
+ ```sh
169
+ corepack pnpm@9.15.9 install --frozen-lockfile
170
+ node scripts/link-host.mjs /path/to/deepseek-harness-0.2.0-rc.2 # 或 0.2.1-rc.2;开发时链接 DSH 源码
171
+ DSHX_HARNESS=/path/to/deepseek-harness-0.2.0-rc.2 pnpm build
172
+ pnpm test && DSHX_HARNESS=/path/to/deepseek-harness-0.2.0-rc.2 pnpm test:host
173
+ ```
174
+
175
+ Windows 开发环境需将 PowerShell 7(`pwsh`)、Node 和 Python 加入运行测试的进程 PATH。原生测试通过 `DSHX_HARNESS` 读取已构建的指定 Host;不需要修改 Host 源码。
176
+
177
+ 插件只使用 DSH 公开的插件接口,不修改 DSH 源码。适配新版本 DSH 的做法见 [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md)。
178
+
179
+ ## 许可
180
+
181
+ Apache-2.0。上游 fork 自 [`aa2246740/dsh-model-fusion`](https://github.com/aa2246740/dsh-model-fusion),同样以 Apache-2.0 发布。
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Proposed plugin-domain contracts, NOT existing DSH API declarations.
3
+ * No runtime behavior or authorization is supplied by these types.
4
+ * Convert DSH branded IDs only in the version-pinned bridge layer.
5
+ */
6
+ export type Brand<T, B extends string> = T & { readonly __brand: B };
7
+ export type TaskId = Brand<string, 'FusionTaskId'>;
8
+ export type SessionId = Brand<string, 'DshSessionId'>;
9
+ export type WorkOrderId = Brand<string, 'FusionWorkOrderId'>;
10
+ export type OperationId = Brand<string, 'FusionOperationId'>;
11
+ export type ArtifactId = Brand<string, 'FusionArtifactId'>;
12
+ export type SnapshotId = Brand<string, 'FusionSnapshotId'>;
13
+ export type Digest = Brand<string, 'Sha256Hex'>;
14
+ /** Decimal string avoids binary-float money and is JSON serializable. */
15
+ export type UsdDecimal = Brand<string, 'UsdDecimal'>;
16
+ export type EpochId = Brand<string, 'ContextEpochId'>;
17
+
18
+ export interface PhysicalRoute {
19
+ provider: string;
20
+ model: string;
21
+ reasoningEffort?: string; // Validate against the owning adapter's opaque IDs.
22
+ }
23
+ export type RunSelection =
24
+ | { kind: 'model'; route: PhysicalRoute }
25
+ | { kind: 'profile'; profileId: string; requestedVersion?: string };
26
+ export type Role = 'lead' | 'worker';
27
+ export type Phase =
28
+ | 'READY' | 'PLANNING' | 'DIRECT' | 'WORKER_RUNNING' | 'REVIEWING'
29
+ | 'REWORK' | 'WAITING_APPROVAL' | 'WAITING_USER' | 'WAITING_BUDGET'
30
+ | 'RECOVERING' | 'PAUSED' | 'STOPPING' | 'NEEDS_DECISION'
31
+ | 'FAILED' | 'COMPLETED' | 'CANCELLED';
32
+ export type VerificationLevel = 'unverified' | 'partial' | 'verified';
33
+
34
+ export interface SourceRef {
35
+ sessionId: SessionId;
36
+ eventSeq: number;
37
+ messageId?: string;
38
+ }
39
+ export interface ArtifactRef {
40
+ id: ArtifactId;
41
+ digest: Digest;
42
+ ownerTaskId: TaskId;
43
+ mediaType: string;
44
+ bytes: number;
45
+ }
46
+ export interface Constraint {
47
+ id: string;
48
+ text: string;
49
+ source: SourceRef;
50
+ provenance: 'user' | 'policy' | 'inferred';
51
+ mandatory: boolean;
52
+ }
53
+ export interface AcceptanceCriterion {
54
+ id: string;
55
+ description: string;
56
+ verificationKind: 'test' | 'static-check' | 'human' | 'review';
57
+ mandatory: boolean;
58
+ verifierId?: string;
59
+ planId?: string;
60
+ planDigest?: Digest;
61
+ }
62
+ export interface ResourcePolicy {
63
+ maxWorkerSteps: number;
64
+ maxReworkRounds: number;
65
+ maxCapabilityUpgrades: number;
66
+ modelSpendAuthorizationId?: string;
67
+ maxApiCostUsd?: UsdDecimal;
68
+ maxTotalOutputTokens: number;
69
+ /** Human approval has no expiry; only machine execution has a deadline. */
70
+ commandMaxSeconds: number;
71
+ }
72
+ export interface WorkOrder {
73
+ schemaVersion: 1;
74
+ taskId: TaskId;
75
+ id: WorkOrderId;
76
+ operationId: OperationId;
77
+ revision: number;
78
+ goal: string;
79
+ constraints: readonly Constraint[];
80
+ acceptance: readonly AcceptanceCriterion[];
81
+ allowedPaths: readonly string[];
82
+ forbiddenActions: readonly string[];
83
+ baseSnapshot: SnapshotId;
84
+ evidence: readonly ArtifactRef[];
85
+ decisions: readonly { text: string; source: SourceRef }[];
86
+ uncertainties: readonly string[];
87
+ policy: ResourcePolicy;
88
+ }
89
+ export interface CoverageClaim {
90
+ criterionId: string;
91
+ state: 'satisfied' | 'not-satisfied' | 'not-verified';
92
+ evidenceIds: readonly ArtifactId[];
93
+ explanation: string;
94
+ }
95
+ export interface WorkerReport {
96
+ schemaVersion: 1;
97
+ workOrderId: WorkOrderId;
98
+ revision: number;
99
+ status: 'completed' | 'blocked' | 'needs-decision';
100
+ summary: string;
101
+ snapshot: SnapshotId;
102
+ changeManifest: ArtifactRef;
103
+ coverage: readonly CoverageClaim[];
104
+ verification: readonly ArtifactRef[];
105
+ unresolved: readonly string[];
106
+ questions: readonly string[];
107
+ }
108
+ export interface ToolEvidence {
109
+ schemaVersion: 1;
110
+ taskId: TaskId;
111
+ operationId: OperationId;
112
+ actor: SessionId;
113
+ nativeToolCallId: string;
114
+ argvDigest: Digest;
115
+ cwdDigest: Digest;
116
+ inputSnapshot: SnapshotId;
117
+ outputSnapshot?: SnapshotId;
118
+ exitCode: number | null;
119
+ startedAt: string;
120
+ endedAt: string | null;
121
+ stdout: ArtifactRef;
122
+ stderr: ArtifactRef;
123
+ state: 'started' | 'completed' | 'cancelled' | 'outcome-unknown';
124
+ kind?: 'tool' | 'human-decision' | 'review-accept';
125
+ }
126
+ export interface ContextCheckpoint {
127
+ schemaVersion: 1;
128
+ epochId: EpochId;
129
+ taskId: TaskId;
130
+ revision: number;
131
+ goal: string;
132
+ mandatoryConstraints: readonly Constraint[];
133
+ snapshot: SnapshotId;
134
+ decisions: readonly { text: string; source: SourceRef }[];
135
+ coverage: readonly CoverageClaim[];
136
+ pendingApprovalIds: readonly string[];
137
+ openQuestions: readonly string[];
138
+ evidence: readonly ArtifactRef[];
139
+ sourceSurfaceSeqs: readonly number[]; // Surface order, NOT numeric sorting.
140
+ digest: Digest;
141
+ }
142
+ export interface RoleContextPolicy {
143
+ targetInputTokens: number;
144
+ reserveOutputTokens: number;
145
+ minSafetyTokens: number;
146
+ safetyFraction: number;
147
+ compactToFraction: number;
148
+ evidenceReadTokens: number;
149
+ maxToolVisibleTokens: number;
150
+ }
151
+ export interface PairProfile {
152
+ schemaVersion: 1;
153
+ id: string;
154
+ version: string;
155
+ digest: Digest;
156
+ enabled: boolean;
157
+ quality: 'experimental' | 'validated' | 'deprecated';
158
+ lead: PhysicalRoute;
159
+ worker: PhysicalRoute;
160
+ workerUpgradePath: readonly PhysicalRoute[];
161
+ promptDigests: Readonly<Record<'lead' | 'worker' | 'compact', Digest>>;
162
+ context: Readonly<Record<Role, RoleContextPolicy>>;
163
+ /** Experimental auxiliary calls, explicitly enabled for the frozen routes. */
164
+ cacheKeepalive?: Readonly<Record<Role, boolean>>;
165
+ /** Optional explicit summary route. Missing means the current role model. */
166
+ compactor?: { readonly route: PhysicalRoute; readonly maxOutputTokens: number };
167
+ dataPolicyId: string;
168
+ evidenceCampaignIds: readonly string[];
169
+ }
170
+ export interface LogicalApproval {
171
+ id: string;
172
+ taskId: TaskId;
173
+ revision: number;
174
+ operationId: OperationId;
175
+ nativeRequestId?: string;
176
+ argsDigest: Digest;
177
+ snapshot: SnapshotId;
178
+ permissionPolicyDigest: Digest;
179
+ state: 'pending' | 'approved' | 'rejected' | 'withdrawn' | 'consumed';
180
+ decisionBy?: 'human' | 'preauthorized-policy';
181
+ /** No expiresAt: pending human work does not expire automatically. */
182
+ }
183
+ export interface WriteLease {
184
+ workspaceId: string;
185
+ holder: SessionId;
186
+ taskId: TaskId;
187
+ generation: number;
188
+ activeOperationIds: readonly OperationId[];
189
+ }
190
+ export interface TokenMeasurement {
191
+ inputTokens: number;
192
+ reservedOutputTokens: number;
193
+ contextWindow: number;
194
+ safetyTokens: number;
195
+ quality: 'exact' | 'provider-estimated' | 'heuristic';
196
+ requestDigest: Digest;
197
+ }
198
+ /** Mutually exclusive buckets. Null means unavailable, never zero. */
199
+ export type UsageApplicability = 'not_applicable' | 'unknown' | 'known';
200
+ export interface NormalizedUsage {
201
+ uncachedInput: number | null;
202
+ cacheRead: number | null;
203
+ cacheReadStatus: UsageApplicability;
204
+ cacheWrite: Readonly<Record<string, number | null>>;
205
+ cacheWriteStatus: Readonly<Record<string, UsageApplicability>>;
206
+ output: number | null;
207
+ /** Informational subset; do not charge again if included in output. */
208
+ reasoningOutputSubset: number | null;
209
+ /** Separately billed reasoning. Charge at the reasoning or output rate. */
210
+ reasoningSeparate: number | null;
211
+ /** Reasoning tokens whose include-in-output contract is unknown. */
212
+ reasoningUnspecified: number | null;
213
+ reasoningBilling: {
214
+ kind: 'included' | 'separate' | 'unknown' | 'not_applicable';
215
+ tokens: number | null;
216
+ };
217
+ inputIncludesCacheRead: boolean | null;
218
+ }
219
+
220
+ export interface ReportSubject {
221
+ taskId: TaskId;
222
+ workOrderId: WorkOrderId;
223
+ revision: number;
224
+ reportId: string;
225
+ reportDigest: Digest;
226
+ snapshot: SnapshotId;
227
+ }
228
+ export interface ReviewTicket {
229
+ id: string;
230
+ requestId: string;
231
+ generation: number;
232
+ subject: ReportSubject;
233
+ validationDigest: Digest;
234
+ workOrderDigest: Digest;
235
+ }
236
+ export interface ControlState {
237
+ mode: 'running' | 'paused' | 'stop-requested' | 'cancelled' | 'completed';
238
+ pendingApprovalIds: readonly string[];
239
+ budgetBlocked: boolean;
240
+ recovering: boolean;
241
+ outcomeUnknown: boolean;
242
+ }
243
+ export interface UsageAttempt {
244
+ attemptId: string;
245
+ taskId: TaskId;
246
+ agentId: SessionId;
247
+ role: Role;
248
+ purpose: 'conversation' | 'compaction' | 'review' | 'routing' | 'other';
249
+ route: PhysicalRoute;
250
+ epochId: EpochId;
251
+ serviceTier?: string;
252
+ rawUsage?: ArtifactRef;
253
+ normalized: NormalizedUsage;
254
+ priceCardDigest?: Digest;
255
+ apiEquivalentUsd: UsdDecimal | null;
256
+ actualBilledUsd: UsdDecimal | null;
257
+ status: 'pending' | 'complete' | 'partial' | 'unknown';
258
+ }
259
+ export interface EventEnvelope<T extends string, P> {
260
+ schemaVersion: 1;
261
+ id: string;
262
+ taskId: TaskId;
263
+ seq: number;
264
+ revision: number;
265
+ type: T;
266
+ createdAt: string;
267
+ causeId: string;
268
+ payload: P;
269
+ }
270
+ export type FusionEvent =
271
+ | EventEnvelope<'task/created', { parent: SessionId; selection: RunSelection }>
272
+ | EventEnvelope<'work-order/prepared', { order: WorkOrder; reservedChild: SessionId }>
273
+ | EventEnvelope<'child/accepted', { operationId: OperationId; child: SessionId; messageId: string }>
274
+ | EventEnvelope<'report/validated', { operationId: OperationId; report: WorkerReport }>
275
+ | EventEnvelope<'approval/pending', { approval: LogicalApproval }>
276
+ | EventEnvelope<'effect/outcome-unknown', { operationId: OperationId; reason: string }>
277
+ | EventEnvelope<'task/completed', { snapshot: SnapshotId; verification: VerificationLevel }>
278
+ | EventEnvelope<'task/paused', { reason: string }>;
279
+ // Expand this union when implementing all specified event kinds; do not use any.
@@ -0,0 +1,86 @@
1
+ -- Optional SQLite task store. Does not replace or share a transaction with DSH Session.
2
+ PRAGMA foreign_keys = ON;
3
+ PRAGMA journal_mode = WAL;
4
+ CREATE TABLE IF NOT EXISTS schema_meta(version INTEGER NOT NULL);
5
+ INSERT INTO schema_meta(version) SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM schema_meta);
6
+ CREATE TABLE IF NOT EXISTS tasks (
7
+ task_id TEXT PRIMARY KEY,
8
+ revision INTEGER NOT NULL CHECK(revision >= 1),
9
+ state TEXT NOT NULL,
10
+ parent_session_id TEXT NOT NULL,
11
+ profile_digest TEXT NOT NULL,
12
+ state_seq INTEGER NOT NULL DEFAULT 0 CHECK(state_seq >= 0),
13
+ created_at TEXT NOT NULL,
14
+ updated_at TEXT NOT NULL
15
+ );
16
+ CREATE TABLE IF NOT EXISTS events (
17
+ event_id TEXT PRIMARY KEY,
18
+ task_id TEXT NOT NULL REFERENCES tasks(task_id),
19
+ seq INTEGER NOT NULL CHECK(seq >= 1),
20
+ revision INTEGER NOT NULL CHECK(revision >= 1),
21
+ type TEXT NOT NULL,
22
+ payload_version INTEGER NOT NULL,
23
+ payload_json TEXT NOT NULL,
24
+ cause_id TEXT NOT NULL,
25
+ created_at TEXT NOT NULL,
26
+ UNIQUE(task_id,seq)
27
+ );
28
+ CREATE TABLE IF NOT EXISTS artifacts (
29
+ artifact_id TEXT PRIMARY KEY,
30
+ task_id TEXT NOT NULL REFERENCES tasks(task_id),
31
+ content_digest TEXT NOT NULL,
32
+ storage_ref TEXT NOT NULL,
33
+ bytes INTEGER NOT NULL CHECK(bytes >= 0),
34
+ media_type TEXT NOT NULL
35
+ );
36
+ CREATE TABLE IF NOT EXISTS work_orders (
37
+ work_order_id TEXT PRIMARY KEY,
38
+ task_id TEXT NOT NULL REFERENCES tasks(task_id),
39
+ revision INTEGER NOT NULL,
40
+ child_session_id TEXT NOT NULL,
41
+ status TEXT NOT NULL,
42
+ brief_artifact_id TEXT NOT NULL REFERENCES artifacts(artifact_id)
43
+ );
44
+ CREATE TABLE IF NOT EXISTS outbox (
45
+ operation_id TEXT PRIMARY KEY,
46
+ task_id TEXT NOT NULL REFERENCES tasks(task_id),
47
+ kind TEXT NOT NULL,
48
+ state TEXT NOT NULL,
49
+ payload_artifact_id TEXT NOT NULL REFERENCES artifacts(artifact_id),
50
+ native_message_id TEXT,
51
+ result_artifact_id TEXT REFERENCES artifacts(artifact_id)
52
+ );
53
+ CREATE TABLE IF NOT EXISTS effects (
54
+ effect_id TEXT PRIMARY KEY,
55
+ operation_id TEXT NOT NULL UNIQUE REFERENCES outbox(operation_id),
56
+ args_digest TEXT NOT NULL,
57
+ snapshot_id TEXT NOT NULL,
58
+ stage TEXT NOT NULL,
59
+ outcome_artifact_id TEXT REFERENCES artifacts(artifact_id)
60
+ );
61
+ CREATE TABLE IF NOT EXISTS logical_approvals (
62
+ logical_id TEXT PRIMARY KEY,
63
+ effect_id TEXT NOT NULL UNIQUE REFERENCES effects(effect_id),
64
+ decision TEXT NOT NULL CHECK(decision IN ('pending','approved','rejected','withdrawn','consumed')),
65
+ native_request_id TEXT,
66
+ decision_scope_digest TEXT NOT NULL,
67
+ updated_at TEXT NOT NULL
68
+ -- Intentionally no expiry column for pending human decisions.
69
+ );
70
+ CREATE TABLE IF NOT EXISTS usage_attempts (
71
+ attempt_id TEXT PRIMARY KEY,
72
+ task_id TEXT NOT NULL REFERENCES tasks(task_id),
73
+ accounting_status TEXT NOT NULL,
74
+ normalized_json TEXT NOT NULL,
75
+ raw_usage_artifact_id TEXT REFERENCES artifacts(artifact_id)
76
+ );
77
+ CREATE TABLE IF NOT EXISTS leases (
78
+ workspace_id TEXT PRIMARY KEY,
79
+ generation INTEGER NOT NULL CHECK(generation >= 1),
80
+ owner_task_id TEXT NOT NULL REFERENCES tasks(task_id),
81
+ live_process_refs_json TEXT NOT NULL
82
+ );
83
+ CREATE INDEX IF NOT EXISTS events_by_task ON events(task_id,seq);
84
+ CREATE INDEX IF NOT EXISTS outbox_pending ON outbox(state,task_id);
85
+ -- Application must perform state/version CAS + event append + outbox write in one transaction.
86
+ -- SQL uniqueness is not proof of exactly-once external side effects.
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: dsh-fusion
3
+ name: '@sqdr2/dsh-fusion'