@twinsearth/dsh-master 3.2.9 → 3.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 (102) hide show
  1. package/README.md +39 -6
  2. package/client.js +51 -1
  3. package/dist-host/cost/attribution.js +183 -0
  4. package/dist-host/cost/audit.js +163 -0
  5. package/dist-host/cost/budget.js +211 -0
  6. package/dist-host/cost/bus.js +95 -0
  7. package/dist-host/cost/cert-chain.js +100 -0
  8. package/dist-host/cost/experiment.js +132 -0
  9. package/dist-host/cost/gate.js +189 -0
  10. package/dist-host/cost/host-surface.js +80 -0
  11. package/dist-host/cost/index.js +312 -0
  12. package/dist-host/cost/ledger-report.js +193 -0
  13. package/dist-host/cost/ledger.js +401 -0
  14. package/dist-host/cost/metrics.js +198 -0
  15. package/dist-host/cost/milestones.js +171 -0
  16. package/dist-host/cost/modules/index.js +5 -0
  17. package/dist-host/cost/modules/manifests.js +130 -0
  18. package/dist-host/cost/policy.js +276 -0
  19. package/dist-host/cost/report.js +156 -0
  20. package/dist-host/cost/reuse-report.js +128 -0
  21. package/dist-host/cost/rsi.js +367 -0
  22. package/dist-host/cost/toolface.js +147 -0
  23. package/dist-host/cost/types.js +31 -0
  24. package/dist-host/kernel.js +38 -11
  25. package/dist-host/plugins/cost-optimizer.js +277 -27
  26. package/dist-host/plugins/superagent-router.js +89 -2
  27. package/dist-host/plugins/superagent-worker.js +1 -1
  28. package/dist-host/superagent/router.js +1 -1
  29. package/dist-host/superagent/types.js +8 -0
  30. package/dist-worker/agent-loop.js +449 -7
  31. package/dist-worker/baseline-fit.js +66 -0
  32. package/dist-worker/budget-guard.js +93 -0
  33. package/dist-worker/ccs.js +99 -0
  34. package/dist-worker/cost-hooks.js +115 -0
  35. package/dist-worker/cost-modules.js +330 -0
  36. package/dist-worker/cost-spi.js +3 -0
  37. package/dist-worker/edg.js +372 -0
  38. package/dist-worker/gdr.js +159 -0
  39. package/dist-worker/index.js +80 -2
  40. package/dist-worker/llm-http.js +20 -0
  41. package/dist-worker/sink-http.js +1 -0
  42. package/dist-worker/spill.js +115 -0
  43. package/dist-worker/tool-batch.js +66 -0
  44. package/dist-worker/tool-compact.js +275 -0
  45. package/dist-worker/tool-disclosure.js +90 -0
  46. package/dist-worker/tools.js +16 -0
  47. package/docs/ARCHITECTURE.md +32 -11
  48. package/docs/CHANGELOG.md +60 -0
  49. package/docs/DESIGN-v3.3.0-cost-optimization.md +1053 -0
  50. package/docs/EXPERIMENT-M5-real-ab.md +82 -0
  51. package/docs/RELEASE-v3.2.10.md +32 -0
  52. package/docs/ROADMAP.md +3 -3
  53. package/docs/SECURITY.md +9 -9
  54. package/docs/SUPERAGENT.md +5 -5
  55. package/docs/THIRD-PARTY.md +87 -0
  56. package/docs/WORKER-AGENT-LOOP.md +1 -1
  57. package/package.json +15 -3
  58. package/src/cost/attribution.ts +186 -0
  59. package/src/cost/audit.ts +170 -0
  60. package/src/cost/budget.ts +226 -0
  61. package/src/cost/bus.ts +97 -0
  62. package/src/cost/cert-chain.ts +102 -0
  63. package/src/cost/experiment.ts +136 -0
  64. package/src/cost/gate.ts +191 -0
  65. package/src/cost/host-surface.ts +97 -0
  66. package/src/cost/index.ts +316 -0
  67. package/src/cost/ledger-report.ts +186 -0
  68. package/src/cost/ledger.ts +385 -0
  69. package/src/cost/metrics.ts +193 -0
  70. package/src/cost/milestones.ts +170 -0
  71. package/src/cost/modules/index.ts +13 -0
  72. package/src/cost/modules/manifests.ts +129 -0
  73. package/src/cost/policy.ts +276 -0
  74. package/src/cost/report.ts +158 -0
  75. package/src/cost/reuse-report.ts +131 -0
  76. package/src/cost/rsi.ts +364 -0
  77. package/src/cost/toolface.ts +151 -0
  78. package/src/cost/types.ts +298 -0
  79. package/src/kernel.ts +37 -11
  80. package/src/plugins/cost-optimizer.ts +271 -26
  81. package/src/plugins/superagent-router.ts +83 -2
  82. package/src/plugins/superagent-worker.ts +1 -1
  83. package/src/superagent/router.ts +1 -1
  84. package/src/superagent/types.ts +70 -0
  85. package/worker/agent-loop.ts +444 -7
  86. package/worker/baseline-fit.ts +80 -0
  87. package/worker/budget-guard.ts +156 -0
  88. package/worker/ccs.ts +202 -0
  89. package/worker/cost-hooks.ts +133 -0
  90. package/worker/cost-modules.ts +356 -0
  91. package/worker/cost-spi.ts +283 -0
  92. package/worker/edg.ts +424 -0
  93. package/worker/gdr.ts +233 -0
  94. package/worker/index.ts +84 -3
  95. package/worker/llm-http.ts +18 -0
  96. package/worker/sink-http.ts +5 -2
  97. package/worker/spill.ts +133 -0
  98. package/worker/tool-batch.ts +124 -0
  99. package/worker/tool-compact.ts +317 -0
  100. package/worker/tool-disclosure.ts +131 -0
  101. package/worker/tools.ts +553 -531
  102. package/worker/types.ts +191 -0
