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
package/index.mjs CHANGED
@@ -1,12 +1,10 @@
1
1
  // index.mjs — dsh-subagent-profile 宿主侧插件 bundle(只做装配)。宿主导入面收敛在
2
- // lib/core/shims.mjs,装配块按模块拆分驻留 lib/core/;apply 的装配辅助函数保持
3
- // section 文本与门控逐字不变,`enabled` 始终经 getter 注入(门控读取当前值)。
4
-
5
- import { readdirSync, renameSync, rmSync } from 'node:fs';
2
+ // lib/core/shims.mjs,装配块按模块拆分驻留 lib/core/;`enabled` 经 getter 实时读取。
6
3
  import { createRequire } from 'node:module';
7
4
  import { join } from 'node:path';
8
5
  import { syncBundledPresets } from './lib/core/presets-sync.mjs';
9
6
  import { dshHome, createProfileStore } from './lib/core/profiles-store.mjs';
7
+ import { removeOwnedData, cleanRemovedBackups } from './lib/core/cleanup.mjs';
10
8
  import { createHttpRoutes } from './lib/core/http-routes.mjs';
11
9
  import { createProfileProvider } from './lib/core/profile-provider.mjs';
12
10
  import { createDispatchTool } from './lib/core/dispatch-tool.mjs';
@@ -16,20 +14,24 @@ import { createFailureLedger } from './lib/core/decision-trace.mjs';
16
14
  import { createEvolutionLedger } from './lib/core/evolution-ledger.mjs';
17
15
  import { createAdoptionTracker } from './lib/core/adoption-tracker.mjs';
18
16
  import { createReminderStore } from './lib/core/reminder-store.mjs';
17
+ import { createSnapshotStore } from './lib/core/snapshots.mjs';
19
18
  import { mintReminder, mintUnadoptedReminder } from './lib/core/adoption-reminder.mjs';
20
19
  import { createBackgroundLedger } from './lib/core/background-ledger.mjs';
21
20
  import { createDraftsStore } from './lib/core/drafts-store.mjs';
22
21
  import { assessDraftProfile } from './lib/core/draft-gates.mjs';
23
22
  import { createEscapeStore, recordEscapeAllowProvider } from './lib/core/escape.mjs';
24
23
  import { resolveWhitelist, FALLBACK_WHITELIST } from './lib/core/whitelist.mjs';
25
- import { profileDirectoryRows, profileStatsFromSummaries, applyProfileStats } from './lib/core/profile-directory.mjs';
26
- import { buildAdviceText, refreshSummaries, readSummaries } from './lib/core/evolution-advice.mjs';
24
+ import { readModelSelectionPolicy, appendOfficialVisibilityLine } from './lib/core/model-policy.mjs';
25
+ import { profileSectionText } from './lib/core/profile-directory.mjs';
26
+ import { createProfilesQueryTool } from './lib/core/profiles-query.mjs';
27
+ import { budgetSectionText } from './lib/core/dispatch-gates.mjs';
28
+ import { refreshSummaries } from './lib/core/evolution-advice.mjs';
27
29
  import { createEvolutionAssembly } from './lib/core/evolution-engine.mjs';
28
30
 
29
31
  export const name = 'dsh-subagent-profile';
30
32
  export const inject = ['subagents', 'tools', 'agents'];
31
33
 
32
- // 自装 bundled 的 "orchestrator" 预设到 agent-presets 根(与官方自安装相同)。
34
+ // 自装 bundled 的 "orchestrator-v2" 预设到 agent-presets 根(与官方自安装相同)。
33
35
  // 幂等:字节一致跳过、bundle 变更重写;fail-soft:写入被拒时工具与设置页照常工作。
