dsh-subagent-profile 0.3.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +13 -8
  2. package/README.zh.md +13 -8
  3. package/index.mjs +99 -95
  4. package/lib/client.js +1288 -189
  5. package/lib/core/decision-trace.mjs +20 -1
  6. package/lib/core/delegation.mjs +3 -2
  7. package/lib/core/dispatch-gates.mjs +122 -9
  8. package/lib/core/dispatch-schema.mjs +45 -7
  9. package/lib/core/dispatch-tool.mjs +24 -25
  10. package/lib/core/evolution-advice.mjs +154 -34
  11. package/lib/core/evolution-assets.mjs +167 -0
  12. package/lib/core/evolution-canary.mjs +427 -0
  13. package/lib/core/evolution-draft.mjs +333 -0
  14. package/lib/core/evolution-engine.mjs +413 -0
  15. package/lib/core/evolution-generate.mjs +180 -0
  16. package/lib/core/evolution-lag.mjs +78 -0
  17. package/lib/core/evolution-ledger.mjs +135 -27
  18. package/lib/core/evolution-persistence.mjs +213 -0
  19. package/lib/core/evolution-renewal.mjs +64 -0
  20. package/lib/core/evolution-routes.mjs +234 -0
  21. package/lib/core/http-helpers.mjs +35 -0
  22. package/lib/core/http-routes.mjs +35 -39
  23. package/lib/core/model-policy.mjs +77 -0
  24. package/lib/core/presets-sync.mjs +24 -2
  25. package/lib/core/profile-directory.mjs +17 -0
  26. package/lib/core/profile-provider.mjs +9 -3
  27. package/lib/core/profiles-query.mjs +178 -0
  28. package/lib/core/profiles-store.mjs +56 -8
  29. package/lib/core/pure.mjs +1 -1
  30. package/lib/core/session-read.mjs +17 -0
  31. package/lib/core/shims.mjs +2 -1
  32. package/package.json +2 -2
  33. package/presets/orchestrator-v2/NOTICE +7 -0
  34. package/presets/{orchestrator → orchestrator-v2}/agent.cordis.yml +78 -7
  35. package/presets/orchestrator-v2/custom-bash.mjs +213 -0
  36. package/presets/orchestrator-v2/preset.yml +2 -0
  37. package/presets/orchestrator-v2/tool-bootstrap.mjs +620 -0
  38. package/presets/orchestrator/preset.yml +0 -2
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  <img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" />
7
7
  <img alt="npm" src="https://img.shields.io/npm/v/dsh-subagent-profile.svg" />
8
8
  <img alt="DSH" src="https://img.shields.io/badge/DSH-0.1.0--rc.6%20~%200.2.0-blue.svg" />
9
+ <img alt="awesome · DSH plugin" src="https://awesome-dsh-plugin.com/badge.svg" />
9
10
  </div>
10
11
 
11
12
  <div align="center">English · <a href="README.zh.md">中文</a></div>