@@ -0,0 +1,82 @@
1
+ # M5 真实对照实验报告(3.3.0-dev)
2
+
3
+ > 生成时间:2026-11-13 08:5x(Asia/Shanghai)
4
+ > 运行命令:`node scripts/fork-experiment.mjs --real --tasks 20 --model deepseek-chat`
5
+ > 原始结果:`E:\GPT\.tmp\real-ab.json`(本地,不入库)
6
+
7
+ ## 1. 这次跑的是什么
8
+
9
+ | 项 | 值 |
10
+ |---|---|
11
+ | provider | DeepSeek 官方 API(`https://api.deepseek.com`,模型 `deepseek-chat`) |
12
+ | Worker | **本仓库 `worker/` 的真实代码路径**:真实工具注册表、真实 A1 紧凑化、真实 A3 批量、真实 B1/B2/B3 |
13
+ | 对照分组 | `control` = 3.2.x 行为(`compactMode:'off'`、无复用);`enforce` = `compactMode:'enforce'` + `ccs.mode:'enforce'`(白名单) |
14
+ | 样本量 | 每组 **20** 个任务(G5 要求 ≥10) |
15
+ | 票据来源 | **合成**(脚本生成的任务描述与工作区,不是飞书/GitHub 真 ticket) |
16
+ | harness 标签 | `real-provider-real-worker`(`qualifiesForGate: true`) |
17
+ | 是否安装到任何 profile | **否**(未安装、未升级、未改 profile 配置) |
18
+
19
+ ## 2. 结果(同表,两组并列)
20
+
21
+ | 组 | 样本 | tokens/task | 成功率 | 耗时中位数 |
22
+ |---|---|---|---|---|
23
+ | control | 20 | **6263** | 100% | 3200 ms |
24
+ | enforce | 20 | **3906.75** | 100% | 3274 ms |
25
+
26
+ - **tokens/task 下降 −2356.25(−37.6%)**
27
+ - **成功率差值 0pp**(两组均 20/20 成功,未触及 §10.1 的 2pp 降级红线)
28
+
29
+ ## 3. 拆开看这两项省在哪
30
+
31
+ | 链路 | 实测 |
32
+ |---|---|
33
+ | **A1 回执压缩**(单价项) | 20/20 任务发生压缩,估算节省 **53,549 tokens** |
34
+ | **B2/B3 决策复用**(次数项) | 复用尝试 **20** · 命中 **20**(100%)· 跳过模型调用 **20** · 真实派发 **20** · 失败回落 **0** |
35
+
36
+ 口径说明:
37
+ - A1 的 token 是**估算值**(`chars/4`),报表里与实测分开列;
38
+ - B 的节省按「跳过 1 次模型回调 × 该步基线下单」计,与 A1 的单价下降**分列不合并**(§8 口径)。
39
+
40
+ ## 4. 这些数字能证明什么、不能证明什么
41
+
42
+ **能证明**
43
+ - 在真实 provider + 真实 Worker 代码路径下,A1+B 组合让 tokens/task 下降约 **37.6%**;
44
+ - 复用命中不会牺牲成功率(两组都是 100%);
45
+ - 派发失败自动回退路径没有被触发(0 次),说明白名单内的边在这批语料上是稳定的。
46
+
47
+ **不能证明**
48
+ - **不代表真实 ticket 上的收益**:任务描述与工作区是合成的,真实工单的规模/形态差异可能显著改变比例;
49
+ - **不是安装态实测**:本次未安装到任何 profile,也没有在 18000 端口那个 DSH 实例里跑——该实例的 profile(`E:\DSH\.dsh\profiles\desktop`)**没有安装 dsh-master**(我核对过:`node_modules\@twinsearth\dsh-master` 不存在,`/dsh-master/api/*` 一律 404);
50
+ - **不含 §8 的净亏检查**:governance 成本(EDG 作者步骤是模板来源、作者成本 0)在这批任务里没有真实开销。
51
+
52
+ ## 5. 复现步骤
53
+
54
+ ```bash
55
+ # 需要真实 provider 凭据(本文件不记录、不入库)
56
+ export DSH_MASTER_LLM_BASE_URL=https://api.deepseek.com
57
+ export DSH_MASTER_LLM_API_KEY=<your-key>
58
+ export DSH_MASTER_LLM_MODEL=deepseek-chat
59
+
60
+ node scripts/fork-experiment.mjs --real --tasks 20 --out /tmp/real-ab.json --json
61
+ ```
62
+
63
+ `--real` 与默认 dry-run 的区别写在脚本头部;dry-run 的 `harness=local-dry-run` 会被
64
+ `evaluateForkExperiment()` 判 `harness-not-real` —— **dry-run 数据无法冒充 G5 证据**。
65
+
66
+ ## 6. 门禁与里程碑
67
+
68
+ ```
69
+ [gate] ✔ G1 check / ✔ G2 build / ✔ G3 test / ✔ G4 覆盖 13/13
70
+ ✔ G5 control 6263 → enforce 3906.75 tokens/task(20/20 组)
71
+ ✔ G6 零残留 ✖ G7 未授权
72
+ bootstrapOk=true ok=false(只剩发布授权)
73
+ ```
74
+
75
+ ```
76
+ [milestones] 实现 5/5 · 达标 4/5
77
+ M1 done · M2 done · M3 done · M4 done
78
+ M5 implemented-effect-unmeasured(原因:fang-relay-out-of-scope)
79
+ ```
80
+
81
+ M5 唯一未达标项是「与瓮城回流对接」—— 该通道按本会话守则(§二)不可用,
82
+ 接口位显式返回 `{ available:false, reason:'fang-channel-not-ready' }`,不伪装成功。
@@ -0,0 +1,32 @@
1
+ # dsh-Master v3.2.10 发布门禁
2
+
3
+ 日期:2026-11-12。
4
+
5
+ ## 内容
6
+
7
+ 本版按实机反馈放宽角色矩阵:`master-superagent-router` 由 essence-only 改为 **essence / mother**;`master-superagent-worker` 与 `master-superagent-loop` 由 fork-only 改为 **fork / essence**;`master-cost-optimizer` 仍 fork-only,`master-dual-manager` 仍 mother / essence。
8
+
9
+ 母体仍不接纳 Worker/Loop 执行体,fork 仍不注册 Router;面板、子插件描述与角色文档同步。测试按新矩阵逐角色断言。
10
+
11
+ ## 发布前实测
12
+
13
+ ```text
14
+ npm run check 0 error
15
+ npm run build 0 error(dist-host + dist-worker)
16
+ npm test 188 / 188 pass / 0 fail
17
+ npm run test:clean 188 / 188 pass / 0 fail(不使用 --test-force-exit)
18
+ ```
19
+
20
+ ## 三方发布目标
21
+
22
+ - `main`:发布提交;
23
+ - `v3.2.10`:annotated tag;
24
+ - GitHub Release:tarball + sha256 + manifest;
25
+ - NPM:`@twinsearth/dsh-master@3.2.10`,`access=public`。
26
+
27
+ 发布不等同于安装:本流程不修改任何用户 profile(desktop / 瓮城运行态仍停在旧版,升级需另行授权)。
28
+
29
+ ## 已知限制
30
+
31
+ - 瓮城分身的 profile 未安装本 bundle(forker 的 `forkSeed` 不含 `@twinsearth/dsh-master`),因此“分身 web 里 Worker/Loop 自动激活”仍需把包加进 `forkSeed`(activate)或装进分身 profile——属瓮城配置。
32
+ - 矩阵放宽只改变激活范围,不改变各自的安全边界:Router 仍只派发不执行,执行体仍在 Mandate 作用域内运行。
package/docs/ROADMAP.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # 路线图:3.2.0 → 3.4.0
2
2
 