34
36
  function syncBundledPresetsToHome(ctx) {
35
37
  try {
@@ -37,40 +39,14 @@ function syncBundledPresetsToHome(ctx) {
37
39
  const sync = syncBundledPresets(presetRoot);
38
40
  for (const { id, error } of sync.failed) ctx.logger.warn(`[dsh-subagent-profile] preset ${id} sync failed: ${error}`);
39
41
  if (sync.synced.length > 0) ctx.logger.info(`[dsh-subagent-profile] presets synced into ${presetRoot}: ${sync.synced.join(', ')}`);
42
+ if (sync.archived.length > 0) ctx.logger.info(`[dsh-subagent-profile] legacy preset archived into ${presetRoot}: ${sync.archived.join(', ')}`);
40
43
  if (sync.userModified.length > 0) ctx.logger.warn(`[dsh-subagent-profile] presets left untouched (user-modified): ${sync.userModified.join(', ')}`);
41
44
  } catch (error) {
42
45
  ctx.logger.warn('[dsh-subagent-profile] preset sync failed:', error instanceof Error ? error.message : String(error));
43
46
  }
44
47
  }
45
48
 
46
- // 卸载清理:本插件 3 个数据文件 + orchestrator 预设目录改名备份(effect 清理器
47
- // 在禁用/热重载/退出时都可能触发,硬删除会静默丢用户数据)。备份保留可恢复副本;
48
- // 规范路径清空后重启按需重建。fail-soft:改名失败不抛。
49
- function removeOwnedData(home) {
50
- const stamp = Date.now();
51
- for (const file of ['subagent-profiles.json', 'subagent-profiles.state.json', 'subagent-profiles.failed-traces.json']) {
52
- try { renameSync(join(home, file), join(home, `${file}.removed-${stamp}`)); } catch { /* 源不存在(正常首启)等:best effort */ }
53
- }
54
- try { renameSync(join(home, '.agent-presets', 'orchestrator'), join(home, '.agent-presets', `orchestrator.removed-${stamp}`)); } catch { /* best effort */ }
55
- }
56
-
57
- // 启动清理 .removed-* 备份残留(只清本插件前缀,防热重载反复产生新备份积累);
58
- // 卸载后用户手动改回原名的场景不受影响。fail-soft。
59
- function cleanRemovedBackups(home) {
60
- for (const dir of [home, join(home, '.agent-presets')]) {
61
- let names;
62
- try { names = readdirSync(dir); } catch { continue; }
63
- for (const name of names) {
64
- try {
65
- if (name.startsWith('subagent-profiles.') && name.includes('.removed-')) {
66
- rmSync(join(dir, name), { force: true });
67
- } else if (name.startsWith('orchestrator.removed-')) {
68
- rmSync(join(dir, name), { recursive: true, force: true });
69
- }
70
- } catch { /* best effort */ }
71
- }
72
- }
73
- }
49
+ // 卸载清理与启动清理辅助驻留 lib/core/cleanup.mjs(装配文件行门)。
74
50
 
75
51
  // parent_adopted 装配:tracker 工厂 + 根 ctx 订阅 session/event(按 session.id 过滤),
76
52
  // 卸载退订并清理定时器。onDecision 经 getRefreshAdvice 延迟读取(先判定后聚合时为空
@@ -82,16 +58,16 @@ function setupAdoptionTracker(ctx, home, getRefreshAdvice, onUnadopted) {
82
58
  onDecision: () => { try { getRefreshAdvice()(); } catch { /* 聚合重算失败不影响判定(fail-soft) */ } },
83
59
  onDecided: onUnadopted,
84
60
  });
85
- const onSessionEvent = (session, event) => adoptionTracker.handleEvent(session, event);
86
- ctx.on('session/event', onSessionEvent);
61
+ const onSessionEvent = (session, event) => adoptionTracker.handleEvent(session, event); ctx.on('session/event', onSessionEvent);
87
62
  ctx.effect(() => () => { ctx.off('session/event', onSessionEvent); adoptionTracker.dispose(); });
88
63
  return adoptionTracker;
89
64
  }
90
65
 
91
66
  // 卸载接线:注销工具 + 清空台账 + 级联取消 + 删数据文件与预设目录。agent/disposed
92
67
  // 挂钩按父释放并发/token 记账,并把该父会话未决采纳判定判「明确未采纳」。
