dsh-subagent-profile 0.4.0 → 0.5.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 +16 -57
- package/README.zh.md +16 -57
- package/index.mjs +97 -122
- package/lib/client.js +631 -88
- package/lib/core/cleanup.mjs +33 -0
- package/lib/core/data-maintenance.mjs +139 -0
- package/lib/core/decision-trace.mjs +20 -1
- package/lib/core/delegation.mjs +16 -0
- package/lib/core/dispatch-gates.mjs +122 -9
- package/lib/core/dispatch-schema.mjs +45 -7
- package/lib/core/dispatch-tool.mjs +30 -46
- package/lib/core/evolution-advice.mjs +31 -10
- package/lib/core/evolution-assets.mjs +13 -0
- package/lib/core/evolution-canary.mjs +427 -0
- package/lib/core/evolution-draft.mjs +21 -0
- package/lib/core/evolution-engine.mjs +176 -31
- package/lib/core/evolution-generate.mjs +7 -23
- package/lib/core/evolution-lag.mjs +78 -0
- package/lib/core/evolution-ledger.mjs +4 -1
- package/lib/core/evolution-routes.mjs +157 -16
- package/lib/core/http-routes.mjs +22 -7
- package/lib/core/model-policy.mjs +104 -0
- package/lib/core/presets-sync.mjs +24 -2
- package/lib/core/profile-directory.mjs +17 -0
- package/lib/core/profile-provider.mjs +7 -2
- package/lib/core/profiles-query.mjs +178 -0
- package/lib/core/profiles-store.mjs +40 -6
- package/lib/core/pure.mjs +1 -1
- package/lib/core/reminder-store.mjs +37 -3
- package/lib/core/snapshots.mjs +148 -0
- 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
|
@@ -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
|
-
import { settleStart, collectChildUsage, collectChildCalls } from './delegation.mjs';
|
|
23
|
-
import { DISPATCH_OUTPUT_SCHEMA, DISPATCH_PARAMETERS, DISPATCH_RENDER } from './dispatch-schema.mjs';
|
|
13
|
+
import { settleStart, collectChildUsage, collectChildCalls, measureChildTokens } from './delegation.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,28 +201,12 @@ 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;
|
|
215
208
|
}
|
|
216
209
|
|
|
217
|
-
// childTotalTokens:宿主 tokenMeter 启发式估算(surface 口径,非计费 usage);仅
|
|
218
|
-
// completed 结算时测量,服务缺失/非函数/抛错均返回 undefined(fail-soft)。
|
|
219
|
-
function measureChildTokens(getService, session) {
|
|
220
|
-
if (session === undefined || session === null) return undefined;
|
|
221
|
-
const meter = getService('tokenMeter');
|
|
222
|
-
if (meter === undefined || typeof meter.measure !== 'function') return undefined;
|
|
223
|
-
try {
|
|
224
|
-
const measured = meter.measure(session);
|
|
225
|
-
return measured !== null && typeof measured === 'object' && typeof measured.totalTokens === 'number'
|
|
226
|
-
? measured.totalTokens
|
|
227
|
-
: undefined;
|
|
228
|
-
} catch {
|
|
229
|
-
return undefined;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
210
|
// 仅 completed 结算时测量一次(tokenMeter 估算 + usage 分解 + 调用明细),dispose
|
|
234
211
|
// 前读子 session 保证事件流完整;全 fail-soft(缺失/抛错 → undefined)绝不阻断结算。
|
|
235
212
|
function measureForegroundRun(deps, run) {
|
|
@@ -268,6 +245,7 @@ async function runForeground(meta, request, parent, deps, pruneResultOutput, t0,
|
|
|
268
245
|
if (failure !== undefined) throw new Error(withPartialText(failure, result.output));
|
|
269
246
|
const metaOut = {
|
|
270
247
|
...meta,
|
|
248
|
+
imageText: imageTextOf(meta),
|
|
271
249
|
...(childTotalTokens !== undefined ? { childTotalTokens } : {}),
|
|
272
250
|
...(childUsage !== undefined ? { childUsage } : {}),
|
|
273
251
|
elapsedMs,
|
|
@@ -304,7 +282,7 @@ async function prepareTrace(args, parent, deps) {
|
|
|
304
282
|
// enabled、cheap-first)——「模型当时看到哪些候选」是回答「为什么这么选」的记录面。
|
|
305
283
|
const rows = profileDirectoryRows(deps.store);
|
|
306
284
|
const snapshot = profileSnapshotOf(rows, { chosenId: args.profile });
|
|
307
|
-
const advicePresent =
|
|
285
|
+
const advicePresent = false; // advice 模型侧下架,trace 门控恒 false(保留埋点键与面板数据源,勿恢复注入)。
|
|
308
286
|
const trace = createDecisionTrace(
|
|
309
287
|
parentContextOf({
|
|
310
288
|
parentPreset: parentComposed,
|
|
@@ -334,14 +312,14 @@ async function runDispatch(args, exec, deps) {
|
|
|
334
312
|
let guardHandle = { release: () => {} };
|
|
335
313
|
try {
|
|
336
314
|
const merged = mergeProfileArgs(args, deps.store);
|
|
337
|
-
// 信封模式 opt-in
|
|
338
|
-
// 影子段、模型可见),不触碰 descriptor;既有 persona 非空时以空行衔接追加。
|
|
315
|
+
// 信封模式 opt-in:把信封骨架追加进子 persona(systemPrompt 影子段、模型可见),不触碰 descriptor;既有 persona 非空时空行衔接。
|
|
339
316
|
if (args.envelope === true) {
|
|
340
317
|
const hasPersona = merged.persona !== undefined && merged.persona.trim() !== '';
|
|
341
318
|
merged.persona = hasPersona ? `${merged.persona}\n\n${ENVELOPE_SKELETON}` : ENVELOPE_SKELETON;
|
|
342
319
|
}
|
|
343
320
|
assertContinuableEnabled(deps, args);
|
|
344
321
|
applyWhitelistGate(trace, merged, whitelist, parentComposed, escapeSet, baseWhitelist);
|
|
322
|
+
runOfficialModelPreflight({ trace, evoLedger: deps.evoLedger, sessionId: parent.session?.header?.id }, parent, merged, args);
|
|
345
323
|
// Cost guard(运行时推导;硬上限始终生效,llm 能力核验由 allowFailOpen 门控)。
|
|
346
324
|
await applyCostGuardGate(trace, merged, parent, deps);
|
|
347
325
|
// 交集闸(三分支统一闸序,见 dispatch-gates.mjs);continuable 的 effectiveAllow
|
|
@@ -351,15 +329,16 @@ async function runDispatch(args, exec, deps) {
|
|
|
351
329
|
const meta = buildMeta(args, merged, parent);
|
|
352
330
|
const request = buildRequest(args, merged, parent, exec.signal);
|
|
353
331
|
const adoptionBase = { promptExcerpt: promptExcerptOf(args?.prompt), requestedModel: merged.model, parentModel: parent.options?.model };
|
|
354
|
-
//
|
|
355
|
-
// pruner 缺失时 pruneBlocks 回退为不剪(剪枝是增强、非硬依赖)。
|
|
332
|
+
// 结果回收默认剪枝:textFrom 前复用宿主 toolResultPruner.pruneContent 预剪;pruner 缺失时 pruneBlocks 回退为不剪(增强、非硬依赖)。
|
|
356
333
|
const pruneResultOutput = (blocks) => pruneBlocks(blocks, deps.getService('toolResultPruner'));
|
|
357
334
|
logDispatchDecision(deps.logger, args, merged, parent);
|
|
358
335
|
// 总预算守卫:前/后台在 execute 入口占并发额度(continuable 不占),结算处 release
|
|
359
336
|
// + recordTokens。guardHandle 传入分支用于释放与父 sessionId 记账。
|
|
360
|
-
guardHandle = applyBudgetGate(deps, parent, args, trace);
|
|
337
|
+
guardHandle = applyBudgetGate(deps, parent, args, merged, trace);
|
|
361
338
|
// 派发台账静态元数据(双栏 cfg/task 指纹),分支结算处补 child_id+outcome 写盘。
|
|
362
|
-
const base = deps.evoLedger.baseEntry({ parent, args, merged, mode });
|
|
339
|
+
const base = deps.evoLedger.baseEntry({ parent, args, merged, mode, effectiveModelSource: trace.requested.source });
|
|
340
|
+
// 配置快照(开关关恒跳过;失败绝不阻断派发):记录完整 config 供「保存为方案」复现。
|
|
341
|
+
if (deps.snapshots !== undefined && typeof deps.snapshots.capture === 'function') { try { deps.snapshots.capture({ profileKey: profileKeyOf(base.effective), config: merged, mode }); } catch { /* fail-soft */ } }
|
|
363
342
|
recordEscapeAllow(deps, parent, merged, trace);
|
|
364
343
|
if (args.continuable === true) return await runContinuable(args, merged, meta, parent, exec, deps, trace, effectiveAllow, base);
|
|
365
344
|
if (args.run_in_background === true) return await runBackground(args, merged, meta, request, parent, deps, pruneResultOutput, t0, trace, guardHandle, base, exec);
|
|
@@ -371,17 +350,22 @@ async function runDispatch(args, exec, deps) {
|
|
|
371
350
|
}
|
|
372
351
|
}
|
|
373
352
|
|
|
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 };
|
|
353
|
+
export function createDispatchTool({ register, store, getEnabled, getService, logger, subagents, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, getEscapeSet, getEvolutionAdvice, snapshots }) {
|
|
354
|
+
const deps = { store, getEnabled, getService, logger, subagents, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, getEscapeSet, getEvolutionAdvice, snapshots };
|
|
376
355
|
const dispatchTool = defineTool({
|
|
377
356
|
name: 'dispatch',
|
|
378
|
-
description: '派发子任务给派生子 Agent,可逐个覆盖其 preset、model、provider、推理档位、persona、工具白名单、token 预算或递归深度。优先使用已保存的 profile,仅少数字段需要临时覆盖时才传 per-call 参数;未指定 model 时子代理继承父模型,查资料/汇总/读文件等轻任务请优先考虑 cheap 方案或显式 model=flash
|
|
357
|
+
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
358
|
parameters: DISPATCH_PARAMETERS,
|
|
380
359
|
output: {
|
|
381
360
|
schema: DISPATCH_OUTPUT_SCHEMA,
|
|
382
361
|
render: DISPATCH_RENDER,
|
|
383
|
-
// 决策轨迹经 presentationMeta 投影进会话块 meta(客户端台账数据源),不进
|
|
384
|
-
|
|
362
|
+
// 决策轨迹经 presentationMeta 投影进会话块 meta(客户端台账数据源),不进
|
|
363
|
+
// render 行;imageText(一句话生效画像)随轨迹一起投影,卡片可见、UI 留后续。
|
|
364
|
+
presentationMeta: (_args, value) => {
|
|
365
|
+
const trace = value.decisionTrace;
|
|
366
|
+
if (trace === undefined) return undefined;
|
|
367
|
+
return { ...trace, ...(typeof value.imageText === 'string' ? { imageText: value.imageText } : {}) };
|
|
368
|
+
},
|
|
385
369
|
},
|
|
386
370
|
isConcurrencySafe: () => true,
|
|
387
371
|
execute: (args, exec) => runDispatch(args, exec, deps),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// lib/core/evolution-advice.mjs — evolution:advice 派发优化建议链(从 evolution-summary.mjs
|
|
2
2
|
// 拆出以守文件行门):suggestAdvice(纯函数四条件 + 升/降轴硬规则)、readSummaries /
|
|
3
|
-
// refreshSummaries / computeAdvice /
|
|
3
|
+
// refreshSummaries / computeAdvice / renderAdviceText(人类面板数据源)。
|
|
4
4
|
// 聚合本体(computeSummaries/writeSummaries)仍驻 evolution-summary.mjs;本模块只读
|
|
5
5
|
// 台账并调度聚合,绝无写回台账路径。fail-soft 纪律与 evolution-summary 同源。
|
|
6
|
+
// 本轮起模型侧注入已下架(index.mjs 的 evolution:advice section 恒空):本链只为
|
|
7
|
+
// 人类面板(建议卡/提醒中心/审计行)服务,不再生成模型可见文本。
|
|
6
8
|
//
|
|
7
9
|
// 依赖方向:本模块 → evolution-summary.mjs(单向,无环)。
|
|
8
10
|
|
|
@@ -18,7 +20,7 @@ import { loadWithMigration } from './evolution-persistence.mjs';
|
|
|
18
20
|
// (平)。阈值 successThreshold / confidenceMinLevel / cooldownMs 为建议初值,
|
|
19
21
|
// 待实测后校准。
|
|
20
22
|
const CONFIDENCE_RANK = { low: 0, medium: 1, high: 2 };
|
|
21
|
-
//
|
|
23
|
+
// 置信度级别中文映射(面板文本人话化,与客户端 ZH.confidenceZh 同口径)。
|
|
22
24
|
const CONFIDENCE_ZH = { low: '低', medium: '中等', high: '高' };
|
|
23
25
|
|
|
24
26
|
function hasCapabilityAxis(profileKey) {
|
|
@@ -53,7 +55,7 @@ function performanceText(entry) {
|
|
|
53
55
|
const perf = entry?.perf ?? {};
|
|
54
56
|
const parts = [
|
|
55
57
|
`过去 ${entry?.deployments_total ?? 0} 次:完成 ${outcome.completed ?? 0}、失败 ${outcome.failed ?? 0}、终止 ${outcome.killed ?? 0}`,
|
|
56
|
-
|
|
58
|
+
`近期表现分 ${entry?.score?.weighted_success ?? 0}`,
|
|
57
59
|
];
|
|
58
60
|
if (perf.avg_elapsed_ms !== null && perf.avg_elapsed_ms !== undefined) parts.push(`平均耗时 ${perf.avg_elapsed_ms}ms`);
|
|
59
61
|
if (perf.avg_output_len !== null && perf.avg_output_len !== undefined) parts.push(`平均输出 ${perf.avg_output_len} 字符`);
|
|
@@ -117,7 +119,7 @@ function downReasonText(segments, entry) {
|
|
|
117
119
|
if (segments.toolFilter) return '移除工具过滤后,子 Agent 的工具面回到父会话范围,配置更简单';
|
|
118
120
|
if (segments.model) return '同类任务改用更低档模型,成本通常更低';
|
|
119
121
|
if (segments.preset) return '相邻低档预设能力相近,成本通常更低';
|
|
120
|
-
return
|
|
122
|
+
return `近期表现低于预期(近期表现分 ${entry?.score?.weighted_success ?? 0}),降低能力配置是更稳妥的方向`;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
function upReasonText(profileKey, l2) {
|
|
@@ -170,7 +172,7 @@ export function suggestAdvice({
|
|
|
170
172
|
};
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
// ---
|
|
175
|
+
// --- 派发优化建议(面板侧;模型侧注入下架,注入段恒空)-------------------
|
|
174
176
|
|
|
175
177
|
// 读 summaries.json(存在才读)。版本化经通用持久化机制:v 低于当前版本时内存
|
|
176
178
|
// 前向迁移后原子写回;损坏/形状不符/未知或超前版本 fail-soft 返回 null,并告警
|
|
@@ -213,7 +215,8 @@ function summariesStale(dispatchFile, summariesFile) {
|
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
|
|
216
|
-
//
|
|
218
|
+
// 面板侧文本:全局聚合摘要 + 各 profile 一条确定性建议(人类读、非指令;
|
|
219
|
+
// 只供人类面板渲染,不再进模型侧注入段)。
|
|
217
220
|
export function renderAdviceText(advice, global) {
|
|
218
221
|
const direction = (value) => (value === '升' ? '可上调预算' : value === '降' ? '建议降级' : '维持现状');
|
|
219
222
|
const lines = advice.map((a) => {
|
|
@@ -229,8 +232,9 @@ export function renderAdviceText(advice, global) {
|
|
|
229
232
|
|
|
230
233
|
// 建议产出后把各 profile 的新 cooldown.until_ts 写回 summaries.json(原子写、
|
|
231
234
|
// fail-soft)。写前重读磁盘再合并:并发注入各自基于最新磁盘态写回,把互相覆盖
|
|
232
|
-
//
|
|
233
|
-
|
|
235
|
+
// 窗口压到最小(残余竞态:读-写间隙,彻底解决需进程内锁,成本不值)。回滚冷却
|
|
236
|
+
// 注入(suggestion-rollback 后同键冷却)复用同一写入口。
|
|
237
|
+
export function writeBackCooldowns(file, data, produced, logger) {
|
|
234
238
|
const fresh = readSummaries(file, logger);
|
|
235
239
|
const base = fresh !== null && typeof fresh === 'object' ? fresh : data;
|
|
236
240
|
const l1 = { ...(base.l1 ?? {}) };
|
|
@@ -244,7 +248,23 @@ function writeBackCooldowns(file, data, produced, logger) {
|
|
|
244
248
|
}
|
|
245
249
|
}
|
|
246
250
|
|
|
247
|
-
//
|
|
251
|
+
// 滞后防振荡 streak 写回 summaries.json(原子写、fail-soft):与 cooldown 同口径
|
|
252
|
+
// 重读磁盘再合并;写入失败只 warn,内存计数仍驱动本轮生成。
|
|
253
|
+
export function writeBackStreaks(summariesFile, streaks, logger) {
|
|
254
|
+
const fresh = readSummaries(summariesFile, logger);
|
|
255
|
+
if (fresh === null || typeof fresh !== 'object') return;
|
|
256
|
+
const l1 = { ...(fresh.l1 ?? {}) };
|
|
257
|
+
for (const streak of streaks) {
|
|
258
|
+
const group = l1[streak.profileKey];
|
|
259
|
+
l1[streak.profileKey] = { ...group, streak: { axis: streak.axis, direction: streak.direction, count: streak.count, last_ts: streak.last_ts } };
|
|
260
|
+
}
|
|
261
|
+
const res = writeSummaries(summariesFile, { l1, l2: fresh.l2 ?? {} });
|
|
262
|
+
if (res.persisted !== true) {
|
|
263
|
+
logger.warn(`[dsh-subagent-profile] evolution:advice streak 写回失败:${res.error ?? '未知错误'}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 结构化建议(读+算,不写回 cooldown、不渲染文本):供人类面板与只读展示共用。
|
|
248
268
|
export function computeAdvice({ summariesFile, dispatchFile, whitelist, logger, onLoss }) {
|
|
249
269
|
if (dispatchFile !== undefined && summariesStale(dispatchFile, summariesFile)) refreshSummaries({ dispatchFile, summariesFile, logger });
|
|
250
270
|
let data = readSummaries(summariesFile, logger, { onLoss });
|
|
@@ -276,7 +296,8 @@ export function computeAdvice({ summariesFile, dispatchFile, whitelist, logger,
|
|
|
276
296
|
return { advice, global: { ...global, total: global.completed + global.failed + global.killed }, produced, summaries: data };
|
|
277
297
|
}
|
|
278
298
|
|
|
279
|
-
//
|
|
299
|
+
// 面板侧建议文本生成:computeAdvice → 写回 cooldown(防每请求重复注入)→ 渲染;空建议返回空串。
|
|
300
|
+
// 仅供人类面板(/list 建议区)调用;模型侧注入段已下架(index.mjs 恒空)。
|
|
280
301
|
export function buildAdviceText({ summariesFile, dispatchFile, whitelist, logger, onLoss }) {
|
|
281
302
|
const { advice, global, produced, summaries } = computeAdvice({ summariesFile, dispatchFile, whitelist, logger, onLoss });
|
|
282
303
|
if (advice.length === 0) return '';
|
|
@@ -81,6 +81,18 @@ function sweepExpiredAssets() {
|
|
|
81
81
|
return 0;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// 数据维护清理:删除全部资产文件 + 索引置空(审计由调用方记)。返回删除条数;
|
|
85
|
+
// 单个文件删除失败不阻断其余(best effort)。
|
|
86
|
+
function purgeAllAssets(state) {
|
|
87
|
+
const count = state.index.assets.length;
|
|
88
|
+
for (const entry of state.index.assets) {
|
|
89
|
+
try { rmSync(join(state.dir, `${entry.id}.json`), { force: true }); } catch { /* best effort */ }
|
|
90
|
+
}
|
|
91
|
+
state.index.assets = [];
|
|
92
|
+
state.persistIndex();
|
|
93
|
+
return count;
|
|
94
|
+
}
|
|
95
|
+
|
|
84
96
|
// 状态流转:资产文件先行(合并 patch + 状态 + updatedAt),成功后同步索引。
|
|
85
97
|
function transitionAsset(state, id, nextState, patch) {
|
|
86
98
|
const safe = safeAssetId(id);
|
|
@@ -161,6 +173,7 @@ export function createEvolutionAssets({ dshHome, logger, onGovernanceFailure = (
|
|
|
161
173
|
.filter((e) => e.profileKey === profileKey && UNRESOLVED_STATES.has(e.state))
|
|
162
174
|
.map((e) => ({ ...e })),
|
|
163
175
|
countBySource: (source) => state.index.assets.filter((e) => e.source === source).length,
|
|
176
|
+
purgeAll: () => purgeAllAssets(state),
|
|
164
177
|
ids: () => state.index.assets.map((e) => e.id),
|
|
165
178
|
sweepExpired: (now) => sweepExpiredAssets(state, now),
|
|
166
179
|
};
|