dsh-subagent-profile 0.3.3 → 0.4.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 +49 -20
- package/README.zh.md +102 -73
- package/docs/screenshots/dispatch-card.png +0 -0
- package/docs/screenshots/settings-page1.png +0 -0
- package/docs/screenshots/settings-page2.png +0 -0
- package/index.mjs +153 -75
- package/lib/client.js +3081 -444
- package/lib/core/adoption-reminder.mjs +48 -0
- package/lib/core/adoption-tracker.mjs +430 -0
- package/lib/core/background-ledger.mjs +71 -0
- package/lib/core/cost-evidence.mjs +145 -0
- package/lib/core/cost-guard.mjs +1 -1
- package/lib/core/decision-trace.mjs +11 -31
- package/lib/core/delegation.mjs +11 -7
- package/lib/core/dispatch-gates.mjs +12 -5
- package/lib/core/dispatch-guard.mjs +6 -0
- package/lib/core/dispatch-schema.mjs +10 -5
- package/lib/core/dispatch-tool.mjs +52 -45
- package/lib/core/draft-gates.mjs +45 -0
- package/lib/core/drafts-store.mjs +45 -0
- package/lib/core/evolution-advice.mjs +323 -0
- package/lib/core/evolution-assets.mjs +167 -0
- package/lib/core/evolution-draft.mjs +312 -0
- package/lib/core/evolution-engine.mjs +269 -0
- package/lib/core/evolution-generate.mjs +196 -0
- package/lib/core/evolution-ledger.mjs +149 -33
- package/lib/core/evolution-persistence.mjs +213 -0
- package/lib/core/evolution-renewal.mjs +64 -0
- package/lib/core/evolution-routes.mjs +109 -0
- package/lib/core/evolution-summary.mjs +46 -223
- package/lib/core/http-helpers.mjs +35 -0
- package/lib/core/http-routes.mjs +169 -115
- package/lib/core/presets-sync.mjs +254 -256
- package/lib/core/prices.mjs +46 -0
- package/lib/core/profile-directory.mjs +139 -0
- package/lib/core/profile-provider.mjs +5 -3
- package/lib/core/profiles-store.mjs +24 -6
- package/lib/core/reminder-store.mjs +172 -0
- package/lib/core/session-read.mjs +17 -0
- package/lib/core/shims.mjs +13 -2
- package/package.json +82 -82
- package/presets/orchestrator/agent.cordis.yml +243 -243
|
@@ -5,33 +5,31 @@
|
|
|
5
5
|
// 注入:所有 apply 闭包 / ctx 依赖都是显式参数——
|
|
6
6
|
// register ctx.tools.register(工具由 syncTool 注册/注销,设置开关可运行时移除),
|
|
7
7
|
// store profile store(resolveProfile 取基方案、getAllowFailOpen 供 cost guard),
|
|
8
|
-
// getEnabled 读取 apply 闭包 `enabled` 标志(continuable fail-loud 门 +
|
|
9
|
-
// syncTool 注册条件),
|
|
8
|
+
// getEnabled 读取 apply 闭包 `enabled` 标志(continuable fail-loud 门 + syncTool 注册条件),
|
|
10
9
|
// getService 请求时服务 getter(ctx.get('toolResultPruner') / ctx.get('jobs')
|
|
11
10
|
// 按次读取,绝不在 apply 时读),
|
|
12
11
|
// logger ctx.logger(决策级派发日志 + continuable 告警),
|
|
13
12
|
// subagents ctx.subagents(start / startContinuable 驱动子 Agent),
|
|
14
|
-
// guard dispatch guard(execute 入口 acquire / 结算 release+记账 /
|
|
15
|
-
//
|
|
16
|
-
// 工厂返回 { syncTool, dispose }:syncTool 交给 HTTP 路由(/set-enabled),
|
|
17
|
-
// dispose 在插件卸载时运行。
|
|
13
|
+
// guard dispatch guard(execute 入口 acquire / 结算 release+记账 / syncTool 禁用时 cancelAll+reset)。
|
|
14
|
+
// 工厂返回 { syncTool, dispose }:syncTool 交给 HTTP 路由(/set-enabled),dispose 在插件卸载时运行。
|
|
18
15
|
//
|
|
19
|
-
// execute 按预检段 + 三分支拆为模块级私有函数;parameters
|
|
20
|
-
// dispatch-schema.mjs,预检闸应用拆至 dispatch-gates.mjs(守文件行门),零漂移。
|
|
16
|
+
// execute 按预检段 + 三分支拆为模块级私有函数;parameters/output schema 拆至 dispatch-schema.mjs,预检闸拆至 dispatch-gates.mjs(守文件行门),零漂移。
|
|
21
17
|
|
|
22
18
|
import { defineTool } from './shims.mjs';
|
|
23
|
-
import { textFrom, stopReasonError, withPartialText, pruneBlocks, assertResultSchemaConsistency, trustLabel, trustAudit, dispatchLabel
|
|
19
|
+
import { textFrom, stopReasonError, withPartialText, pruneBlocks, assertResultSchemaConsistency, trustLabel, trustAudit, dispatchLabel } from './pure.mjs';
|
|
24
20
|
import { resolveWhitelist } from './whitelist.mjs';
|
|
25
21
|
import { recordEscapeAllow } from './escape.mjs';
|
|
26
22
|
import { settleStart, collectChildUsage, collectChildCalls } from './delegation.mjs';
|
|
27
23
|
import { DISPATCH_OUTPUT_SCHEMA, DISPATCH_PARAMETERS, DISPATCH_RENDER } from './dispatch-schema.mjs';
|
|
28
24
|
import { createDecisionTrace, finalizeTrace, assertTraceSize, parentContextOf, requestedOf, effectiveMeta, recordApprovalGate, recordFailure, profileSnapshotOf } from './decision-trace.mjs';
|
|
25
|
+
import { profileDirectoryRows } from './profile-directory.mjs';
|
|
29
26
|
import { applyWhitelistGate, applyCostGuardGate, applyIntersectionGate, assertContinuableEnabled, readParentToolNames, applyBudgetGate } from './dispatch-gates.mjs';
|
|
30
27
|
import { trackForegroundInflight, trackBackgroundInflight, trackContinuableInflight } from './dispatch-guard.mjs';
|
|
31
28
|
import { finishForegroundLedger, finishBackgroundLedger, finishContinuableLedger } from './evolution-ledger.mjs';
|
|
29
|
+
import { profileKeyOf } from './evolution-summary.mjs';
|
|
30
|
+
import { promptExcerptOf } from './adoption-reminder.mjs';
|
|
32
31
|
|
|
33
|
-
// 信封骨架:envelope:true 时追加进子 persona
|
|
34
|
-
// 只进 persona(systemPrompt 影子段、模型可见),不进 descriptor(model-hidden,仅 session 记录)。
|
|
32
|
+
// 信封骨架:envelope:true 时追加进子 persona 的结构化汇报契约(三分支共享);只进 persona(模型可见),不进 descriptor(仅 session 记录)。
|
|
35
33
|
const ENVELOPE_SKELETON = '完成前按以下骨架输出:## 结论(1-2 句)\n## 结构分项(逐项)\n## 关键发现落点(文件路径/数据位置)';
|
|
36
34
|
|
|
37
35
|
// --- execute 预检段(从 execute 拆出;行为逐字不变)-----------------------------
|
|
@@ -126,7 +124,7 @@ async function runContinuable(args, merged, meta, parent, exec, deps, trace, eff
|
|
|
126
124
|
if (args.run_in_background === true) {
|
|
127
125
|
deps.logger.warn('[dsh-subagent-profile] dispatch: both continuable and run_in_background are true; continuable takes precedence');
|
|
128
126
|
}
|
|
129
|
-
// 已知降级:continuable 标准路径不支持 preset swap 和 reasoningEffort
|
|
127
|
+
// 已知降级:continuable 标准路径不支持 preset swap 和 reasoningEffort(宿主请求面无对应字段)。
|
|
130
128
|
if (merged.preset !== undefined && merged.preset !== 'inherit') {
|
|
131
129
|
deps.logger.warn(`[dsh-subagent-profile] continuable mode cannot swap preset; ignoring "${merged.preset}" (child inherits the parent preset)`);
|
|
132
130
|
}
|
|
@@ -141,10 +139,10 @@ async function runContinuable(args, merged, meta, parent, exec, deps, trace, eff
|
|
|
141
139
|
signal: exec.signal
|
|
142
140
|
});
|
|
143
141
|
trackContinuableInflight(deps.guard, deps.subagents, childId, parent.session?.header?.id);
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
142
|
+
deps.adoptionTracker?.register({ parentSessionId: parent.session?.header?.id, id: childId, ledgerKey: profileKeyOf(base.effective), mode: 'continuable', dispatchedAt: Date.now(), promptExcerpt: promptExcerptOf(args?.prompt), requestedModel: merged.model, parentModel: parent.options?.model });
|
|
143
|
+
// Continuable 丢弃方案的 preset swap 与 reasoningEffort(子继承父预设),故可观测
|
|
144
|
+
// meta 必须报告实际生效值:reasoningEffort 回显请求值,preset:'inherit' 是生效值,
|
|
145
|
+
// ignored 明确列出被丢弃项。
|
|
148
146
|
// 信任标注不适用:continuable 只回 subagentId、无文本 output(后续经宿主的
|
|
149
147
|
// send_message 流转),没有可加前缀的回收文本,故此处不加 trustLabel。
|
|
150
148
|
const out = {
|
|
@@ -171,7 +169,7 @@ async function runContinuable(args, merged, meta, parent, exec, deps, trace, eff
|
|
|
171
169
|
|
|
172
170
|
// 后台 one-shot(job)路径:jobs.start 包 start() 并提供原生
|
|
173
171
|
// AbortController;仍是一轮即弃,非 continuable。
|
|
174
|
-
async function runBackground(args, merged, meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base) {
|
|
172
|
+
async function runBackground(args, merged, meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base, exec) {
|
|
175
173
|
const jobs = deps.getService('jobs');
|
|
176
174
|
if (jobs === undefined) {
|
|
177
175
|
throw new Error('dispatch: 后台派发不可用:缺少 jobs 服务(请安装 @deepseek-ai/dsh-jobs 与 @deepseek-ai/dsh-tool-jobs,或改用前台派发)');
|
|
@@ -181,6 +179,9 @@ async function runBackground(args, merged, meta, request, parent, deps, pruneRes
|
|
|
181
179
|
const onSettled = (settled) => {
|
|
182
180
|
guardHandle.finish(jobId, settled.childTotalTokens);
|
|
183
181
|
finishBackgroundLedger(deps.evoLedger, base, jobId, settled);
|
|
182
|
+
if (deps.backgroundLedger !== undefined) deps.backgroundLedger.record(base.session_id, jobId, settled, exec?.callId);
|
|
183
|
+
deps.adoptionTracker?.updateOutput(base.session_id, jobId, typeof settled.output === 'string' ? settled.output : '');
|
|
184
|
+
deps.adoptionTracker?.updateSettled?.(base.session_id, jobId, settled);
|
|
184
185
|
};
|
|
185
186
|
jobId = jobs.start({
|
|
186
187
|
kind: 'subagent',
|
|
@@ -188,10 +189,12 @@ async function runBackground(args, merged, meta, request, parent, deps, pruneRes
|
|
|
188
189
|
owner: parent,
|
|
189
190
|
run: () => {
|
|
190
191
|
const controller = new AbortController();
|
|
192
|
+
const startPromise = deps.subagents.start('profile', { ...request, signal: controller.signal });
|
|
193
|
+
startPromise.then((run) => { if (run !== null && run !== undefined && typeof run.id === 'string') deps.backgroundLedger?.recordChild?.(base.session_id, jobId, run.id); }).catch(() => {}); // 结算前补记 jobId → childSessionId,供页头识别后台派发。
|
|
191
194
|
return {
|
|
192
195
|
cancel: (reason) => controller.abort(reason ?? 'dispatch: background subagent task killed'),
|
|
193
196
|
done: settleStart(
|
|
194
|
-
|
|
197
|
+
startPromise,
|
|
195
198
|
controller.signal,
|
|
196
199
|
meta,
|
|
197
200
|
pruneResultOutput,
|
|
@@ -204,15 +207,15 @@ async function runBackground(args, merged, meta, request, parent, deps, pruneRes
|
|
|
204
207
|
}
|
|
205
208
|
});
|
|
206
209
|
trackBackgroundInflight(deps.guard, jobs, jobId, parent);
|
|
207
|
-
|
|
210
|
+
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 结果携带,不入会话块。
|
|
208
211
|
const out = { kind: 'background', jobId, ...meta };
|
|
209
212
|
finalizeTrace(trace, { effective: effectiveMeta(meta, []), execution: { kind: 'background', parentSessionId: parent.session?.header?.id, jobId, mode: 'one-shot' } });
|
|
210
213
|
out.decisionTrace = assertTraceSize(trace);
|
|
211
214
|
return out;
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
// childTotalTokens:宿主 tokenMeter 启发式估算(surface 口径,非计费 usage
|
|
215
|
-
//
|
|
217
|
+
// childTotalTokens:宿主 tokenMeter 启发式估算(surface 口径,非计费 usage);仅
|
|
218
|
+
// completed 结算时测量,服务缺失/非函数/抛错均返回 undefined(fail-soft)。
|
|
216
219
|
function measureChildTokens(getService, session) {
|
|
217
220
|
if (session === undefined || session === null) return undefined;
|
|
218
221
|
const meter = getService('tokenMeter');
|
|
@@ -239,13 +242,13 @@ function measureForegroundRun(deps, run) {
|
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
// 前台:收集结果,无论 reject 与否都释放句柄,非 completed 停因 fail-loud;
|
|
242
|
-
// run.result reject(undefined)也写失败 outcome
|
|
243
|
-
async function runForeground(meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base) {
|
|
245
|
+
// run.result reject(undefined)也写失败 outcome。
|
|
246
|
+
async function runForeground(meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base, adoptionBase) {
|
|
244
247
|
const run = await deps.subagents.start('profile', request);
|
|
245
248
|
finalizeTrace(trace, { execution: { kind: 'foreground', parentSessionId: parent.session?.header?.id, childSessionId: run.id, mode: 'one-shot' } });
|
|
246
249
|
trackForegroundInflight(deps.guard, run);
|
|
247
|
-
|
|
248
|
-
let measurement;
|
|
250
|
+
deps.adoptionTracker?.register({ parentSessionId: parent.session?.header?.id, id: run.id, ledgerKey: profileKeyOf(base.effective), mode: 'foreground', dispatchedAt: t0, ...adoptionBase }); // 前台派发登记 parent_adopted 待判定。
|
|
251
|
+
let result, measurement;
|
|
249
252
|
try {
|
|
250
253
|
result = await run.result;
|
|
251
254
|
if (result.stopReason === 'completed') measurement = measureForegroundRun(deps, run);
|
|
@@ -254,13 +257,13 @@ async function runForeground(meta, request, parent, deps, pruneResultOutput, t0,
|
|
|
254
257
|
await run.dispose().catch(() => {});
|
|
255
258
|
guardHandle.finish(run.id, measurement?.childTotalTokens); // untrack + recordTokens + release
|
|
256
259
|
}
|
|
257
|
-
const childTotalTokens = measurement
|
|
258
|
-
const childUsage = measurement?.childUsage;
|
|
259
|
-
const childCalls = measurement?.childCalls;
|
|
260
|
+
const { childTotalTokens, childUsage, childCalls } = measurement ?? {};
|
|
260
261
|
// 台账:completed 与非 completed 都写 outcome(对照 settleStart 语义),随后非 completed 仍 throw。
|
|
261
262
|
const pruned = pruneResultOutput(result.output);
|
|
262
263
|
const elapsedMs = Date.now() - t0;
|
|
263
|
-
finishForegroundLedger(deps.evoLedger, base, run.id, result, pruned, elapsedMs, childCalls);
|
|
264
|
+
finishForegroundLedger(deps.evoLedger, base, run.id, result, pruned, elapsedMs, childCalls, childTotalTokens, childUsage);
|
|
265
|
+
deps.adoptionTracker?.updateOutput(parent.session?.header?.id, run.id, textFrom(pruned));
|
|
266
|
+
deps.adoptionTracker?.updateSettled?.(parent.session?.header?.id, run.id, { stopReason: result.stopReason, elapsedMs, ...(childTotalTokens !== undefined ? { childTotalTokens } : {}), ...(childUsage !== undefined ? { childUsage } : {}), childSessionId: run.id }); // 结算摘要回填供未采纳提醒结果行,已拒/未完成同样登记。
|
|
264
267
|
const failure = stopReasonError(result);
|
|
265
268
|
if (failure !== undefined) throw new Error(withPartialText(failure, result.output));
|
|
266
269
|
const metaOut = {
|
|
@@ -281,8 +284,7 @@ async function runForeground(meta, request, parent, deps, pruneResultOutput, t0,
|
|
|
281
284
|
},
|
|
282
285
|
});
|
|
283
286
|
// 信任标注:completed 子结果回灌父上下文前加结构化前缀(profile/preset 元数据、
|
|
284
|
-
// 无 prompt 原文),审计同字段 JSON 只进 logger
|
|
285
|
-
// 格式不变——前缀只进 output 文本(模型看到的正文带头)。
|
|
287
|
+
// 无 prompt 原文),审计同字段 JSON 只进 logger、不进结果字符串;前缀只进 output 文本。
|
|
286
288
|
const output = `${trustLabel(metaOut)}${textFrom(pruned)}`;
|
|
287
289
|
deps.logger.info('[dsh-subagent-profile] trusted-output: ' + trustAudit(metaOut));
|
|
288
290
|
return { output, ...metaOut, decisionTrace: assertTraceSize(trace) };
|
|
@@ -292,13 +294,17 @@ async function runForeground(meta, request, parent, deps, pruneResultOutput, t0,
|
|
|
292
294
|
// 与轨迹(避免重复 list() / schemas()),并组装 trace 骨架与交集闸 input。
|
|
293
295
|
async function prepareTrace(args, parent, deps) {
|
|
294
296
|
const agentPresets = parent.ctx.get('agentPresets');
|
|
295
|
-
const
|
|
297
|
+
const baseWhitelist = new Set(await resolveWhitelist(agentPresets));
|
|
298
|
+
const escapeSet = new Set(deps.getEscapeSet());
|
|
299
|
+
const whitelist = new Set([...baseWhitelist, ...escapeSet]);
|
|
296
300
|
const parentComposed = agentPresets !== undefined ? agentPresets.composedPreset(parent.ctx) : undefined;
|
|
297
301
|
const parentToolNames = readParentToolNames(parent);
|
|
298
302
|
const parentToolCount = parentToolNames.length;
|
|
299
|
-
// 决策输入快照:与 dispatch:profiles section 同源同序(
|
|
300
|
-
//
|
|
301
|
-
const
|
|
303
|
+
// 决策输入快照:与 dispatch:profiles section 同源同序(profileDirectoryRows,
|
|
304
|
+
// enabled、cheap-first)——「模型当时看到哪些候选」是回答「为什么这么选」的记录面。
|
|
305
|
+
const rows = profileDirectoryRows(deps.store);
|
|
306
|
+
const snapshot = profileSnapshotOf(rows, { chosenId: args.profile });
|
|
307
|
+
const advicePresent = typeof deps.getEvolutionAdvice === 'function' && deps.getEvolutionAdvice() === true && parentComposed === 'orchestrator';
|
|
302
308
|
const trace = createDecisionTrace(
|
|
303
309
|
parentContextOf({
|
|
304
310
|
parentPreset: parentComposed,
|
|
@@ -307,11 +313,11 @@ async function prepareTrace(args, parent, deps) {
|
|
|
307
313
|
parentToolCount,
|
|
308
314
|
allowFailOpen: deps.store.getAllowFailOpen(),
|
|
309
315
|
}),
|
|
310
|
-
requestedOf(args, { profiles: snapshot.entries })
|
|
316
|
+
requestedOf(args, { profiles: snapshot.entries, advicePresent })
|
|
311
317
|
);
|
|
312
318
|
const mode = args.continuable === true ? 'continuable' : (args.run_in_background === true ? 'background' : 'foreground');
|
|
313
319
|
const intersectionInput = { parentToolCount, requestedToolFilter: args.toolFilter, mode };
|
|
314
|
-
return { whitelist, parentComposed, parentToolNames, trace, mode, intersectionInput };
|
|
320
|
+
return { whitelist, baseWhitelist, escapeSet, parentComposed, parentToolNames, trace, mode, intersectionInput };
|
|
315
321
|
}
|
|
316
322
|
|
|
317
323
|
// execute 主体:预检段 + 三分支分派。任一闸 fail 先记 fail 闸再 rethrow 原错误
|
|
@@ -321,7 +327,7 @@ async function runDispatch(args, exec, deps) {
|
|
|
321
327
|
if (!parent) throw new Error('dispatch: 缺少调用 Agent(值需来自模型/编排者会话,请勿直接调用本工具)');
|
|
322
328
|
// 派发耗时基准:execute 入口记 t0,前台/后台各自在结算处算 elapsedMs。
|
|
323
329
|
const t0 = Date.now();
|
|
324
|
-
const { whitelist, parentComposed, parentToolNames, trace, mode, intersectionInput } = await prepareTrace(args, parent, deps);
|
|
330
|
+
const { whitelist, baseWhitelist, escapeSet, parentComposed, parentToolNames, trace, mode, intersectionInput } = await prepareTrace(args, parent, deps);
|
|
325
331
|
// 预算闸句柄提至 try 外:任何分支 throw(前台 start reject / 后台 jobs 缺失等)
|
|
326
332
|
// 都在 catch 里幂等 release 并发槽——防错误路径把父会话的并发额度永久耗尽
|
|
327
333
|
// (正常路径 finish 已 release,catch 再调为 no-op)。
|
|
@@ -335,7 +341,7 @@ async function runDispatch(args, exec, deps) {
|
|
|
335
341
|
merged.persona = hasPersona ? `${merged.persona}\n\n${ENVELOPE_SKELETON}` : ENVELOPE_SKELETON;
|
|
336
342
|
}
|
|
337
343
|
assertContinuableEnabled(deps, args);
|
|
338
|
-
applyWhitelistGate(trace, merged, whitelist, parentComposed);
|
|
344
|
+
applyWhitelistGate(trace, merged, whitelist, parentComposed, escapeSet, baseWhitelist);
|
|
339
345
|
// Cost guard(运行时推导;硬上限始终生效,llm 能力核验由 allowFailOpen 门控)。
|
|
340
346
|
await applyCostGuardGate(trace, merged, parent, deps);
|
|
341
347
|
// 交集闸(三分支统一闸序,见 dispatch-gates.mjs);continuable 的 effectiveAllow
|
|
@@ -344,6 +350,7 @@ async function runDispatch(args, exec, deps) {
|
|
|
344
350
|
recordApprovalGate(trace);
|
|
345
351
|
const meta = buildMeta(args, merged, parent);
|
|
346
352
|
const request = buildRequest(args, merged, parent, exec.signal);
|
|
353
|
+
const adoptionBase = { promptExcerpt: promptExcerptOf(args?.prompt), requestedModel: merged.model, parentModel: parent.options?.model };
|
|
347
354
|
// 结果回收默认剪枝:在 textFrom 前复用宿主 toolResultPruner.pruneContent 预剪;
|
|
348
355
|
// pruner 缺失时 pruneBlocks 回退为不剪(剪枝是增强、非硬依赖)。
|
|
349
356
|
const pruneResultOutput = (blocks) => pruneBlocks(blocks, deps.getService('toolResultPruner'));
|
|
@@ -355,8 +362,8 @@ async function runDispatch(args, exec, deps) {
|
|
|
355
362
|
const base = deps.evoLedger.baseEntry({ parent, args, merged, mode });
|
|
356
363
|
recordEscapeAllow(deps, parent, merged, trace);
|
|
357
364
|
if (args.continuable === true) return await runContinuable(args, merged, meta, parent, exec, deps, trace, effectiveAllow, base);
|
|
358
|
-
if (args.run_in_background === true) return await runBackground(args, merged, meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base);
|
|
359
|
-
return await runForeground(meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base);
|
|
365
|
+
if (args.run_in_background === true) return await runBackground(args, merged, meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base, exec);
|
|
366
|
+
return await runForeground(meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base, adoptionBase);
|
|
360
367
|
} catch (error) {
|
|
361
368
|
guardHandle.release();
|
|
362
369
|
recordFailure(deps.ledger, parent, trace);
|
|
@@ -364,16 +371,16 @@ async function runDispatch(args, exec, deps) {
|
|
|
364
371
|
}
|
|
365
372
|
}
|
|
366
373
|
|
|
367
|
-
export function createDispatchTool({ register, store, getEnabled, getService, logger, subagents, catalog, ledger, guard, evoLedger, getEscapeSet }) {
|
|
368
|
-
const deps = { store, getEnabled, getService, logger, subagents, catalog, ledger, guard, evoLedger, getEscapeSet };
|
|
374
|
+
export function createDispatchTool({ register, store, getEnabled, getService, logger, subagents, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, getEscapeSet, getEvolutionAdvice }) {
|
|
375
|
+
const deps = { store, getEnabled, getService, logger, subagents, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, getEscapeSet, getEvolutionAdvice };
|
|
369
376
|
const dispatchTool = defineTool({
|
|
370
377
|
name: 'dispatch',
|
|
371
|
-
description: '派发子任务给派生子 Agent,可逐个覆盖其 preset、model、provider、推理档位、persona、工具白名单、token
|
|
378
|
+
description: '派发子任务给派生子 Agent,可逐个覆盖其 preset、model、provider、推理档位、persona、工具白名单、token 预算或递归深度。优先使用已保存的 profile,仅少数字段需要临时覆盖时才传 per-call 参数;未指定 model 时子代理继承父模型,查资料/汇总/读文件等轻任务请优先考虑 cheap 方案或显式 model=flash。前台等待结果;run_in_background: true 启动后台任务(单轮即弃);continuable: true 启动持久子 Agent,后续轮次经 send_message 工具延续对话。前瞻:continuable 模式忽略 preset 换用与 reasoningEffort(结果以 ignored 提示)。',
|
|
372
379
|
parameters: DISPATCH_PARAMETERS,
|
|
373
380
|
output: {
|
|
374
381
|
schema: DISPATCH_OUTPUT_SCHEMA,
|
|
375
382
|
render: DISPATCH_RENDER,
|
|
376
|
-
// 决策轨迹经 presentationMeta 投影进会话块 meta
|
|
383
|
+
// 决策轨迹经 presentationMeta 投影进会话块 meta(客户端台账数据源),不进 render 行。
|
|
377
384
|
presentationMeta: (_args, value) => value.decisionTrace,
|
|
378
385
|
},
|
|
379
386
|
isConcurrencySafe: () => true,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// lib/core/draft-gates.mjs — auto-profile S1 draft 落库前的闸评估。
|
|
2
|
+
// 与派发守卫闸同源(形状/白名单/目录),但不落库、不 throw——只产出结构化 checks
|
|
3
|
+
// 供 /draft/preview 与应用复用。依赖注入:ctx / catalog 由调用方传入;store 只读
|
|
4
|
+
// profiles Map 判断 id 冲突。
|
|
5
|
+
|
|
6
|
+
import { sanitizeProfile } from './pure.mjs';
|
|
7
|
+
import { resolveWhitelist } from './whitelist.mjs';
|
|
8
|
+
|
|
9
|
+
// 评估一个 draft:sanitize(形状/硬上限)→ id 冲突 → 白名单 → 目录。checks 每项
|
|
10
|
+
// 为 { name, verdict: 'pass'|'fail', reason? };ok = 全部 pass。任一闸 fail 时 clean
|
|
11
|
+
// 仍返回(sanitize 归一后的形态),调用方可用其展示「将被写入的生效值」。
|
|
12
|
+
export async function assessDraftProfile({ ctx, store, catalog, getEscapeSet, draft }) {
|
|
13
|
+
if (draft === null || typeof draft !== 'object') {
|
|
14
|
+
return { ok: false, checks: [{ name: 'draft', verdict: 'fail', reason: 'draft 不存在' }], clean: null };
|
|
15
|
+
}
|
|
16
|
+
const config = draft.config !== null && typeof draft.config === 'object' ? draft.config : {};
|
|
17
|
+
const profile = { ...config, name: draft.name, description: draft.description };
|
|
18
|
+
const { clean, warnings } = sanitizeProfile(profile, { strict: true });
|
|
19
|
+
const checks = [];
|
|
20
|
+
checks.push(warnings.length > 0
|
|
21
|
+
? { name: 'sanitize', verdict: 'fail', reason: warnings.map((w) => w.field + ':' + w.reason).join(';') }
|
|
22
|
+
: { name: 'sanitize', verdict: 'pass' });
|
|
23
|
+
if (typeof clean.id !== 'string' || clean.id === '') {
|
|
24
|
+
checks.push({ name: 'id', verdict: 'fail', reason: 'draft 缺少 profile id' });
|
|
25
|
+
} else if (store.profiles.has(clean.id)) {
|
|
26
|
+
checks.push({ name: 'id', verdict: 'fail', reason: 'profile ' + clean.id + ' 已存在(请先删除或改名)' });
|
|
27
|
+
} else {
|
|
28
|
+
checks.push({ name: 'id', verdict: 'pass' });
|
|
29
|
+
}
|
|
30
|
+
const whitelist = new Set(await resolveWhitelist(ctx.get('agentPresets'), getEscapeSet()));
|
|
31
|
+
if (typeof clean.preset === 'string' && clean.preset !== '' && clean.preset !== 'inherit' && !whitelist.has(clean.preset)) {
|
|
32
|
+
checks.push({ name: 'whitelist', verdict: 'fail', reason: '目标预设 ' + clean.preset + ' 不在 system-trust 白名单(可开启逃生舱并添加该预设后重试)' });
|
|
33
|
+
} else {
|
|
34
|
+
checks.push({ name: 'whitelist', verdict: 'pass' });
|
|
35
|
+
}
|
|
36
|
+
const snapshot = await catalog.getSnapshot();
|
|
37
|
+
if (typeof clean.model === 'string' && clean.model !== '' && !snapshot.models.some((m) => m && m.id === clean.model)) {
|
|
38
|
+
checks.push({ name: 'catalog', verdict: 'fail', reason: '模型 ' + clean.model + ' 不在当前模型目录' });
|
|
39
|
+
} else if (typeof clean.provider === 'string' && clean.provider !== '' && !snapshot.models.some((m) => m && m.provider === clean.provider)) {
|
|
40
|
+
checks.push({ name: 'catalog', verdict: 'fail', reason: '提供方 ' + clean.provider + ' 不在当前模型目录' });
|
|
41
|
+
} else {
|
|
42
|
+
checks.push({ name: 'catalog', verdict: 'pass' });
|
|
43
|
+
}
|
|
44
|
+
return { ok: checks.every((c) => c.verdict === 'pass'), checks, clean };
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// lib/core/drafts-store.mjs — auto-profile draft 容器。
|
|
2
|
+
// 与生产注册表 subagent-profiles.json 隔离:draft 只写 subagent-evolution/drafts.json,
|
|
3
|
+
// 未经人确认并过闸不得进入 profiles Map。原子写(tmp+rename),损坏 fail-soft 从空。
|
|
4
|
+
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
function loadDrafts(file) {
|
|
9
|
+
if (!existsSync(file)) return [];
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
12
|
+
if (parsed !== null && typeof parsed === 'object' && Array.isArray(parsed.drafts)) {
|
|
13
|
+
return parsed.drafts.filter((d) => d !== null && typeof d === 'object' && typeof d.id === 'string');
|
|
14
|
+
}
|
|
15
|
+
} catch { /* fail-soft 从空 */ }
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createDraftsStore({ dshHome, onGovernanceFailure = () => {} } = {}) {
|
|
20
|
+
const dir = join(dshHome, 'subagent-evolution');
|
|
21
|
+
const file = join(dir, 'drafts.json');
|
|
22
|
+
const drafts = loadDrafts(file);
|
|
23
|
+
const persist = () => {
|
|
24
|
+
const tmp = file + '.tmp';
|
|
25
|
+
try {
|
|
26
|
+
mkdirSync(dir, { recursive: true });
|
|
27
|
+
writeFileSync(tmp, JSON.stringify({ v: 1, drafts }, null, 2), 'utf8');
|
|
28
|
+
renameSync(tmp, file);
|
|
29
|
+
return { persisted: true };
|
|
30
|
+
} catch (error) {
|
|
31
|
+
try { rmSync(tmp, { force: true }); } catch { /* best effort */ }
|
|
32
|
+
onGovernanceFailure();
|
|
33
|
+
return { persisted: false, error: error instanceof Error ? error.message : String(error) };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
list: () => drafts.map((d) => ({ ...d })),
|
|
38
|
+
get: (id) => { const d = drafts.find((x) => x.id === id); return d === undefined ? undefined : { ...d }; },
|
|
39
|
+
add: (draft) => { drafts.push(draft); return persist(); },
|
|
40
|
+
remove: (id) => { const i = drafts.findIndex((x) => x.id === id); if (i < 0) return { persisted: true, removed: false }; drafts.splice(i, 1); return { ...persist(), removed: true }; },
|
|
41
|
+
clear: () => { drafts.length = 0; },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { createDraftsStore };
|