dsh-subagent-profile 0.3.4 → 0.5.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.
- package/README.md +13 -8
- package/README.zh.md +13 -8
- package/index.mjs +99 -95
- package/lib/client.js +1288 -189
- package/lib/core/decision-trace.mjs +20 -1
- package/lib/core/delegation.mjs +3 -2
- package/lib/core/dispatch-gates.mjs +122 -9
- package/lib/core/dispatch-schema.mjs +45 -7
- package/lib/core/dispatch-tool.mjs +24 -25
- package/lib/core/evolution-advice.mjs +154 -34
- package/lib/core/evolution-assets.mjs +167 -0
- package/lib/core/evolution-canary.mjs +427 -0
- package/lib/core/evolution-draft.mjs +333 -0
- package/lib/core/evolution-engine.mjs +413 -0
- package/lib/core/evolution-generate.mjs +180 -0
- package/lib/core/evolution-lag.mjs +78 -0
- package/lib/core/evolution-ledger.mjs +135 -27
- package/lib/core/evolution-persistence.mjs +213 -0
- package/lib/core/evolution-renewal.mjs +64 -0
- package/lib/core/evolution-routes.mjs +234 -0
- package/lib/core/http-helpers.mjs +35 -0
- package/lib/core/http-routes.mjs +35 -39
- package/lib/core/model-policy.mjs +77 -0
- package/lib/core/presets-sync.mjs +24 -2
- package/lib/core/profile-directory.mjs +17 -0
- package/lib/core/profile-provider.mjs +9 -3
- package/lib/core/profiles-query.mjs +178 -0
- package/lib/core/profiles-store.mjs +56 -8
- package/lib/core/pure.mjs +1 -1
- package/lib/core/session-read.mjs +17 -0
- package/lib/core/shims.mjs +2 -1
- package/package.json +2 -2
- package/presets/orchestrator-v2/NOTICE +7 -0
- package/presets/{orchestrator → orchestrator-v2}/agent.cordis.yml +78 -7
- package/presets/orchestrator-v2/custom-bash.mjs +213 -0
- package/presets/orchestrator-v2/preset.yml +2 -0
- package/presets/orchestrator-v2/tool-bootstrap.mjs +620 -0
- package/presets/orchestrator/preset.yml +0 -2
|
@@ -99,12 +99,19 @@ export function requestedOf(args, opts = {}) {
|
|
|
99
99
|
const unknownKeys = args !== null && typeof args === 'object' && !Array.isArray(args)
|
|
100
100
|
? Object.keys(args).filter((key) => args[key] !== undefined && !DISPATCH_PARAMETER_KEYS.includes(key))
|
|
101
101
|
: [];
|
|
102
|
+
// 字段双名:requested 对外显示补官方名 reasoning_effort(与官方 subagent 工具
|
|
103
|
+
// 字段对齐);内部记录键 reasoningEffort 保持(台账/客户端读取方不动,后续 UI
|
|
104
|
+
// 批次迁移读取方后可移除旧键)。内部键记录归一后值(camel 优先),官方键记录
|
|
105
|
+
// 调用方实际使用的名(snake 优先)——同送两义时两值并留、交由 fail-loud 拒绝。
|
|
106
|
+
const internalEffort = args.reasoningEffort !== undefined ? args.reasoningEffort : args.reasoning_effort;
|
|
107
|
+
const officialEffort = args.reasoning_effort !== undefined ? args.reasoning_effort : args.reasoningEffort;
|
|
102
108
|
return {
|
|
103
109
|
profile: args.profile,
|
|
104
110
|
preset: args.preset,
|
|
105
111
|
provider: args.provider,
|
|
106
112
|
model: args.model,
|
|
107
|
-
reasoningEffort:
|
|
113
|
+
reasoningEffort: internalEffort,
|
|
114
|
+
...(officialEffort !== undefined ? { reasoning_effort: officialEffort } : {}),
|
|
108
115
|
tokenTier: args.tokenTier,
|
|
109
116
|
persona_present: typeof args.persona === 'string' && args.persona.length > 0,
|
|
110
117
|
toolFilter_present: args.toolFilter !== undefined,
|
|
@@ -119,6 +126,18 @@ export function requestedOf(args, opts = {}) {
|
|
|
119
126
|
};
|
|
120
127
|
}
|
|
121
128
|
|
|
129
|
+
// 生效来源判定(per-call > profile > parent > default):回答「这次生效的
|
|
130
|
+
// provider/model 路由是谁决定的」。call=per-call 显式指定任一模型字段;
|
|
131
|
+
// profile=方案绑定;parent=继承父会话路由;default=provider 默认或符号默认。
|
|
132
|
+
// 与官方 README 的解析优先级语义对齐;写入 requested.source 与派发台账
|
|
133
|
+
// effectiveModelSource 共用同一判定。
|
|
134
|
+
export function effectiveModelSourceOf(args, merged, parent) {
|
|
135
|
+
if (args?.provider !== undefined || args?.model !== undefined) return 'call';
|
|
136
|
+
if (merged?.provider !== undefined || merged?.model !== undefined) return 'profile';
|
|
137
|
+
if (parent?.options?.provider !== undefined || parent?.options?.model !== undefined) return 'parent';
|
|
138
|
+
return 'default';
|
|
139
|
+
}
|
|
140
|
+
|
|
122
141
|
// 生效值(与 buildMeta 同源字段 + ignored 列表)。ignored 与结果块的 ignored
|
|
123
142
|
// 列表同源:continuable 为 ['preset','reasoningEffort'],前/后台为空数组。
|
|
124
143
|
export function effectiveMeta(meta, ignored = []) {
|
package/lib/core/delegation.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// lib/core/profile-provider.mjs 的前台结果闭包使用);无 @deepseek-ai 依赖。
|
|
5
5
|
|
|
6
6
|
import { stopReasonError, withPartialText, textFrom, trustLabel, trustAudit } from './pure.mjs';
|
|
7
|
+
import { readSessionEvents } from './session-read.mjs';
|
|
7
8
|
|
|
8
9
|
// 子 Agent 会话的真实计费 token 五段分解:累加 session.events 里每条
|
|
9
10
|
// assistant/message 事件携带的 provider usage(inputTokens / outputTokens /
|
|
@@ -20,7 +21,7 @@ import { stopReasonError, withPartialText, textFrom, trustLabel, trustAudit } fr
|
|
|
20
21
|
// 前缀匹配或累加 chunk,此守卫必须同步改并补测试)。
|
|
21
22
|
export function collectChildUsage(session) {
|
|
22
23
|
if (session === undefined || session === null) return undefined;
|
|
23
|
-
const events = session
|
|
24
|
+
const events = readSessionEvents(session);
|
|
24
25
|
if (!Array.isArray(events)) return undefined;
|
|
25
26
|
const totals = { inputTokens: 0, outputTokens: 0 };
|
|
26
27
|
const optionalTotals = { cacheReadTokens: 0, cacheWriteTokens: 0, reasoningTokens: 0 };
|
|
@@ -65,7 +66,7 @@ export function collectChildUsage(session) {
|
|
|
65
66
|
// 若需更严可继续下调 maxCalls。
|
|
66
67
|
export function collectChildCalls(session, maxCalls = 6) {
|
|
67
68
|
if (session === undefined || session === null) return undefined;
|
|
68
|
-
const events = session
|
|
69
|
+
const events = readSessionEvents(session);
|
|
69
70
|
if (!Array.isArray(events)) return undefined;
|
|
70
71
|
const calls = [];
|
|
71
72
|
let totalCalls = 0;
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// 从 dispatch-tool.mjs 拆出以守文件行门。闸逻辑与 fail-loud 文案逐字不变;
|
|
3
3
|
// 这里只「应用闸并就地记录 decisionTrace 的 pass/fail」,不改变任何派发行为。
|
|
4
4
|
//
|
|
5
|
-
// 依赖边界(无 @deepseek-ai 依赖):只 import lib/core
|
|
6
|
-
// (assertCostGuard)、decision-trace(recordGate / hardLimitChecks
|
|
7
|
-
// (MAX_TOKENS / MAX_DEPTH
|
|
5
|
+
// 依赖边界(无 @deepseek-ai 依赖):只 import lib/core 模块——cost-guard
|
|
6
|
+
// (assertCostGuard)、decision-trace(recordGate / hardLimitChecks /
|
|
7
|
+
// effectiveModelSourceOf)、pure(MAX_TOKENS / MAX_DEPTH)、model-policy
|
|
8
|
+
// (官方模型选择投影读取与路由断言)、escape(放行审计快照口径)。
|
|
8
9
|
|
|
9
10
|
import { assertCostGuard } from './cost-guard.mjs';
|
|
10
|
-
import { recordGate, hardLimitChecks } from './decision-trace.mjs';
|
|
11
|
+
import { recordGate, hardLimitChecks, effectiveModelSourceOf } from './decision-trace.mjs';
|
|
11
12
|
import { MAX_TOKENS, MAX_DEPTH, computeContinuableAllow } from './pure.mjs';
|
|
13
|
+
import { assertOfficialModelRoute, readModelSelectionPolicy } from './model-policy.mjs';
|
|
14
|
+
import { escapeGatesSnapshot } from './escape.mjs';
|
|
12
15
|
|
|
13
16
|
// 预检:显式具体 preset 必须在运行时推导的白名单内;与父 composed 预设相同的
|
|
14
17
|
// preset 改写为 'inherit'(不换装)。纯函数化:whitelist / parentComposed 由调用方
|
|
@@ -34,6 +37,64 @@ export function applyWhitelistGate(trace, merged, whitelist, parentComposed) {
|
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
// 治理审计(official-model-reject):dispatch 渠道带决策轨迹快照(escapeGatesSnapshot),
|
|
41
|
+
// provider 渠道带渠道口径快照(whitelist/cost 已过、intersection deferred、approval never)。
|
|
42
|
+
function recordOfficialModelReject(deps, effective) {
|
|
43
|
+
deps.evoLedger.recordGovernanceAudit({
|
|
44
|
+
kind: 'official-model-reject',
|
|
45
|
+
provider: effective.provider ?? null,
|
|
46
|
+
model: effective.model ?? null,
|
|
47
|
+
session_id: deps.sessionId ?? null,
|
|
48
|
+
gates_snapshot: deps.gatesSnapshot ?? { whitelist: 'pass', cost: 'pass', intersection: 'deferred', approval: 'never' },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 官方模型选择闸:policy 未配置(投影缺失)→ 静默跳过——轨迹不新增闸条目,
|
|
53
|
+
// 行为与 0.4.0 完全一致;配置后对生效路由(解析链完成后的最终 provider/model,
|
|
54
|
+
// 含继承带出的模型)精确成对校验,越界 fail-loud 三层中文错误 + 治理审计。
|
|
55
|
+
// fail 时先记 fail 闸再 rethrow。
|
|
56
|
+
export function applyOfficialModelGate(deps, { policy, effective }) {
|
|
57
|
+
if (policy === undefined || policy.configured !== true) return;
|
|
58
|
+
const input = {
|
|
59
|
+
policy: { enabled: policy.enabled, allowedModels: [...policy.allowedModels] },
|
|
60
|
+
effective: { provider: effective.provider, model: effective.model },
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
assertOfficialModelRoute(policy, effective.provider, effective.model);
|
|
64
|
+
recordGate(deps.trace, { name: 'official-model', input, output: { allowed: true }, verdict: 'pass' });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
recordGate(deps.trace, { name: 'official-model', input, output: { allowed: false }, verdict: 'fail', reason: error.message });
|
|
67
|
+
recordOfficialModelReject({ ...deps, gatesSnapshot: escapeGatesSnapshot(deps.trace) }, effective);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// dispatch 渠道官方模型预检:一次完成「生效来源判定(写入 requested 快照供台账
|
|
73
|
+
// 与派发台账共用)→ 投影读取 → 生效路由判定 → 闸记录/审计」,调用方只需一行接线。
|
|
74
|
+
// 投影缺失时闸静默跳过;source 值 call/profile/parent/default 恒写入 requested。
|
|
75
|
+
export function runOfficialModelPreflight(deps, parent, merged, args) {
|
|
76
|
+
deps.trace.requested.source = effectiveModelSourceOf(args, merged, parent);
|
|
77
|
+
const policy = readModelSelectionPolicy(parent?.ctx, parent?.session);
|
|
78
|
+
const effective = {
|
|
79
|
+
provider: merged.provider ?? parent?.options?.provider,
|
|
80
|
+
model: merged.model ?? parent?.options?.model,
|
|
81
|
+
};
|
|
82
|
+
applyOfficialModelGate(deps, { policy, effective });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// provider 渠道执行(宿主直连 start 的路径无 decisionTrace):只写治理审计 +
|
|
86
|
+
// 抛同一三层中文文案;判定输入为 resolveChildAgentOptions 解析后的最终
|
|
87
|
+
// agentOptions provider/model(含父继承)。
|
|
88
|
+
export function applyOfficialModelProviderGate(deps, policy, effective) {
|
|
89
|
+
if (policy === undefined || policy.configured !== true) return;
|
|
90
|
+
try {
|
|
91
|
+
assertOfficialModelRoute(policy, effective.provider, effective.model);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
recordOfficialModelReject(deps, effective);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
37
98
|
// 成本闸:硬上限(maxTokens/maxDepth)在调 assertCostGuard 前自行预判(不重复
|
|
38
99
|
// throw,真 throw 仍在 assertHardLimits);llm 能力面各检查项经 assertCostGuard
|
|
39
100
|
// 的 onCheck 回调逐项回填。预判读 merged(profile 合并后的实际判定值)而非原始
|
|
@@ -98,15 +159,67 @@ export function applyIntersectionGate(trace, mode, intersectionInput, merged, pa
|
|
|
98
159
|
}
|
|
99
160
|
}
|
|
100
161
|
|
|
162
|
+
// --- 预算余量三态与低位封顶 ---------------------------------------------------
|
|
163
|
+
// 阈值常量(后续校准只改这里):可用比例 ≥50% 充足 / ≥15% 紧张 / 其余告警。
|
|
164
|
+
export const BUDGET_AMPLE_RATIO = 0.5;
|
|
165
|
+
export const BUDGET_TIGHT_RATIO = 0.15;
|
|
166
|
+
|
|
167
|
+
// 剩余 token 与可用比例(剩余按 0 兜底;上限非正数时比例按 0 = 告警)。
|
|
168
|
+
export function budgetRemaining(snapshot) {
|
|
169
|
+
const max = snapshot !== null && typeof snapshot === 'object' && typeof snapshot.maxParentTokens === 'number' ? snapshot.maxParentTokens : 0;
|
|
170
|
+
const tokens = snapshot !== null && typeof snapshot === 'object' && typeof snapshot.tokens === 'number' ? snapshot.tokens : 0;
|
|
171
|
+
const remaining = Math.max(0, max - tokens);
|
|
172
|
+
return { remaining, ratio: max > 0 ? remaining / max : 0 };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// dispatch:budget section 三态文案(与 index.mjs 共用)。
|
|
176
|
+
export function budgetSectionText(snapshot) {
|
|
177
|
+
const { remaining, ratio } = budgetRemaining(snapshot);
|
|
178
|
+
const max = snapshot !== null && typeof snapshot === 'object' && typeof snapshot.maxParentTokens === 'number' ? snapshot.maxParentTokens : 0;
|
|
179
|
+
if (ratio >= BUDGET_AMPLE_RATIO) return `派发预算:充足(剩余 ${remaining} token / 上限 ${max})`;
|
|
180
|
+
if (ratio >= BUDGET_TIGHT_RATIO) return `派发预算:紧张(剩余 ${remaining} token / 上限 ${max}),优先 cheap 方案`;
|
|
181
|
+
return `派发预算:告警(剩余 ${remaining} token / 上限 ${max}),仅允许 cheap 方案;当前可用最高档=cheap`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 请求档位是否超出 cheap(tokenTier 或 model/effort 推定):tokenTier 缺省
|
|
185
|
+
// balanced;tokenTier=cheap 但 reasoningEffort 显式高于 off、或显式模型名非 flash
|
|
186
|
+
// 系 → 推定超出。不做 tokenTier→effort 映射(禁止事项),flash 名称启发式只拦
|
|
187
|
+
// 显式重负载。
|
|
188
|
+
export function requestedTierBeyondCheap(merged) {
|
|
189
|
+
const tier = merged !== null && typeof merged === 'object' && typeof merged.tokenTier === 'string' && merged.tokenTier !== '' ? merged.tokenTier : 'balanced';
|
|
190
|
+
if (tier !== 'cheap') return true;
|
|
191
|
+
const effort = merged !== null && typeof merged === 'object' ? merged.reasoningEffort : undefined;
|
|
192
|
+
if (typeof effort === 'string' && effort !== '' && effort !== 'off') return true;
|
|
193
|
+
const model = merged !== null && typeof merged === 'object' ? merged.model : undefined;
|
|
194
|
+
return typeof model === 'string' && model !== '' && !model.toLowerCase().includes('flash');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 低位封顶预检:余量 <15% 且请求档位超出 cheap → 记 budget fail 闸 + fail-loud。
|
|
198
|
+
function assertBudgetTierCap(deps, trace, parentSessionId, budget, merged) {
|
|
199
|
+
if (typeof parentSessionId !== 'string' || parentSessionId === '' || budgetRemaining(budget).ratio >= BUDGET_TIGHT_RATIO || !requestedTierBeyondCheap(merged)) return;
|
|
200
|
+
const message = '预算不足:当前可用最高档=cheap,请改用 cheap 方案或为会话增加预算';
|
|
201
|
+
recordGate(trace, {
|
|
202
|
+
name: 'budget',
|
|
203
|
+
input: { parentSessionId, current: budget, requestedTier: merged?.tokenTier ?? 'balanced(缺省)' },
|
|
204
|
+
output: { allowed: false },
|
|
205
|
+
verdict: 'fail',
|
|
206
|
+
reason: message,
|
|
207
|
+
});
|
|
208
|
+
deps.logger.warn(`[dsh-subagent-profile] ${message}`);
|
|
209
|
+
throw new Error(message);
|
|
210
|
+
}
|
|
211
|
+
|
|
101
212
|
// 总预算闸(并发上限 + 每父累计 token):前/后台在派发入口占并发额度(continuable
|
|
102
|
-
// 不占——持久会话启动、非在途 one-shot
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
213
|
+
// 不占——持久会话启动、非在途 one-shot)。低位封顶在 acquire 之前(fail-loud 不占
|
|
214
|
+
// 并发槽)。拒绝时记一道 budget fail 闸 + warn + 抛可行动中文错误(只拦新增派发,
|
|
215
|
+
// 数值内零影响);无父 sessionId 时 acquire 内部跳过守卫。返回
|
|
216
|
+
// { parentSessionId, finish }:finish(key, childTotalTokens) 是结算共用钩子
|
|
217
|
+
// (untrack + recordTokens + release),前/后台 settle 时恰好调用一次。
|
|
218
|
+
export function applyBudgetGate(deps, parent, args, merged, trace) {
|
|
107
219
|
if (args.continuable === true) return { parentSessionId: undefined, finish: () => {}, release: () => {} };
|
|
108
220
|
const parentSessionId = parent.session?.header?.id;
|
|
109
221
|
const budget = deps.guard.snapshot(parentSessionId);
|
|
222
|
+
assertBudgetTierCap(deps, trace, parentSessionId, budget, merged);
|
|
110
223
|
const acquired = deps.guard.acquire(parentSessionId);
|
|
111
224
|
if (!acquired.ok) {
|
|
112
225
|
const message = acquired.reason === 'concurrency'
|
|
@@ -14,20 +14,23 @@ export const DISPATCH_PARAMETERS = {
|
|
|
14
14
|
// 参数轴归属与约束(能力轴 / 预算轴 / 防护 / 模式):
|
|
15
15
|
// 能力轴 preset→whitelist 闸;model/provider→llm 目录核验;persona→仅 shadow
|
|
16
16
|
// order 0(不进任何闸);toolFilter→工具交集闸(只减不增)。
|
|
17
|
-
// 预算轴 reasoningEffort
|
|
17
|
+
// 预算轴 reasoningEffort/reasoning_effort(双名,官方名经 normalizeReasoningEffort
|
|
18
|
+
// 归一为 camel)→resolveCallConfig;maxTokens→MAX_TOKENS 硬上限;
|
|
18
19
|
// tokenTier→档位元数据(估算口径)。
|
|
19
20
|
// 防护 maxDepth→MAX_DEPTH 硬上限。
|
|
20
21
|
// 模式字段(run_in_background/continuable/envelope)是派发方式选择,不入配置覆盖。
|
|
21
22
|
// 新增字段时须按上列轴归类,并同步键集测试(percall-spec)的 WHITELIST_KEYS。
|
|
22
23
|
// per-call 规范化清单闭合:宿主 dsh-tools 的 DSL 参数解析**默认拒绝
|
|
23
24
|
// 未知键**(2026-08 实测:参数级 additionalProperties 只接受 value schema 对象、
|
|
24
|
-
// 不接受 boolean,故无法显式声明;键集闭合由 percall-spec 测试「键集恰为
|
|
25
|
-
//
|
|
25
|
+
// 不接受 boolean,故无法显式声明;键集闭合由 percall-spec 测试「键集恰为 15 键
|
|
26
|
+
// (含双名键 reasoningEffort/reasoning_effort)」锁定;mergeProfileArgs 只透传
|
|
27
|
+
// 白名单 9 键(merge 前经 normalizeReasoningEffort 归一官方名),未知字段到不了子请求)。
|
|
26
28
|
profile: { type: 'string', description: '容器选择(profile 注册表 id:内建 swap-standard/researcher,或设置页自建);省略则该 subagent 原样继承父预设与工具集,不加配置覆盖。' },
|
|
27
29
|
preset: { type: 'string', description: '能力轴·目标预设强制覆盖;必须是本运行时 system-trust 预设(白名单内的受信任预设),否则 fail-loud 拒绝;与父 composed 预设相同时改写为 inherit(不换装)。' },
|
|
28
|
-
model: { type: 'string', description: '能力轴·子 Agent 显式模型覆盖,per-call 优先于 profile;必须在该 provider 目录中已发布,否则 fail-loud 拒绝(无发现能力的空目录 adapter
|
|
30
|
+
model: { type: 'string', description: '能力轴·子 Agent 显式模型覆盖,per-call 优先于 profile;必须在该 provider 目录中已发布,否则 fail-loud 拒绝(无发现能力的空目录 adapter 无法校验时按兼容模式开关决定)。派发已遵循官方模型选择白名单(本会话启用时;未启用则继承父模型)。' },
|
|
29
31
|
provider: { type: 'string', description: '能力轴·子 Agent 显式 provider 覆盖,per-call 优先于 profile;必须已注册在其 provider 目录中,否则 fail-loud 拒绝。' },
|
|
30
|
-
reasoningEffort: { type: 'string', description: '预算轴·注入每个子请求的推理档位覆盖,per-call 优先于 profile;经 resolveCallConfig 校验该档受显式 provider/model 支持,否则 fail-loud 拒绝;continuable
|
|
32
|
+
reasoningEffort: { type: 'string', description: '预算轴·注入每个子请求的推理档位覆盖,per-call 优先于 profile;经 resolveCallConfig 校验该档受显式 provider/model 支持,否则 fail-loud 拒绝;continuable 模式忽略本字段(子会话不支持)。(弃用别名,请用 reasoning_effort)' },
|
|
33
|
+
reasoning_effort: { type: 'string', description: '预算轴·与 reasoningEffort 同义的官方字段名(与官方 subagent 工具字段同名):注入每个子请求的推理档位覆盖,per-call 优先于 profile;经 resolveCallConfig 校验该档受显式 provider/model 支持,否则 fail-loud 拒绝;continuable 模式忽略本字段(子会话不支持)。与 reasoningEffort 同送(两义即歧义)时 fail-loud 拒绝,请二选一。' },
|
|
31
34
|
tokenTier: { type: 'string', enum: ['cheap', 'balanced', 'premium'], description: '预算轴·成本/深度档位(估算口径,非计费):cheap 省 token、balanced 均衡、premium 高成本。覆盖 profile 的 tokenTier;缺省 balanced。' },
|
|
32
35
|
persona: { type: 'string', description: '能力轴·覆盖子 Agent 的部署 persona 段(只 shadow order 0 的 persona 影子段,不进 descriptor);per-call 优先于 profile。' },
|
|
33
36
|
toolFilter: {
|
|
@@ -45,7 +48,7 @@ export const DISPATCH_PARAMETERS = {
|
|
|
45
48
|
maxTokens: { type: 'number', description: '预算轴·子 Agent 显式 token 预算覆盖,per-call 优先于 profile;超过部署硬上限(MAX_TOKENS)fail-loud 拒绝。' },
|
|
46
49
|
maxDepth: { type: 'number', description: '防护·子 Agent 显式绝对递归深度上限覆盖,per-call 优先于 profile;超过部署硬上限(MAX_DEPTH)fail-loud 拒绝。' },
|
|
47
50
|
run_in_background: { type: 'boolean', description: '模式选择·异步 one-shot:走 jobs.start 包 start(),返回 jobId;仍单轮即弃,非 continuable。不入配置覆盖。' },
|
|
48
|
-
continuable: { type: 'boolean', description: '模式选择·启动持久可 continuation 的 subagent 而非 one-shot:立即返回 subagentId 并经 send_message 工具在后续轮次延续对话;默认 false。不入配置覆盖;与 run_in_background 同真时本字段优先,忽略 preset 换用与 reasoningEffort
|
|
51
|
+
continuable: { type: 'boolean', description: '模式选择·启动持久可 continuation 的 subagent 而非 one-shot:立即返回 subagentId 并经 send_message 工具在后续轮次延续对话;默认 false。不入配置覆盖;与 run_in_background 同真时本字段优先,忽略 preset 换用与 reasoningEffort。失败经结算通知回报;超时可 send_message 追问。' },
|
|
49
52
|
// 信封模式 = opt-in(仅「中段即交付物」的任务用):true 时把信封骨架追加进
|
|
50
53
|
// 子 persona(systemPrompt 影子段,模型可见),子 Agent 按结构化信封汇报;
|
|
51
54
|
// 默认 false 走结果剪枝回收。
|
|
@@ -55,9 +58,27 @@ export const DISPATCH_PARAMETERS = {
|
|
|
55
58
|
|
|
56
59
|
// 已知 per-call 参数键集(单一事实来源):decisionTrace 的 requested 快照据此把
|
|
57
60
|
// 未知字段记录为 unknown_keys(fail-visible,不静默吞)。宿主 dsh-tools DSL 对
|
|
58
|
-
//
|
|
61
|
+
// 参数默认拒绝未知键,此处是插件侧决策轨迹的可观测兜底。双名键(reasoningEffort /
|
|
62
|
+
// reasoning_effort)均在其中,任一名的请求都不会误报 unknown_keys。
|
|
59
63
|
export const DISPATCH_PARAMETER_KEYS = Object.freeze(Object.keys(DISPATCH_PARAMETERS));
|
|
60
64
|
|
|
65
|
+
// 字段双名归一(mergeProfileArgs 合并前调用):官方名 reasoning_effort 与内部旧名
|
|
66
|
+
// reasoningEffort 等效(同值同行为)。只传官方名时归一为 reasoningEffort(下游
|
|
67
|
+
// 只读 camel);同送两义即歧义 → fail-loud 拒绝(与「未知键拒绝」同风格,绝不
|
|
68
|
+
// 静默二选一)。旧名保留为弃用别名(description 已标),后续 major 移除。
|
|
69
|
+
export function normalizeReasoningEffort(args) {
|
|
70
|
+
if (args === null || typeof args !== 'object') return args;
|
|
71
|
+
const snake = args.reasoning_effort;
|
|
72
|
+
const camel = args.reasoningEffort;
|
|
73
|
+
if (snake !== undefined && camel !== undefined) {
|
|
74
|
+
throw new Error('dispatch: reasoning_effort 与 reasoningEffort 不得同送(两义即歧义,请二选一)');
|
|
75
|
+
}
|
|
76
|
+
if (snake === undefined) return args;
|
|
77
|
+
const normalized = { ...args, reasoningEffort: snake };
|
|
78
|
+
delete normalized.reasoning_effort;
|
|
79
|
+
return normalized;
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
export const DISPATCH_OUTPUT_SCHEMA = {
|
|
62
83
|
// 每个结果上的可观测元数据。oneOf 覆盖 background 变体(kind/jobId)与
|
|
63
84
|
// foreground 变体(output),两者闭合且都携带生效的委派值。`ignored` 必须
|
|
@@ -78,6 +99,7 @@ export const DISPATCH_OUTPUT_SCHEMA = {
|
|
|
78
99
|
model: { type: 'string' },
|
|
79
100
|
reasoningEffort: { type: 'string' },
|
|
80
101
|
tokenTier: { type: 'string', enum: ['cheap', 'balanced', 'premium'], description: '成本/深度分层(估算口径,非计费)。' },
|
|
102
|
+
imageText: { type: 'string', description: '一句话生效画像(方案/模型路由/档位),经 presentationMeta 供决策卡片展示;不进 render 行。' },
|
|
81
103
|
childTotalTokens: { type: 'number', description: '子 Agent 会话 token 估算总量(宿主启发式估算,非计费 usage token);后台结算经 job 结果携带,dispatch 结果不带。' },
|
|
82
104
|
childUsage: {
|
|
83
105
|
type: 'object',
|
|
@@ -109,6 +131,7 @@ export const DISPATCH_OUTPUT_SCHEMA = {
|
|
|
109
131
|
model: { type: 'string' },
|
|
110
132
|
reasoningEffort: { type: 'string' },
|
|
111
133
|
tokenTier: { type: 'string', enum: ['cheap', 'balanced', 'premium'], description: '成本/深度分层(估算口径,非计费)。' },
|
|
134
|
+
imageText: { type: 'string', description: '一句话生效画像(方案/模型路由/档位),经 presentationMeta 供决策卡片展示;不进 render 行。' },
|
|
112
135
|
childTotalTokens: { type: 'number', description: '子 Agent 会话 token 估算总量(宿主启发式估算,非计费 usage token);仅前台/后台结算时携带,continuable 实际省略。' },
|
|
113
136
|
childUsage: {
|
|
114
137
|
type: 'object',
|
|
@@ -139,6 +162,7 @@ export const DISPATCH_OUTPUT_SCHEMA = {
|
|
|
139
162
|
model: { type: 'string' },
|
|
140
163
|
reasoningEffort: { type: 'string' },
|
|
141
164
|
tokenTier: { type: 'string', enum: ['cheap', 'balanced', 'premium'], description: '成本/深度分层(估算口径,非计费)。' },
|
|
165
|
+
imageText: { type: 'string', description: '一句话生效画像(方案/模型路由/档位),经 presentationMeta 供决策卡片展示;不进 render 行。' },
|
|
142
166
|
childTotalTokens: { type: 'number', description: '子 Agent 会话 token 估算总量(宿主启发式估算,非计费 usage token);仅前台 completed 结算时携带。' },
|
|
143
167
|
childUsage: {
|
|
144
168
|
type: 'object',
|
|
@@ -161,6 +185,20 @@ export const DISPATCH_OUTPUT_SCHEMA = {
|
|
|
161
185
|
]
|
|
162
186
|
};
|
|
163
187
|
|
|
188
|
+
// 一句话生效画像(imageText):有 profile → `方案 X;provider/model 或继承父;
|
|
189
|
+
// 档位 Y`;无 profile(继承父)→ `继承父方案;provider/model;档位 Y`。字段随
|
|
190
|
+
// presentationMeta 投影进决策卡片,不进 render 行(UI 展示留后续版本)。
|
|
191
|
+
export function imageTextOf(meta) {
|
|
192
|
+
const tier = meta !== null && typeof meta === 'object' && typeof meta.tokenTier === 'string' && meta.tokenTier !== '' ? meta.tokenTier : 'balanced';
|
|
193
|
+
const profile = meta !== null && typeof meta === 'object' ? meta.profile : undefined;
|
|
194
|
+
const hasProfile = typeof profile === 'string' && profile !== '' && profile !== '(inline)';
|
|
195
|
+
const provider = meta !== null && typeof meta === 'object' && typeof meta.provider === 'string' ? meta.provider : '(parent)';
|
|
196
|
+
const model = meta !== null && typeof meta === 'object' && typeof meta.model === 'string' ? meta.model : '(parent)';
|
|
197
|
+
const routeKnown = provider !== '(parent)' && model !== '(parent)';
|
|
198
|
+
if (hasProfile) return `方案 ${profile};${routeKnown ? `${provider}/${model}` : '继承父'};档位 ${tier}`;
|
|
199
|
+
return `继承父方案;${provider}/${model};档位 ${tier}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
164
202
|
// 结果 render 行(从 dispatch-tool.mjs 逐字移入以守文件行门):continuable 丢弃
|
|
165
203
|
// preset 换用与 reasoningEffort —— 渲染行把 `ignored` 列表回显出来
|
|
166
204
|
// (`reasoningEffort=<值>(ignored)`,再加 ignored 项明细),让模型「看见」被丢弃项;
|
|
@@ -1,29 +1,20 @@
|
|
|
1
1
|
// lib/core/dispatch-tool.mjs — `dispatch` 工具(defineTool schema + execute)、
|
|
2
2
|
// 结果 schema 一致性锁与 syncTool 注册/注销逻辑,从 index.mjs 逐字拆出。
|
|
3
|
-
// 仅引用 lib + shims;无 @deepseek-ai 依赖(shims
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// getEnabled 读取 apply 闭包 `enabled` 标志(continuable fail-loud 门 + syncTool 注册条件),
|
|
9
|
-
// getService 请求时服务 getter(ctx.get('toolResultPruner') / ctx.get('jobs')
|
|
10
|
-
// 按次读取,绝不在 apply 时读),
|
|
11
|
-
// logger ctx.logger(决策级派发日志 + continuable 告警),
|
|
12
|
-
// subagents ctx.subagents(start / startContinuable 驱动子 Agent),
|
|
13
|
-
// guard dispatch guard(execute 入口 acquire / 结算 release+记账 / syncTool 禁用时 cancelAll+reset)。
|
|
14
|
-
// 工厂返回 { syncTool, dispose }:syncTool 交给 HTTP 路由(/set-enabled),dispose 在插件卸载时运行。
|
|
15
|
-
//
|
|
16
|
-
// execute 按预检段 + 三分支拆为模块级私有函数;parameters/output schema 拆至 dispatch-schema.mjs,预检闸拆至 dispatch-gates.mjs(守文件行门),零漂移。
|
|
3
|
+
// 仅引用 lib + shims;无 @deepseek-ai 依赖(shims 是唯一入口)。注入全是显式参数
|
|
4
|
+
// (register/store/getEnabled/getService/logger/subagents/guard/catalog/ledger/
|
|
5
|
+
// evoLedger/backgroundLedger/adoptionTracker/getEscapeSet/getEvolutionAdvice)。
|
|
6
|
+
// 工厂返回 { syncTool, dispose };execute 按预检段 + 三分支拆为模块级私有函数,
|
|
7
|
+
// parameters/output schema 拆至 dispatch-schema.mjs,预检闸拆至 dispatch-gates.mjs。
|
|
17
8
|
|
|
18
9
|
import { defineTool } from './shims.mjs';
|
|
19
10
|
import { textFrom, stopReasonError, withPartialText, pruneBlocks, assertResultSchemaConsistency, trustLabel, trustAudit, dispatchLabel } from './pure.mjs';
|
|
20
11
|
import { resolveWhitelist } from './whitelist.mjs';
|
|
21
12
|
import { recordEscapeAllow } from './escape.mjs';
|
|
22
13
|
import { settleStart, collectChildUsage, collectChildCalls } from './delegation.mjs';
|
|
23
|
-
import { DISPATCH_OUTPUT_SCHEMA, DISPATCH_PARAMETERS, DISPATCH_RENDER } from './dispatch-schema.mjs';
|
|
14
|
+
import { DISPATCH_OUTPUT_SCHEMA, DISPATCH_PARAMETERS, DISPATCH_RENDER, imageTextOf, normalizeReasoningEffort } from './dispatch-schema.mjs';
|
|
24
15
|
import { createDecisionTrace, finalizeTrace, assertTraceSize, parentContextOf, requestedOf, effectiveMeta, recordApprovalGate, recordFailure, profileSnapshotOf } from './decision-trace.mjs';
|
|
25
16
|
import { profileDirectoryRows } from './profile-directory.mjs';
|
|
26
|
-
import { applyWhitelistGate, applyCostGuardGate, applyIntersectionGate, assertContinuableEnabled, readParentToolNames, applyBudgetGate } from './dispatch-gates.mjs';
|
|
17
|
+
import { applyWhitelistGate, applyCostGuardGate, applyIntersectionGate, assertContinuableEnabled, readParentToolNames, applyBudgetGate, runOfficialModelPreflight } from './dispatch-gates.mjs';
|
|
27
18
|
import { trackForegroundInflight, trackBackgroundInflight, trackContinuableInflight } from './dispatch-guard.mjs';
|
|
28
19
|
import { finishForegroundLedger, finishBackgroundLedger, finishContinuableLedger } from './evolution-ledger.mjs';
|
|
29
20
|
import { profileKeyOf } from './evolution-summary.mjs';
|
|
@@ -36,6 +27,7 @@ const ENVELOPE_SKELETON = '完成前按以下骨架输出:## 结论(1-2 句
|
|
|
36
27
|
|
|
37
28
|
// 解析基方案(旁路),再叠加显式参数。
|
|
38
29
|
function mergeProfileArgs(args, store) {
|
|
30
|
+
args = normalizeReasoningEffort(args); // 字段双名归一(merge 前):官方名 reasoning_effort→reasoningEffort;同送两义 fail-loud。
|
|
39
31
|
const base = args.profile !== undefined ? store.resolveProfile(args.profile) : {};
|
|
40
32
|
const merged = { ...base };
|
|
41
33
|
for (const key of ['preset', 'model', 'provider', 'reasoningEffort', 'persona', 'toolFilter', 'maxTokens', 'maxDepth', 'tokenTier']) {
|
|
@@ -154,6 +146,7 @@ async function runContinuable(args, merged, meta, parent, exec, deps, trace, eff
|
|
|
154
146
|
model: meta.model,
|
|
155
147
|
reasoningEffort: meta.reasoningEffort,
|
|
156
148
|
tokenTier: meta.tokenTier,
|
|
149
|
+
imageText: imageTextOf(meta),
|
|
157
150
|
ignored: ['preset', 'reasoningEffort']
|
|
158
151
|
};
|
|
159
152
|
finalizeTrace(trace, {
|
|
@@ -208,7 +201,7 @@ async function runBackground(args, merged, meta, request, parent, deps, pruneRes
|
|
|
208
201
|
});
|
|
209
202
|
trackBackgroundInflight(deps.guard, jobs, jobId, parent);
|
|
210
203
|
deps.adoptionTracker?.register({ parentSessionId: parent.session?.header?.id, id: jobId, ledgerKey: profileKeyOf(base.effective), mode: 'background', dispatchedAt: t0, promptExcerpt: promptExcerptOf(args?.prompt), requestedModel: merged.model, parentModel: parent.options?.model }); // 后台结算经 job 结果携带,不入会话块。
|
|
211
|
-
const out = { kind: 'background', jobId, ...meta };
|
|
204
|
+
const out = { kind: 'background', jobId, ...meta, imageText: imageTextOf(meta) };
|
|
212
205
|
finalizeTrace(trace, { effective: effectiveMeta(meta, []), execution: { kind: 'background', parentSessionId: parent.session?.header?.id, jobId, mode: 'one-shot' } });
|
|
213
206
|
out.decisionTrace = assertTraceSize(trace);
|
|
214
207
|
return out;
|
|
@@ -268,6 +261,7 @@ async function runForeground(meta, request, parent, deps, pruneResultOutput, t0,
|
|
|
268
261
|
if (failure !== undefined) throw new Error(withPartialText(failure, result.output));
|
|
269
262
|
const metaOut = {
|
|
270
263
|
...meta,
|
|
264
|
+
imageText: imageTextOf(meta),
|
|
271
265
|
...(childTotalTokens !== undefined ? { childTotalTokens } : {}),
|
|
272
266
|
...(childUsage !== undefined ? { childUsage } : {}),
|
|
273
267
|
elapsedMs,
|
|
@@ -304,7 +298,7 @@ async function prepareTrace(args, parent, deps) {
|
|
|
304
298
|
// enabled、cheap-first)——「模型当时看到哪些候选」是回答「为什么这么选」的记录面。
|
|
305
299
|
const rows = profileDirectoryRows(deps.store);
|
|
306
300
|
const snapshot = profileSnapshotOf(rows, { chosenId: args.profile });
|
|
307
|
-
const advicePresent =
|
|
301
|
+
const advicePresent = false; // advice 模型侧下架,trace 门控恒 false(保留埋点键与面板数据源,勿恢复注入)。
|
|
308
302
|
const trace = createDecisionTrace(
|
|
309
303
|
parentContextOf({
|
|
310
304
|
parentPreset: parentComposed,
|
|
@@ -334,14 +328,14 @@ async function runDispatch(args, exec, deps) {
|
|
|
334
328
|
let guardHandle = { release: () => {} };
|
|
335
329
|
try {
|
|
336
330
|
const merged = mergeProfileArgs(args, deps.store);
|
|
337
|
-
// 信封模式 opt-in
|
|
338
|
-
// 影子段、模型可见),不触碰 descriptor;既有 persona 非空时以空行衔接追加。
|
|
331
|
+
// 信封模式 opt-in:把信封骨架追加进子 persona(systemPrompt 影子段、模型可见),不触碰 descriptor;既有 persona 非空时空行衔接。
|
|
339
332
|
if (args.envelope === true) {
|
|
340
333
|
const hasPersona = merged.persona !== undefined && merged.persona.trim() !== '';
|
|
341
334
|
merged.persona = hasPersona ? `${merged.persona}\n\n${ENVELOPE_SKELETON}` : ENVELOPE_SKELETON;
|
|
342
335
|
}
|
|
343
336
|
assertContinuableEnabled(deps, args);
|
|
344
337
|
applyWhitelistGate(trace, merged, whitelist, parentComposed, escapeSet, baseWhitelist);
|
|
338
|
+
runOfficialModelPreflight({ trace, evoLedger: deps.evoLedger, sessionId: parent.session?.header?.id }, parent, merged, args);
|
|
345
339
|
// Cost guard(运行时推导;硬上限始终生效,llm 能力核验由 allowFailOpen 门控)。
|
|
346
340
|
await applyCostGuardGate(trace, merged, parent, deps);
|
|
347
341
|
// 交集闸(三分支统一闸序,见 dispatch-gates.mjs);continuable 的 effectiveAllow
|
|
@@ -357,9 +351,9 @@ async function runDispatch(args, exec, deps) {
|
|
|
357
351
|
logDispatchDecision(deps.logger, args, merged, parent);
|
|
358
352
|
// 总预算守卫:前/后台在 execute 入口占并发额度(continuable 不占),结算处 release
|
|
359
353
|
// + recordTokens。guardHandle 传入分支用于释放与父 sessionId 记账。
|
|
360
|
-
guardHandle = applyBudgetGate(deps, parent, args, trace);
|
|
354
|
+
guardHandle = applyBudgetGate(deps, parent, args, merged, trace);
|
|
361
355
|
// 派发台账静态元数据(双栏 cfg/task 指纹),分支结算处补 child_id+outcome 写盘。
|
|
362
|
-
const base = deps.evoLedger.baseEntry({ parent, args, merged, mode });
|
|
356
|
+
const base = deps.evoLedger.baseEntry({ parent, args, merged, mode, effectiveModelSource: trace.requested.source });
|
|
363
357
|
recordEscapeAllow(deps, parent, merged, trace);
|
|
364
358
|
if (args.continuable === true) return await runContinuable(args, merged, meta, parent, exec, deps, trace, effectiveAllow, base);
|
|
365
359
|
if (args.run_in_background === true) return await runBackground(args, merged, meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base, exec);
|
|
@@ -375,13 +369,18 @@ export function createDispatchTool({ register, store, getEnabled, getService, lo
|
|
|
375
369
|
const deps = { store, getEnabled, getService, logger, subagents, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, getEscapeSet, getEvolutionAdvice };
|
|
376
370
|
const dispatchTool = defineTool({
|
|
377
371
|
name: 'dispatch',
|
|
378
|
-
description: '派发子任务给派生子 Agent,可逐个覆盖其 preset、model、provider、推理档位、persona、工具白名单、token 预算或递归深度。优先使用已保存的 profile,仅少数字段需要临时覆盖时才传 per-call 参数;未指定 model 时子代理继承父模型,查资料/汇总/读文件等轻任务请优先考虑 cheap 方案或显式 model=flash
|
|
372
|
+
description: '派发子任务给派生子 Agent,可逐个覆盖其 preset、model、provider、推理档位、persona、工具白名单、token 预算或递归深度。优先使用已保存的 profile,仅少数字段需要临时覆盖时才传 per-call 参数;未指定 model 时子代理继承父模型,查资料/汇总/读文件等轻任务请优先考虑 cheap 方案或显式 model=flash。细粒度路由/授权发现请用官方 subagent 工具 + list_subagent_models(provider/model 成对、受本会话官方模型选择白名单);dispatch 只管方案(profile)及方案内覆盖。前台等待结果;run_in_background: true 启动后台任务(单轮即弃);continuable: true 启动持久子 Agent,后续轮次经 send_message 工具延续对话;失败经结算通知回报;超时可 send_message 追问。失败动作映射:stopReason=max-tokens→用 profiles_query 降档或 profile 加 maxTokens;error→换 profile/preset;refusal→重述任务;连续失败→告知用户。前瞻:continuable 模式忽略 preset 换用与 reasoningEffort(结果以 ignored 提示)。',
|
|
379
373
|
parameters: DISPATCH_PARAMETERS,
|
|
380
374
|
output: {
|
|
381
375
|
schema: DISPATCH_OUTPUT_SCHEMA,
|
|
382
376
|
render: DISPATCH_RENDER,
|
|
383
|
-
// 决策轨迹经 presentationMeta 投影进会话块 meta(客户端台账数据源),不进
|
|
384
|
-
|
|
377
|
+
// 决策轨迹经 presentationMeta 投影进会话块 meta(客户端台账数据源),不进
|
|
378
|
+
// render 行;imageText(一句话生效画像)随轨迹一起投影,卡片可见、UI 留后续。
|
|
379
|
+
presentationMeta: (_args, value) => {
|
|
380
|
+
const trace = value.decisionTrace;
|
|
381
|
+
if (trace === undefined) return undefined;
|
|
382
|
+
return { ...trace, ...(typeof value.imageText === 'string' ? { imageText: value.imageText } : {}) };
|
|
383
|
+
},
|
|
385
384
|
},
|
|
386
385
|
isConcurrencySafe: () => true,
|
|
387
386
|
execute: (args, exec) => runDispatch(args, exec, deps),
|