@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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
|
|
3
|
+
// @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
|
|
4
|
+
/**
|
|
5
|
+
* 3.3.0 · M2 交付物:**工具面 token 下降 + 归因**。
|
|
6
|
+
*
|
|
7
|
+
* 两件事必须分开算,因为它们属于两本不同的账:
|
|
8
|
+
*
|
|
9
|
+
* * **A1 紧凑输出**:回执变短 ⇒ 每次交互更便宜 ⇒ **execution 节省**。
|
|
10
|
+
* 默认 `shadow` 时只能报「本可节省」,不许算成真节省。
|
|
11
|
+
* * **A2 渐进纰漏**:常驻工具面收敛 ⇒ 每轮 schema 更少;但 `load_tools` 的装载
|
|
12
|
+
* 是**作者/选择成本** ⇒ **governance 账**(与 §5.1 一致),永不混进节省。
|
|
13
|
+
*
|
|
14
|
+
* 关闭 A2 时必须 `reductionRatio === 0`(fail-open 回全量工具面),这是可测的。
|
|
15
|
+
*/
|
|
16
|
+
const num = (value) => {
|
|
17
|
+
const n = Number(value);
|
|
18
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
19
|
+
};
|
|
20
|
+
const tokensOf = (text) => (String(text ?? '').length > 0 ? Math.ceil(String(text).length / 4) : 0);
|
|
21
|
+
/**
|
|
22
|
+
* 计算工具面:全量(基线)vs 可见(常驻 + 已加载)。
|
|
23
|
+
* @param {{
|
|
24
|
+
* schemas: Array<{name: string, description?: string, parameters?: unknown}>,
|
|
25
|
+
* enabled?: boolean,
|
|
26
|
+
* resident?: string[],
|
|
27
|
+
* loaded?: string[],
|
|
28
|
+
* }} input
|
|
29
|
+
*/
|
|
30
|
+
export function planToolFace(input = {}) {
|
|
31
|
+
const schemas = Array.isArray(input.schemas) ? input.schemas : [];
|
|
32
|
+
const enabled = input.enabled === true;
|
|
33
|
+
const resident = new Set(Array.isArray(input.resident) ? input.resident.map(String) : []);
|
|
34
|
+
const loaded = new Set(Array.isArray(input.loaded) ? input.loaded.map(String) : []);
|
|
35
|
+
const tokenOfSchema = (schema) => tokensOf(JSON.stringify({ name: schema?.name, description: schema?.description, parameters: schema?.parameters }));
|
|
36
|
+
const baselineTokens = schemas.reduce((sum, schema) => sum + tokenOfSchema(schema), 0);
|
|
37
|
+
const visible = enabled ? schemas.filter((schema) => resident.has(String(schema?.name)) || loaded.has(String(schema?.name))) : schemas;
|
|
38
|
+
const residentTokens = visible.reduce((sum, schema) => sum + tokenOfSchema(schema), 0);
|
|
39
|
+
const savedTokens = Math.max(0, baselineTokens - residentTokens);
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
enabled,
|
|
42
|
+
baseline: Object.freeze({ count: schemas.length, tokens: baselineTokens }),
|
|
43
|
+
resident: Object.freeze({ count: visible.length, tokens: residentTokens, names: visible.map((schema) => String(schema?.name)) }),
|
|
44
|
+
savedTokens,
|
|
45
|
+
reductionRatio: baselineTokens > 0 ? savedTokens / baselineTokens : 0,
|
|
46
|
+
/** 关闭时必须是 0(fail-open 回全量),调用方可直接断言。 */
|
|
47
|
+
perTurn: Object.freeze({ tokensBefore: baselineTokens, tokensAfter: residentTokens }),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* M2 报表:工具面下降(A2) + 回执压缩(A1) + 归因分列。
|
|
52
|
+
*
|
|
53
|
+
* @param {{
|
|
54
|
+
* schemas?: object[],
|
|
55
|
+
* disclosure?: {enabled?: boolean, resident?: string[], loaded?: string[]},
|
|
56
|
+
* compactions?: Array<{toolName?: string, applied?: boolean, evaluated?: boolean, estimated?: boolean, savedTokens?: number, savedChars?: number}>,
|
|
57
|
+
* loads?: Array<{toolName?: string, tokens?: number}>,
|
|
58
|
+
* mode?: 'off' | 'shadow' | 'enforce',
|
|
59
|
+
* }} input
|
|
60
|
+
*/
|
|
61
|
+
export function buildToolFaceReport(input = {}) {
|
|
62
|
+
const disclosure = input.disclosure ?? {};
|
|
63
|
+
const face = planToolFace({ schemas: input.schemas, enabled: disclosure.enabled === true, resident: disclosure.resident, loaded: disclosure.loaded });
|
|
64
|
+
const mode = input.mode === 'off' || input.mode === 'enforce' ? input.mode : 'shadow';
|
|
65
|
+
const byTool = {};
|
|
66
|
+
let evaluated = 0;
|
|
67
|
+
let applied = 0;
|
|
68
|
+
let appliedSaved = 0;
|
|
69
|
+
let wouldSave = 0;
|
|
70
|
+
let estimatedSaved = 0;
|
|
71
|
+
for (const event of Array.isArray(input.compactions) ? input.compactions : []) {
|
|
72
|
+
if (event?.evaluated === false)
|
|
73
|
+
continue;
|
|
74
|
+
evaluated += 1;
|
|
75
|
+
const saved = num(event?.savedTokens ?? (num(event?.savedChars) / 4));
|
|
76
|
+
if (event?.applied === true) {
|
|
77
|
+
applied += 1;
|
|
78
|
+
appliedSaved += saved;
|
|
79
|
+
if (event?.estimated === true)
|
|
80
|
+
estimatedSaved += saved;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
wouldSave += saved;
|
|
84
|
+
}
|
|
85
|
+
const toolName = String(event?.toolName ?? 'unknown');
|
|
86
|
+
if (!byTool[toolName])
|
|
87
|
+
byTool[toolName] = { evaluated: 0, applied: 0, savedTokens: 0 };
|
|
88
|
+
byTool[toolName].evaluated += 1;
|
|
89
|
+
if (event?.applied === true) {
|
|
90
|
+
byTool[toolName].applied += 1;
|
|
91
|
+
byTool[toolName].savedTokens += saved;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// A2 的装载成本:进 governance,不进节省
|
|
95
|
+
const loadEvents = Array.isArray(input.loads) ? input.loads.length : 0;
|
|
96
|
+
const governanceTokens = (Array.isArray(input.loads) ? input.loads : []).reduce((sum, event) => sum + num(event?.tokens), 0);
|
|
97
|
+
// A1 的节省口径:只有 enforce 且真的替换了才算执行节省
|
|
98
|
+
const a1ExecutionSaved = mode === 'enforce' ? appliedSaved : 0;
|
|
99
|
+
const a1WouldSaveTokens = mode === 'enforce' ? 0 : wouldSave + appliedSaved;
|
|
100
|
+
const executionSaved = a1ExecutionSaved;
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
ok: true,
|
|
103
|
+
mode,
|
|
104
|
+
toolFace: face,
|
|
105
|
+
a1: Object.freeze({
|
|
106
|
+
module: 'a1-compact',
|
|
107
|
+
evaluated,
|
|
108
|
+
applied,
|
|
109
|
+
/** enforce 且替换成功时才算的节省。 */
|
|
110
|
+
executionSavedTokens: a1ExecutionSaved,
|
|
111
|
+
/** shadow 下「本可节省」——不是节省,报表必须显式标出。 */
|
|
112
|
+
wouldSaveTokens: a1WouldSaveTokens,
|
|
113
|
+
estimatedSavedTokens: estimatedSaved,
|
|
114
|
+
byTool: Object.freeze(byTool),
|
|
115
|
+
}),
|
|
116
|
+
a2: Object.freeze({
|
|
117
|
+
module: 'a2-disclosure',
|
|
118
|
+
enabled: face.enabled,
|
|
119
|
+
loadEvents,
|
|
120
|
+
/** 装载成本属于作者/选择成本 ⇒ governance(永不并入节省)。 */
|
|
121
|
+
governanceTokens,
|
|
122
|
+
perTurnSavedTokens: face.savedTokens,
|
|
123
|
+
}),
|
|
124
|
+
attribution: Object.freeze({
|
|
125
|
+
/** 工具面下降算 A2 的贡献,但**只有在 enabled 时才非 0**。 */
|
|
126
|
+
'a2-disclosure': face.savedTokens,
|
|
127
|
+
'a1-compact': a1ExecutionSaved,
|
|
128
|
+
governance: governanceTokens,
|
|
129
|
+
}),
|
|
130
|
+
totals: Object.freeze({
|
|
131
|
+
executionSaved,
|
|
132
|
+
governanceTokens,
|
|
133
|
+
/** 净收益 = 执行节省(A1,enforce 才有) + 工具面下降(A2,per-turn) − 装载治理成本。 */
|
|
134
|
+
netBenefit: executionSaved + face.savedTokens - governanceTokens,
|
|
135
|
+
/** 两项分列(图省事合并会导致 §8 的重复计风险)。 */
|
|
136
|
+
byKind: Object.freeze({
|
|
137
|
+
toolFaceSavedTokens: face.savedTokens,
|
|
138
|
+
receiptSavedTokens: a1ExecutionSaved,
|
|
139
|
+
}),
|
|
140
|
+
}),
|
|
141
|
+
discipline: Object.freeze({
|
|
142
|
+
shadowNeverCountedAsSavings: mode !== 'enforce',
|
|
143
|
+
loadCostIsGovernance: true,
|
|
144
|
+
disabledDisclosureReducesNothing: face.enabled !== true || face.savedTokens >= 0,
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
|
|
3
|
+
export const COST_PHASES = Object.freeze(['prompt', 'completion', 'tool', 'authoring', 'substitution']);
|
|
4
|
+
export const COST_MODULES = Object.freeze([
|
|
5
|
+
'a1-compact',
|
|
6
|
+
'a2-disclosure',
|
|
7
|
+
'a3-batch',
|
|
8
|
+
'b1-edg',
|
|
9
|
+
'b2-gdr',
|
|
10
|
+
'b3-ccs',
|
|
11
|
+
'b4-rsi',
|
|
12
|
+
'unattributed',
|
|
13
|
+
]);
|
|
14
|
+
export const COST_SOURCES = Object.freeze([
|
|
15
|
+
'model',
|
|
16
|
+
'tool',
|
|
17
|
+
'substitution',
|
|
18
|
+
'retry',
|
|
19
|
+
'authoring',
|
|
20
|
+
'offline',
|
|
21
|
+
]);
|
|
22
|
+
export const POLICY_MODES = Object.freeze(['observe', 'shadow', 'enforce']);
|
|
23
|
+
/** 两个作用面(设计 §3.3):不可合并统计。 */
|
|
24
|
+
export const COST_SURFACES = Object.freeze(['worker', 'host']);
|
|
25
|
+
/** 默认预算:与 Worker 现有 maxTokens 对齐(task=50k),fork=500k。 */
|
|
26
|
+
export const DEFAULT_BUDGET_LIMITS = Object.freeze([
|
|
27
|
+
Object.freeze({ unit: 'task', limitTokens: 50_000, onExhaust: 'hard-stop' }),
|
|
28
|
+
Object.freeze({ unit: 'fork', limitTokens: 500_000, onExhaust: 'hard-stop' }),
|
|
29
|
+
]);
|
|
30
|
+
/** enforce 门禁(设计 §13):准确率 ≥0.98、样本 ≥20、零破坏性命中、非空白名单。 */
|
|
31
|
+
export const ENFORCE_GATE = Object.freeze({ minAccuracy: 0.98, minSamples: 20 });
|
package/dist-host/kernel.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 与瓮城 dsh-Fang 的协作只走 DSH 服务键,不 import fang 代码。
|
|
8
8
|
*/
|
|
9
9
|
import crypto from 'node:crypto';
|
|
10
|
-
export const MASTER_VERSION = '3.
|
|
10
|
+
export const MASTER_VERSION = '3.3.1';
|
|
11
11
|
export const FANG_CONTRACT_MIN = '3.1.14';
|
|
12
12
|
export const SERVICE = Object.freeze({
|
|
13
13
|
master: 'dshMaster',
|
|
@@ -16,6 +16,32 @@ export const SERVICE = Object.freeze({
|
|
|
16
16
|
cost: 'dshMasterCost',
|
|
17
17
|
dual: 'dshMasterDual',
|
|
18
18
|
});
|
|
19
|
+
/**
|
|
20
|
+
* 3.3.0 §11:成本系统的**可选子服务**。
|
|
21
|
+
*
|
|
22
|
+
* 主服务 `SERVICE.cost` 的形态不变(向后兼容);子服务由 cost-optimizer 在能力允许时
|
|
23
|
+
* 额外 `provide`,消费方必须用 `probeOptionalServices(ctx)` **探测**而不是假定存在 ——
|
|
24
|
+
* 缺了就是缺了,不能假装拿到空壳对象。
|
|
25
|
+
*/
|
|
26
|
+
export const OPTIONAL_SERVICE = Object.freeze({
|
|
27
|
+
costLedger: 'dshMasterCostLedger',
|
|
28
|
+
costBudget: 'dshMasterCostBudget',
|
|
29
|
+
costPolicy: 'dshMasterCostPolicy',
|
|
30
|
+
costAudit: 'dshMasterCostAudit',
|
|
31
|
+
});
|
|
32
|
+
/** 探测可选子服务是否就位(只读、无副作用)。 */
|
|
33
|
+
export function probeOptionalServices(ctx) {
|
|
34
|
+
const present = {};
|
|
35
|
+
for (const [key, name] of Object.entries(OPTIONAL_SERVICE)) {
|
|
36
|
+
try {
|
|
37
|
+
present[key] = typeof ctx?.get === 'function' ? Boolean(ctx.get(name)) : false;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
present[key] = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return present;
|
|
44
|
+
}
|
|
19
45
|
export const FANG_SERVICE = Object.freeze({
|
|
20
46
|
orchestrator: 'fang',
|
|
21
47
|
forker: 'fangForker',
|
|
@@ -33,7 +59,7 @@ export const ROW = Object.freeze({
|
|
|
33
59
|
});
|
|
34
60
|
export const ROLE = Object.freeze({ mother: 'mother', essence: 'essence', fork: 'fork' });
|
|
35
61
|
/**
|
|
36
|
-
*
|
|
62
|
+
* 角色矩阵:
|
|
37
63
|
* * Router 在 essence 与 mother 激活:只评估/建票/派发,不执行代码;fork 不注册。
|
|
38
64
|
* * Worker / Worker Agent Loop 在 fork 与 essence 激活:原身可直接执行工单,
|
|
39
65
|
* 分身仍是默认执行体;mother 不执行。
|
|
@@ -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:成本优化(
|
|
5
|
+
* 子插件 2:成本优化(3.3.0 M1:系统层接入,observe-only)。
|
|
6
6
|
*
|
|
7
|
-
* 3.2.
|
|
8
|
-
*
|
|
9
|
-
* *
|
|
10
|
-
* *
|
|
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, createFangRelayFromContext, 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,15 @@ function finiteNonNegative(value) {
|
|
|
26
33
|
}
|
|
27
34
|
/**
|
|
28
35
|
* @param {object} ctx
|
|
29
|
-
* @param {{
|
|
36
|
+
* @param {{
|
|
37
|
+
* enabled?: boolean, mode?: string, targetSavingsRatio?: number,
|
|
38
|
+
* budget?: Array<{unit: string, limitTokens: number, onExhaust?: string}>,
|
|
39
|
+
* enforceAllowlist?: string[],
|
|
40
|
+
* relay?: {enabled?: boolean, httpBaseUrl?: string, httpToken?: string, fileDir?: string,
|
|
41
|
+
* timeoutMs?: number, maxBatch?: number, maxQueue?: number, probeTtlMs?: number,
|
|
42
|
+
* autoFlushMs?: number},
|
|
43
|
+
* dispatch?: {motherUrl?: string, motherToken?: string, contractFileDir?: string},
|
|
44
|
+
* }} [config]
|
|
30
45
|
*/
|
|
31
46
|
export function apply(ctx, config = {}) {
|
|
32
47
|
if (!enabledOf(config))
|
|
@@ -44,11 +59,45 @@ export function apply(ctx, config = {}) {
|
|
|
44
59
|
return { skipped: true, reason: 'master-core-unavailable' };
|
|
45
60
|
}
|
|
46
61
|
const targetSavingsRatio = finiteNonNegative(config.targetSavingsRatio);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
/**
|
|
63
|
+
* §5.6 / M5:瓮城回流出口。
|
|
64
|
+
* 传输按「插件配置 relay → dispatch 配置 → 环境变量 DSH_FANG_*」装配;
|
|
65
|
+
* 一个都没有时不造客户端,出口状态显式 `relay-client-absent`。
|
|
66
|
+
*/
|
|
67
|
+
const relayWiring = createFangRelayFromContext(ctx, config, { logger: ctx?.logger });
|
|
68
|
+
const system = createCostSystem({
|
|
69
|
+
mode: config.mode,
|
|
70
|
+
allowlist: config.enforceAllowlist ?? config.allowlist,
|
|
71
|
+
policyScopes: config.scopes,
|
|
72
|
+
budgetLimits: Array.isArray(config.budget) && config.budget.length > 0 ? config.budget : DEFAULT_BUDGET_LIMITS,
|
|
73
|
+
degradeModels: config.degradeModels,
|
|
74
|
+
// 宿主会话工具面适配器:默认关闭;开启也只做外部收益登记,不重写任何宿主插件。
|
|
75
|
+
hostSurface: config.hostSurface && typeof config.hostSurface === 'object'
|
|
76
|
+
? { enabled: config.hostSurface.enabled === true, source: config.hostSurface.source }
|
|
77
|
+
: undefined,
|
|
78
|
+
relay: relayWiring.relay,
|
|
79
|
+
logger: ctx?.logger,
|
|
51
80
|
});
|
|
81
|
+
const policy = system.policy.describe();
|
|
82
|
+
// M5:加载后异步探测一次回流对端(不阻塞 apply;没有客户端时立即返回 absent)。
|
|
83
|
+
try {
|
|
84
|
+
void Promise.resolve(system.relayProbe()).catch(() => { });
|
|
85
|
+
}
|
|
86
|
+
catch { /* 探测失败不影响主流程 */ }
|
|
87
|
+
/** M5:回流自动投递;没有客户端或 autoFlushMs=0 时不开定时器。 */
|
|
88
|
+
const autoFlushMs = Math.max(0, Number(config.relay?.autoFlushMs ?? 2_000) || 0);
|
|
89
|
+
let relayTimer = null;
|
|
90
|
+
if (relayWiring.client && autoFlushMs > 0) {
|
|
91
|
+
relayTimer = setInterval(() => { try {
|
|
92
|
+
void system.relayFlush().catch(() => { });
|
|
93
|
+
}
|
|
94
|
+
catch { /* ignore */ } }, autoFlushMs);
|
|
95
|
+
try {
|
|
96
|
+
relayTimer.unref?.();
|
|
97
|
+
}
|
|
98
|
+
catch { /* ignore */ }
|
|
99
|
+
}
|
|
100
|
+
// 3.2.x 兼容计数器(任务级实测汇总;系统层另有账本,两者口径不同、互不覆盖)。
|
|
52
101
|
const metrics = {
|
|
53
102
|
tasks: 0,
|
|
54
103
|
baselineTokens: 0,
|
|
@@ -59,20 +108,23 @@ export function apply(ctx, config = {}) {
|
|
|
59
108
|
};
|
|
60
109
|
const service = {
|
|
61
110
|
id: SERVICE.cost,
|
|
62
|
-
version: '3.
|
|
111
|
+
version: '3.3.1',
|
|
63
112
|
role,
|
|
64
|
-
mode
|
|
65
|
-
|
|
113
|
+
get mode() {
|
|
114
|
+
return system.policy.describe().mode;
|
|
115
|
+
},
|
|
116
|
+
/** 基于 HarnessMM 基准 + 目标比例做派生前预估(不承诺节省)。 */
|
|
66
117
|
estimate({ baselineTokens } = {}) {
|
|
67
118
|
const baseline = finiteNonNegative(baselineTokens);
|
|
68
119
|
if (baseline === null)
|
|
69
120
|
return masterError('baseline-tokens-invalid', 'baselineTokens must be a non-negative number');
|
|
70
|
-
const
|
|
121
|
+
const current = system.policy.describe();
|
|
122
|
+
const projectedOptimized = Math.round(baseline * (1 - (targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8)));
|
|
71
123
|
return {
|
|
72
124
|
ok: true,
|
|
73
|
-
mode:
|
|
125
|
+
mode: current.mode,
|
|
74
126
|
baselineTokens: baseline,
|
|
75
|
-
targetSavingsRatio:
|
|
127
|
+
targetSavingsRatio: targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8,
|
|
76
128
|
projectedOptimizedTokens: projectedOptimized,
|
|
77
129
|
projectedSavingsTokens: baseline - projectedOptimized,
|
|
78
130
|
basis: {
|
|
@@ -83,8 +135,8 @@ export function apply(ctx, config = {}) {
|
|
|
83
135
|
},
|
|
84
136
|
};
|
|
85
137
|
},
|
|
86
|
-
/** 归档后记录实测;3.3.0
|
|
87
|
-
record({ taskId, baselineTokens, optimizedTokens, success } = {}) {
|
|
138
|
+
/** 归档后记录实测;3.3.0 起同时写系统层 execution 账本(baseline 给定时才算 saved)。 */
|
|
139
|
+
record({ taskId, baselineTokens, optimizedTokens, success, estimated = false, estimatedTokens = 0, forkId, sessionId } = {}) {
|
|
88
140
|
const baseline = finiteNonNegative(baselineTokens);
|
|
89
141
|
const optimized = finiteNonNegative(optimizedTokens);
|
|
90
142
|
if (baseline === null)
|
|
@@ -102,13 +154,129 @@ export function apply(ctx, config = {}) {
|
|
|
102
154
|
if (success)
|
|
103
155
|
metrics.observedSuccess += 1;
|
|
104
156
|
}
|
|
157
|
+
const meta = { success: success === undefined ? null : Boolean(success) };
|
|
158
|
+
// §5.1:估算与实测分列,绝不混算。两部分各自入账(baseline 按比例拆分,保证 sum 一致)。
|
|
159
|
+
const estimatedPart = Math.min(optimized, Math.max(0, Number(estimatedTokens) || 0));
|
|
160
|
+
const measuredPart = optimized - estimatedPart;
|
|
161
|
+
const entries = [];
|
|
162
|
+
if (measuredPart > 0 || estimatedPart === 0) {
|
|
163
|
+
const measuredBaseline = estimatedPart === 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: measuredPart,
|
|
173
|
+
baselineTokens: measuredBaseline,
|
|
174
|
+
estimated: estimated === true && estimatedPart === 0,
|
|
175
|
+
meta,
|
|
176
|
+
});
|
|
177
|
+
if (!measured.ok)
|
|
178
|
+
return masterError(measured.code, 'ledger append failed');
|
|
179
|
+
entries.push(measured.entry.id);
|
|
180
|
+
}
|
|
181
|
+
if (estimatedPart > 0) {
|
|
182
|
+
const estimatedBaseline = Math.max(0, baseline - Math.round(baseline * (measuredPart / optimized)));
|
|
183
|
+
const measured = system.recordUsage({
|
|
184
|
+
account: 'execution',
|
|
185
|
+
module: 'unattributed',
|
|
186
|
+
source: 'model',
|
|
187
|
+
phase: 'completion',
|
|
188
|
+
taskId: taskId === undefined ? null : String(taskId),
|
|
189
|
+
forkId,
|
|
190
|
+
sessionId,
|
|
191
|
+
totalTokens: estimatedPart,
|
|
192
|
+
baselineTokens: estimatedBaseline,
|
|
193
|
+
estimated: true,
|
|
194
|
+
meta: { ...meta, estimatedFallback: true },
|
|
195
|
+
});
|
|
196
|
+
if (!measured.ok)
|
|
197
|
+
return masterError(measured.code, 'ledger append failed');
|
|
198
|
+
entries.push(measured.entry.id);
|
|
199
|
+
}
|
|
105
200
|
return {
|
|
106
201
|
ok: true,
|
|
107
202
|
taskId: taskId === undefined ? null : String(taskId),
|
|
108
203
|
savedTokens: baseline - optimized,
|
|
204
|
+
ledger: { ids: entries, measuredTokens: measuredPart, estimatedTokens: estimatedPart },
|
|
109
205
|
summary: service.summary(),
|
|
110
206
|
};
|
|
111
207
|
},
|
|
208
|
+
/** 3.3.0 M2:A1 归因。enforce 记入 execution 账本;shadow 只累计「本可节省」。 */
|
|
209
|
+
recordAttribution({ taskId, module, savedTokens, mode = 'shadow', meta, forkId, sessionId } = {}) {
|
|
210
|
+
return system.attribute({ taskId, forkId, sessionId, module, savedTokens, mode, meta });
|
|
211
|
+
},
|
|
212
|
+
/** 3.3.0 §5.5:写替换证书 + 对应账本事件(共用 taskId+step)。 */
|
|
213
|
+
recordSubstitution(cert = {}) {
|
|
214
|
+
return system.substitute(cert);
|
|
215
|
+
},
|
|
216
|
+
/** 3.3.0 §5.5:按 taskId(+step) 回放「这一省是怎么来的」。 */
|
|
217
|
+
trace({ taskId, step } = {}) {
|
|
218
|
+
return system.trace({ taskId, step });
|
|
219
|
+
},
|
|
220
|
+
/** 3.3.0 §6 A2:工具装载成本记 **governance**(明确归类,绝不算节省)。 */
|
|
221
|
+
recordGovernance({ taskId, module = 'a2-disclosure', tokens = 0, kind = 'tools.load', meta, forkId, sessionId } = {}) {
|
|
222
|
+
const amount = finiteNonNegative(tokens);
|
|
223
|
+
if (amount === null)
|
|
224
|
+
return masterError('tokens-invalid', 'tokens must be a non-negative number');
|
|
225
|
+
const out = system.recordUsage({
|
|
226
|
+
account: 'governance',
|
|
227
|
+
module,
|
|
228
|
+
source: 'authoring',
|
|
229
|
+
phase: 'authoring',
|
|
230
|
+
taskId: taskId === undefined ? null : String(taskId),
|
|
231
|
+
forkId,
|
|
232
|
+
sessionId,
|
|
233
|
+
totalTokens: amount,
|
|
234
|
+
meta: { ...(meta && typeof meta === 'object' ? meta : {}), kind },
|
|
235
|
+
});
|
|
236
|
+
if (!out.ok)
|
|
237
|
+
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
|
+
},
|
|
112
280
|
summary() {
|
|
113
281
|
const ratio = metrics.baselineTokens > 0 ? metrics.savedTokens / metrics.baselineTokens : 0;
|
|
114
282
|
const successRate = metrics.observedTotal > 0 ? metrics.observedSuccess / metrics.observedTotal : null;
|
|
@@ -116,20 +284,119 @@ export function apply(ctx, config = {}) {
|
|
|
116
284
|
},
|
|
117
285
|
reset() {
|
|
118
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();
|
|
119
291
|
return { ok: true };
|
|
120
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
|
+
},
|
|
121
352
|
status() {
|
|
353
|
+
const current = system.policy.describe();
|
|
122
354
|
return {
|
|
123
355
|
ok: true,
|
|
124
356
|
id: 'cost-optimizer',
|
|
125
|
-
version: '3.
|
|
357
|
+
version: '3.3.1',
|
|
126
358
|
role,
|
|
127
|
-
mode:
|
|
128
|
-
requestedMode:
|
|
129
|
-
targetSavingsRatio:
|
|
359
|
+
mode: current.mode,
|
|
360
|
+
requestedMode: current.requestedMode,
|
|
361
|
+
targetSavingsRatio: targetSavingsRatio !== null && targetSavingsRatio <= 1 ? targetSavingsRatio : 0.8,
|
|
130
362
|
benchmark: HARNESSMM_BENCHMARK,
|
|
131
363
|
summary: service.summary(),
|
|
132
|
-
|
|
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 默认)。',
|
|
133
400
|
};
|
|
134
401
|
},
|
|
135
402
|
};
|
|
@@ -137,11 +404,46 @@ export function apply(ctx, config = {}) {
|
|
|
137
404
|
ctx.effect(() => ctx.provide(SERVICE.cost, service));
|
|
138
405
|
}
|
|
139
406
|
catch { /* fake ctx */ }
|
|
407
|
+
try {
|
|
408
|
+
ctx.effect(() => () => {
|
|
409
|
+
if (relayTimer)
|
|
410
|
+
clearInterval(relayTimer);
|
|
411
|
+
relayTimer = null;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
catch { /* fake ctx */ }
|
|
415
|
+
// 3.3.0 §11:可选子服务(主服务形态不变;消费方必须探测,不能假定存在)。
|
|
416
|
+
const optionalFaces = {
|
|
417
|
+
[OPTIONAL_SERVICE.costLedger]: Object.freeze({
|
|
418
|
+
query: (input) => system.query(input),
|
|
419
|
+
replay: (direction) => system.replay(direction),
|
|
420
|
+
snapshot: () => system.status().ledger,
|
|
421
|
+
}),
|
|
422
|
+
[OPTIONAL_SERVICE.costBudget]: Object.freeze({
|
|
423
|
+
authorize: (input) => system.authorize(input),
|
|
424
|
+
snapshot: () => system.status().budget,
|
|
425
|
+
}),
|
|
426
|
+
[OPTIONAL_SERVICE.costPolicy]: Object.freeze({
|
|
427
|
+
snapshot: () => system.status().policy,
|
|
428
|
+
stats: () => system.status().policyStats,
|
|
429
|
+
}),
|
|
430
|
+
[OPTIONAL_SERVICE.costAudit]: Object.freeze({
|
|
431
|
+
verify: () => system.audit().verify(),
|
|
432
|
+
substitutions: (filter) => system.audit().substitutions(filter),
|
|
433
|
+
trace: (input) => system.trace(input),
|
|
434
|
+
}),
|
|
435
|
+
};
|
|
436
|
+
for (const [name, face] of Object.entries(optionalFaces)) {
|
|
437
|
+
try {
|
|
438
|
+
ctx.effect(() => ctx.provide(name, face));
|
|
439
|
+
}
|
|
440
|
+
catch { /* 不支持可选子服务的运行时也无妨 */ }
|
|
441
|
+
}
|
|
140
442
|
master.registerSubplugin({
|
|
141
443
|
id: 'cost-optimizer',
|
|
142
444
|
name: 'dsh-master-cost-optimizer',
|
|
143
|
-
capabilities: ['token-estimate', 'savings-record', 'cost-status'],
|
|
144
|
-
description: 'HarnessMM observe 层(仅 fork
|
|
445
|
+
capabilities: ['token-estimate', 'savings-record', 'cost-status', 'cost-ledger', 'cost-budget', 'cost-policy', 'cost-audit'],
|
|
446
|
+
description: 'HarnessMM observe 层(仅 fork;3.3.0 M1 系统层)',
|
|
145
447
|
});
|
|
146
448
|
return service;
|
|
147
449
|
}
|