@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
@@ -2,18 +2,25 @@
2
2
  // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
3
  // @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
4
4
  /**
5
- * 子插件 2:成本优化(HarnessMM 观察层)—— 为分身装“燃油表”。
5
+ * 子插件 2:成本优化(3.3.0 M1:系统层接入,observe-only)。
6
6
  *
7
- * 3.2.0 先交付可观测/可估算的骨架,不做推理拦截:
8
- * * `observe` 模式只统计/估算,不改写任何模型调用;
9
- * * HarnessMM 的 EGD/GDR/CCS 真实接入在 3.3.0 里程碑(见 docs/ROADMAP.md);
10
- * * 角色:只允许 fork(分身生产盘),母体/元主不加载。
7
+ * 本行在 3.2.x 的 observe 骨架上扩展,**保持** `estimate/record/summary/reset/status`
8
+ * 兼容面,内部改为委托 3.3.0 系统层(`src/cost`):
9
+ * * ledger:两本账(execution / governance)+ 归因;
10
+ * * budget:task/fork 预算闸(fail-closed,附 remaining);
11
+ * * policy:observe → shadow → enforce 三档与 enforce 门禁;
12
+ * * audit:治理事件哈希链。
13
+ *
14
+ * M1 只记账:不拦截模型调用、不改写工具输出、不替换推理回调;
15
+ * 工具面(A1–A3)与决策复用(B1–B4)在 M2/M3/M4 接入。
16
+ * 角色:仍只允许 fork(分身生产盘)。
11
17
  */
12
- import { ROW, ROLE, SERVICE, detectRole, enabledOf, masterError, roleAllowed, safeGet, } from "../kernel.js";
18
+ import { OPTIONAL_SERVICE, ROW, ROLE, SERVICE, detectRole, enabledOf, masterError, roleAllowed, safeGet, } from "../kernel.js";
19
+ import { createCostSystem, DEFAULT_BUDGET_LIMITS } from "../cost/index.js";
13
20
  export const name = 'dsh-master-cost-optimizer';
14
21
  export const inject = ['dshMaster'];
15
22
  export const ROW_ID = ROW.cost;