@@ -18,13 +19,15 @@ For [DeepSeek Harness](https://github.com/deepseek-ai/dsh) (DSH).
18
19
 
19
20
  | | Built-in `subagent` | `dsh-subagent-profile` |
20
21
  |---|---|---|
21
- | Per-subtask model / preset | same brain for every subtask | ✅ pick per subtask |
22
+ | Per-subtask model / preset | optional `provider` / `model` / `reasoning_effort` (after enabling model selection) | ✅ one profile bundles preset + model + effort + tool scope + budget; overridable per call |
22
23
  | Reusable named setups | ❌ | ✅ profiles |
23
24
  | Tool-scope narrowing | ❌ | ✅ whitelist ∩ parent, `run_code` always removed |
24
25
  | Safety checks | ❌ | ✅ whitelist / cost / intersection / approval / budget, all recorded |
25
26
  | Cost visibility | ❌ | ✅ per-dispatch estimate + savings comparison |
26
- | Decision ledger | ❌ | ✅ full trace: request vs. effective, gates, execution, settlement |
27
+ | Decision ledger | ❌ | ✅ full trace: request vs. effective, checks, execution, settlement |
27
28
  | GUI management | ❌ | ✅ settings page + per-session ledger tab |
29
+ | Performance-based suggestions | ❌ | ✅ confirm to apply, undo anytime |
30
+ | Official model-selection interplay | ✅ enforced natively once enabled | ✅ dispatch follows the same whitelist (out-of-range routes are rejected) |
28
31
 
29
32
  ## Installation
30
33
 
@@ -33,7 +36,7 @@ dsh plugin --profile web add dsh-subagent-profile # published package
33
36
  dsh plugin --profile web add ./dsh-subagent-profile # from a local checkout
34
37
  ```
35
38
 
36
- Restart `dsh web`. This is a standard **bundle plugin**: it provides the `dispatch` tool, the profile provider, the `subagent-profiles` service, the `/subagent-profiles/*` loopback management routes, the settings page (「子 Agent 方案」), the per-session ledger tab, and the `dispatch` tool-call card in the web GUI. On startup it also **self-installs an agent preset** — **`orchestrator`** (「编排者模式」) — pick it in the new-session preset picker. The sync is idempotent and re-runs on every startup, so upgrading the plugin updates the preset.
39
+ Restart `dsh web`. This is a standard **bundle plugin**: it provides the `dispatch` tool, the profile provider, the `subagent-profiles` service, the `/subagent-profiles/*` loopback management routes, the settings page (「子 Agent 方案」), the per-session ledger tab, and the `dispatch` tool-call card in the web GUI. On startup it also **self-installs an agent preset** — **`orchestrator-v2`** (「编排者模式 V2」) — pick it in the new-session preset picker. The sync is idempotent and re-runs on every startup, so upgrading the plugin updates the preset.
37
40
 
38
41
  ## Usage
39
42
 
@@ -94,14 +97,16 @@ Delegation never lets a subagent gain more power than you already have — this
94
97
  - `summaries.json` — aggregated per-profile statistics (versioned, rebuilt on corruption).
95
98
  - `adopted-state.json` — adoption verdict state for child results (cross-restart).
96
99
  - `reminders.json` — reminder store (one reminder = one audit record).
97
- - `~/.dsh/.agent-presets/orchestrator/` — the self-installed `orchestrator` agent preset (synced from the bundled `presets/orchestrator/` on every startup).
100
+ - `~/.dsh/.agent-presets/orchestrator-v2/` — the self-installed `orchestrator-v2` agent preset (synced from the bundled `presets/orchestrator-v2/` on every startup).
98
101
 
99
- `DSH_HOME` is respected and defaults to `~/.dsh`. Uninstalling the plugin removes the data files above and the self-installed `orchestrator` preset directory (other plugins' presets are left untouched); re-installing or re-launching re-syncs the preset and regenerates the data files.
102
+ `DSH_HOME` is respected and defaults to `~/.dsh`. Uninstalling the plugin removes the data files above and the self-installed `orchestrator-v2` preset directory (other plugins' presets are left untouched); re-installing or re-launching re-syncs the preset and regenerates the data files.
100
103
 
101
104
  ## Known limitations
102
105
 
103
106
  - **Background dispatch** requires `@deepseek-ai/dsh-jobs` and `@deepseek-ai/dsh-tool-jobs` to be loaded; otherwise it fails with "dispatch: 后台派发不可用:缺少 jobs 服务".
104
107
  - **Continuable mode** goes through the DSH standard composition path, so the `preset` swap and `reasoningEffort` are ignored (the child inherits the parent preset at the default reasoning effort).
108
+ - **Continuable follow-ups** are delivered through the official `send_message` channel (the plugin drives the child via the official child handle).
109
+ - **Official model selection is a web-line feature**: the session policy for the built-in subagent tools ships with the web app and is not available in headless deployments. In production it is off by default and must be enabled before it takes effect.
105
110
  - **Who decides the child's final tool set depends on the mode**:
106
111
  - Continuable: narrowed by this plugin up front — the child's `allow` = (parent tool set − `run_code` − `deny`) ∩ `allow`. Assumption: continuable inherits the parent preset, so the child's tool set ≈ the parent's; if dsh's behavior changes so the two differ (e.g. a future preset swap composing a different tool set), tool narrowing fails with a hard error (conservatively safe — never silently granting more), to be replaced with a true parent ∩ child intersection once dsh provides an official seam.
107
112
  - One-shot: decided by dsh; the plugin cannot read the final restricted result and the card reports "tools are finally granted by the system".
@@ -145,7 +150,7 @@ dsh-subagent-profile/
145
150
  │ ├── profile-directory.mjs # read-only profile directory (for the model)
146
151
  │ ├── profiles-store.mjs # profile registry store + switch persistence
147
152
  │ └── whitelist.mjs # system-trust preset whitelist
148
- ├── presets/orchestrator/ # bundled "orchestrator" agent preset (self-installed, synced on every startup)
153
+ ├── presets/orchestrator-v2/ # bundled "orchestrator-v2" agent preset (self-installed, synced on every startup)
149
154
  ├── cordis.patch.yml # bundle patch: inserts the plugin row into the host composition
150
155
  ├── .gitea/workflows/ci.yml # bare-CI (Gitea Actions; needs an Act runner on the server)
151
156
  ├── package.json # metadata, files whitelist, exports (test / test:bare / preflight scripts)
@@ -154,7 +159,7 @@ dsh-subagent-profile/
154
159
  │ └── leak-scan.mjs # public-release gate: scans all history + worktree for sensitive patterns
155
160
  ├── docs/
156
161
  │ └── screenshots/ # README screenshots
157
- ├── test/ # host-side tests (node:test, zero extra deps; 438 cases)
162
+ ├── test/ # host-side tests (node:test, zero extra deps)
158
163
  │ ├── README.md / README.zh.md # test directory guide (EN/ZH) — two-tier split explained
159
164
  │ ├── harness/ctx.mjs # fake Cordis ctx + ~/.dsh isolation
160
165
  │ ├── pure / input-schema / catalog-integrity.test.mjs # bare tier (import-free, runs in bare CI)
@@ -173,7 +178,7 @@ If this plugin has been useful to you, please give it a ⭐ on GitHub — it hel
173
178
 
174
179
  ## Credits
175
180
 
176
- The bundled `orchestrator` agent preset was inspired by [dsh-liangshen](https://github.com/zhu1090093659/dsh-web-ui/tree/main/packages/dsh-liangshen) (梁神模式) from [dsh-web-ui](https://github.com/zhu1090093659/dsh-web-ui), licensed under Apache-2.0. Thanks to its author for the great work.
181
+ The bundled `orchestrator-v2` agent preset was inspired by [dsh-liangshen](https://github.com/zhu1090093659/dsh-web-ui/tree/main/packages/dsh-liangshen) (梁神模式) from [dsh-web-ui](https://github.com/zhu1090093659/dsh-web-ui), licensed under Apache-2.0. Thanks to its author for the great work.
177
182
 
178
183
  ## License
179
184
 
package/README.zh.md CHANGED
@@ -6,6 +6,7 @@
6
6
  <img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" />
7
7
  <img alt="npm" src="https://img.shields.io/npm/v/dsh-subagent-profile.svg" />
8
8
  <img alt="DSH" src="https://img.shields.io/badge/DSH-0.1.0--rc.6%20~%200.2.0-blue.svg" />
9
+ <img alt="awesome · DSH plugin" src="https://awesome-dsh-plugin.com/badge.svg" />
9
10
  </div>
10
11
 
11
12
  <div align="center"><a href="README.md">English</a> · 中文</div>
@@ -18,13 +19,15 @@
18
19
 
19
20
  | | 内置 `subagent` | `dsh-subagent-profile` |
20
21
  |---|---|---|
21
- | 按子任务选模型/预设 | 每个子任务同一个「大脑」 | ✅ 按任务逐个指定 |
22
+ | 按子任务选模型/预设 | 可选 `provider` / `model` / `reasoning_effort`(需先启用模型选择) | ✅ 方案级 profile:预设+模型+推理强度+工具范围+预算一捆,per-call 可覆盖 |
22
23
  | 可复用的命名方案 | ❌ | ✅ 方案(profile) |
23
24
  | 工具范围收敛 | ❌ | ✅ 白名单 ∩ 父会话,`run_code` 恒移除 |
24
25
  | 派发前安全检查 | ❌ | ✅ 白名单/成本/交集/审批/预算,全程记录 |
25
26
  | 成本可见 | ❌ | ✅ 每次派发的成本估算 + 节省对照 |
26
- | 派发决策台账 | ❌ | ✅ 请求vs生效、检查结果、执行、结算全留痕 |
27
+ | 派发决策台账 | ❌ | ✅ 请求 vs 生效、检查结果、执行、结算全留痕 |
27
28
  | 界面管理 | ❌ | ✅ 设置页 + 会话内台账标签页 |
29
+ | 基于表现的改进建议 | ❌ | ✅ 确认后应用 · 随时撤销 |
30
+ | 官方模型选择联动 | ✅ 启用后由官方工具自身校验 | ✅ 官方启用后派发遵循同一白名单(越界路由直接拒绝) |
28
31
 
29
32
  ## 安装
30
33
 
@@ -33,7 +36,7 @@ dsh plugin --profile web add dsh-subagent-profile # 发布包
33
36
  dsh plugin --profile web add ./dsh-subagent-profile # 本地源码
34
37
  ```
35
38
 
36
- 重启 `dsh web`。这是一个标准的 **bundle 插件**:提供 `dispatch` 工具、方案提供者、`subagent-profiles` 服务、`/subagent-profiles/*` 回环管理路由、设置页(「子 Agent 方案」)、会话内派发决策台账标签页,以及 web 界面中的 `dispatch` 工具卡片。启动时还会**自动安装一个 agent 预设**——**`orchestrator`**(「编排者模式」)——在新会话的预设选择器里选用。同步是幂等的且每次启动重跑,升级插件即更新预设。
39
+ 重启 `dsh web`。这是一个标准的 **bundle 插件**:提供 `dispatch` 工具、方案提供者、`subagent-profiles` 服务、`/subagent-profiles/*` 回环管理路由、设置页(「子 Agent 方案」)、会话内派发决策台账标签页,以及 web 界面中的 `dispatch` 工具卡片。启动时还会**自动安装一个 agent 预设**——**`orchestrator-v2`**(「编排者模式 V2」)——在新会话的预设选择器里选用。同步是幂等的且每次启动重跑,升级插件即更新预设。
37
40
 
38
41
  ## 使用
39
42
 
@@ -94,14 +97,16 @@ dispatch(
94
97
  - `summaries.json` —— 按方案聚合的统计(带版本号,损坏自动重建)。
95
98
  - `adopted-state.json` —— 子结果采纳判定状态(跨重启)。
96
99
  - `reminders.json` —— 提醒存储(一条提醒 = 一条审计记录)。
97
- - `~/.dsh/.agent-presets/orchestrator/` —— 自动安装的 `orchestrator` 预设(每次启动从内置 `presets/orchestrator/` 同步)。
100
+ - `~/.dsh/.agent-presets/orchestrator-v2/` —— 自动安装的 `orchestrator-v2` 预设(每次启动从内置 `presets/orchestrator-v2/` 同步)。
98
101
 
99
- 尊重 `DSH_HOME` 环境变量(默认 `~/.dsh`)。卸载插件会移除上述数据文件与自动安装的 `orchestrator` 预设目录(其他插件的预设不动);重装或重启会重新同步预设并重建数据文件。
102
+ 尊重 `DSH_HOME` 环境变量(默认 `~/.dsh`)。卸载插件会移除上述数据文件与自动安装的 `orchestrator-v2` 预设目录(其他插件的预设不动);重装或重启会重新同步预设并重建数据文件。
100
103
 
101
104
  ## 已知限制
102
105
 
103
106
  - **后台派发**需要 `@deepseek-ai/dsh-jobs` 与 `@deepseek-ai/dsh-tool-jobs` 已加载;否则报「dispatch: 后台派发不可用:缺少 jobs 服务」。
104
107
  - **持久(continuable)模式**走 DSH 标准组合路径,`preset` 换用与 `reasoningEffort` 会被忽略(子代理继承父预设与默认推理强度)。
108
+ - **持久模式的后续轮次走官方 `send_message`**(插件通过官方子代理句柄驱动后续对话)。
109
+ - **官方模型选择是 web 线功能**:内置 subagent 工具的会话策略随 web 应用提供,headless 部署没有;生产默认未启用,需手动开启后才会生效。
105
110
  - **子代理的最终工具集由谁决定,取决于模式**:
106
111
  - 持久(continuable)模式:由本插件预先收敛——子代理 `allow` =(父工具集 − `run_code` − `deny`)∩ `allow`。前提假设:持久模式沿用父会话预设,故子工具集与父会话基本一致;一旦 dsh 的行为变化导致两者不同(如未来支持预设换用后组合出不同工具集),工具收敛会**直接报错拒绝**(保守安全,绝不静默放行),待 dsh 官方提供相应接口后替换为真正的父 ∩ 子交集。
107
112
  - 一次性模式:由 dsh 决定,插件读不到最终受限结果;卡片显示「工具由系统最终授予」。
@@ -145,7 +150,7 @@ dsh-subagent-profile/
145
150
  │ ├── profile-directory.mjs # 只读方案目录(供模型参考)
146
151
  │ ├── profiles-store.mjs # 方案注册表存储 + 开关持久化
147
152
  │ └── whitelist.mjs # system-trust 预设白名单
148
- ├── presets/orchestrator/ # 内置「orchestrator」预设(自动安装,每次启动同步)
153
+ ├── presets/orchestrator-v2/ # 内置「orchestrator-v2」预设(自动安装,每次启动同步)
149
154
  ├── cordis.patch.yml # bundle patch:把插件行插入宿主组合
150
155
  ├── .gitea/workflows/ci.yml # bare-CI(Gitea Actions;需服务器上的 Act runner)
151
156
  ├── package.json # 元数据、files 白名单、exports(test / test:bare / preflight 脚本)
@@ -154,7 +159,7 @@ dsh-subagent-profile/
154
159
  │ └── leak-scan.mjs # 公开发布门:扫描全历史与工作区的敏感模式
155
160
  ├── docs/
156
161
  │ └── screenshots/ # README 截图
157
- ├── test/ # 宿主侧测试(node:test,零额外依赖;438 用例)
162
+ ├── test/ # 宿主侧测试(node:test,零额外依赖)
158
163
  │ ├── README.md / README.zh.md # 测试目录指南(中英)——两层拆分说明
159
164
  │ ├── harness/ctx.mjs # 假 Cordis ctx + ~/.dsh 隔离
160
165
  │ ├── pure / input-schema / catalog-integrity.test.mjs # bare 层(免导入,bare CI 可跑)
@@ -173,7 +178,7 @@ dsh-subagent-profile/
173
178
 
174
179
  ## 致谢
175
180
 
176
- 内置的 `orchestrator` 预设灵感来自 [dsh-web-ui](https://github.com/zhu1090093659/dsh-web-ui) 的 [dsh-liangshen](https://github.com/zhu1090093659/dsh-web-ui/tree/main/packages/dsh-liangshen)(梁神模式),Apache-2.0 许可。感谢作者。
181
+ 内置的 `orchestrator-v2` 预设灵感来自 [dsh-web-ui](https://github.com/zhu1090093659/dsh-web-ui) 的 [dsh-liangshen](https://github.com/zhu1090093659/dsh-web-ui/tree/main/packages/dsh-liangshen)(梁神模式),Apache-2.0 许可。感谢作者。
177
182
 
178
183
  ## 许可
179
184
 
package/index.mjs CHANGED
@@ -1,7 +1,5 @@
1
1
  // index.mjs — dsh-subagent-profile 宿主侧插件 bundle(只做装配)。宿主导入面收敛在
2
- // lib/core/shims.mjs,装配块按模块拆分驻留 lib/core/;apply 的装配辅助函数保持
3
- // section 文本与门控逐字不变,`enabled` 始终经 getter 注入(门控读取当前值)。
4
-
2
+ // lib/core/shims.mjs,装配块按模块拆分驻留 lib/core/;`enabled` 经 getter 实时读取。
5
3
  import { readdirSync, renameSync, rmSync } from 'node:fs';
6
4
  import { createRequire } from 'node:module';
7
5
  import { join } from 'node:path';
@@ -22,13 +20,17 @@ import { createDraftsStore } from './lib/core/drafts-store.mjs';
22
20
  import { assessDraftProfile } from './lib/core/draft-gates.mjs';
23
21
  import { createEscapeStore, recordEscapeAllowProvider } from './lib/core/escape.mjs';
24
22
  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';
23
+ import { readModelSelectionPolicy } from './lib/core/model-policy.mjs';
24
+ import { profileSectionText } from './lib/core/profile-directory.mjs';
25
+ import { createProfilesQueryTool } from './lib/core/profiles-query.mjs';
26
+ import { budgetSectionText } from './lib/core/dispatch-gates.mjs';
27
+ import { refreshSummaries } from './lib/core/evolution-advice.mjs';
28
+ import { createEvolutionAssembly } from './lib/core/evolution-engine.mjs';
27
29
 
28
30
  export const name = 'dsh-subagent-profile';
29
31
  export const inject = ['subagents', 'tools', 'agents'];
30
32
 
31
- // 自装 bundled 的 "orchestrator" 预设到 agent-presets 根(与官方自安装相同)。
33
+ // 自装 bundled 的 "orchestrator-v2" 预设到 agent-presets 根(与官方自安装相同)。
32
34
  // 幂等:字节一致跳过、bundle 变更重写;fail-soft:写入被拒时工具与设置页照常工作。
33
35
  function syncBundledPresetsToHome(ctx) {
34
36
  try {
@@ -36,21 +38,21 @@ function syncBundledPresetsToHome(ctx) {
36
38
  const sync = syncBundledPresets(presetRoot);
37
39
  for (const { id, error } of sync.failed) ctx.logger.warn(`[dsh-subagent-profile] preset ${id} sync failed: ${error}`);
38
40
  if (sync.synced.length > 0) ctx.logger.info(`[dsh-subagent-profile] presets synced into ${presetRoot}: ${sync.synced.join(', ')}`);
41
+ if (sync.archived.length > 0) ctx.logger.info(`[dsh-subagent-profile] legacy preset archived into ${presetRoot}: ${sync.archived.join(', ')}`);
39
42
  if (sync.userModified.length > 0) ctx.logger.warn(`[dsh-subagent-profile] presets left untouched (user-modified): ${sync.userModified.join(', ')}`);
40
43
  } catch (error) {
41
44
  ctx.logger.warn('[dsh-subagent-profile] preset sync failed:', error instanceof Error ? error.message : String(error));
42
45
  }
43
46
  }
44
47
 
45
- // 卸载清理:本插件 3 个数据文件 + orchestrator 预设目录改名备份(effect 清理器
46
- // 在禁用/热重载/退出时都可能触发,硬删除会静默丢用户数据)。备份保留可恢复副本;
47
- // 规范路径清空后重启按需重建。fail-soft:改名失败不抛。
48
+ // 卸载清理:本插件 3 个数据文件 + orchestrator-v2 预设目录改名备份(effect 清理器
49
+ // 在禁用/热重载/退出时都可能触发;硬删除会静默丢用户数据)。fail-soft:改名失败不抛。
48
50
  function removeOwnedData(home) {
49
51
  const stamp = Date.now();
50
52
  for (const file of ['subagent-profiles.json', 'subagent-profiles.state.json', 'subagent-profiles.failed-traces.json']) {
51
53
  try { renameSync(join(home, file), join(home, `${file}.removed-${stamp}`)); } catch { /* 源不存在(正常首启)等:best effort */ }