93
- function registerTeardown(ctx, dispatch, ledger, guard, home, backgroundLedger, adoptionTracker) {
68
+ function registerTeardown(ctx, dispatch, profilesQuery, ledger, guard, home, backgroundLedger, adoptionTracker) {
94
69
  ctx.effect(() => dispatch.dispose);
70
+ ctx.effect(() => profilesQuery.dispose);
95
71
  ctx.effect(() => ledger.clear);
96
72
  ctx.effect(() => backgroundLedger.clear);
97
73
  ctx.effect(() => () => {
@@ -101,10 +77,7 @@ function registerTeardown(ctx, dispatch, ledger, guard, home, backgroundLedger,
101
77
  });
102
78
  const onAgentDisposed = ({ agent }) => {
103
79
  const parentSessionId = agent?.session?.header?.id;
104
- if (parentSessionId !== undefined && parentSessionId !== null && parentSessionId !== '') {
105
- guard.resetParent(parentSessionId);
106
- adoptionTracker.parentDisposed(parentSessionId);
107
- }
80
+ if (parentSessionId !== undefined && parentSessionId !== null && parentSessionId !== '') { guard.resetParent(parentSessionId); adoptionTracker.parentDisposed(parentSessionId); }
108
81
  };
109
82
  ctx.on('agent/disposed', onAgentDisposed);
110
83
  ctx.effect(() => () => ctx.off('agent/disposed', onAgentDisposed));
@@ -139,56 +112,45 @@ function provideProfileService(ctx, store) {
139
112
  });
140
113
  }
141
114
 
142
- // 系统提示门控:两段 profile-mode section 只在当前会话 Agent 是编排者预设
143
- // (orchestrator)时注入,从而消除从 standard/code/minimal 等不派发会话上的
144
- // 每请求固定泄漏。判据(以源码为准确认,见 test/README.md):主判据
145
- // composedPreset(agentCtx)==='orchestrator';否决 schemas(agent) 明确不含
146
- // dispatch → 必空;回退 无 agentPresets → 保守不注入。
147
- function sectionGatePasses(ctx, getEnabled, context) {
115
+ // 系统提示门控(门控拆分):dispatch:profiles 按「能力」判据(schemas 含 dispatch 即注入);
116
+ // orchestrator:mode / dispatch:budget 保持「模式」判据(composedPreset==='orchestrator-v2')。禁止成对放宽共享 gate。
117
+ // evolution:advice 自本轮起模型侧恒空(下架),不挂任何注入门控——见注册处注释。
118
+ function profileSectionGate(ctx, getEnabled, context) {
148
119
  if (!getEnabled()) return false;
149
- // 否决(防御性):schemas(agent) 明确不含 dispatch → 必空。
120
+ // schemas(agent) 明确不含 dispatch → 必空(否决);缺失 → 注入机会 + debug。
150
121
  if (context !== undefined && context.agent !== undefined) {
151
122
  const schemas = ctx.tools.schemas(context.agent);
152
- if (Array.isArray(schemas) && !schemas.some((s) => s && s.name === 'dispatch')) {
153
- return false;
154
- }
123
+ if (Array.isArray(schemas)) return schemas.some((s) => s && s.name === 'dispatch');
124
+ }
125
+ if (ctx.logger !== undefined && typeof ctx.logger.debug === 'function') {
126
+ ctx.logger.debug('[dsh-subagent-profile] dispatch:profiles 门控:schemas 不可用,按注入机会处理');
127
+ }
128
+ return true;
129
+ }
130
+ // 编排者专属 gate:与 0.4.0 原判据一致(enabled + 否决 + composedPreset,预设名随 V2 收编更新为 orchestrator-v2)。
131
+ function orchestrationGate(ctx, getEnabled, context) {
132
+ if (!getEnabled()) return false;
133
+ if (context !== undefined && context.agent !== undefined) {
134
+ const schemas = ctx.tools.schemas(context.agent);
135
+ if (Array.isArray(schemas) && !schemas.some((s) => s && s.name === 'dispatch')) return false;
155
136
  }
156
- // 主判据:preset 特征 —— composedPreset(agentCtx) === 'orchestrator'。
137
+ // 主判据:preset 特征 —— composedPreset(agentCtx) === 'orchestrator-v2'(精确匹配新名;旧名 orchestrator 已由 V2 收编取代,不构成注入判据)。
157
138
  const agentPresets = ctx.get('agentPresets');
158
139
  if (agentPresets !== undefined && typeof agentPresets.composedPreset === 'function') {
159
140
  const agentCtx = context !== undefined && context.agent !== undefined && context.agent.ctx !== undefined
160
141
  ? context.agent.ctx
161
142
  : ctx;
162
- try { return agentPresets.composedPreset(agentCtx) === 'orchestrator'; }
143
+ try { return agentPresets.composedPreset(agentCtx) === 'orchestrator-v2'; }
163
144
  catch { return false; }
164
145
  }
165
146
  // 无 agentPresets → 无法判别 → 保守不注入。
166
147
  return false;
167
148
  }
168
149
 
169
- // dispatch:profiles section 文本。引号引用:description 套引号;为空时显示
170
- // 占位符(不套引号)。压平在存储层完成(sanitizeProfile),此处仅负责显示层包裹。
171
- // 门控 profiles section 内附一行行为规则提示(非开放的 persona 注入)。
172
- function profileSectionText(store, gate, context, summaries) {
173
- if (!gate(context)) return '';
174
- const rows = applyProfileStats(profileDirectoryRows(store), profileStatsFromSummaries(store, summaries));
175
- if (rows.length === 0) return '';
176
- const lines = rows.map((p) => {
177
- const desc = p.description.length > 0 ? `"${p.description}"` : '(无描述)';
178
- const meta = [];
179
- if (p.preset !== undefined) meta.push(`preset: ${p.preset}`);
180
- else if (p.model !== undefined) meta.push(`model: ${p.model}`);
181
- if (p.tokenTier !== undefined) meta.push(`tier: ${p.tokenTier}`);
182
- if (p.avgCost !== undefined) meta.push(`平均 ${p.avgCost} 元/次`);
183
- if (p.successRate !== undefined) meta.push(`成功率 ${p.successRate}${p.n !== undefined ? ` (N=${p.n})` : ''}`);
184
- return `- ${p.id}: ${desc}${meta.length > 0 ? ` (${meta.join(' · ')})` : ''}`;
185
- });
186
- const note = '- 选择方案时先匹配任务复杂度与方案描述的能力边界,成本只在能力都胜任的方案之间比较——复杂任务不得为省 token 改用能力不足的便宜方案。\n- 别把 1-2 步即可自查/可搜完的小事委派出去 —— 几分钟内能自查完的直接做。\n- 优先使用已保存的 profile;只有少数字段需要临时覆盖时才传 per-call 参数。查资料/汇总/读文件等轻任务优先 cheap 或 flash。';
187
- return `Available dispatch profiles (dispatch.profile):\n${lines.join('\n')}\n${note}`;
188
- }
189
-
190
- // orchestrator:mode section 文本(逐字保留;与 profiles 同门控)。
191
- const ORCHESTRATOR_MODE_TEXT = '本机已安装 dsh-subagent-profile 插件的「编排者模式」agent preset:新建会话的预设选择器中可选「编排者模式」。该模式把 Agent 定位为主协调者——拆解任务后按场景用 dispatch(内置 swap-standard=标准编码、researcher=调研检索,可在「子 Agent 方案」设置页自定义)与 subagent/subagent_fork/workflow 委派给子 Agent,再整合结果。preset 文件由插件维护于 ~/.dsh/.agent-presets,安装/升级时自动同步;用户提到「编排者模式 / orchestrator / 主协调模式」时即指本预设,请据此协作。';
150
+ // orchestrator:mode section 文本(编排者模式 V2 定稿文案;与 profiles 同门控。
151
+ // 追加官方错误对照句:官方全英文报错补中文行动引导,与 dispatch 的 stopReason
152
+ // 映射共同构成错误闭环)。
153
+ const ORCHESTRATOR_MODE_TEXT = '本机已安装「编排者模式 V2」agent 预设(字段 orchestrator-v2):新建会话可选。把自己定位为主协调者——拆解任务后用 dispatch(方案)或官方 subagent/workflow 委派子 Agent,再整合结果;第一期采用 Minimal 双工具锚定轨迹,稳定后升满编排能力。需要 1-2 步自查的小事直接自己干,别派;方案在「子 Agent 方案」设置页管理。官方子代理工具若报 “not allowed for this Session”,意为该模型不在本会话白名单——改用 list_subagent_models 查允许路由,或转用 dispatch(profile)。官方工具没出现=模型选择未启用。';
192
154
 
193
155
  // 建议注入候选池:受信任预设白名单(解析失败回退内置名单)。不叠加逃生舱放行集。
194
156
  async function resolveAdviceWhitelist(ctx) {
@@ -199,61 +161,58 @@ async function resolveAdviceWhitelist(ctx) {
199
161
  }
200
162
  }
201
163
 
202
- async function setupEvolution(ctx, home, store, catalog, evoLedger, getEvolutionAdvice) {
164
+ // 回滚建议提醒 mint:仅 regress 判定时触发一次(提醒写失败吞掉);标题人话化,
165
+ // 内容引导到自进化与建议页签查看建议(只读跳转)。
166
+ async function setupEvolution(ctx, home, store, catalog, evoLedger, getEvolutionAdvice, getReminderStore) {
203
167
  const adviceWhitelist = await resolveAdviceWhitelist(ctx);
204
168
  const adviceEnv = { summariesFile: join(home, 'subagent-evolution', 'summaries.json'), dispatchFile: join(home, 'subagent-evolution', 'dispatch.jsonl'), whitelist: adviceWhitelist, logger: ctx.logger, onLoss: () => evoLedger.markMigrationLoss() };
205
- const { adviceSource, evolution } = createEvolutionAssembly({ ctx, home, store, catalog, evoLedger, adviceWhitelist, getEvolutionAdvice, pluginVersion: readPluginVersion() });
169
+ const mintRollbackReminder = (asset) => mintReminder(getReminderStore(), {
170
+ severity: 'P2', kind: 'suggestion-rollback',
171
+ title: '自进化撤销建议:' + ((asset && asset.applied_config && typeof asset.applied_config.name === 'string' && asset.applied_config.name !== '') ? asset.applied_config.name : ((asset && typeof asset.name === 'string' && asset.name !== '') ? asset.name : '未知方案')),
172
+ detail: '该方案调整后观察期表现下降,建议撤销。可在「自进化与建议」页签查看并处理。', sessionId: null,
173
+ });
174
+ const { adviceSource, evolution } = createEvolutionAssembly({ ctx, home, store, catalog, evoLedger, adviceWhitelist, getEvolutionAdvice, getReminderStore: mintRollbackReminder, pluginVersion: readPluginVersion() });
206
175
  return { adviceEnv, adviceSource, evolution };
207
176
  }
208
177
 
209
- // 系统提示各 section:门控经 sectionGatePasses;`enabled` 经 getter 实时读取。
178
+ // 系统提示各 section:profiles 段挂 profileSectionGate(能力判据),orchestrator:mode
179
+ // 与 dispatch:budget 挂 orchestrationGate(模式判据,编排者专属);evolution:advice
180
+ // 自本轮起模型侧恒空(下架,见注册处注释)。`enabled` 经 getter 实时读取。
210
181
  function registerSystemPromptSections(ctx, store, getEnabled, getEvolutionAdvice, adviceEnv, guard) {
211
182
  const pluginSystemPrompt = ctx.get('systemPrompt');
212
183
  if (pluginSystemPrompt === undefined) return;
213
- const gate = (context) => sectionGatePasses(ctx, getEnabled, context);
184
+ const profileGate = (context) => profileSectionGate(ctx, getEnabled, context);
185
+ const orchestratorGate = (context) => orchestrationGate(ctx, getEnabled, context);
214
186
  pluginSystemPrompt.section({
215
187
  name: 'dispatch:profiles',
216
188
  order: 116.5,
217
- text: (context) => {
218
- let summaries = null;
219
- try { summaries = readSummaries(adviceEnv.summariesFile, adviceEnv.logger, { onLoss: adviceEnv.onLoss }); } catch { /* 统计只增强,不影响目录注入 */ }
220
- return profileSectionText(store, gate, context, summaries);
221
- },
189
+ text: (context) => appendOfficialVisibilityLine(profileSectionText(store, profileGate, context), ctx, context, ctx.logger),
222
190
  });
223
- // 宣告自装的 orchestrator 预设,让当前 agent 知道该模式存在、可引导用户使用。
224
- // 门控与 profiles 段相同——只有可派发的 agent 才看得到。
191
+ // 宣告自装的 orchestrator-v2 预设(门控与 0.4.0 相同:仅编排者 agent 可见)。
225
192
  pluginSystemPrompt.section({
226
193
  name: 'orchestrator:mode',
227
194
  order: 117,
228
- text: (context) => (gate(context) ? ORCHESTRATOR_MODE_TEXT : ''),
195
+ text: (context) => (orchestratorGate(context) ? ORCHESTRATOR_MODE_TEXT : ''),
229
196
  });
230
- // 派发优化建议段:门控 = orchestrator + evolutionAdvice 开关;异常兜底不阻断装配。
197
+ // 派发优化建议段(模型侧下架):建议文本曾注入模型可见面却自称「仅供人类
198
+ // 参考」——噪声 + 诱导降档,故恒不注入(text 恒 '',不再过任何门控/开关)。
199
+ // 注册结构保留(section 名与 order 不变,客户端依赖其存在性);人类面板
200
+ // (建议卡/提醒中心/审计行)经 http-routes 读取同一数据源,不受影响。勿在此恢复注入。
231
201
  pluginSystemPrompt.section({
232
202
  name: 'evolution:advice',
233
203
  order: 116.8,
234
- text: (context) => {
235
- if (!getEvolutionAdvice() || !gate(context)) return '';
236
- try {
237
- return buildAdviceText(adviceEnv);
238
- } catch (error) {
239
- // 候选校验失败(如逃生舱放行的非 system 预设)不再静默空转——logger.warn
240
- // 留痕 + 注入段标注「建议暂不可用及原因」,父 Agent 可见、派发行为不受影响。
241
- const reason = error instanceof Error ? error.message : String(error);
242
- adviceEnv.logger.warn(`[dsh-subagent-profile] evolution:advice 生成失败:${reason}`);
243
- return `(派发优化建议暂不可用:${reason}。派发行为不受影响。)`;
244
- }
245
- },
204
+ text: () => '',
246
205
  });
247
- // 派发预算余量段:与 profiles 同门控;无父 sessionId 不注入(无法关联具体会话)。
206
+ // 派发预算余量段(三态文案,见 dispatch-gates.mjs):门控 = orchestrator-v2 预设;
207
+ // 无父 sessionId 不注入(无法关联具体会话)。
248
208
  pluginSystemPrompt.section({
249
209
  name: 'dispatch:budget',
250
210
  order: 116.7,
251
211
  text: (context) => {
252
- if (!gate(context)) return '';
212
+ if (!orchestratorGate(context)) return '';
253
213
  const parentSessionId = context?.agent?.session?.header?.id;
254
214
  if (typeof parentSessionId !== 'string' || parentSessionId === '') return '';
255
- const b = guard.snapshot(parentSessionId);
256
- return `派发预算余量:本会话在途 ${b.concurrency}/${b.maxConcurrent},累计 token ${b.tokens}/${b.maxParentTokens},剩余 ${Math.max(0, b.maxParentTokens - b.tokens)} token。`;
215
+ return budgetSectionText(guard.snapshot(parentSessionId));
257
216
  },
258
217
  });
259
218
  }
@@ -263,7 +222,7 @@ function registerSystemPromptSections(ctx, store, getEnabled, getEvolutionAdvice
263
222
  // webServer 可选——无头部署保留 dispatch 工具、只丢设置页。webServer 的激活
264
223
  // (listen)是异步的,可能晚于本插件 inject 依赖解析完成,故在等它的 inject
265
224
  // 子 scope 内注册(apply 时 ctx.get 会读到 undefined)。
266
- function registerSettingsRoutes(ctx, store, getEnabled, setEnabled, syncTool, catalog, ledger, backgroundLedger, draftsStore, applyDraft, getAudit, getEvolutionAdvice, setEvolutionAdvice, getEscapeEnabled, setEscapeEnabled, escape, refreshAdvice, summariesFile, dispatchFile, adviceWhitelist, previewDraft, reminderStore, adviceSource, evolution) {
225
+ function registerSettingsRoutes(ctx, store, getEnabled, setEnabled, syncTool, catalog, ledger, backgroundLedger, draftsStore, applyDraft, getAudit, getEvolutionAdvice, setEvolutionAdvice, getEscapeEnabled, setEscapeEnabled, escape, refreshAdvice, summariesFile, dispatchFile, adviceWhitelist, previewDraft, reminderStore, adviceSource, evolution, snapshots, recordAudit) {
267
226
  ctx.inject(['webServer'], (scope) => {
268
227
  scope.effect(createHttpRoutes({
269
228
  webServer: scope.webServer,
@@ -290,6 +249,8 @@ function registerSettingsRoutes(ctx, store, getEnabled, setEnabled, syncTool, ca
290
249
  reminderStore,
291
250
  adviceSource,
292
251
  evolution,
252
+ snapshots,
253
+ recordAudit,
293
254
  logger: ctx.logger,
294
255
  }), 'dsh-subagent-profile: settings routes');
295
256
  });
@@ -338,7 +299,7 @@ function setupEscape(ctx, store, evoLedger, home, getReminderStore) {
338
299
 
339
300
  // `dispatch` 工具装配(defineTool + execute + syncTool)。须在 HTTP inject 之前
340
301
  // 构造,使 createHttpRoutes 能拿 dispatch.syncTool 供 /set-enabled 注册/注销。
341
- function createDispatch(ctx, store, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, getEscapeSet, getEnabled, getEvolutionAdvice) {
302
+ function createDispatch(ctx, store, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, getEscapeSet, getEnabled, getEvolutionAdvice, snapshots) {
342
303
  return createDispatchTool({
343
304
  register: (tool) => ctx.tools.register(tool),
344
305
  store,
@@ -354,6 +315,21 @@ function createDispatch(ctx, store, catalog, ledger, guard, evoLedger, backgroun
354
315
  adoptionTracker,
355
316
  getEscapeSet,
356
317
  getEvolutionAdvice,
318
+ snapshots,
319
+ });
320
+ }
321
+
322
+ // profiles_query 工具装配(apply 前抽出):syncTool 机制自动可见、不依赖静态段;
323
+ // 与 dispatch 共用 enabled 开关(/set-enabled 一并注册/注销)。
324
+ function createProfilesQuery(ctx, store, catalog, adviceEnv, getEnabled) {
325
+ return createProfilesQueryTool({
326
+ register: (tool) => ctx.tools.register(tool),
327
+ store,
328
+ catalog,
329
+ summariesFile: adviceEnv.summariesFile,
330
+ onLoss: adviceEnv.onLoss,
331
+ logger: ctx.logger,
332
+ getEnabled,
357
333
  });
358
334
  }
359
335
 
@@ -364,15 +340,14 @@ async function applyDraftProfile({ ctx, store, catalog, getEscapeSet, evoLedger,
364
340
  const failed = checks.find((c) => c.verdict === 'fail');
365
341
  throw new Error(failed !== undefined && typeof failed.reason === 'string' ? failed.reason : 'draft 未通过安全检查');
366
342
  }
367
- store.profiles.set(clean.id, { ...clean, persisted: true });
343
+ store.saveProfile({ ...clean, persisted: true });
368
344
  const persisted = store.persistProfiles();
369
345
  evoLedger.recordGovernanceAudit({ kind: 'draft-apply', profileId: clean.id, source: typeof draft.source === 'string' ? draft.source : 'human' });
370
346
  return { id: clean.id, persisted: persisted.persisted };
371
347
  }
372
348
 
373
349
  export async function apply(ctx) {
374
- const home = dshHome();
375
- cleanRemovedBackups(home); // 上个生命周期留下的 .removed-* 备份残留。
350
+ const home = dshHome(); cleanRemovedBackups(home); // 上个生命周期留下的 .removed-* 备份残留。
376
351
  // reminderStore 变量提前声明:evoLedger.onAlert 与 escape mint 钩子延迟读取。
377
352
  let reminderStore;
378
353
  // 派发台账 + 审计分级须在 store 之前构造:store 的治理审计钩子指向其 markGovernanceFailure。
@@ -382,7 +357,6 @@ export async function apply(ctx) {
382
357
  let enabled = store.loadEnabled();
383
358
  let evolutionAdvice = store.loadEvolutionAdvice();
384
359
  store.loadProfiles();
385
- // 自装 bundled 的 orchestrator 预设(幂等、fail-soft)。
386
360
  syncBundledPresetsToHome(ctx);
387
361
  const catalog = createSharedCatalog(ctx);
388
362
  const ledger = createFailureLedger({ warn: (message) => ctx.logger.warn(`[dsh-subagent-profile] ${message}`), stateFile: join(home, 'subagent-profiles.failed-traces.json') });
@@ -390,16 +364,18 @@ export async function apply(ctx) {
390
364
  const backgroundLedger = createBackgroundLedger({ warn: (message) => ctx.logger.warn(`[dsh-subagent-profile] ${message}`) });
391
365
  const draftsStore = createDraftsStore({ dshHome: home, onGovernanceFailure: () => evoLedger.markGovernanceFailure() });
392
366
  reminderStore = createReminderStore({ dshHome: home, audit: (entry) => evoLedger.recordGovernanceAudit(entry), warn: (message) => ctx.logger.warn(`[dsh-subagent-profile] ${message}`) });
393
- let refreshAdvice = () => {};
394
- const adoptionTracker = setupAdoptionTracker(ctx, home, () => refreshAdvice, (rec) => mintUnadoptedReminder(reminderStore, evoLedger, rec));
395
- const dispatch = createDispatch(ctx, store, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, escapeCtl.getEscapeSet, () => enabled, () => evolutionAdvice);
367
+ // 配置快照域:开关关恒跳过(store.loadSnapshotEnabled 默认关);写审计与治理失败同源。
368
+ const snapshots = createSnapshotStore({ dshHome: home, logger: ctx.logger, getEnabled: () => store.loadSnapshotEnabled(), recordAudit: (entry) => evoLedger.recordGovernanceAudit(entry), onGovernanceFailure: () => evoLedger.markGovernanceFailure() });
369
+ let refreshAdvice = () => {}; const adoptionTracker = setupAdoptionTracker(ctx, home, () => refreshAdvice, (rec) => mintUnadoptedReminder(reminderStore, evoLedger, rec));
370
+ const dispatch = createDispatch(ctx, store, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, escapeCtl.getEscapeSet, () => enabled, () => evolutionAdvice, snapshots);
396
371
  provideProfileService(ctx, store);
397
- const { adviceEnv, adviceSource, evolution } = await setupEvolution(ctx, home, store, catalog, evoLedger, () => evolutionAdvice);
398
- // 生产聚合触发点(T1 修复):/options/refresh 与 /list 触发的惰性重算;parent_adopted
399
- // 的「已确认未采纳」计数经 opts 惰性 join 进 weighted_success(-0.3 惩罚)。
372
+ const { adviceEnv, adviceSource, evolution } = await setupEvolution(ctx, home, store, catalog, evoLedger, () => evolutionAdvice, () => reminderStore);
373
+ const profilesQuery = createProfilesQuery(ctx, store, catalog, adviceEnv, () => enabled);
374
+ const syncTools = () => { dispatch.syncTool(); profilesQuery.syncTool(); };
375
+ // 生产聚合触发点(T1 修复):/options/refresh 与 /list 的惰性重算。
400
376
  refreshAdvice = () => refreshSummaries({ dispatchFile: adviceEnv.dispatchFile, summariesFile: adviceEnv.summariesFile, logger: ctx.logger, opts: { parentAdoptedConfirmedFalse: adoptionTracker.confirmedFalseCounts() } });
401
377
  registerSystemPromptSections(ctx, store, () => enabled, () => evolutionAdvice, adviceEnv, guard);
402
- const disposeProvider = createProfileProvider({ subagents: ctx.subagents, store, getEnabled: () => enabled, logger: ctx.logger, catalog, getEscapeSet: escapeCtl.getEscapeSet, recordEscapeAllowProvider: escapeCtl.recordEscapeAllowProvider });
378
+ const disposeProvider = createProfileProvider({ subagents: ctx.subagents, store, getEnabled: () => enabled, logger: ctx.logger, catalog, getEscapeSet: escapeCtl.getEscapeSet, recordEscapeAllowProvider: escapeCtl.recordEscapeAllowProvider, readModelSelectionPolicy, evoLedger });
403
379
  if (typeof disposeProvider === 'function') ctx.effect(() => disposeProvider);
404
380
  const applyDraft = (draft) => applyDraftProfile({ ctx, store, catalog, getEscapeSet: escapeCtl.getEscapeSet, evoLedger, draft });
405
381
  // /draft/preview:只读安全检查预览(不落库)。body 为 {config, name, description, source}。
@@ -415,7 +391,6 @@ export async function apply(ctx) {
415
391
  source: body !== null && typeof body === 'object' && typeof body.source === 'string' ? body.source : 'human',
416
392
  },
417
393
  });
418
- // Client 设置 UI 的 HTTP loopback 路由 —— lib/core/http-routes.mjs。
419
- registerSettingsRoutes(ctx, store, () => enabled, (next) => { enabled = next; }, dispatch.syncTool, catalog, ledger, backgroundLedger, draftsStore, applyDraft, () => evoLedger.auditState(), () => evolutionAdvice, (next) => { evolutionAdvice = next; }, escapeCtl.getEscapeEnabled, escapeCtl.setEscapeEnabled, escapeCtl.escape, refreshAdvice, join(home, 'subagent-evolution', 'summaries.json'), adviceEnv.dispatchFile, adviceEnv.whitelist, previewDraft, reminderStore, adviceSource, evolution);
420
- registerTeardown(ctx, dispatch, ledger, guard, home, backgroundLedger, adoptionTracker);
394
+ registerSettingsRoutes(ctx, store, () => enabled, (next) => { enabled = next; }, syncTools, catalog, ledger, backgroundLedger, draftsStore, applyDraft, () => evoLedger.auditState(), () => evolutionAdvice, (next) => { evolutionAdvice = next; }, escapeCtl.getEscapeEnabled, escapeCtl.setEscapeEnabled, escapeCtl.escape, refreshAdvice, join(home, 'subagent-evolution', 'summaries.json'), adviceEnv.dispatchFile, adviceEnv.whitelist, previewDraft, reminderStore, adviceSource, evolution, snapshots, (entry) => evoLedger.recordGovernanceAudit(entry));
395
+ registerTeardown(ctx, dispatch, profilesQuery, ledger, guard, home, backgroundLedger, adoptionTracker);
421
396
  }