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