52
54
  }
53
- try { renameSync(join(home, '.agent-presets', 'orchestrator'), join(home, '.agent-presets', `orchestrator.removed-${stamp}`)); } catch { /* best effort */ }
55
+ try { renameSync(join(home, '.agent-presets', 'orchestrator-v2'), join(home, '.agent-presets', `orchestrator-v2.removed-${stamp}`)); } catch { /* best effort */ }
54
56
  }
55
57
 
56
58
  // 启动清理 .removed-* 备份残留(只清本插件前缀,防热重载反复产生新备份积累);
@@ -63,7 +65,7 @@ function cleanRemovedBackups(home) {
63
65
  try {
64
66
  if (name.startsWith('subagent-profiles.') && name.includes('.removed-')) {
65
67
  rmSync(join(dir, name), { force: true });
66
- } else if (name.startsWith('orchestrator.removed-')) {
68
+ } else if (name.startsWith('orchestrator-v2.removed-')) {
67
69
  rmSync(join(dir, name), { recursive: true, force: true });
68
70
  }
69
71
  } catch { /* best effort */ }
@@ -81,16 +83,16 @@ function setupAdoptionTracker(ctx, home, getRefreshAdvice, onUnadopted) {
81
83
  onDecision: () => { try { getRefreshAdvice()(); } catch { /* 聚合重算失败不影响判定(fail-soft) */ } },
82
84
  onDecided: onUnadopted,
83
85
  });