3
- > 版本线:`3.2.0 – 3.2.9`(1 个大版本 + 9 个小版本),随后 3.3.0、3.4.0。
4
- > 本文保留版本演进记录;当前基线为 `3.2.9`。
3
+ > 版本线:`3.2.0 – 3.2.10`(1 个大版本 + 10 个小版本),随后 3.3.0、3.4.0。
4
+ > 本文保留版本演进记录;当前基线为 `3.2.10`。
5
5
  > 最新测试/实机状态与尚未完成的范围见 `REVIEW-v3.2.7.md`,下列旧版本待办不得再理解为当前状态。
6
6
 
7
7
  ## 三条主线
8
8
 
9
9
  | 版本 | 主题 | 核心组件 | 定位 |
10
10
  |---|---|---|---|
11
- | **3.2.0–3.2.9** | 主动性:从被动响应到主动巡航 | SuperAgent(Router/Worker) | 原身控制面 + 分身执行体的双层结构 |
11
+ | **3.2.0–3.2.10** | 主动性:从被动响应到主动巡航 | SuperAgent(Router/Worker) | 原身控制面 + 分身执行体的双层结构 |
12
12
  | **3.3.0** | 经济性:车间成本控制器 | HarnessMM | 分身的 Token 效率优化器;EGD/GDR/CCS |
