@twinsearth/dsh-master 3.2.10 → 3.3.1
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.
- package/README.md +4 -0
- package/client.js +50 -0
- package/dist-host/contract/client.js +7 -0
- package/dist-host/contract/constants.js +16 -2
- package/dist-host/contract/index.js +1 -0
- package/dist-host/contract/relay.js +239 -0
- package/dist-host/cost/attribution.js +183 -0
- package/dist-host/cost/audit.js +163 -0
- package/dist-host/cost/budget.js +211 -0
- package/dist-host/cost/bus.js +95 -0
- package/dist-host/cost/cert-chain.js +100 -0
- package/dist-host/cost/experiment.js +132 -0
- package/dist-host/cost/fang-relay.js +329 -0
- package/dist-host/cost/gate.js +189 -0
- package/dist-host/cost/host-surface.js +80 -0
- package/dist-host/cost/index.js +345 -0
- package/dist-host/cost/ledger-report.js +193 -0
- package/dist-host/cost/ledger.js +401 -0
- package/dist-host/cost/metrics.js +198 -0
- package/dist-host/cost/milestones.js +209 -0
- package/dist-host/cost/modules/index.js +5 -0
- package/dist-host/cost/modules/manifests.js +130 -0
- package/dist-host/cost/policy.js +276 -0
- package/dist-host/cost/report.js +156 -0
- package/dist-host/cost/reuse-report.js +128 -0
- package/dist-host/cost/rsi.js +367 -0
- package/dist-host/cost/toolface.js +147 -0
- package/dist-host/cost/types.js +31 -0
- package/dist-host/kernel.js +28 -2
- package/dist-host/plugins/cost-optimizer.js +329 -27
- package/dist-host/plugins/superagent-router.js +104 -1
- package/dist-host/superagent/types.js +8 -0
- package/dist-worker/agent-loop.js +449 -7
- package/dist-worker/baseline-fit.js +66 -0
- package/dist-worker/budget-guard.js +93 -0
- package/dist-worker/ccs.js +99 -0
- package/dist-worker/cost-hooks.js +115 -0
- package/dist-worker/cost-modules.js +330 -0
- package/dist-worker/cost-spi.js +3 -0
- package/dist-worker/edg.js +372 -0
- package/dist-worker/gdr.js +159 -0
- package/dist-worker/index.js +80 -2
- package/dist-worker/llm-http.js +20 -0
- package/dist-worker/sink-http.js +1 -0
- package/dist-worker/spill.js +115 -0
- package/dist-worker/tool-batch.js +66 -0
- package/dist-worker/tool-compact.js +275 -0
- package/dist-worker/tool-disclosure.js +90 -0
- package/dist-worker/tools.js +16 -0
- package/docs/ARCHITECTURE.md +24 -0
- package/docs/CHANGELOG.md +66 -0
- package/docs/DESIGN-v3.3.0-cost-optimization.md +1058 -0
- package/docs/EXPERIMENT-M5-fang-relay.md +117 -0
- package/docs/EXPERIMENT-M5-real-ab.md +87 -0
- package/docs/THIRD-PARTY.md +87 -0
- package/package.json +2 -2
- package/src/contract/client.js +7 -0
- package/src/contract/constants.js +17 -2
- package/src/contract/index.js +1 -0
- package/src/contract/relay.js +223 -0
- package/src/cost/attribution.ts +186 -0
- package/src/cost/audit.ts +170 -0
- package/src/cost/budget.ts +226 -0
- package/src/cost/bus.ts +97 -0
- package/src/cost/cert-chain.ts +102 -0
- package/src/cost/experiment.ts +136 -0
- package/src/cost/fang-relay.ts +318 -0
- package/src/cost/gate.ts +191 -0
- package/src/cost/host-surface.ts +97 -0
- package/src/cost/index.ts +348 -0
- package/src/cost/ledger-report.ts +186 -0
- package/src/cost/ledger.ts +385 -0
- package/src/cost/metrics.ts +193 -0
- package/src/cost/milestones.ts +206 -0
- package/src/cost/modules/index.ts +13 -0
- package/src/cost/modules/manifests.ts +129 -0
- package/src/cost/policy.ts +276 -0
- package/src/cost/report.ts +158 -0
- package/src/cost/reuse-report.ts +131 -0
- package/src/cost/rsi.ts +364 -0
- package/src/cost/toolface.ts +151 -0
- package/src/cost/types.ts +298 -0
- package/src/kernel.ts +27 -2
- package/src/plugins/cost-optimizer.ts +313 -26
- package/src/plugins/superagent-router.ts +96 -1
- package/src/superagent/types.ts +70 -0
- package/worker/agent-loop.ts +444 -7
- package/worker/baseline-fit.ts +80 -0
- package/worker/budget-guard.ts +156 -0
- package/worker/ccs.ts +202 -0
- package/worker/cost-hooks.ts +133 -0
- package/worker/cost-modules.ts +356 -0
- package/worker/cost-spi.ts +283 -0
- package/worker/edg.ts +424 -0
- package/worker/gdr.ts +233 -0
- package/worker/index.ts +84 -3
- package/worker/llm-http.ts +18 -0
- package/worker/sink-http.ts +5 -2
- package/worker/spill.ts +133 -0
- package/worker/tool-batch.ts +124 -0
- package/worker/tool-compact.ts +317 -0
- package/worker/tool-disclosure.ts +131 -0
- package/worker/tools.ts +553 -531
- package/worker/types.ts +191 -0
|
@@ -3,15 +3,22 @@
|
|
|
3
3
|
// @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* 子插件 2:成本优化(
|
|
6
|
+
* 子插件 2:成本优化(3.3.0 M1:系统层接入,observe-only)。
|
|
7
7
|
*
|
|
8
|
-
* 3.2.
|
|
9
|
-
*
|
|
10
|
-
* *
|
|
11
|
-
* *
|
|
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, createFangRelayFromContext, 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,15 @@ function finiteNonNegative(value) {
|
|
|
41
49
|
|
|
42
50
|
/**
|
|
43
51
|
* @param {object} ctx
|
|
44
|
-
* @param {{
|
|
52
|
+
* @param {{
|
|
53
|
+
* enabled?: boolean, mode?: string, targetSavingsRatio?: number,
|
|
54
|
+
* budget?: Array<{unit: string, limitTokens: number, onExhaust?: string}>,
|
|
55
|
+
* enforceAllowlist?: string[],
|
|
56
|
+
* relay?: {enabled?: boolean, httpBaseUrl?: string, httpToken?: string, fileDir?: string,
|
|
57
|
+
* timeoutMs?: number, maxBatch?: number, maxQueue?: number, probeTtlMs?: number,
|
|
58
|
+
* autoFlushMs?: number},
|
|
59
|
+
* dispatch?: {motherUrl?: string, motherToken?: string, contractFileDir?: string},
|
|
60
|
+
* }} [config]
|
|
45
61
|
*/
|
|
46
62
|
export function apply(ctx, config = {}) {
|
|
47
63
|
if (!enabledOf(config)) return { skipped: true, reason: 'disabled-by-config' }
|
|
@@ -58,11 +74,38 @@ export function apply(ctx, config = {}) {
|
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
const targetSavingsRatio = finiteNonNegative(config.targetSavingsRatio)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
/**
|
|
78
|
+
* §5.6 / M5:瓮城回流出口。
|
|
79
|
+
* 传输按「插件配置 relay → dispatch 配置 → 环境变量 DSH_FANG_*」装配;
|
|
80
|
+
* 一个都没有时不造客户端,出口状态显式 `relay-client-absent`。
|
|
81
|
+
*/
|
|
82
|
+
const relayWiring = createFangRelayFromContext(ctx, config, { logger: ctx?.logger })
|
|
83
|
+
const system = createCostSystem({
|
|
84
|
+
mode: config.mode,
|
|
85
|
+
allowlist: config.enforceAllowlist ?? config.allowlist,
|
|
86
|
+
policyScopes: config.scopes,
|
|
87
|
+
budgetLimits: Array.isArray(config.budget) && config.budget.length > 0 ? config.budget : DEFAULT_BUDGET_LIMITS,
|
|
88
|
+
degradeModels: config.degradeModels,
|
|
89
|
+
// 宿主会话工具面适配器:默认关闭;开启也只做外部收益登记,不重写任何宿主插件。
|
|
90
|
+
hostSurface: config.hostSurface && typeof config.hostSurface === 'object'
|
|
91
|
+
? { enabled: config.hostSurface.enabled === true, source: config.hostSurface.source }
|
|
92
|
+
: undefined,
|
|
93
|
+
relay: relayWiring.relay,
|
|
94
|
+
logger: ctx?.logger,
|
|
65
95
|
})
|
|
96
|
+
const policy = system.policy.describe()
|
|
97
|
+
|
|
98
|
+
// M5:加载后异步探测一次回流对端(不阻塞 apply;没有客户端时立即返回 absent)。
|
|
99
|
+
try { void Promise.resolve(system.relayProbe()).catch(() => {}) } catch { /* 探测失败不影响主流程 */ }
|
|
100
|
+
/** M5:回流自动投递;没有客户端或 autoFlushMs=0 时不开定时器。 */
|
|
101
|
+
const autoFlushMs = Math.max(0, Number(config.relay?.autoFlushMs ?? 2_000) || 0)
|
|
102
|
+
let relayTimer = null
|
|
103
|
+
if (relayWiring.client && autoFlushMs > 0) {
|
|
104
|
+
relayTimer = setInterval(() => { try { void system.relayFlush().catch(() => {}) } catch { /* ignore */ } }, autoFlushMs)
|
|
105
|
+
try { relayTimer.unref?.() } catch { /* ignore */ }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3.2.x 兼容计数器(任务级实测汇总;系统层另有账本,两者口径不同、互不覆盖)。
|
|
66
109
|
const metrics = {
|
|
67
110
|
tasks: 0,
|
|
68
111
|
baselineTokens: 0,
|
|
@@ -74,19 +117,22 @@ export function apply(ctx, config = {}) {
|
|
|
74
117
|
|
|
75
118
|
const service = {
|
|
76
119
|
id: SERVICE.cost,
|
|
77
|
-
version: '3.
|
|
120
|
+
version: '3.3.1',
|
|
78
121
|
role,
|
|
79
|
-
mode
|
|
80
|
-
|
|
122
|
+
get mode() {
|
|
123
|
+
return system.policy.describe().mode
|
|
124
|
+
},
|
|
125
|
+
/** 基于 HarnessMM 基准 + 目标比例做派生前预估(不承诺节省)。 */
|
|
81
126
|
estimate({ baselineTokens } = {}) {
|
|
82
127
|
const baseline = finiteNonNegative(baselineTokens)
|
|
83
128
|
if (baseline === null) return masterError('baseline-tokens-invalid', 'baselineTokens must be a non-negative number')
|
|
84
|
-
const
|
|
129
|
+
const current = system.policy.describe()
|
|
130
|
+
const projectedOptimized = Math.round(baseline * (1 - (targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8)))
|
|
85
131
|
return {
|
|
86
132
|
ok: true,
|
|
87
|
-
mode:
|
|
133
|
+
mode: current.mode,
|
|
88
134
|
baselineTokens: baseline,
|
|
89
|
-
targetSavingsRatio:
|
|
135
|
+
targetSavingsRatio: targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8,
|
|
90
136
|
projectedOptimizedTokens: projectedOptimized,
|
|
91
137
|
projectedSavingsTokens: baseline - projectedOptimized,
|
|
92
138
|
basis: {
|
|
@@ -97,8 +143,8 @@ export function apply(ctx, config = {}) {
|
|
|
97
143
|
},
|
|
98
144
|
}
|
|
99
145
|
},
|
|
100
|
-
/** 归档后记录实测;3.3.0
|
|
101
|
-
record({ taskId, baselineTokens, optimizedTokens, success } = {}) {
|
|
146
|
+
/** 归档后记录实测;3.3.0 起同时写系统层 execution 账本(baseline 给定时才算 saved)。 */
|
|
147
|
+
record({ taskId, baselineTokens, optimizedTokens, success, estimated = false, estimatedTokens = 0, forkId, sessionId } = {}) {
|
|
102
148
|
const baseline = finiteNonNegative(baselineTokens)
|
|
103
149
|
const optimized = finiteNonNegative(optimizedTokens)
|
|
104
150
|
if (baseline === null) return masterError('baseline-tokens-invalid', 'baselineTokens must be a non-negative number')
|
|
@@ -112,13 +158,125 @@ export function apply(ctx, config = {}) {
|
|
|
112
158
|
metrics.observedTotal += 1
|
|
113
159
|
if (success) metrics.observedSuccess += 1
|
|
114
160
|
}
|
|
161
|
+
const meta = { success: success === undefined ? null : Boolean(success) }
|
|
162
|
+
// §5.1:估算与实测分列,绝不混算。两部分各自入账(baseline 按比例拆分,保证 sum 一致)。
|
|
163
|
+
const estimatedPart = Math.min(optimized, Math.max(0, Number(estimatedTokens) || 0))
|
|
164
|
+
const measuredPart = optimized - estimatedPart
|
|
165
|
+
const entries = []
|
|
166
|
+
if (measuredPart > 0 || estimatedPart === 0) {
|
|
167
|
+
const measuredBaseline = estimatedPart === 0 ? baseline : Math.round(baseline * (measuredPart / optimized))
|
|
168
|
+
const measured = system.recordUsage({
|
|
169
|
+
account: 'execution',
|
|
170
|
+
module: 'unattributed',
|
|
171
|
+
source: 'model',
|
|
172
|
+
phase: 'completion',
|
|
173
|
+
taskId: taskId === undefined ? null : String(taskId),
|
|
174
|
+
forkId,
|
|
175
|
+
sessionId,
|
|
176
|
+
totalTokens: measuredPart,
|
|
177
|
+
baselineTokens: measuredBaseline,
|
|
178
|
+
estimated: estimated === true && estimatedPart === 0,
|
|
179
|
+
meta,
|
|
180
|
+
})
|
|
181
|
+
if (!measured.ok) return masterError(measured.code, 'ledger append failed')
|
|
182
|
+
entries.push(measured.entry.id)
|
|
183
|
+
}
|
|
184
|
+
if (estimatedPart > 0) {
|
|
185
|
+
const estimatedBaseline = Math.max(0, baseline - Math.round(baseline * (measuredPart / optimized)))
|
|
186
|
+
const measured = system.recordUsage({
|
|
187
|
+
account: 'execution',
|
|
188
|
+
module: 'unattributed',
|
|
189
|
+
source: 'model',
|
|
190
|
+
phase: 'completion',
|
|
191
|
+
taskId: taskId === undefined ? null : String(taskId),
|
|
192
|
+
forkId,
|
|
193
|
+
sessionId,
|
|
194
|
+
totalTokens: estimatedPart,
|
|
195
|
+
baselineTokens: estimatedBaseline,
|
|
196
|
+
estimated: true,
|
|
197
|
+
meta: { ...meta, estimatedFallback: true },
|
|
198
|
+
})
|
|
199
|
+
if (!measured.ok) return masterError(measured.code, 'ledger append failed')
|
|
200
|
+
entries.push(measured.entry.id)
|
|
201
|
+
}
|
|
115
202
|
return {
|
|
116
203
|
ok: true,
|
|
117
204
|
taskId: taskId === undefined ? null : String(taskId),
|
|
118
205
|
savedTokens: baseline - optimized,
|
|
206
|
+
ledger: { ids: entries, measuredTokens: measuredPart, estimatedTokens: estimatedPart },
|
|
119
207
|
summary: service.summary(),
|
|
120
208
|
}
|
|
121
209
|
},
|
|
210
|
+
/** 3.3.0 M2:A1 归因。enforce 记入 execution 账本;shadow 只累计「本可节省」。 */
|
|
211
|
+
recordAttribution({ taskId, module, savedTokens, mode = 'shadow', meta, forkId, sessionId } = {}) {
|
|
212
|
+
return system.attribute({ taskId, forkId, sessionId, module, savedTokens, mode, meta })
|
|
213
|
+
},
|
|
214
|
+
/** 3.3.0 §5.5:写替换证书 + 对应账本事件(共用 taskId+step)。 */
|
|
215
|
+
recordSubstitution(cert = {}) {
|
|
216
|
+
return system.substitute(cert)
|
|
217
|
+
},
|
|
218
|
+
/** 3.3.0 §5.5:按 taskId(+step) 回放「这一省是怎么来的」。 */
|
|
219
|
+
trace({ taskId, step } = {}) {
|
|
220
|
+
return system.trace({ taskId, step })
|
|
221
|
+
},
|
|
222
|
+
/** 3.3.0 §6 A2:工具装载成本记 **governance**(明确归类,绝不算节省)。 */
|
|
223
|
+
recordGovernance({ taskId, module = 'a2-disclosure', tokens = 0, kind = 'tools.load', meta, forkId, sessionId } = {}) {
|
|
224
|
+
const amount = finiteNonNegative(tokens)
|
|
225
|
+
if (amount === null) return masterError('tokens-invalid', 'tokens must be a non-negative number')
|
|
226
|
+
const out = system.recordUsage({
|
|
227
|
+
account: 'governance',
|
|
228
|
+
module,
|
|
229
|
+
source: 'authoring',
|
|
230
|
+
phase: 'authoring',
|
|
231
|
+
taskId: taskId === undefined ? null : String(taskId),
|
|
232
|
+
forkId,
|
|
233
|
+
sessionId,
|
|
234
|
+
totalTokens: amount,
|
|
235
|
+
meta: { ...(meta && typeof meta === 'object' ? meta : {}), kind },
|
|
236
|
+
})
|
|
237
|
+
if (!out.ok) return masterError(out.code, out.message ?? 'governance ledger append failed')
|
|
238
|
+
return { ok: true, account: 'governance', module, tokens: amount, ledger: { id: out.entry.id } }
|
|
239
|
+
},
|
|
240
|
+
/** M1 交付物:记账报表(估算/实测分列、两本账不合并、外部面单列)。 */
|
|
241
|
+
ledgerReport(input = {}) {
|
|
242
|
+
return system.ledgerReport(input)
|
|
243
|
+
},
|
|
244
|
+
/** 3.3.0 §10.1:七项验收指标(必须给全;缺项返回 complete:false + missing[])。 */
|
|
245
|
+
metrics(input = {}) {
|
|
246
|
+
return system.metrics(input)
|
|
247
|
+
},
|
|
248
|
+
/** 3.3.0 §8:A(单价)/ B(跳过调用)分列报表 + 逐任务净额(净亏不隐藏)。 */
|
|
249
|
+
savingsReport(input = {}) {
|
|
250
|
+
return system.savingsReport(input)
|
|
251
|
+
},
|
|
252
|
+
/** 3.3.0 §5.4:总额结算,差额自动进 unattributed(不 blame 到任何模块)。 */
|
|
253
|
+
recordAttributionTotal({ taskId, totalSavedTokens, items = [], mode = 'shadow', forkId, sessionId } = {}) {
|
|
254
|
+
return system.attributeTotal({ taskId, totalSavedTokens, items, mode, forkId, sessionId })
|
|
255
|
+
},
|
|
256
|
+
/** 3.3.0 目标 2.1-5:模块异常降级留痕(审计 + 事件总线,不计入节省)。 */
|
|
257
|
+
recordDegraded({ taskId, module, phase = 'transform', message = '', count = 1 } = {}) {
|
|
258
|
+
return system.degraded({ taskId, module, phase, message, count })
|
|
259
|
+
},
|
|
260
|
+
/** 3.3.0:宿主会话工具面(外部插件)收益登记;只作对照,不计入本项目节省。 */
|
|
261
|
+
recordHostSurface({ taskId, savedTokens, meta } = {}) {
|
|
262
|
+
return system.hostSurface.record({ taskId, savedTokens, meta })
|
|
263
|
+
},
|
|
264
|
+
/** 3.3.0 §5.1:按维度查询账本(task/fork/session/module/account/phase/时间窗)。 */
|
|
265
|
+
query(filter = {}) {
|
|
266
|
+
return system.query(filter)
|
|
267
|
+
},
|
|
268
|
+
/** 3.3.0 §5.1:从 JSONL 回放。 */
|
|
269
|
+
replayLedger(direction = 'forward') {
|
|
270
|
+
return system.replay(direction)
|
|
271
|
+
},
|
|
272
|
+
/** 3.3.0:归因快照(按模块与任务聚合)。 */
|
|
273
|
+
attribution() {
|
|
274
|
+
return system.attribution.snapshot()
|
|
275
|
+
},
|
|
276
|
+
/** 3.3.0:订阅成本事件(宿主可转发给瓮城回流/信息栏);返回取消订阅函数。 */
|
|
277
|
+
subscribeCost(listener) {
|
|
278
|
+
return system.subscribe(listener)
|
|
279
|
+
},
|
|
122
280
|
summary() {
|
|
123
281
|
const ratio = metrics.baselineTokens > 0 ? metrics.savedTokens / metrics.baselineTokens : 0
|
|
124
282
|
const successRate = metrics.observedTotal > 0 ? metrics.observedSuccess / metrics.observedTotal : null
|
|
@@ -126,30 +284,159 @@ export function apply(ctx, config = {}) {
|
|
|
126
284
|
},
|
|
127
285
|
reset() {
|
|
128
286
|
Object.assign(metrics, { tasks: 0, baselineTokens: 0, optimizedTokens: 0, savedTokens: 0, observedSuccess: 0, observedTotal: 0 })
|
|
287
|
+
system.ledger.reset()
|
|
288
|
+
system.budget.reset()
|
|
289
|
+
system.attribution.reset()
|
|
290
|
+
system.bus.reset()
|
|
129
291
|
return { ok: true }
|
|
130
292
|
},
|
|
293
|
+
/** 3.3.0 系统层:账本快照(两本账 + 归因)。 */
|
|
294
|
+
ledger() {
|
|
295
|
+
return system.ledger.snapshot()
|
|
296
|
+
},
|
|
297
|
+
/** M4:账本事件查询(证书链核对需要原始事件,不只要快照)。 */
|
|
298
|
+
ledgerEntries(filter = {}) {
|
|
299
|
+
return system.query(filter)
|
|
300
|
+
},
|
|
301
|
+
/** 3.3.0 系统层:预算授权(① 唯一检查点;超限 masterError + 结构化 remaining)。 */
|
|
302
|
+
authorizeBudget({ unit, key = 'default', tokens = 0, cost = 0 } = {}) {
|
|
303
|
+
return system.authorize({ unit, key, tokens, cost })
|
|
304
|
+
},
|
|
305
|
+
/** 3.3.0 系统层:当前账单 + 预算 + 审计 + 节省口径。 */
|
|
306
|
+
cost() {
|
|
307
|
+
return system.status()
|
|
308
|
+
},
|
|
309
|
+
/** 3.3.0 §5.3:解析某上下文的生效档(global → fork → task,取最严格)。 */
|
|
310
|
+
resolvePolicy(context = {}) {
|
|
311
|
+
return system.policy.resolve(context)
|
|
312
|
+
},
|
|
313
|
+
/** 3.3.0 §5.3:守卫命中上报(observe 只计数 / shadow 落证书不替换 / enforce 才替换)。 */
|
|
314
|
+
recordGuardHit(input = {}) {
|
|
315
|
+
return system.policy.recordHit(input)
|
|
316
|
+
},
|
|
317
|
+
/** 3.3.0 §5.3:设置 fork/task 级档位。 */
|
|
318
|
+
setScope(scope, id, next) {
|
|
319
|
+
return system.policy.setScope(scope, id, next)
|
|
320
|
+
},
|
|
321
|
+
/** 3.3.0 §5.3:三档命中统计与证书。 */
|
|
322
|
+
policyStats() {
|
|
323
|
+
return system.policy.stats()
|
|
324
|
+
},
|
|
325
|
+
/** 3.3.0 系统层:灰度模式(enforce 需过门禁,见 policy.requestMode)。 */
|
|
326
|
+
requestMode(next, evidence = {}) {
|
|
327
|
+
return system.policy.requestMode(next, evidence)
|
|
328
|
+
},
|
|
329
|
+
/** 回滚:任何时刻可降级(observe 一行回滚)。 */
|
|
330
|
+
setMode(next) {
|
|
331
|
+
return system.policy.setMode(next)
|
|
332
|
+
},
|
|
333
|
+
audit() {
|
|
334
|
+
return system.audit.snapshot()
|
|
335
|
+
},
|
|
336
|
+
/** M4:证书列表(供证书链核对报表用;只读)。 */
|
|
337
|
+
substitutions(filter = {}) {
|
|
338
|
+
return system.audit.substitutions(filter)
|
|
339
|
+
},
|
|
340
|
+
/** M5:瓮城回流出口状态(只读;不可用时必须带具体原因)。 */
|
|
341
|
+
relayStatus() {
|
|
342
|
+
return system.relayStatus()
|
|
343
|
+
},
|
|
344
|
+
/** M5:立即探测回流对端(hello 协商 + relay 状态),返回最新状态。 */
|
|
345
|
+
async relayProbe(input = {}) {
|
|
346
|
+
return await system.relayProbe(input)
|
|
347
|
+
},
|
|
348
|
+
/** M5:立即投递队列中的回流事件;回执里的数字才是送达证据。 */
|
|
349
|
+
async relayFlush() {
|
|
350
|
+
return await system.relayFlush()
|
|
351
|
+
},
|
|
131
352
|
status() {
|
|
353
|
+
const current = system.policy.describe()
|
|
132
354
|
return {
|
|
133
355
|
ok: true,
|
|
134
356
|
id: 'cost-optimizer',
|
|
135
|
-
version: '3.
|
|
357
|
+
version: '3.3.1',
|
|
136
358
|
role,
|
|
137
|
-
mode:
|
|
138
|
-
requestedMode:
|
|
139
|
-
targetSavingsRatio:
|
|
359
|
+
mode: current.mode,
|
|
360
|
+
requestedMode: current.requestedMode,
|
|
361
|
+
targetSavingsRatio: targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8,
|
|
140
362
|
benchmark: HARNESSMM_BENCHMARK,
|
|
141
363
|
summary: service.summary(),
|
|
142
|
-
|
|
364
|
+
policy: current,
|
|
365
|
+
policyStats: system.policy.stats(),
|
|
366
|
+
budget: system.budget.state(),
|
|
367
|
+
ledger: system.ledger.snapshot(),
|
|
368
|
+
audit: system.audit.verify(),
|
|
369
|
+
shadow: Object.fromEntries(system.attribution.snapshot().modules.map((row) => [row.module, {
|
|
370
|
+
events: row.events,
|
|
371
|
+
projectedTokens: row.projectedTokens,
|
|
372
|
+
appliedTokens: row.enforceSavedTokens,
|
|
373
|
+
}])),
|
|
374
|
+
attribution: system.attribution.snapshot(),
|
|
375
|
+
attributionSummary: system.status().attributionSummary,
|
|
376
|
+
bus: system.bus.snapshot(1),
|
|
377
|
+
/** §5.6 / M5:瓮城回流出口(可达 ⇒ available:true + 对端自报 peer;否则带原因)。 */
|
|
378
|
+
relay: system.relayStatus(),
|
|
379
|
+
/** §5.6:内部事件类型(冒号命名)与对外出口状态。 */
|
|
380
|
+
events: { types: ['cost:event', 'cost:degraded', 'cost:substitution'], relays: system.bus.snapshot(1).relays },
|
|
381
|
+
degraded: system.status().degraded,
|
|
382
|
+
surfaces: system.status().surfaces,
|
|
383
|
+
hostSurface: system.hostSurface.status(),
|
|
384
|
+
tokens: system.status().tokens,
|
|
385
|
+
savings: system.status().savings,
|
|
386
|
+
/** §8:两项分列的总节省(单价项 + 跳过调用项),避免只报合计。 */
|
|
387
|
+
savingsColumns: (() => {
|
|
388
|
+
const report = system.savingsReport({ limit: 5000 })
|
|
389
|
+
return {
|
|
390
|
+
unitPriceSaved: report.columns.unitPrice.savedTokens,
|
|
391
|
+
skippedCallSaved: report.columns.skippedCalls.savedTokens,
|
|
392
|
+
skippedCalls: report.columns.skippedCalls.calls,
|
|
393
|
+
governanceTotal: report.governance.totalTokens,
|
|
394
|
+
netSaved: report.netSaved,
|
|
395
|
+
netLossTasks: report.netLossTasks.map((task) => task.taskId),
|
|
396
|
+
doubleCountGuard: report.doubleCountGuard,
|
|
397
|
+
}
|
|
398
|
+
})(),
|
|
399
|
+
note: '3.3.1:系统层记账/预算/灰度/审计 + A1 工具输出紧凑化(默认 shadow)+ 瓮城回流出口(contract 1.1 真实投递);工具面 A2–A3 与决策复用 B1–B4 已接入(shadow 默认)。',
|
|
143
400
|
}
|
|
144
401
|
},
|
|
145
402
|
}
|
|
146
403
|
|
|
147
404
|
try { ctx.effect(() => ctx.provide(SERVICE.cost, service)) } catch { /* fake ctx */ }
|
|
405
|
+
try {
|
|
406
|
+
ctx.effect(() => () => {
|
|
407
|
+
if (relayTimer) clearInterval(relayTimer)
|
|
408
|
+
relayTimer = null
|
|
409
|
+
})
|
|
410
|
+
} catch { /* fake ctx */ }
|
|
411
|
+
// 3.3.0 §11:可选子服务(主服务形态不变;消费方必须探测,不能假定存在)。
|
|
412
|
+
const optionalFaces = {
|
|
413
|
+
[OPTIONAL_SERVICE.costLedger]: Object.freeze({
|
|
414
|
+
query: (input) => system.query(input),
|
|
415
|
+
replay: (direction) => system.replay(direction),
|
|
416
|
+
snapshot: () => system.status().ledger,
|
|
417
|
+
}),
|
|
418
|
+
[OPTIONAL_SERVICE.costBudget]: Object.freeze({
|
|
419
|
+
authorize: (input) => system.authorize(input),
|
|
420
|
+
snapshot: () => system.status().budget,
|
|
421
|
+
}),
|
|
422
|
+
[OPTIONAL_SERVICE.costPolicy]: Object.freeze({
|
|
423
|
+
snapshot: () => system.status().policy,
|
|
424
|
+
stats: () => system.status().policyStats,
|
|
425
|
+
}),
|
|
426
|
+
[OPTIONAL_SERVICE.costAudit]: Object.freeze({
|
|
427
|
+
verify: () => system.audit().verify(),
|
|
428
|
+
substitutions: (filter) => system.audit().substitutions(filter),
|
|
429
|
+
trace: (input) => system.trace(input),
|
|
430
|
+
}),
|
|
431
|
+
}
|
|
432
|
+
for (const [name, face] of Object.entries(optionalFaces)) {
|
|
433
|
+
try { ctx.effect(() => ctx.provide(name, face)) } catch { /* 不支持可选子服务的运行时也无妨 */ }
|
|
434
|
+
}
|
|
148
435
|
master.registerSubplugin({
|
|
149
436
|
id: 'cost-optimizer',
|
|
150
437
|
name: 'dsh-master-cost-optimizer',
|
|
151
|
-
capabilities: ['token-estimate', 'savings-record', 'cost-status'],
|
|
152
|
-
description: 'HarnessMM observe 层(仅 fork
|
|
438
|
+
capabilities: ['token-estimate', 'savings-record', 'cost-status', 'cost-ledger', 'cost-budget', 'cost-policy', 'cost-audit'],
|
|
439
|
+
description: 'HarnessMM observe 层(仅 fork;3.3.0 M1 系统层)',
|
|
153
440
|
})
|
|
154
441
|
return service
|
|
155
442
|
}
|
|
@@ -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,87 @@ 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
|
+
/**
|
|
316
|
+
* §5.6 / M5:回流出口状态来自成本服务(3.3.1 起是真实探测,不再是硬编码)。
|
|
317
|
+
* 没有成本服务时如实给 `cost-service-unavailable`,不编造“未就绪”原因。
|
|
318
|
+
*/
|
|
319
|
+
const relayBlock = (() => {
|
|
320
|
+
try {
|
|
321
|
+
const cost = safeGet(ctx, SERVICE.cost)
|
|
322
|
+
if (cost && typeof cost.relayStatus === 'function') {
|
|
323
|
+
const relay = cost.relayStatus()
|
|
324
|
+
if (relay && typeof relay === 'object') return relay
|
|
325
|
+
}
|
|
326
|
+
} catch { /* 回流状态读取失败不阻塞成本摘要 */ }
|
|
327
|
+
return { target: 'fang', available: false, reason: 'cost-service-unavailable' }
|
|
328
|
+
})()
|
|
329
|
+
const rows = []
|
|
330
|
+
const totals = { events: 0, degraded: 0, substitutions: 0, estimated: 0, savedTokens: 0 }
|
|
331
|
+
for (const key of tickets) {
|
|
332
|
+
const events = (workerEvents.get(key) ?? []).filter((event) => event && event.type === 'cost')
|
|
333
|
+
if (events.length === 0) continue
|
|
334
|
+
const row = { ticketId: key, events: 0, degraded: 0, substitutions: 0, estimated: 0, savedTokens: 0, points: {}, modules: {} }
|
|
335
|
+
for (const event of events) {
|
|
336
|
+
const payload = event.payload ?? {}
|
|
337
|
+
row.events += 1
|
|
338
|
+
row.points[String(payload.point ?? 'unknown')] = (row.points[String(payload.point ?? 'unknown')] ?? 0) + 1
|
|
339
|
+
if (payload.point === 'degraded') row.degraded += 1
|
|
340
|
+
if (payload.estimated === true) row.estimated += 1
|
|
341
|
+
const compaction = payload.compaction
|
|
342
|
+
if (compaction && compaction.applied === true) {
|
|
343
|
+
row.savedTokens += Math.max(0, Number(compaction.estimatedTokens ?? 0) || 0)
|
|
344
|
+
const moduleKey = String(compaction.module ?? 'a1-compact')
|
|
345
|
+
row.modules[moduleKey] = (row.modules[moduleKey] ?? 0) + Math.max(0, Number(compaction.estimatedTokens ?? 0) || 0)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
totals.events += row.events
|
|
349
|
+
totals.degraded += row.degraded
|
|
350
|
+
totals.substitutions += row.substitutions
|
|
351
|
+
totals.estimated += row.estimated
|
|
352
|
+
totals.savedTokens += row.savedTokens
|
|
353
|
+
rows.push(row)
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
ok: true,
|
|
357
|
+
ticketId: id,
|
|
358
|
+
tickets: rows,
|
|
359
|
+
totals,
|
|
360
|
+
/* §11:只读成本块(A 单价项 / B 跳过调用项分列 + 命中率),面板只读消费。 */
|
|
361
|
+
service: serviceBlock,
|
|
362
|
+
/* §5.6 / M5:瓮城回流出口真实状态(不可用必须显式带原因)。 */
|
|
363
|
+
relay: relayBlock,
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
284
367
|
const service = {
|
|
285
368
|
id: 'superagent-router',
|
|
286
369
|
version: '3.2.5',
|
|
@@ -290,6 +373,8 @@ export function apply(ctx, config = {}) {
|
|
|
290
373
|
forkerService: hasForker(),
|
|
291
374
|
streaming: { configured: Boolean(origin), ...broadcaster.describe(), tokens: tokenManager.counts() },
|
|
292
375
|
workerEvents: Object.fromEntries([...workerEvents.entries()].map(([k, v]) => [k, v.length])),
|
|
376
|
+
cost: costSummary().totals,
|
|
377
|
+
costRelay: costSummary().relay,
|
|
293
378
|
writeback: writeback.describe(),
|
|
294
379
|
quota: quotaStatus(),
|
|
295
380
|
sessions: { count: sessionMap.size, last: [...sessionMap.values()].slice(-3) },
|
|
@@ -307,6 +392,8 @@ export function apply(ctx, config = {}) {
|
|
|
307
392
|
workerEvents(ticketId) {
|
|
308
393
|
return { ok: true, ticketId: String(ticketId ?? ''), events: workerEvents.get(String(ticketId ?? '')) ?? [] }
|
|
309
394
|
},
|
|
395
|
+
/** §5.6:成本事件摘要(状态页/分身信息栏的数据源)。 */
|
|
396
|
+
cost(ticketId) { return costSummary(ticketId) },
|
|
310
397
|
tokenAudit(limit) { return tokenManager.audit(limit) },
|
|
311
398
|
rotateStreamToken(oldToken = '') { return tokenManager.rotate(oldToken, { kind: 'ui' }) },
|
|
312
399
|
quota: () => quotaStatus(),
|
|
@@ -431,6 +518,14 @@ export function apply(ctx, config = {}) {
|
|
|
431
518
|
return jsonResponse(res, 200, { ok: true, session: mapped })
|
|
432
519
|
},
|
|
433
520
|
})
|
|
521
|
+
register({
|
|
522
|
+
kind: 'exact', path: COST_PATH, method: 'GET',
|
|
523
|
+
handler: async (req, res) => {
|
|
524
|
+
const query = new URL(String(req?.url ?? '/'), 'http://x').searchParams
|
|
525
|
+
const ticketId = query.get('ticketId')
|
|
526
|
+
return jsonResponse(res, 200, service.cost(ticketId === null ? undefined : String(ticketId)))
|
|
527
|
+
},
|
|
528
|
+
})
|
|
434
529
|
register({
|
|
435
530
|
kind: 'exact', path: GITHUB_PATH, method: 'POST',
|
|
436
531
|
handler: async (req, res) => {
|
|
@@ -462,4 +557,4 @@ export function apply(ctx, config = {}) {
|
|
|
462
557
|
return service
|
|
463
558
|
}
|
|
464
559
|
|
|
465
|
-
export { EVENTS_PATH, STREAM_PATH, STREAM_TOKEN_PATH, STATUS_PATH, SESSION_PATH, readRawBody }
|
|
560
|
+
export { EVENTS_PATH, STREAM_PATH, STREAM_TOKEN_PATH, STATUS_PATH, SESSION_PATH, COST_PATH, readRawBody }
|
package/src/superagent/types.ts
CHANGED
|
@@ -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
|
+
}
|