84
- const onSessionEvent = (session, event) => adoptionTracker.handleEvent(session, event);
85
- ctx.on('session/event', onSessionEvent);
86
+ const onSessionEvent = (session, event) => adoptionTracker.handleEvent(session, event); ctx.on('session/event', onSessionEvent);
86
87
  ctx.effect(() => () => { ctx.off('session/event', onSessionEvent); adoptionTracker.dispose(); });
87
88
  return adoptionTracker;
88
89
  }
89
90
 
90
91
  // 卸载接线:注销工具 + 清空台账 + 级联取消 + 删数据文件与预设目录。agent/disposed
91
92
  // 挂钩按父释放并发/token 记账,并把该父会话未决采纳判定判「明确未采纳」。
92
- function registerTeardown(ctx, dispatch, ledger, guard, home, backgroundLedger, adoptionTracker) {
93
+ function registerTeardown(ctx, dispatch, profilesQuery, ledger, guard, home, backgroundLedger, adoptionTracker) {
93
94
  ctx.effect(() => dispatch.dispose);
95
+ ctx.effect(() => profilesQuery.dispose);
94
96
  ctx.effect(() => ledger.clear);
95
97
  ctx.effect(() => backgroundLedger.clear);
96
98
  ctx.effect(() => () => {
@@ -100,10 +102,7 @@ function registerTeardown(ctx, dispatch, ledger, guard, home, backgroundLedger,
100
102
  });
101
103
  const onAgentDisposed = ({ agent }) => {
102
104
  const parentSessionId = agent?.session?.header?.id;
103
- if (parentSessionId !== undefined && parentSessionId !== null && parentSessionId !== '') {
104
- guard.resetParent(parentSessionId);
105
- adoptionTracker.parentDisposed(parentSessionId);
106
- }
105
+ if (parentSessionId !== undefined && parentSessionId !== null && parentSessionId !== '') { guard.resetParent(parentSessionId); adoptionTracker.parentDisposed(parentSessionId); }
107
106
  };
108
107
  ctx.on('agent/disposed', onAgentDisposed);
109
108
  ctx.effect(() => () => ctx.off('agent/disposed', onAgentDisposed));
@@ -138,56 +137,45 @@ function provideProfileService(ctx, store) {
138
137
  });
139
138
  }
140
139
 
141
- // 系统提示门控:两段 profile-mode section 只在当前会话 Agent 是编排者预设
142
- // orchestrator)时注入,从而消除从 standard/code/minimal 等不派发会话上的
143
- // 每请求固定泄漏。判据(以源码为准确认,见 test/README.md):主判据
144
- // composedPreset(agentCtx)==='orchestrator';否决 schemas(agent) 明确不含
145
- // dispatch → 必空;回退 无 agentPresets → 保守不注入。
146
- function sectionGatePasses(ctx, getEnabled, context) {
140
+ // 系统提示门控(门控拆分):dispatch:profiles 按「能力」判据(schemas dispatch 即注入);
141
+ // orchestrator:mode / dispatch:budget 保持「模式」判据(composedPreset==='orchestrator-v2')。禁止成对放宽共享 gate。
142
+ // evolution:advice 自本轮起模型侧恒空(下架),不挂任何注入门控——见注册处注释。
143
+ function profileSectionGate(ctx, getEnabled, context) {
147
144
  if (!getEnabled()) return false;
148
- // 否决(防御性):schemas(agent) 明确不含 dispatch → 必空。
145
+ // schemas(agent) 明确不含 dispatch → 必空(否决);缺失 → 注入机会 + debug。
149
146
  if (context !== undefined && context.agent !== undefined) {
150
147
  const schemas = ctx.tools.schemas(context.agent);
151
- if (Array.isArray(schemas) && !schemas.some((s) => s && s.name === 'dispatch')) {
152
- return false;
153
- }
148
+ if (Array.isArray(schemas)) return schemas.some((s) => s && s.name === 'dispatch');
149
+ }
150
+ if (ctx.logger !== undefined && typeof ctx.logger.debug === 'function') {
151
+ ctx.logger.debug('[dsh-subagent-profile] dispatch:profiles 门控:schemas 不可用,按注入机会处理');
152
+ }
153
+ return true;
154
+ }
155
+ // 编排者专属 gate:与 0.4.0 原判据一致(enabled + 否决 + composedPreset,预设名随 V2 收编更新为 orchestrator-v2)。
156
+ function orchestrationGate(ctx, getEnabled, context) {
157
+ if (!getEnabled()) return false;
158
+ if (context !== undefined && context.agent !== undefined) {
159
+ const schemas = ctx.tools.schemas(context.agent);
160
+ if (Array.isArray(schemas) && !schemas.some((s) => s && s.name === 'dispatch')) return false;
154
161
  }
155
- // 主判据:preset 特征 —— composedPreset(agentCtx) === 'orchestrator'
162
+ // 主判据:preset 特征 —— composedPreset(agentCtx) === 'orchestrator-v2'(精确匹配新名;旧名 orchestrator 已由 V2 收编取代,不构成注入判据)。
156
163
  const agentPresets = ctx.get('agentPresets');
157
164
  if (agentPresets !== undefined && typeof agentPresets.composedPreset === 'function') {
158
165
  const agentCtx = context !== undefined && context.agent !== undefined && context.agent.ctx !== undefined
159
166
  ? context.agent.ctx
160
167
  : ctx;
161
- try { return agentPresets.composedPreset(agentCtx) === 'orchestrator'; }
168
+ try { return agentPresets.composedPreset(agentCtx) === 'orchestrator-v2'; }
162
169
  catch { return false; }
163
170
  }
164
171
  // 无 agentPresets → 无法判别 → 保守不注入。
165
172
  return false;
166
173
  }
167
174
 
168
- // dispatch:profiles section 文本。引号引用:description 套引号;为空时显示
169
- // 占位符(不套引号)。压平在存储层完成(sanitizeProfile),此处仅负责显示层包裹。
170
- // 门控 profiles section 内附一行行为规则提示(非开放的 persona 注入)。
171
- function profileSectionText(store, gate, context, summaries) {
172
- if (!gate(context)) return '';
173
- const rows = applyProfileStats(profileDirectoryRows(store), profileStatsFromSummaries(store, summaries));
174
- if (rows.length === 0) return '';
175
- const lines = rows.map((p) => {
176
- const desc = p.description.length > 0 ? `"${p.description}"` : '(无描述)';
177
- const meta = [];
178
- if (p.preset !== undefined) meta.push(`preset: ${p.preset}`);
179
- else if (p.model !== undefined) meta.push(`model: ${p.model}`);
180
- if (p.tokenTier !== undefined) meta.push(`tier: ${p.tokenTier}`);
181
- if (p.avgCost !== undefined) meta.push(`平均 ${p.avgCost} 元/次`);
182
- if (p.successRate !== undefined) meta.push(`成功率 ${p.successRate}${p.n !== undefined ? ` (N=${p.n})` : ''}`);
183
- return `- ${p.id}: ${desc}${meta.length > 0 ? ` (${meta.join(' · ')})` : ''}`;
184
- });
185
- const note = '- 选择方案时先匹配任务复杂度与方案描述的能力边界,成本只在能力都胜任的方案之间比较——复杂任务不得为省 token 改用能力不足的便宜方案。\n- 别把 1-2 步即可自查/可搜完的小事委派出去 —— 几分钟内能自查完的直接做。\n- 优先使用已保存的 profile;只有少数字段需要临时覆盖时才传 per-call 参数。查资料/汇总/读文件等轻任务优先 cheap 或 flash。';
186
- return `Available dispatch profiles (dispatch.profile):\n${lines.join('\n')}\n${note}`;
187
- }
188
-
189
- // orchestrator:mode section 文本(逐字保留;与 profiles 同门控)。
190
- 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 / 主协调模式」时即指本预设,请据此协作。';
175
+ // orchestrator:mode section 文本(编排者模式 V2 定稿文案;与 profiles 同门控。
176
+ // 追加官方错误对照句:官方全英文报错补中文行动引导,与 dispatch 的 stopReason
177
+ // 映射共同构成错误闭环)。
178
+ 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)。';
191
179
 
192
180
  // 建议注入候选池:受信任预设白名单(解析失败回退内置名单)。不叠加逃生舱放行集。
193
181
  async function resolveAdviceWhitelist(ctx) {
@@ -198,54 +186,58 @@ async function resolveAdviceWhitelist(ctx) {
198
186
  }
199
187
  }
200
188
 
201
- // 系统提示各 section:门控经 sectionGatePasses;`enabled` 经 getter 实时读取。
189
+ // 回滚建议提醒 mint:仅 regress 判定时触发一次(提醒写失败吞掉);标题人话化,
190
+ // 内容引导到自进化与建议页签查看建议(只读跳转)。
191
+ async function setupEvolution(ctx, home, store, catalog, evoLedger, getEvolutionAdvice, getReminderStore) {
192
+ const adviceWhitelist = await resolveAdviceWhitelist(ctx);
193
+ const adviceEnv = { summariesFile: join(home, 'subagent-evolution', 'summaries.json'), dispatchFile: join(home, 'subagent-evolution', 'dispatch.jsonl'), whitelist: adviceWhitelist, logger: ctx.logger, onLoss: () => evoLedger.markMigrationLoss() };
194
+ const mintRollbackReminder = (asset) => mintReminder(getReminderStore(), {
195
+ severity: 'P2', kind: 'suggestion-rollback',
196
+ 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 : '未知方案')),
197
+ detail: '该方案调整后观察期表现下降,建议撤销。可在「自进化与建议」页签查看并处理。', sessionId: null,
198
+ });
199
+ const { adviceSource, evolution } = createEvolutionAssembly({ ctx, home, store, catalog, evoLedger, adviceWhitelist, getEvolutionAdvice, getReminderStore: mintRollbackReminder, pluginVersion: readPluginVersion() });
200
+ return { adviceEnv, adviceSource, evolution };
201
+ }
202
+
203
+ // 系统提示各 section:profiles 段挂 profileSectionGate(能力判据),orchestrator:mode
204
+ // 与 dispatch:budget 挂 orchestrationGate(模式判据,编排者专属);evolution:advice
205
+ // 自本轮起模型侧恒空(下架,见注册处注释)。`enabled` 经 getter 实时读取。
202
206
  function registerSystemPromptSections(ctx, store, getEnabled, getEvolutionAdvice, adviceEnv, guard) {
203
207
  const pluginSystemPrompt = ctx.get('systemPrompt');
204
208
  if (pluginSystemPrompt === undefined) return;
205
- const gate = (context) => sectionGatePasses(ctx, getEnabled, context);
209
+ const profileGate = (context) => profileSectionGate(ctx, getEnabled, context);
210
+ const orchestratorGate = (context) => orchestrationGate(ctx, getEnabled, context);
206
211
  pluginSystemPrompt.section({
207
212
  name: 'dispatch:profiles',
208
213
  order: 116.5,
209
- text: (context) => {
210
- let summaries = null;
211
- try { summaries = readSummaries(adviceEnv.summariesFile, adviceEnv.logger); } catch { /* 统计只增强,不影响目录注入 */ }
212
- return profileSectionText(store, gate, context, summaries);
213
- },
214
+ text: (context) => profileSectionText(store, profileGate, context),
214
215
  });
215
- // 宣告自装的 orchestrator 预设,让当前 agent 知道该模式存在、可引导用户使用。
216
- // 门控与 profiles 段相同——只有可派发的 agent 才看得到。
216
+ // 宣告自装的 orchestrator-v2 预设(门控与 0.4.0 相同:仅编排者 agent 可见)。
217
217
  pluginSystemPrompt.section({
218
218
  name: 'orchestrator:mode',
219
219
  order: 117,
220
- text: (context) => (gate(context) ? ORCHESTRATOR_MODE_TEXT : ''),
220
+ text: (context) => (orchestratorGate(context) ? ORCHESTRATOR_MODE_TEXT : ''),
221
221
  });
222
- // 派发优化建议段:门控 = orchestrator + evolutionAdvice 开关;异常兜底不阻断装配。
222
+ // 派发优化建议段(模型侧下架):建议文本曾注入模型可见面却自称「仅供人类
223
+ // 参考」——噪声 + 诱导降档,故恒不注入(text 恒 '',不再过任何门控/开关)。
224
+ // 注册结构保留(section 名与 order 不变,客户端依赖其存在性);人类面板
225
+ // (建议卡/提醒中心/审计行)经 http-routes 读取同一数据源,不受影响。勿在此恢复注入。
223
226
  pluginSystemPrompt.section({
224
227
  name: 'evolution:advice',
225
228
  order: 116.8,
226
- text: (context) => {
227
- if (!getEvolutionAdvice() || !gate(context)) return '';
228
- try {
229
- return buildAdviceText(adviceEnv);
230
- } catch (error) {
231
- // 候选校验失败(如逃生舱放行的非 system 预设)不再静默空转——logger.warn
232
- // 留痕 + 注入段标注「建议暂不可用及原因」,父 Agent 可见、派发行为不受影响。
233
- const reason = error instanceof Error ? error.message : String(error);
234
- adviceEnv.logger.warn(`[dsh-subagent-profile] evolution:advice 生成失败:${reason}`);
235
- return `(派发优化建议暂不可用:${reason}。派发行为不受影响。)`;
236
- }
237
- },
229
+ text: () => '',
238
230
  });
239
- // 派发预算余量段:与 profiles 同门控;无父 sessionId 不注入(无法关联具体会话)。
231
+ // 派发预算余量段(三态文案,见 dispatch-gates.mjs):门控 = orchestrator-v2 预设;
232
+ // 无父 sessionId 不注入(无法关联具体会话)。
240
233
  pluginSystemPrompt.section({
241
234
  name: 'dispatch:budget',
242
235
  order: 116.7,
243
236
  text: (context) => {
244
- if (!gate(context)) return '';
237
+ if (!orchestratorGate(context)) return '';
245
238
  const parentSessionId = context?.agent?.session?.header?.id;
246
239
  if (typeof parentSessionId !== 'string' || parentSessionId === '') return '';
247
- const b = guard.snapshot(parentSessionId);
248
- return `派发预算余量:本会话在途 ${b.concurrency}/${b.maxConcurrent},累计 token ${b.tokens}/${b.maxParentTokens},剩余 ${Math.max(0, b.maxParentTokens - b.tokens)} token。`;
240
+ return budgetSectionText(guard.snapshot(parentSessionId));
249
241
  },
250
242
  });
251
243
  }