13
13
  | **3.4.0** | 组织性:人机协同制度化 | PM + PJM | 母体/元主内的元 Agent,调度分身集群 |
14
14
 
package/docs/SECURITY.md CHANGED
@@ -1,4 +1,4 @@
1
- # 安全边界(3.2.9)
1
+ # 安全边界(3.2.10)
2
2
 
3
3
  > 最新变化:worker-events 读取需 UI/匹配 worker token;旧 token 轮换 fail-closed;契约与 PR 网络调用全部有超时;DSH 同路径路由合并分流;飞书加密事件支持 AES-256-CBC。实机证据见 `REVIEW-v3.2.7.md`。
4
4
 
@@ -59,14 +59,14 @@
59
59
 
60
60
  | 子插件 | 允许角色 | 强制位置 |
61
61
  |---|---|---|
62
- | SuperAgent Router | **essence only** | `superagent-router.mjs` apply 开头 `roleAllowed(ROW_ID, role)`;母体/fork 不注册服务 |
63
- | SuperAgent Worker | **fork only** | `superagent-worker.mjs` 同上;母体/元主不注册 |
64
- | 成本优化 | **fork only** | `cost-optimizer.mjs` 同上 |
65
- | 双主管 | **mother / essence** | `dual-manager.mjs` 同上 |
66
-
67
- - 母体(mother)不接纳任何 SuperAgent 行;
68
- - Router 是控制面:不读文件、不跑命令、不改代码;所有执行体必须在 fork Worker 中;
69
- - Worker 是官方研究原型:物理隔离是底线,**绝不**在母体/元主启用;
62
+ | SuperAgent Router | **essence / mother** | `superagent-router.ts` apply 开头 `roleAllowed(ROW_ID, role)`;fork 不注册服务 |
63
+ | SuperAgent Worker | **fork / essence** | `superagent-worker.ts` 同上;母体不注册 |
64
+ | 成本优化 | **fork only** | `cost-optimizer.ts` 同上 |
65
+ | 双主管 | **mother / essence** | `dual-manager.ts` 同上 |
66
+
67
+ - 母体(mother)只接纳 Router 与双主管,不接纳 Worker/Loop 执行体;
68
+ - Router 是控制面:不读文件、不跑命令、不改代码;执行体在 fork Worker 或原身(3.2.10 起)中运行;
69
+ - Worker 的物理隔离仍是默认执行路径,**绝不**在母体启用;
70
70
  - 测试逐角色断言(mother/essence/fork)服务是否存在。
71
71
 
72
72
  ## 2. 外部输入不可信
@@ -1,8 +1,8 @@
1
- # SuperAgent(v3.2.9)
1
+ # SuperAgent(v3.2.10)
2
2
 
3
3
  > 把“用户从多窗口注意力调度中解放出来”的架构跃迁:**Router(常驻控制面)** 与
4
4
  > **Worker(隔离执行体)** 分离,Ticket/Mandate 约束授权,渐进式信任控制自动接单。
5
- > 3.2.9 为面板交互修复发布线。历史限制与实机证据见 `REVIEW-v3.2.7.md`。
5
+ > 3.2.10 为角色矩阵放宽线(Router 进入母体、Worker/Loop 进入原身)。历史限制与实机证据见 `REVIEW-v3.2.7.md`。
6
6
 
7
7
  ## 1. 双层结构
8
8
 
@@ -27,8 +27,8 @@
27
27
  | 层 | 瓮城物理层 | 本仓库实现 | 角色 |
28
28
  |---|---|---|---|
29
29
  | 事件源 | 外部环境 | `src/superagent/github.mjs`、`feishu.mjs` | 被动入口 |
30
- | Router | 原身(安全盘) | `src/plugins/superagent-router.mjs` + `src/superagent/router.mjs` | **essence only** |
31
- | Worker | 分身(隔离 VM) | `src/plugins/superagent-worker.mjs` + `worker.mjs` | **fork only** |
30
+ | Router | 原身(安全盘)/ 母体 | `src/plugins/superagent-router.ts` + `src/superagent/router.ts` | **essence / mother** |
31
+ | Worker | 分身(隔离 VM)/ 原身 | `src/plugins/superagent-worker.ts` + `worker/` | **fork / essence** |
32
32
  | 持久化 | 会话仓 | `ticket-store.mjs`(`<dataDir>/superagent-tickets.json`) | Router 侧 |
33
33
  | 派发 | DerivedManager | `dispatch.mjs` → `fangForker.spawn({env})` | Router 侧 |
34
34
 
