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.
Files changed (37) hide show
  1. package/README.md +16 -57
  2. package/README.zh.md +16 -57
  3. package/index.mjs +97 -122
  4. package/lib/client.js +631 -88
  5. package/lib/core/cleanup.mjs +33 -0
  6. package/lib/core/data-maintenance.mjs +139 -0
  7. package/lib/core/decision-trace.mjs +20 -1
  8. package/lib/core/delegation.mjs +16 -0
  9. package/lib/core/dispatch-gates.mjs +122 -9
  10. package/lib/core/dispatch-schema.mjs +45 -7
  11. package/lib/core/dispatch-tool.mjs +30 -46
  12. package/lib/core/evolution-advice.mjs +31 -10
  13. package/lib/core/evolution-assets.mjs +13 -0
  14. package/lib/core/evolution-canary.mjs +427 -0
  15. package/lib/core/evolution-draft.mjs +21 -0
  16. package/lib/core/evolution-engine.mjs +176 -31
  17. package/lib/core/evolution-generate.mjs +7 -23
  18. package/lib/core/evolution-lag.mjs +78 -0
  19. package/lib/core/evolution-ledger.mjs +4 -1
  20. package/lib/core/evolution-routes.mjs +157 -16
  21. package/lib/core/http-routes.mjs +22 -7
  22. package/lib/core/model-policy.mjs +104 -0
  23. package/lib/core/presets-sync.mjs +24 -2
  24. package/lib/core/profile-directory.mjs +17 -0
  25. package/lib/core/profile-provider.mjs +7 -2
  26. package/lib/core/profiles-query.mjs +178 -0
  27. package/lib/core/profiles-store.mjs +40 -6
  28. package/lib/core/pure.mjs +1 -1
  29. package/lib/core/reminder-store.mjs +37 -3
  30. package/lib/core/snapshots.mjs +148 -0
  31. package/package.json +2 -2
  32. package/presets/orchestrator-v2/NOTICE +7 -0
  33. package/presets/{orchestrator → orchestrator-v2}/agent.cordis.yml +78 -7
  34. package/presets/orchestrator-v2/custom-bash.mjs +213 -0
  35. package/presets/orchestrator-v2/preset.yml +2 -0
  36. package/presets/orchestrator-v2/tool-bootstrap.mjs +620 -0
  37. 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
- // 注入:所有 apply 闭包 / ctx 依赖都是显式参数——
6
- // register ctx.tools.register(工具由 syncTool 注册/注销,设置开关可运行时移除),
7
- // store profile store(resolveProfile 取基方案、getAllowFailOpen 供 cost guard),
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 = typeof deps.getEvolutionAdvice === 'function' && deps.getEvolutionAdvice() === true && parentComposed === 'orchestrator';
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:三分支创建子 Agent 前,把信封骨架追加进子 persona(systemPrompt
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
- // 结果回收默认剪枝:在 textFrom 前复用宿主 toolResultPruner.pruneContent 预剪;
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。前台等待结果;run_in_background: true 启动后台任务(单轮即弃);continuable: true 启动持久子 Agent,后续轮次经 send_message 工具延续对话。前瞻:continuable 模式忽略 preset 换用与 reasoningEffort(结果以 ignored 提示)。',
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(客户端台账数据源),不进 render 行。
384
- presentationMeta: (_args, value) => value.decisionTrace,
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 / buildAdviceText(生产聚合触发与注入文本)。
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
- // 置信度级别中文映射(注入文本人话化,与客户端 ZH.confidenceZh 同口径)。
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
- `加权成功分 ${entry?.score?.weighted_success ?? 0}`,
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 `近期表现低于预期(加权成功分 ${entry?.score?.weighted_success ?? 0}),降低能力配置是更稳妥的方向`;
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
- // --- 派发优化建议(evolution:advice 注入段,默认关)----------------------------
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
- // 注入段文本:全局聚合摘要 + 各 profile 一条确定性建议(人类读、非指令)。
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
- function writeBackCooldowns(file, data, produced, logger) {
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
- // 结构化建议(读+算,不写回 cooldown、不渲染文本):供注入段与只读面板共用。
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
- // 派发优化建议段文本生成:computeAdvice → 写回 cooldown(防每请求重复注入)→ 渲染;空建议返回空串。
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
  };