16
- /** HarnessMM 官方基准(3.2.0 文档引用;3.3.0 接入后应被实测值覆盖)。 */
23
+ /** HarnessMM 官方基准(3.2.0 文档引用;3.3.0 的实测值应覆盖它,而不是替代对标)。 */
17
24
  export const HARNESSMM_BENCHMARK = Object.freeze({
18
25
  baselineTokens: 96280,
19
26
  optimizedTokens: 11620,
@@ -26,7 +33,11 @@ function finiteNonNegative(value) {
26
33
  }
27
34
  /**
28
35
  * @param {object} ctx
29
- * @param {{enabled?:boolean, mode?:string, targetSavingsRatio?:number}} [config]
36
+ * @param {{
37
+ * enabled?: boolean, mode?: string, targetSavingsRatio?: number,
38
+ * budget?: Array<{unit: string, limitTokens: number, onExhaust?: string}>,
39
+ * enforceAllowlist?: string[],
40
+ * }} [config]
30
41
  */
31
42
  export function apply(ctx, config = {}) {
32
43
  if (!enabledOf(config))
@@ -44,11 +55,19 @@ export function apply(ctx, config = {}) {
44
55
  return { skipped: true, reason: 'master-core-unavailable' };
45
56
  }
46
57
  const targetSavingsRatio = finiteNonNegative(config.targetSavingsRatio);
47
- const policy = Object.freeze({
48
- mode: 'observe', // 3.2.0 只允许 observe;replay 在 3.3.0 打开
49
- requestedMode: String(config.mode ?? 'observe'),
50
- targetSavingsRatio: targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8,
58
+ const system = createCostSystem({
59
+ mode: config.mode,
60
+ allowlist: config.enforceAllowlist ?? config.allowlist,
61
+ policyScopes: config.scopes,
62
+ budgetLimits: Array.isArray(config.budget) && config.budget.length > 0 ? config.budget : DEFAULT_BUDGET_LIMITS,
63
+ degradeModels: config.degradeModels,
64
+ // 宿主会话工具面适配器:默认关闭;开启也只做外部收益登记,不重写任何宿主插件。
65
+ hostSurface: config.hostSurface && typeof config.hostSurface === 'object'
66
+ ? { enabled: config.hostSurface.enabled === true, source: config.hostSurface.source }
67
+ : undefined,
51
68
  });
69
+ const policy = system.policy.describe();
70
+ // 3.2.x 兼容计数器(任务级实测汇总;系统层另有账本,两者口径不同、互不覆盖)。
52
71
  const metrics = {
53
72
  tasks: 0,
54
73
  baselineTokens: 0,
@@ -59,20 +78,23 @@ export function apply(ctx, config = {}) {
59
78
  };
60
79
  const service = {
61
80
  id: SERVICE.cost,
62
- version: '3.2.5',
81
+ version: '3.3.0',
63
82
  role,
64
- mode: policy.mode,
65
- /** 基于 HarnessMM 基准 + 目标比例做派生前预估。 */
83
+ get mode() {
84
+ return system.policy.describe().mode;
85
+ },
86
+ /** 基于 HarnessMM 基准 + 目标比例做派生前预估(不承诺节省)。 */
66
87
  estimate({ baselineTokens } = {}) {
67
88
  const baseline = finiteNonNegative(baselineTokens);
68
89
  if (baseline === null)
69
90
  return masterError('baseline-tokens-invalid', 'baselineTokens must be a non-negative number');
70
- const projectedOptimized = Math.round(baseline * (1 - policy.targetSavingsRatio));
91
+ const current = system.policy.describe();
92
+ const projectedOptimized = Math.round(baseline * (1 - (targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8)));
71
93
  return {
72
94
  ok: true,
73
- mode: policy.mode,
95
+ mode: current.mode,
74
96
  baselineTokens: baseline,
75
- targetSavingsRatio: policy.targetSavingsRatio,
97
+ targetSavingsRatio: targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8,
76
98
  projectedOptimizedTokens: projectedOptimized,
77
99
  projectedSavingsTokens: baseline - projectedOptimized,
78
100
  basis: {
@@ -83,8 +105,8 @@ export function apply(ctx, config = {}) {
83
105
  },
84
106
  };
85
107
  },
86
- /** 归档后记录实测;3.3.0 起由 HarnessMM 的 EGD/GDR/CCS 自动上报。 */
87
- record({ taskId, baselineTokens, optimizedTokens, success } = {}) {
108
+ /** 归档后记录实测;3.3.0 起同时写系统层 execution 账本(baseline 给定时才算 saved)。 */
109
+ record({ taskId, baselineTokens, optimizedTokens, success, estimated = false, estimatedTokens = 0, forkId, sessionId } = {}) {
88
110
  const baseline = finiteNonNegative(baselineTokens);
89
111
  const optimized = finiteNonNegative(optimizedTokens);
90
112
  if (baseline === null)
@@ -102,13 +124,129 @@ export function apply(ctx, config = {}) {
102
124
  if (success)
103
125
  metrics.observedSuccess += 1;
104
126
  }
127
+ const meta = { success: success === undefined ? null : Boolean(success) };
128
+ // §5.1:估算与实测分列,绝不混算。两部分各自入账(baseline 按比例拆分,保证 sum 一致)。
129
+ const estimatedPart = Math.min(optimized, Math.max(0, Number(estimatedTokens) || 0));
130
+ const measuredPart = optimized - estimatedPart;
131
+ const entries = [];
132
+ if (measuredPart > 0 || estimatedPart === 0) {
133
+ const measuredBaseline = estimatedPart === 0 ? baseline : Math.round(baseline * (measuredPart / optimized));
134
+ const measured = system.recordUsage({
135
+ account: 'execution',
136
+ module: 'unattributed',
137
+ source: 'model',
138
+ phase: 'completion',
139
+ taskId: taskId === undefined ? null : String(taskId),
140
+ forkId,
141
+ sessionId,
142
+ totalTokens: measuredPart,
143
+ baselineTokens: measuredBaseline,
144
+ estimated: estimated === true && estimatedPart === 0,
145
+ meta,
146
+ });
147
+ if (!measured.ok)
148
+ return masterError(measured.code, 'ledger append failed');
149
+ entries.push(measured.entry.id);
150
+ }
151
+ if (estimatedPart > 0) {
152
+ const estimatedBaseline = Math.max(0, baseline - Math.round(baseline * (measuredPart / optimized)));
153
+ const measured = system.recordUsage({
154
+ account: 'execution',
155
+ module: 'unattributed',
156
+ source: 'model',
157
+ phase: 'completion',
158
+ taskId: taskId === undefined ? null : String(taskId),
159
+ forkId,
160
+ sessionId,
161
+ totalTokens: estimatedPart,
162
+ baselineTokens: estimatedBaseline,
163
+ estimated: true,
164
+ meta: { ...meta, estimatedFallback: true },
165
+ });
166
+ if (!measured.ok)
167
+ return masterError(measured.code, 'ledger append failed');
168
+ entries.push(measured.entry.id);
169
+ }
105
170
  return {
106
171
  ok: true,
107
172
  taskId: taskId === undefined ? null : String(taskId),
108
173
  savedTokens: baseline - optimized,
174
+ ledger: { ids: entries, measuredTokens: measuredPart, estimatedTokens: estimatedPart },
109
175
  summary: service.summary(),
110
176
  };
111
177
  },
178
+ /** 3.3.0 M2:A1 归因。enforce 记入 execution 账本;shadow 只累计「本可节省」。 */
179
+ recordAttribution({ taskId, module, savedTokens, mode = 'shadow', meta, forkId, sessionId } = {}) {
180
+ return system.attribute({ taskId, forkId, sessionId, module, savedTokens, mode, meta });
181
+ },
182
+ /** 3.3.0 §5.5:写替换证书 + 对应账本事件(共用 taskId+step)。 */
183
+ recordSubstitution(cert = {}) {
184
+ return system.substitute(cert);
185
+ },
186
+ /** 3.3.0 §5.5:按 taskId(+step) 回放「这一省是怎么来的」。 */
187
+ trace({ taskId, step } = {}) {
188
+ return system.trace({ taskId, step });
189
+ },
190
+ /** 3.3.0 §6 A2:工具装载成本记 **governance**(明确归类,绝不算节省)。 */
191
+ recordGovernance({ taskId, module = 'a2-disclosure', tokens = 0, kind = 'tools.load', meta, forkId, sessionId } = {}) {
192
+ const amount = finiteNonNegative(tokens);
193
+ if (amount === null)
194
+ return masterError('tokens-invalid', 'tokens must be a non-negative number');
195
+ const out = system.recordUsage({
196
+ account: 'governance',
197
+ module,
198
+ source: 'authoring',
199
+ phase: 'authoring',
200
+ taskId: taskId === undefined ? null : String(taskId),
201
+ forkId,
202
+ sessionId,
203
+ totalTokens: amount,
204
+ meta: { ...(meta && typeof meta === 'object' ? meta : {}), kind },
205
+ });
206
+ if (!out.ok)
207
+ return masterError(out.code, out.message ?? 'governance ledger append failed');
208
+ return { ok: true, account: 'governance', module, tokens: amount, ledger: { id: out.entry.id } };
209
+ },
210
+ /** M1 交付物:记账报表(估算/实测分列、两本账不合并、外部面单列)。 */
211
+ ledgerReport(input = {}) {
212
+ return system.ledgerReport(input);
213
+ },
214
+ /** 3.3.0 §10.1:七项验收指标(必须给全;缺项返回 complete:false + missing[])。 */
215
+ metrics(input = {}) {
216
+ return system.metrics(input);
217
+ },
218
+ /** 3.3.0 §8:A(单价)/ B(跳过调用)分列报表 + 逐任务净额(净亏不隐藏)。 */
219
+ savingsReport(input = {}) {
220
+ return system.savingsReport(input);
221
+ },
222
+ /** 3.3.0 §5.4:总额结算,差额自动进 unattributed(不 blame 到任何模块)。 */
223
+ recordAttributionTotal({ taskId, totalSavedTokens, items = [], mode = 'shadow', forkId, sessionId } = {}) {
224
+ return system.attributeTotal({ taskId, totalSavedTokens, items, mode, forkId, sessionId });
225
+ },
226
+ /** 3.3.0 目标 2.1-5:模块异常降级留痕(审计 + 事件总线,不计入节省)。 */
227
+ recordDegraded({ taskId, module, phase = 'transform', message = '', count = 1 } = {}) {
228
+ return system.degraded({ taskId, module, phase, message, count });
229
+ },
230
+ /** 3.3.0:宿主会话工具面(外部插件)收益登记;只作对照,不计入本项目节省。 */
231
+ recordHostSurface({ taskId, savedTokens, meta } = {}) {
232
+ return system.hostSurface.record({ taskId, savedTokens, meta });
233
+ },
234
+ /** 3.3.0 §5.1:按维度查询账本(task/fork/session/module/account/phase/时间窗)。 */
235
+ query(filter = {}) {
236
+ return system.query(filter);
237
+ },
238
+ /** 3.3.0 §5.1:从 JSONL 回放。 */
239
+ replayLedger(direction = 'forward') {
240
+ return system.replay(direction);
241
+ },
242
+ /** 3.3.0:归因快照(按模块与任务聚合)。 */
243
+ attribution() {
244
+ return system.attribution.snapshot();
245
+ },
246
+ /** 3.3.0:订阅成本事件(宿主可转发给瓮城回流/信息栏);返回取消订阅函数。 */
247
+ subscribeCost(listener) {
248
+ return system.subscribe(listener);
249
+ },
112
250
  summary() {
113
251
  const ratio = metrics.baselineTokens > 0 ? metrics.savedTokens / metrics.baselineTokens : 0;
114
252
  const successRate = metrics.observedTotal > 0 ? metrics.observedSuccess / metrics.observedTotal : null;
@@ -116,20 +254,105 @@ export function apply(ctx, config = {}) {
116
254
  },
117
255
  reset() {
118
256
  Object.assign(metrics, { tasks: 0, baselineTokens: 0, optimizedTokens: 0, savedTokens: 0, observedSuccess: 0, observedTotal: 0 });
257
+ system.ledger.reset();
258
+ system.budget.reset();
259
+ system.attribution.reset();
260
+ system.bus.reset();
119
261
  return { ok: true };
120
262
  },
263
+ /** 3.3.0 系统层:账本快照(两本账 + 归因)。 */
264
+ ledger() {
265
+ return system.ledger.snapshot();
266
+ },
267
+ /** M4:账本事件查询(证书链核对需要原始事件,不只要快照)。 */
268
+ ledgerEntries(filter = {}) {
269
+ return system.query(filter);
270
+ },
271
+ /** 3.3.0 系统层:预算授权(① 唯一检查点;超限 masterError + 结构化 remaining)。 */
272
+ authorizeBudget({ unit, key = 'default', tokens = 0, cost = 0 } = {}) {
273
+ return system.authorize({ unit, key, tokens, cost });
274
+ },
275
+ /** 3.3.0 系统层:当前账单 + 预算 + 审计 + 节省口径。 */
276
+ cost() {
277
+ return system.status();
278
+ },
279
+ /** 3.3.0 §5.3:解析某上下文的生效档(global → fork → task,取最严格)。 */
280
+ resolvePolicy(context = {}) {
281
+ return system.policy.resolve(context);
282
+ },
283
+ /** 3.3.0 §5.3:守卫命中上报(observe 只计数 / shadow 落证书不替换 / enforce 才替换)。 */
284
+ recordGuardHit(input = {}) {
285
+ return system.policy.recordHit(input);
286
+ },
287
+ /** 3.3.0 §5.3:设置 fork/task 级档位。 */
288
+ setScope(scope, id, next) {
289
+ return system.policy.setScope(scope, id, next);
290
+ },
291
+ /** 3.3.0 §5.3:三档命中统计与证书。 */
292
+ policyStats() {
293
+ return system.policy.stats();
294
+ },
295
+ /** 3.3.0 系统层:灰度模式(enforce 需过门禁,见 policy.requestMode)。 */
296
+ requestMode(next, evidence = {}) {
297
+ return system.policy.requestMode(next, evidence);
298
+ },
299
+ /** 回滚:任何时刻可降级(observe 一行回滚)。 */
300
+ setMode(next) {
301
+ return system.policy.setMode(next);
302
+ },
303
+ audit() {
304
+ return system.audit.snapshot();
305
+ },
306
+ /** M4:证书列表(供证书链核对报表用;只读)。 */
307
+ substitutions(filter = {}) {
308
+ return system.audit.substitutions(filter);
309
+ },
121
310
  status() {
311
+ const current = system.policy.describe();
122
312
  return {
123
313
  ok: true,
124
314
  id: 'cost-optimizer',
125
- version: '3.2.5',
315
+ version: '3.3.0',
126
316
  role,
127
- mode: policy.mode,
128
- requestedMode: policy.requestedMode,
129
- targetSavingsRatio: policy.targetSavingsRatio,
317
+ mode: current.mode,
318
+ requestedMode: current.requestedMode,
319
+ targetSavingsRatio: targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8,
130
320
  benchmark: HARNESSMM_BENCHMARK,
131
321
  summary: service.summary(),
132
- note: '3.2.0 observe-only:不拦截/不改写模型调用;EGD/GDR/CCS 在 3.3.0 接入。',
322
+ policy: current,
323
+ policyStats: system.policy.stats(),
324
+ budget: system.budget.state(),
325
+ ledger: system.ledger.snapshot(),
326
+ audit: system.audit.verify(),
327
+ shadow: Object.fromEntries(system.attribution.snapshot().modules.map((row) => [row.module, {
328
+ events: row.events,
329
+ projectedTokens: row.projectedTokens,
330
+ appliedTokens: row.enforceSavedTokens,
331
+ }])),
332
+ attribution: system.attribution.snapshot(),
333
+ attributionSummary: system.status().attributionSummary,
334
+ bus: system.bus.snapshot(1),
335
+ /** §5.6:内部事件类型(冒号命名)与对外出口状态(fang 未就绪 ⇒ unavailable)。 */
336
+ events: { types: ['cost:event', 'cost:degraded', 'cost:substitution'], relays: system.bus.snapshot(1).relays },
337
+ degraded: system.status().degraded,
338
+ surfaces: system.status().surfaces,
339
+ hostSurface: system.hostSurface.status(),
340
+ tokens: system.status().tokens,
341
+ savings: system.status().savings,
342
+ /** §8:两项分列的总节省(单价项 + 跳过调用项),避免只报合计。 */
343
+ savingsColumns: (() => {
344
+ const report = system.savingsReport({ limit: 5000 });
345
+ return {
346
+ unitPriceSaved: report.columns.unitPrice.savedTokens,
347
+ skippedCallSaved: report.columns.skippedCalls.savedTokens,
348
+ skippedCalls: report.columns.skippedCalls.calls,
349
+ governanceTotal: report.governance.totalTokens,
350
+ netSaved: report.netSaved,
351
+ netLossTasks: report.netLossTasks.map((task) => task.taskId),
352
+ doubleCountGuard: report.doubleCountGuard,
353
+ };
354
+ })(),
355
+ note: '3.3.0 M1+M2:系统层记账/预算/灰度/审计 + A1 工具输出紧凑化(默认 shadow);工具面 A2–A3 与决策复用 B1–B4 在 M3–M4 接入。',
133
356
  };
134
357
  },
135
358
  };
@@ -137,11 +360,38 @@ export function apply(ctx, config = {}) {
137
360
  ctx.effect(() => ctx.provide(SERVICE.cost, service));
138
361
  }
139
362
  catch { /* fake ctx */ }
363
+ // 3.3.0 §11:可选子服务(主服务形态不变;消费方必须探测,不能假定存在)。
364
+ const optionalFaces = {
365
+ [OPTIONAL_SERVICE.costLedger]: Object.freeze({
366
+ query: (input) => system.query(input),
367
+ replay: (direction) => system.replay(direction),
368
+ snapshot: () => system.status().ledger,
369
+ }),
370
+ [OPTIONAL_SERVICE.costBudget]: Object.freeze({
371
+ authorize: (input) => system.authorize(input),
372
+ snapshot: () => system.status().budget,
373
+ }),
374
+ [OPTIONAL_SERVICE.costPolicy]: Object.freeze({
375
+ snapshot: () => system.status().policy,
376
+ stats: () => system.status().policyStats,
377
+ }),
378
+ [OPTIONAL_SERVICE.costAudit]: Object.freeze({
379
+ verify: () => system.audit().verify(),
380
+ substitutions: (filter) => system.audit().substitutions(filter),
381
+ trace: (input) => system.trace(input),
382
+ }),
383
+ };
384
+ for (const [name, face] of Object.entries(optionalFaces)) {
385
+ try {
386
+ ctx.effect(() => ctx.provide(name, face));
387
+ }
388
+ catch { /* 不支持可选子服务的运行时也无妨 */ }
389
+ }
140
390
  master.registerSubplugin({
141
391
  id: 'cost-optimizer',
142
392
  name: 'dsh-master-cost-optimizer',
143
- capabilities: ['token-estimate', 'savings-record', 'cost-status'],
144
- description: 'HarnessMM observe 层(仅 fork)',
393
+ capabilities: ['token-estimate', 'savings-record', 'cost-status', 'cost-ledger', 'cost-budget', 'cost-policy', 'cost-audit'],
394
+ description: 'HarnessMM observe 层(仅 fork;3.3.0 M1 系统层)',
145
395
  });
146
396
  return service;
147
397
  }
@@ -27,6 +27,8 @@ const EVENTS_PATH = '/dsh-master/api/superagent/worker-events';
27
27
  const STREAM_PATH = '/dsh-master/api/superagent/stream';
28
28
  const STREAM_TOKEN_PATH = '/dsh-master/api/superagent/stream-token';
29
29
  const STATUS_PATH = '/dsh-master/api/superagent/status';
30
+ /** §5.6:成本事件摘要(状态页 / 分身信息栏读取)。 */
31
+ const COST_PATH = '/dsh-master/api/superagent/cost';
30
32
  const SESSION_PATH = '/dsh-master/api/superagent/session';
31
33
  const PR_PATH = '/dsh-master/api/superagent/pr';
32
34
  const GITHUB_PATH = '/dsh-master/api/superagent/github';
@@ -275,6 +277,79 @@ export function apply(ctx, config = {}) {
275
277
  if (token)
276
278
  tokenManager.revoke(token, 'ticket-done');
277
279
  };
280
+ /**
281
+ * §5.6:把 Worker 上报的 `cost` 事件按 ticket 汇成只读摘要,
282
+ * 供状态页与「分身信息栏」读取(不暴露任何 token)。
283
+ */
284
+ const costSummary = (ticketId) => {
285
+ const id = ticketId === undefined ? null : String(ticketId);
286
+ // §11:只读成本块的数据源——系统层 savings 两列(A 单价 / B 跳过调用)+ 命中率。
287
+ const serviceBlock = (() => {
288
+ try {
289
+ const cost = safeGet(ctx, SERVICE.cost);
290
+ if (!cost)
291
+ return null;
292
+ const status = typeof cost.status === 'function' ? cost.status() : null;
293
+ const report = typeof cost.savingsReport === 'function' ? cost.savingsReport({ amortize: 'even', limit: 2000 }) : null;
294
+ return {
295
+ savings: status?.savings ?? null,
296
+ columns: status?.savingsColumns ?? null,
297
+ policy: status?.policy ?? null,
298
+ ledger: status?.ledger?.entries !== undefined ? { entries: status.ledger.entries } : null,
299
+ unitPriceSaved: report?.columns?.unitPrice?.savedTokens ?? 0,
300
+ skippedCallSaved: report?.columns?.skippedCalls?.savedTokens ?? 0,
301
+ skippedCalls: report?.columns?.skippedCalls?.calls ?? 0,
302
+ netLossTasks: report?.netLossTasks?.map((task) => task.taskId) ?? [],
303
+ doubleCountGuard: report?.doubleCountGuard === true,
304
+ /** 守卫命中率需要各任务上报的 guard 统计聚合;未聚合前如实给 null,不猜。 */
305
+ hitRate: null,
306
+ };
307
+ }
308
+ catch {
309
+ return null;
310
+ }
311
+ })();
312
+ const tickets = id === null ? [...workerEvents.keys()] : [id];
313
+ const rows = [];
314
+ const totals = { events: 0, degraded: 0, substitutions: 0, estimated: 0, savedTokens: 0 };
315
+ for (const key of tickets) {
316
+ const events = (workerEvents.get(key) ?? []).filter((event) => event && event.type === 'cost');
317
+ if (events.length === 0)
318
+ continue;
319
+ const row = { ticketId: key, events: 0, degraded: 0, substitutions: 0, estimated: 0, savedTokens: 0, points: {}, modules: {} };
320
+ for (const event of events) {
321
+ const payload = event.payload ?? {};
322
+ row.events += 1;
323
+ row.points[String(payload.point ?? 'unknown')] = (row.points[String(payload.point ?? 'unknown')] ?? 0) + 1;
324
+ if (payload.point === 'degraded')
325
+ row.degraded += 1;
326
+ if (payload.estimated === true)
327
+ row.estimated += 1;
328
+ const compaction = payload.compaction;
329
+ if (compaction && compaction.applied === true) {
330
+ row.savedTokens += Math.max(0, Number(compaction.estimatedTokens ?? 0) || 0);
331
+ const moduleKey = String(compaction.module ?? 'a1-compact');
332
+ row.modules[moduleKey] = (row.modules[moduleKey] ?? 0) + Math.max(0, Number(compaction.estimatedTokens ?? 0) || 0);
333
+ }
334
+ }
335
+ totals.events += row.events;
336
+ totals.degraded += row.degraded;
337
+ totals.substitutions += row.substitutions;
338
+ totals.estimated += row.estimated;
339
+ totals.savedTokens += row.savedTokens;
340
+ rows.push(row);
341
+ }
342
+ return {
343
+ ok: true,
344
+ ticketId: id,
345
+ tickets: rows,
346
+ totals,
347
+ /* §11:只读成本块(A 单价项 / B 跳过调用项分列 + 命中率),面板只读消费。 */
348
+ service: serviceBlock,
349
+ /* §5.6:瓮城「产物回流」尚未就绪 —— 显式 unavailable,不假装成功、不阻塞主线。 */
350
+ relay: { target: 'fang', available: false, reason: 'fang-channel-not-ready' },
351
+ };
352
+ };
278
353
  const service = {
279
354
  id: 'superagent-router',
280
355
  version: '3.2.5',
@@ -284,6 +359,8 @@ export function apply(ctx, config = {}) {
284
359
  forkerService: hasForker(),
285
360
  streaming: { configured: Boolean(origin), ...broadcaster.describe(), tokens: tokenManager.counts() },
286
361
  workerEvents: Object.fromEntries([...workerEvents.entries()].map(([k, v]) => [k, v.length])),
362
+ cost: costSummary().totals,
363
+ costRelay: costSummary().relay,
287
364
  writeback: writeback.describe(),
288
365
  quota: quotaStatus(),
289
366
  sessions: { count: sessionMap.size, last: [...sessionMap.values()].slice(-3) },
@@ -301,6 +378,8 @@ export function apply(ctx, config = {}) {
301
378
  workerEvents(ticketId) {
302
379
  return { ok: true, ticketId: String(ticketId ?? ''), events: workerEvents.get(String(ticketId ?? '')) ?? [] };
303
380
  },
381
+ /** §5.6:成本事件摘要(状态页/分身信息栏的数据源)。 */
382
+ cost(ticketId) { return costSummary(ticketId); },
304
383
  tokenAudit(limit) { return tokenManager.audit(limit); },
305
384
  rotateStreamToken(oldToken = '') { return tokenManager.rotate(oldToken, { kind: 'ui' }); },
306
385
  quota: () => quotaStatus(),
@@ -328,7 +407,7 @@ export function apply(ctx, config = {}) {
328
407
  id: 'superagent-router',
329
408
  name: 'dsh-master-superagent-router',
330
409
  capabilities: ['webhook-ingest', 'trust-eval', 'ticket-mandate', 'worker-dispatch', 'worker-stream', 'writeback'],
331
- description: 'SuperAgent Router(仅原身;只派发不执行)',
410
+ description: 'SuperAgent Router(原身/母体;只派发不执行)',
332
411
  });
333
412
  // HTTP:Webhook 入口 + Worker 实时事件 + 只读状态。
334
413
  try {
@@ -459,6 +538,14 @@ export function apply(ctx, config = {}) {
459
538
  return jsonResponse(res, 200, { ok: true, session: mapped });
460
539
  },
461
540
  });
541
+ register({
542
+ kind: 'exact', path: COST_PATH, method: 'GET',
543
+ handler: async (req, res) => {
544
+ const query = new URL(String(req?.url ?? '/'), 'http://x').searchParams;
545
+ const ticketId = query.get('ticketId');
546
+ return jsonResponse(res, 200, service.cost(ticketId === null ? undefined : String(ticketId)));
547
+ },
548
+ });
462
549
  register({
463
550
  kind: 'exact', path: GITHUB_PATH, method: 'POST',
464
551
  handler: async (req, res) => {
@@ -496,4 +583,4 @@ export function apply(ctx, config = {}) {
496
583
  catch { /* 无 webServer 时仍可通过服务调用 ingest */ }
497
584
  return service;
498
585
  }
499
- export { EVENTS_PATH, STREAM_PATH, STREAM_TOKEN_PATH, STATUS_PATH, SESSION_PATH, readRawBody };
586
+ export { EVENTS_PATH, STREAM_PATH, STREAM_TOKEN_PATH, STATUS_PATH, SESSION_PATH, COST_PATH, readRawBody };
@@ -67,7 +67,7 @@ export function apply(ctx, config = {}) {
67
67
  id: 'superagent-worker',
68
68
  name: 'dsh-master-superagent-worker',
69
69
  capabilities: ['ticket-context', 'mandate-scope-guard', 'worker-prompt', 'long-task-plan'],
70
- description: restored.ok ? 'SuperAgent Worker 已恢复工单(仅 fork)' : 'SuperAgent Worker 待工单(仅 fork)',
70
+ description: restored.ok ? 'SuperAgent Worker 已恢复工单(fork/essence)' : 'SuperAgent Worker 待工单(fork/essence)',
71
71
  });
72
72
  return service;
73
73
  }
@@ -4,7 +4,7 @@
4
4
  /**
5
5
  * SuperAgent Router(v3.2.1):事件 → 评估 → Ticket/Mandate → 派发 Worker。
6
6
  *
7
- * Router 常驻原身(essence),**不执行代码改动**:所有执行体通过 dispatching
7
+ * Router 在 essence/mother 常驻,**不执行代码改动**:所有执行体通过 dispatching
8
8
  * 适配器交给瓮城 fangForker.spawn(分身),本模块不做文件/进程操作。
9
9
  * 纯核心逻辑(评估/建票)在 trust.mjs;本模块只做编排、去重、持久化与审计。
10
10
  */
@@ -142,3 +142,11 @@ export function publicTicket(ticket, extra = {}) {
142
142
  ...extra,
143
143
  };
144
144
  }
145
+ /* ------------------------------------------------------------------ *
146
+ * 3.3.0 §11:成本域对外类型(宿主侧)
147
+ *
148
+ * 这些是**跨进程/跨插件边界的形状**:Worker 侧同名类型在 worker/*.ts,
149
+ * 宿主侧以本文件为准。字段名保持一致,便于证书与事件在两侧无损传递。
150
+ * ------------------------------------------------------------------ */
151
+ /** 成本事件类型(§5.6 事件总线)。 */
152
+ export const COST_EVENT_TYPE = Object.freeze(['cost:event', 'cost:degraded', 'cost:substitution']);