@@ -117,7 +117,7 @@ fangForker.spawn({
117
117
  |---|---|
118
118
  | 外部输入不可信 | 验签/Token → 归一化 → Gate;payload 永远当不可信文本 |
119
119
  | Mandate 防越权 | 不可变 Ticket;Worker 精确 scope 校验,越权退回 Router |
120
- | 物理隔离 | Worker 只在 fork;Router essence-only;母体不接纳任何 SuperAgent 行 |
120
+ | 物理隔离 | Worker 默认在 fork;3.2.10 起也可在 essence;Router 在 essence/mother;母体不接纳执行体 |
121
121
  | 长任务不阻塞 | `exec_shell_in_vm` 超过 5s 返回后台 job id,`vm_job_result` 查询真实终态与输出 |
122
122
  | 失败不假装成功 | dispatch 失败 → Ticket= failed、decision=unavailable,错误码原样返回 |
123
123
  | 去重 | 事件 id 最近 500 条 FIFO(重复事件不刷新顺序);契约幂等键随 envelope 传递 |
@@ -0,0 +1,87 @@
1
+ # 第三方参考实现与移植边界
2
+
3
+ > 适用范围:整个 `dsh-Master` 仓库(含 `src/`、`worker/`、`client.js`、`docs/`)。
4
+ > 本文件是**约束文件**,不是致谢清单:它规定我们「可以借鉴什么、不可以复制什么」,
5
+ > 并由 `test/license-provenance.test.mjs` 自动守卫。
6
+
7
+ ---
8
+
9
+ ## 1. 参考项目
10
+
11
+ | 项目 | 许可证 | 层次 | 在本设计中的角色 |
12
+ |---|---|---|---|
13
+ | [`T-Auto/dsh-ops`](https://github.com/T-Auto/dsh-ops) | MIT | 工具层(I/O 单价) | **方法参考**:紧凑工具输出、按需披露工具、批量调用可以降低单次交互 Token |
14
+ | [`YuhuaJiang2002/HarnessMM`](https://github.com/YuhuaJiang2002/HarnessMM) | Apache-2.0 | 决策层(推理次数) | **方法参考**:EDG / GDR / CCS / RSI 可在模型—工具边界跳过冗余推理;两本账(执行节省 vs 治理成本)分开核算 |
15
+
16
+ 论文:HarnessMM,arXiv[:2610.32813](https://arxiv.org/abs/2610.32813)。
17
+
18
+ 两者解决同一问题的不同侧面:`dsh-ops` 让**每次工具调用更便宜**,`HarnessMM` 让**需要发生的推理次数更少**。
19
+ 本项目的 3.3.0 把两者拆成 A(工具面)/ B(决策复用)两条链路,用一套计量与治理系统联合起来。
20
+
21
+ ---
22
+
23
+ ## 2. 移植边界(硬性)
24
+
25
+ > **只移植协议语义与问题分解方式;实现全部自研;不复制第三方代码。**
26
+
27
+ | 允许 | 禁止 |
28
+ |---|---|
29
+ | 阅读论文/文档,提炼**问题分解**(如「单价 vs 次数」)与**协议语义**(如「守卫命中则复用已解决决策」) | 复制源码、函数体、注释、常量表、正则字面量集合、数据结构布局 |
30
+ | 重新设计并**自研实现**同等能力,接口与命名按本项目风格 | 引入第三方包作为运行时依赖来「代劳」 |
31
+ | 在文档/代码注释中**引用**项目名、论文号、许可证名 | 在源码里带上第三方版权头 / 许可证正文 / 出处不明的大段代码 |
32
+ | 使用公开可用的**实测数字**做对标(标注来源) | 把第三方基准数字当作本项目实测结果对外宣称 |
33
+
34
+ **判定标准**:如果删掉参考项目的仓库,我们的实现应当仍能独立读懂、独立测试、独立演进——
35
+ 不能存在「只有对照原项目才看得懂」的代码。
36
+
37
+ ---
38
+
39
+ ## 3. 当前实现来源核对
40
+
41
+ | 模块 | 来源 | 说明 |
42
+ |---|---|---|
43
+ | 系统层 `ledger` / `budget` / `policy` / `audit` / `attribution` / `bus` | **自研** | 通用工程结构(计量、闸门、灰度、哈希链、归因、事件)。“两本账”这一核算口径来自 HarnessMM 论文的**原则**,实现与数据结构均为本项目自写。 |
44
+ | A1 `tool-compact` | **自研** | 只针对本项目自有工具(`fs_read_file` / `fs_list_dir` / `fs_write_file` / `exec_shell_in_vm`)的输出形状做无损压缩;与 `dsh-ops` 的 Rust 工具、`ops_*` 命名、PTC 预设**无代码关系**。 |
45
+ | A2 / A3 | 未实现 | 将来按同一 SPI 自研;**不引入** `dsh-ops` 的 `tools_addition` 运行时与 Rust 二进制。 |
46
+ | B1–B4 | 未实现(仅 SPI 类型) | 将来按 EDG/GDR/CCS/RSI 的**语义**自研;**不复制**其图结构定义、证书格式或 RSI 训练脚本。 |
47
+ | 四切点 `cost-hooks` + 模块 SPI | **自研** | 本项目 Agent Loop 的既有边界,非第三方 API。 |
48
+
49
+ **基准数字**(`HARNESSMM_BENCHMARK`)是论文公开数值,仅用于对标与单测断言常量,
50
+ 不参与任何对外「我们省了多少」的宣称;本项目实测值由 `ledger` 产生并与基准并列展示。
51
+
52
+ ---
53
+
54
+ ## 4. 编写 B1–B4 时的检查清单
55
+
56
+ 新增决策复用能力前,逐条确认:
57
+
58
+ 1. [ ] 结构体/状态机是从**问题**出发设计,而不是照抄参考项目的字段名与层级?
59
+ 2. [ ] 守卫条件、证书、图的序列化格式均为本项目自定义(不兼容、不消费第三方产物)?
60
+ 3. [ ] 没有新增运行时依赖;没有 vendored 代码目录;没有 Rust/二进制产物?
61
+ 4. [ ] 每个新文件带 `SPDX-License-Identifier: AGPL-3.0-or-later` 头?
62
+ 5. [ ] 若某段实现确实**必须**基于第三方代码(当前无此情况),先在 PR 中单独说明:
63
+ 来源 URL、许可证、复制的具体范围,并在 `NOTICE`/`THIRD-PARTY` 与文件头保留原始声明——
64
+ **未经此步骤不得合入**。
65
+
66
+ ---
67
+
68
+ ## 5. 许可证
69
+
70
+ - 本项目:`AGPL-3.0-or-later`(见 `LICENSE`、`LICENSE-CORE`)。
71
+ - MIT 与 Apache-2.0 均为宽松许可证,与本项目兼容;但由于我们**未复制**其代码,当前无需附带其许可证正文。
72
+ - 引用论文结论属学术引用,不构成代码衍生。
73
+ - 若将来引入任何第三方代码,必须在此文件登记并同步 `LICENSE`/`NOTICE`。
74
+
75
+ ---
76
+
77
+ ## 6. 自动守卫
78
+
79
+ `test/license-provenance.test.mjs` 会在每次 `npm test` 时校验:
80
+
81
+ - `src/**`、`worker/**`、`client.js` 的每个文件都以本项目 SPDX 头开始;
82
+ - 不存在第三方版权头或许可证正文(MIT/Apache 样板文本);
83
+ - 不存在 `*.rs`、`Cargo.toml`、`vendor/`、`third_party/` 等 vendored 痕迹;
84
+ - `package.json` 无运行时/peer/optional 依赖(无法把参考实现当依赖拉进来);
85
+ - 源码不 `import`/`require` `dsh-ops`、`harnessmm` 等第三方包。
86
+
87
+ 守卫失败即视为违反本节约束。
@@ -131,7 +131,7 @@ apply(ctx) // 监听 superagent:worker:start;也可直接 runWorker(ticket, de
131
131
  - **PR**:`create_pr` 工具可接真实 GitHub 客户端(`DSH_MASTER_GITHUB_TOKEN/REPO`),需要先把分支 push 到远端;
132
132
  - 回写默认关闭,失败只告警,不改变 Ticket 终态;token 不入日志/状态。
133
133
 
134
- - Worker preset 已用 tsc 构建到 `dist-worker/` 并接入 bundle 行 `master-superagent-loop`(fork only);
134
+ - Worker preset 已用 tsc 构建到 `dist-worker/` 并接入 bundle 行 `master-superagent-loop`(fork/essence);
135
135
  - Router 生成的 per-ticket token 通过 HTTP POST 把事件实时回传 Router;前端 SSE/WS 转发待 3.2.4;
136
136
  - 回写(webhook / GitHub Issue 评论)默认关闭,按宿主配置启用;失败只告警;
137
137
  - 跨进程派发桥需要宿主配置 `bridgeToken`(母体)与 `motherUrl/motherToken`(原身),默认关闭;
package/package.json CHANGED
@@ -1,11 +1,23 @@
1
1
  {
2
2
  "name": "@twinsearth/dsh-master",
3
- "version": "3.2.9",
3
+ "version": "3.3.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "type": "module",
9
+ "license": "AGPL-3.0-or-later",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/TwinsEarth/dsh-Master.git"
13
+ },
14
+ "homepage": "https://github.com/TwinsEarth/dsh-Master#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/TwinsEarth/dsh-Master/issues"
17
+ },
18
+ "engines": {
19
+ "dsh": ">=0.2.2-rc.1 <0.3.0-0"
20
+ },
9
21
  "description": "Dsh-Master(房主):与瓮城 dsh-Fang 并行的主 Agent 插件,托管 SuperAgent Router/Worker、HarnessMM、PM+PJM。",
10
22
  "keywords": [
11
23
  "dsh",
@@ -51,8 +63,8 @@
51
63
  "README.md"
52
64
  ],
53
65
  "scripts": {
54
- "test": "node --test --test-force-exit test/",
55
- "test:clean": "node --test --test-concurrency=1 test/",
66
+ "test": "node --test --test-force-exit \"test/**/*.test.mjs\" \"test/**/*.test.ts\"",
67
+ "test:clean": "node --test --test-concurrency=1 \"test/**/*.test.mjs\" \"test/**/*.test.ts\"",
56
68
  "test:worker": "node --test --test-force-exit test/worker-agent-loop.test.ts test/worker-tools.test.ts test/worker-llm-http.test.ts test/worker-sink-http.test.ts",
57
69
  "typecheck:worker": "tsc -p tsconfig.worker.json",
58
70
  "build:worker": "tsc -p tsconfig.worker.build.json",
@@ -0,0 +1,186 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
4
+
5
+ /**
6
+ * 3.3.0 系统层 · attribution(归因,设计 §5.4)。
7
+ *
8
+ * 三条硬规则:
9
+ * 1. **每条 savedTokens 必须带 moduleId**(缺失 ⇒ `module-missing`,不猜、不默认);
10
+ * 2. 差额进 `unattributed`,报表**单列**——未归因收益不得算作任何模块收益;
11
+ * 3. 归因算法(工具面):`baselineOutputTokens(toolName, 长度) − actualOutputTokens`,
12
+ * baseline 由 A1 固定拟合式给出(`worker/baseline-fit.ts`,参数在 docs §5.4.1 冻结)。
13
+ *
14
+ * 另守 §5.3 口径:`shadow` 只累计「本可节省」,`enforce` 才写入 execution 账本且
15
+ * `totalTokens=0`(不重复计入已消耗 token)。
16
+ */
17
+
18
+ import { COST_MODULES } from './types.ts'
19
+
20
+ function finiteNonNegative(value) {
21
+ const n = Number(value)
22
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : null
23
+ }
24
+
25
+ /**
26
+ * @param {{ledger: {append: Function}, now?: () => number}} options
27
+ */
28
+ export function createAttribution(options = {}) {
29
+ const ledger = options.ledger
30
+ if (!ledger || typeof ledger.append !== 'function') throw new Error('attribution requires a ledger')
31
+ const now = typeof options.now === 'function' ? options.now : () => Date.now()
32
+ const allowed = new Set(COST_MODULES)
33
+ const modules = new Map()
34
+ const tasks = new Map()
35
+
36
+ const moduleRow = (module) => {
37
+ if (!modules.has(module)) modules.set(module, { module, events: 0, projectedTokens: 0, enforceSavedTokens: 0 })
38
+ return modules.get(module)
39
+ }
40
+ const taskRow = (taskId, module) => {
41
+ const key = `${taskId ?? ''}|${module}`
42
+ if (!tasks.has(key)) tasks.set(key, { key, taskId: taskId === undefined ? null : String(taskId), module, events: 0, projectedTokens: 0, enforceSavedTokens: 0 })
43
+ return tasks.get(key)
44
+ }
45
+
46
+ /**
47
+ * @param {{taskId?: string, module: string, savedTokens: number, mode?: string, meta?: object,
48
+ * forkId?: string, sessionId?: string}} input
49
+ */
50
+ function attribute(input = {}) {
51
+ const saved = finiteNonNegative(input.savedTokens)
52
+ if (saved === null) return { ok: false, code: 'saved-tokens-invalid', message: 'savedTokens must be a non-negative number' }
53
+ // §5.4-1:moduleId 必填,缺失不猜默认值。
54
+ if (input.module === undefined || input.module === null || String(input.module).trim() === '') {
55
+ return { ok: false, code: 'module-missing', message: 'every savedTokens must carry a moduleId' }
56
+ }
57
+ const module = String(input.module)
58
+ if (!allowed.has(module)) return { ok: false, code: 'module-invalid', message: `module must be one of ${COST_MODULES.join('/')}` }
59
+ const enforce = String(input.mode ?? 'shadow') === 'enforce'
60
+ const row = moduleRow(module)
61
+ const perTask = taskRow(input.taskId, module)
62
+
63
+ if (!enforce) {
64
+ row.events += 1
65
+ row.projectedTokens += saved
66
+ perTask.events += 1
67
+ perTask.projectedTokens += saved
68
+ return {
69
+ ok: true,
70
+ mode: 'shadow',
71
+ module,
72
+ projectedTokens: saved,
73
+ shadow: { events: row.events, projectedTokens: row.projectedTokens, appliedTokens: row.enforceSavedTokens },
74
+ at: now(),
75
+ }
76
+ }
77
+
78
+ const appended = ledger.append({
79
+ account: 'execution',
80
+ module,
81
+ source: 'tool',
82
+ phase: 'tool',
83
+ taskId: input.taskId === undefined ? null : String(input.taskId),
84
+ forkId: input.forkId,
85
+ sessionId: input.sessionId,
86
+ step: input.step,
87
+ turn: input.turn,
88
+ totalTokens: 0,
89
+ savedTokens: saved,
90
+ meta: { kind: 'attribution', ...(input.meta && typeof input.meta === 'object' ? input.meta : {}), mode: 'enforce' },
91
+ })
92
+ if (!appended.ok) return { ok: false, code: appended.code, message: appended.message }
93
+ row.events += 1
94
+ row.enforceSavedTokens += saved
95
+ perTask.events += 1
96
+ perTask.enforceSavedTokens += saved
97
+ return {
98
+ ok: true,
99
+ mode: 'enforce',
100
+ module,
101
+ savedTokens: saved,
102
+ ledger: { id: appended.entry.id },
103
+ shadow: { events: row.events, projectedTokens: row.projectedTokens, appliedTokens: row.enforceSavedTokens },
104
+ at: now(),
105
+ }
106
+ }
107
+
108
+ /**
109
+ * §5.4-2:按「总额 − 各模块之和」结算,差额自动进 `unattributed`。
110
+ * 任何未提供 moduleId 的条目都会被拒绝(模块收益不得凭空出现)。
111
+ *
112
+ * @param {{taskId?: string, totalSavedTokens: number, items?: Array<{module: string, savedTokens: number}>,
113
+ * mode?: string, forkId?: string, sessionId?: string}} input
114
+ */
115
+ function attributeTotal(input = {}) {
116
+ const total = finiteNonNegative(input.totalSavedTokens)
117
+ if (total === null) return { ok: false, code: 'saved-tokens-invalid', message: 'totalSavedTokens must be a non-negative number' }
118
+ const items = Array.isArray(input.items) ? input.items : []
119
+ let attributed = 0
120
+ const applied = []
121
+ for (const item of items) {
122
+ const out = attribute({
123
+ taskId: input.taskId,
124
+ module: item?.module,
125
+ savedTokens: item?.savedTokens,
126
+ mode: input.mode,
127
+ forkId: input.forkId,
128
+ sessionId: input.sessionId,
129
+ meta: { kind: 'module-share' },
130
+ })
131
+ if (!out.ok) return out
132
+ attributed += finiteNonNegative(item?.savedTokens) ?? 0
133
+ applied.push({ module: String(item.module), savedTokens: finiteNonNegative(item?.savedTokens) ?? 0 })
134
+ }
135
+ if (attributed > total) {
136
+ return { ok: false, code: 'attribution-overflow', message: `attributed ${attributed} exceeds total ${total}` }
137
+ }
138
+ const residual = total - attributed
139
+ if (residual > 0) {
140
+ const out = attribute({
141
+ taskId: input.taskId,
142
+ module: 'unattributed',
143
+ savedTokens: residual,
144
+ mode: input.mode,
145
+ forkId: input.forkId,
146
+ sessionId: input.sessionId,
147
+ meta: { kind: 'residual' },
148
+ })
149
+ if (!out.ok) return out
150
+ }
151
+ return { ok: true, totalSavedTokens: total, attributedTokens: attributed, unattributedTokens: residual, items: applied, at: now() }
152
+ }
153
+
154
+ function snapshot() {
155
+ const moduleRows = [...modules.values()]
156
+ .map((item) => Object.freeze({ ...item }))
157
+ .sort((a, b) => b.projectedTokens + b.enforceSavedTokens - (a.projectedTokens + a.enforceSavedTokens))
158
+ const taskRows = [...tasks.values()]
159
+ .map((item) => Object.freeze({ ...item }))
160
+ .sort((a, b) => b.projectedTokens + b.enforceSavedTokens - (a.projectedTokens + a.enforceSavedTokens))
161
+ const named = moduleRows.filter((row) => row.module !== 'unattributed')
162
+ const unattributed = moduleRows.filter((row) => row.module === 'unattributed')
163
+ const sum = (rows, key) => rows.reduce((total, row) => total + row[key], 0)
164
+ return Object.freeze({
165
+ ok: true,
166
+ modules: moduleRows,
167
+ tasks: taskRows,
168
+ /** 模块收益(不含 unattributed)——报表只能把这一项当「模块业绩」。 */
169
+ attributedSavedTotal: sum(named, 'enforceSavedTokens'),
170
+ attributedProjectedTotal: sum(named, 'projectedTokens'),
171
+ /** 未归因收益:单列,永不并入上面的模块业绩。 */
172
+ unattributedSavedTotal: sum(unattributed, 'enforceSavedTokens'),
173
+ unattributedProjectedTotal: sum(unattributed, 'projectedTokens'),
174
+ projectedTotal: sum(moduleRows, 'projectedTokens'),
175
+ enforceSavedTotal: sum(moduleRows, 'enforceSavedTokens'),
176
+ })
177
+ }
178
+
179
+ function reset() {
180
+ modules.clear()
181
+ tasks.clear()
182
+ return { ok: true }
183
+ }
184
+
185
+ return Object.freeze({ attribute, attributeTotal, snapshot, reset })
186
+ }