@@ -255,7 +247,7 @@ function registerSystemPromptSections(ctx, store, getEnabled, getEvolutionAdvice
255
247
  // webServer 可选——无头部署保留 dispatch 工具、只丢设置页。webServer 的激活
256
248
  // (listen)是异步的,可能晚于本插件 inject 依赖解析完成,故在等它的 inject
257
249
  // 子 scope 内注册(apply 时 ctx.get 会读到 undefined)。
258
- function registerSettingsRoutes(ctx, store, getEnabled, setEnabled, syncTool, catalog, ledger, backgroundLedger, draftsStore, applyDraft, getAudit, getEvolutionAdvice, setEvolutionAdvice, getEscapeEnabled, setEscapeEnabled, escape, refreshAdvice, summariesFile, dispatchFile, adviceWhitelist, previewDraft, reminderStore) {
250
+ 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) {
259
251
  ctx.inject(['webServer'], (scope) => {
260
252
  scope.effect(createHttpRoutes({
261
253
  webServer: scope.webServer,
@@ -280,6 +272,8 @@ function registerSettingsRoutes(ctx, store, getEnabled, setEnabled, syncTool, ca
280
272
  adviceWhitelist,
281
273
  previewDraft,
282
274
  reminderStore,
275
+ adviceSource,
276
+ evolution,
283
277
  logger: ctx.logger,
284
278
  }), 'dsh-subagent-profile: settings routes');
285
279
  });
@@ -347,6 +341,20 @@ function createDispatch(ctx, store, catalog, ledger, guard, evoLedger, backgroun
347
341
  });
348
342
  }
349
343
 
344
+ // profiles_query 工具装配(apply 前抽出):syncTool 机制自动可见、不依赖静态段;
345
+ // 与 dispatch 共用 enabled 开关(/set-enabled 一并注册/注销)。
346
+ function createProfilesQuery(ctx, store, catalog, adviceEnv, getEnabled) {
347
+ return createProfilesQueryTool({
348
+ register: (tool) => ctx.tools.register(tool),
349
+ store,
350
+ catalog,
351
+ summariesFile: adviceEnv.summariesFile,
352
+ onLoss: adviceEnv.onLoss,
353
+ logger: ctx.logger,
354
+ getEnabled,
355
+ });
356
+ }
357
+
350
358
  // auto-profile 落库:三道安全检查评估(draft-gates)→ 任一 fail 即 throw → 落库 + 审计。
351
359
  async function applyDraftProfile({ ctx, store, catalog, getEscapeSet, evoLedger, draft }) {
352
360
  const { ok, checks, clean } = await assessDraftProfile({ ctx, store, catalog, getEscapeSet, draft });
@@ -354,15 +362,14 @@ async function applyDraftProfile({ ctx, store, catalog, getEscapeSet, evoLedger,
354
362
  const failed = checks.find((c) => c.verdict === 'fail');
355
363
  throw new Error(failed !== undefined && typeof failed.reason === 'string' ? failed.reason : 'draft 未通过安全检查');
356
364
  }
357
- store.profiles.set(clean.id, { ...clean, persisted: true });
365
+ store.saveProfile({ ...clean, persisted: true });
358
366
  const persisted = store.persistProfiles();
359
367
  evoLedger.recordGovernanceAudit({ kind: 'draft-apply', profileId: clean.id, source: typeof draft.source === 'string' ? draft.source : 'human' });
360
368
  return { id: clean.id, persisted: persisted.persisted };
361
369
  }
362
370
 
363
371
  export async function apply(ctx) {
364
- const home = dshHome();
365
- cleanRemovedBackups(home); // 上个生命周期留下的 .removed-* 备份残留。
372
+ const home = dshHome(); cleanRemovedBackups(home); // 上个生命周期留下的 .removed-* 备份残留。
366
373
  // reminderStore 变量提前声明:evoLedger.onAlert 与 escape mint 钩子延迟读取。
367
374
  let reminderStore;
368
375
  // 派发台账 + 审计分级须在 store 之前构造:store 的治理审计钩子指向其 markGovernanceFailure。
@@ -372,7 +379,6 @@ export async function apply(ctx) {
372
379
  let enabled = store.loadEnabled();
373
380
  let evolutionAdvice = store.loadEvolutionAdvice();
374
381
  store.loadProfiles();
375
- // 自装 bundled 的 orchestrator 预设(幂等、fail-soft)。
376
382
  syncBundledPresetsToHome(ctx);
377
383
  const catalog = createSharedCatalog(ctx);
378
384
  const ledger = createFailureLedger({ warn: (message) => ctx.logger.warn(`[dsh-subagent-profile] ${message}`), stateFile: join(home, 'subagent-profiles.failed-traces.json') });
@@ -380,17 +386,16 @@ export async function apply(ctx) {
380
386
  const backgroundLedger = createBackgroundLedger({ warn: (message) => ctx.logger.warn(`[dsh-subagent-profile] ${message}`) });
381
387
  const draftsStore = createDraftsStore({ dshHome: home, onGovernanceFailure: () => evoLedger.markGovernanceFailure() });
382
388
  reminderStore = createReminderStore({ dshHome: home, audit: (entry) => evoLedger.recordGovernanceAudit(entry), warn: (message) => ctx.logger.warn(`[dsh-subagent-profile] ${message}`) });
383
- let refreshAdvice = () => {};
384
- const adoptionTracker = setupAdoptionTracker(ctx, home, () => refreshAdvice, (rec) => mintUnadoptedReminder(reminderStore, evoLedger, rec));
389
+ let refreshAdvice = () => {}; const adoptionTracker = setupAdoptionTracker(ctx, home, () => refreshAdvice, (rec) => mintUnadoptedReminder(reminderStore, evoLedger, rec));
385
390
  const dispatch = createDispatch(ctx, store, catalog, ledger, guard, evoLedger, backgroundLedger, adoptionTracker, escapeCtl.getEscapeSet, () => enabled, () => evolutionAdvice);
386
391
  provideProfileService(ctx, store);
387
- const adviceWhitelist = await resolveAdviceWhitelist(ctx);
388
- const adviceEnv = { summariesFile: join(home, 'subagent-evolution', 'summaries.json'), dispatchFile: join(home, 'subagent-evolution', 'dispatch.jsonl'), whitelist: adviceWhitelist, logger: ctx.logger };
389
- // 生产聚合触发点(T1 修复):/options/refresh /list 触发的惰性重算;parent_adopted
390
- // 的「已确认未采纳」计数经 opts 惰性 join 进 weighted_success(-0.3 惩罚)。
392
+ const { adviceEnv, adviceSource, evolution } = await setupEvolution(ctx, home, store, catalog, evoLedger, () => evolutionAdvice, () => reminderStore);
393
+ const profilesQuery = createProfilesQuery(ctx, store, catalog, adviceEnv, () => enabled);
394
+ const syncTools = () => { dispatch.syncTool(); profilesQuery.syncTool(); };
395
+ // 生产聚合触发点(T1 修复):/options/refresh /list 的惰性重算。
391
396
  refreshAdvice = () => refreshSummaries({ dispatchFile: adviceEnv.dispatchFile, summariesFile: adviceEnv.summariesFile, logger: ctx.logger, opts: { parentAdoptedConfirmedFalse: adoptionTracker.confirmedFalseCounts() } });
392
397
  registerSystemPromptSections(ctx, store, () => enabled, () => evolutionAdvice, adviceEnv, guard);
393
- const disposeProvider = createProfileProvider({ subagents: ctx.subagents, store, getEnabled: () => enabled, logger: ctx.logger, catalog, getEscapeSet: escapeCtl.getEscapeSet, recordEscapeAllowProvider: escapeCtl.recordEscapeAllowProvider });
398
+ const disposeProvider = createProfileProvider({ subagents: ctx.subagents, store, getEnabled: () => enabled, logger: ctx.logger, catalog, getEscapeSet: escapeCtl.getEscapeSet, recordEscapeAllowProvider: escapeCtl.recordEscapeAllowProvider, readModelSelectionPolicy, evoLedger });
394
399
  if (typeof disposeProvider === 'function') ctx.effect(() => disposeProvider);
395
400
  const applyDraft = (draft) => applyDraftProfile({ ctx, store, catalog, getEscapeSet: escapeCtl.getEscapeSet, evoLedger, draft });
396
401
  // /draft/preview:只读安全检查预览(不落库)。body 为 {config, name, description, source}。
@@ -406,7 +411,6 @@ export async function apply(ctx) {
406
411
  source: body !== null && typeof body === 'object' && typeof body.source === 'string' ? body.source : 'human',
407
412
  },
408
413
  });
409
- // Client 设置 UI HTTP loopback 路由 —— lib/core/http-routes.mjs。
410
- 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);
411
- registerTeardown(ctx, dispatch, ledger, guard, home, backgroundLedger, adoptionTracker);
414
+ 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);
415
+ registerTeardown(ctx, dispatch, profilesQuery, ledger, guard, home, backgroundLedger, adoptionTracker);
412
416
  }