@menghuan1918/dsh-apollo 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,13 +24,15 @@
24
24
 
25
25
  ## 这是什么
26
26
 
27
- Apollo 是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的**插件集合入口**:安装一次,同时得到流程技能、两套工程预设,并把三个各自独立发布、可单独使用的插件装配进同一个 profile:
27
+ Apollo 是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的**插件集合入口**:安装一次,同时得到流程技能、两套工程预设,并把五个各自独立发布、可单独使用的插件装配进同一个 profile:
28
28
 
29
- - **[`dsh-better-goal`](https://github.com/Menghuan1918/dsh-better-goal)**:长期目标、暂停与提问超时;
29
+ - **[`dsh-better-goal`](https://github.com/Menghuan1918/dsh-better-goal)**:长期目标、暂停与轮数策略;
30
30
  - **[`@menghuan1918/dsh-better-subagent`](https://github.com/Menghuan1918/dsh-better-subagent)**:Codex / Claude 可续接 CLI 子代理、`wait_subagent`、运行中子代理守卫、模型分档与子代理提示词;
31
- - **[`dsh-better-compact`](https://github.com/Menghuan1918/dsh-better-compact)**:native / goal / file-handoff 三种压缩模式与只读诊断。
31
+ - **[`dsh-better-compact`](https://github.com/Menghuan1918/dsh-better-compact)**:native / goal / file-handoff 三种压缩模式与只读诊断;
32
+ - **[`dsh-better-ask-user`](https://github.com/Menghuan1918/dsh-better-ask-user)**:提问等待、输入续期、Markdown/移动端卡片、超时行为与旧 goal 等待迁移;
33
+ - **[`dsh-better-ptc`](https://github.com/Menghuan1918/dsh-better-ptc)**:ask-user 与 subagent 共享的 PTC deadline 豁免单例。
32
34
 
33
- > **0.5.0 的所有权变化**:Apollo 不再实现 CLI 协议/路由、`wait_subagent`、能力探针、子代理设置页、模型分档/委派提示词或运行中子代理守卫。这些代码只在独立插件里保留一份;Apollo 只做**装配、通用工程纪律、后台任务退出守卫、旧配置迁移**。Apollo 也不再注册 `/apollo/api/*` —— 该前缀由 better-subagent 作为只读兼容别名拥有。
35
+ > **0.6.0 的所有权变化**:提问等待从 better-goal 拆到 better-ask-user;PTC deadline 豁免从 Apollo 主行拆到共享 better-ptc。Apollo 只做**装配、通用工程纪律、后台任务退出守卫、旧配置迁移**。Apollo 也不再注册 `/apollo/api/*` —— 该前缀由 better-subagent 作为只读兼容别名拥有。
34
36
 
35
37
  ```mermaid
36
38
  flowchart TB
@@ -76,6 +78,8 @@ flowchart TB
76
78
  | 工程模式 — `eng` | 编码 / 基础设施任务 | 原生工具调用;保留 workflow 工具 |
77
79
  | 工程(PTC)模式 — `eng-ptc` | 多步、循环、需要过滤汇总的任务 | 工具经 `run_code` 的 TypeScript SDK 调用;与官方 PTC 模式一致不提供 workflow |
78
80
 
81
+ 在 PTC 模式里,`wait_subagent` / `ask_user_question` 的等待不消耗 `run_code` 的 elapsed budget;普通嵌套工具仍按原预算计时。该豁免由单例 `dsh-better-ptc` 行提供,ask-user 与 subagent 各自向同一服务注册。提问等待由 dsh-better-ask-user 统一管理,默认 120 秒;在它的行设置里可改为 `-1`,普通会话与 goal active 可分别选原 pending 或超时默认选择。两套预设的 `tool-ask-user` 不再固定 `timeout: -1`。
82
+
79
83
  两套预设共用 Apollo 的通用工程纪律(KISS、证据先行、修复根因、防过度设计与过度防御、权限与依赖边界、命令与进程纪律)。**委派规则与模型分档不在 Apollo**:由 better-subagent 在 `agent/created` 时按预设与活配置注册自己的 prompt section。完整组装结果见 [prompts/eng.md](prompts/eng.md) 与 [prompts/eng-ptc.md](prompts/eng-ptc.md)。
80
84
 
81
85
  两套预设的压缩组里,原生 `compaction-basic` 被替换为 `dsh-better-compact/engine`(与独立 bundle 完全相同的稳定行),保留 `command-compact` 与 `tool-result-pruner`。工程模式不提供自动化任务工具;退出守卫默认关闭,只检查本会话仍运行的后台 job,不管理 pm2、systemd、nohup 等外部进程。
@@ -86,15 +90,15 @@ flowchart TB
86
90
 
87
91
  ```sh
88
92
  # npm 安装(发布后)
89
- dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.5.2
93
+ dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.0
90
94
 
91
95
  # 或从 GitHub main 源码安装(备选)
92
96
  dsh plugin --profile <profile> add github:Menghuan1918/dsh-apollo
93
97
  ```
94
98
 
95
- > **发布状态(诚实说明)**:Apollo `0.5.0` 已发布,`dsh-better-subagent@0.1.0` / `dsh-better-compact@0.1.1` 作为其精确依赖已可从 npm 取得。当前版本 `0.5.2` 是 compact 0.1.1 的设置表单 schema 修复版;本仓库 0.5.1 的验证使用本地 tarball `npm install --no-save`,提交的 manifest 中**没有** `file:` / `link:` / Git 说明符。
99
+ > **发布状态(诚实说明)**:Apollo `0.6.0` 及 `dsh-better-ask-user@0.1.0` / `dsh-better-ptc@0.1.0` 目前**尚未发布**;本轮在本地 worktrees 和临时 profile 中验证。提交的 manifest 使用精确 npm 版本,**没有** `file:` / `link:` / Git 说明符。发布顺序见[发布顺序](#发布顺序)。
96
100
 
97
- 安装、升级或换包都会改变运行的代码,必须重启对应 profile 的 DSH 进程;只刷新网页不够。目标行、子代理行与压缩行由 Apollo 的 bundle patch 插入,不需要再单独 `add` 三个子包。
101
+ 安装、升级或换包都会改变运行的代码,必须重启对应 profile 的 DSH 进程;只刷新网页不够。目标行、子代理行、压缩行、共享 PTC 行与 ask-user 行由 Apollo 的 bundle patch 插入,不需要再单独 `add` 五个子包。
98
102
 
99
103
  > **不要重复添加**:已经显式选中 better-subagent / better-compact bundle 的 profile,再加 Apollo 会形成“双显式来源”。Loader 仍只激活一行,但 DSH 0.2.1-rc.2 会把同一 id 的配置写入拒绝为只读(见[独立使用与组合安装](#独立使用与组合安装))。只保留一个显式入口。
100
104
 
@@ -102,14 +106,14 @@ dsh plugin --profile <profile> add github:Menghuan1918/dsh-apollo
102
106
 
103
107
  ```sh
104
108
  dsh plugin --profile <profile> remove dsh-apollo
105
- dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.5.2
109
+ dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.0
106
110
  ```
107
111
 
108
112
  > `dsh-apollo` 只是旧入口名;unscoped 的 `dsh-apollo` 已被其他项目占用,不要从 npm 安装它。
109
113
 
110
114
  ## 从 0.4.x 升级
111
115
 
112
- 0.5.0 把子代理与压缩能力移出 Apollo,0.5.1 修复旧 profile 行的 `name` guard 迁移缺口;0.5.2 依赖 compact 0.1.1,修复 Web 设置表单的 schema rehydrate。旧 profile 不需要手工重填配置:Apollo 启动后在 settings/loader 可用时运行一次**幂等迁移**([src/migrate.js](src/migrate.js))。
116
+ 0.6.0 把提问等待从 better-goal 移到独立的 dsh-better-ask-user,并把 PTC deadline 豁免移到共享 dsh-better-ptc;better-goal 的旧 `questionTimeoutSeconds` 仍可读取,ask-user 行会做一次性迁移。旧 profile 不需要手工重填配置:Apollo 启动后在 settings/loader 可用时运行一次**幂等迁移**([src/migrate.js](src/migrate.js));ask-user 行的迁移会在其设置服务就绪后运行。
113
117
 
114
118
  > **0.5.1 迁移修复**:如果旧 profile patch 的 `apollo-skills` 覆盖还带历史名称 `name: dsh-apollo`,DSH 会把 `name` 当 guard 并跳过该覆盖,导致有效配置看起来仍是默认值。0.5.1 从 `configEditor.configuration().override` 恢复这些显式值(含 `false` / `0` / `""` / `[]`),再合并到迁移来源;被跳过的旧预设覆盖(`preset-eng` / `preset-eng-ptc`)同样处理。无需手工修改旧行;升级后重启 profile 即可。
115
119
 
@@ -145,9 +149,10 @@ flowchart LR
145
149
  | `apollo-skills` | Apollo | `exitGuard`(**只**拦运行中后台 job) | volatile,保存即生效 |
146
150
  | `dsh-better-subagent` | better-subagent | `childExitGuard`、`tiers.*`、两个 CLI 后端、`subagents.eng/global` | volatile,保存即生效 |
147
151
  | `dsh-better-compact` | better-compact | 模式、auto、阈值/headroom/保留、摘要模型、重试、模型策略 | volatile 保存;**引擎快照需重载 profile** |
148
- | `dsh-better-goal` | goal | 轮数策略、提问超时 | volatile,保存即生效 |
152
+ | `dsh-better-goal` | goal | 轮数策略 | volatile,保存即生效 |
153
+ | `dsh-better-ask-user` | ask-user | 等待时长、输入续期、普通/Goal 超时行为 | volatile,保存即生效;旧 goal 值一次性迁移 |
149
154
 
150
- - Apollo 的最小行页只保留后台任务退出守卫与迁移说明。
155
+ - Apollo 的最小行页只保留后台任务退出守卫与迁移说明;PTC deadline 豁免由独立的 `dsh-better-ptc` 单例行拥有,没有设置页。
151
156
  - 子代理页、压缩页分别由两个独立包自己的浏览器半边注册;它们也注册 Apollo 集成槽别名(`@menghuan1918/dsh-apollo#<row>`、`dsh-apollo#<row>`),独立与集成共用同一份实现,不产生三份配置。
152
157
  - 运行中子代理的拦截与 Apollo 的后台 job 守卫是**两个独立开关**;任意一个关闭都不影响原生工具。
153
158
  - 原生 `subagent` / `subagent_fork` / `send_message` / `interrupt_agent` 注册表不变;better-subagent 只是在它上面加路由、等待工具与提示词,不拆第二套 registry。
@@ -159,6 +164,7 @@ flowchart LR
159
164
  | Apollo `exitGuard`、子代理行、goal 行设置 | 保存后即时生效 |
160
165
  | better-compact 设置 | 保存后写入 profile;重启 profile 后引擎采用新快照 |
161
166
  | 安装 / 升级 / 移除 bundle、修改预设或顶层 patch | 重启对应 profile 的 DSH 进程 |
167
+ | 共享 PTC 行 / ask-user 行 / 预设内 `tool-ask-user` 行 | 重启对应 profile 的 DSH 进程(host 逻辑与预设不热更) |
162
168
  | 已有 CLI 子代理的模型 / effort / profile / 可执行文件 / 初始目录 | 创建时固定;改设置只影响新任务 |
163
169
  | 已有 CLI 子代理的权限 | 每轮读取最新设置 |
164
170
 
@@ -172,6 +178,9 @@ dsh plugin --profile <profile> add @menghuan1918/dsh-better-subagent@0.1.0
172
178
 
173
179
  # 只装压缩
174
180
  dsh plugin --profile <profile> add dsh-better-compact@0.1.1
181
+
182
+ # 只装提问增强(同时需要共享 PTC 行)
183
+ dash plugin --profile <profile> add dsh-better-ask-user@0.1.1 dsh-better-ptc@0.1.0
175
184
  ```
176
185
 
177
186
  独立安装时的行、页面和提示词与 Apollo 集成完全相同;行 id/name 也是同一组稳定值。区别只在**由谁插入这些行**:Apollo 的 bundle patch 自己携带同样的行,因为 DSH 不会自动应用传递依赖的 bundle patch。
@@ -226,20 +235,20 @@ dsh plugin --profile <profile> remove dsh-better-compact
226
235
  - `package.json` 的 `files` 只列入 `docs/DEVELOPMENT.md`,并额外用 [.npmignore](.npmignore) 排除 `docs/specs/`;
227
236
  - `tests/packaging.test.mjs` 直接运行 `npm pack --dry-run --json`,断言发布清单没有 Specs、测试、脚本、快照或 tarball。
228
237
 
229
- 发布顺序:**先发布并验证两个独立包(`@menghuan1918/dsh-better-subagent@0.1.0`、`dsh-better-compact@0.1.1`),再发布 Apollo 0.5.2**。两个 0.1.0 已随 0.5.0 发布;0.5.1 可只发布本包。Apollo 的精确依赖指向尚不存在的版本时,`npm install` 会失败;不要用开发 `file:`/`link:` 依赖发布。两个子仓各自有 trusted publishing workflow;Apollo 的 [release workflow](.github/workflows/release.yml) 只发布本包。
238
+ 发布顺序:**先发布并验证五个独立包(`dsh-better-ask-user@0.1.1`、`dsh-better-ptc@0.1.0`、`@menghuan1918/dsh-better-subagent@0.2.0`、`dsh-better-goal@0.2.1`、`dsh-better-compact@0.1.2`),再发布 Apollo 0.6.0**。Apollo 的精确依赖指向尚不存在的版本时,`npm install` 会失败;不要用开发 `file:`/`link:` 依赖发布。各子仓各自有 trusted publishing workflow;Apollo 的 [release workflow](.github/workflows/release.yml) 只发布本包。
230
239
 
231
240
  ## 验证状态(诚实记录)
232
241
 
233
242
  在本实现提交时验证过(命令与结果见 [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)):
234
243
 
235
244
  - `npm test`:配置/装配/漂移/迁移纯函数/settings/客户端/prompt/发布清单全部通过,含真实 temp-profile 迁移测试;
236
- - `npm run prompts`:用发布布局复制 Apollo + 三个独立包,由真实 headless 宿主重新生成两份快照;
245
+ - `npm run prompts`:用发布布局复制 Apollo + 五个独立包,由真实 headless 宿主重新生成两份快照;
237
246
  - `npm pack --dry-run --json`:发布清单无 Specs/tests/scripts/prompts/tarball;
238
247
  - `node scripts/runtime-smoke.mjs apollo|standalone|both`:临时 profile + 脚本化 LLM 适配器(无凭据),覆盖单实例行、旧配置迁移、eng 回合、native subagent create/wait、手动 `/compact`、inspect、真实后台 job 退出守卫、保存→`requiresReload`→重启→`activeConfig`、独立组合与双显式 bundle 的只读限制。
239
248
 
240
249
  **未验证 / 明确限制**:
241
250
 
242
- - 子包 `@menghuan1918/dsh-better-subagent@0.1.0` 与 `dsh-better-compact@0.1.1` 已可从 npm 取得。
251
+ - 五个独立包的 0.6.0 配套版本尚未发布;本轮只在本地 worktrees 与临时 profile 验证。
243
252
  - 未修改或重启用户当前运行的 profile,未验证当前 GUI(`http://127.0.0.1:3384`)的设置页/卡片;S3 视觉设置页的 GUI 验收归 S3/最终宿主验收。
244
253
  - 未运行真实 Codex/Claude CLI 的真实模型回合(那属于两个子包自己的验证;Apollo 侧本轮只用脚本化适配器)。
245
254
  - 真实模型摘要保真、缓存命中率与跨版本预设漂移仍需按实际环境复验。
package/cordis.patch.yml CHANGED
@@ -4,10 +4,11 @@
4
4
  # presets/ load after it); activate with
5
5
  # `dsh plugin --profile <name> add @menghuan1918/dsh-apollo`.
6
6
  #
7
- # Apollo 0.5.0 is an integration bundle. The subagent and compaction features
8
- # are owned by two independent plugins, and Apollo repeats their *same stable
9
- # rows* here because DSH does not auto-apply a transitive dependency's bundle
10
- # patch (`dsh plugin add` only selects directly installed bundles):
7
+ # Apollo 0.6.0 is an integration bundle. The subagent, compaction, question-wait
8
+ # and PTC-exemption features are owned by independent plugins, and Apollo
9
+ # repeats their *same stable rows* here because DSH does not auto-apply a
10
+ # transitive dependency's bundle patch (`dsh plugin add` only selects directly
11
+ # installed bundles):
11
12
  #
12
13
  # - `@menghuan1918/dsh-better-subagent` owns the Codex/Claude CLI subagents,
13
14
  # `wait_subagent`, the running-child exit guard, the subagent settings page
@@ -16,7 +17,10 @@
16
17
  # (engine row) own compaction; the native host-plane `compaction-basic` is
17
18
  # disabled here exactly like the standalone bundle, and the eng/eng-ptc
18
19
  # presets carry the engine row inside their own compaction realm;
19
- # - `dsh-better-goal` keeps owning the goal domain.
20
+ # - `dsh-better-goal` keeps owning the goal domain;
21
+ # - `dsh-better-ask-user` owns the question wait, input extension, Web card
22
+ # and timeout behavior; `dsh-better-ptc` is the singleton PTC exemption
23
+ # service shared by the ask-user and subagent rows.
20
24
  #
21
25
  # Selecting both the standalone bundle and Apollo still loads one instance: the
22
26
  # Loader keys entries by row id. The same rule makes the double-explicit-bundle
@@ -69,6 +73,21 @@
69
73
  - id: dsh-better-subagent
70
74
  name: '@menghuan1918/dsh-better-subagent'
71
75
 
76
+ # ── question wait + PTC exemption: independent rows ─────────────────────────
77
+ # The official Web card is disabled by the ask-user bundle; repeat the disable
78
+ # and the two stable rows here for the same transitive-dependency reason.
79
+ # dsh-better-ptc is a singleton: ask-user and better-subagent both insert the
80
+ # same row id, and the Loader keeps one service and one runtime wrapper.
81
+ - id: ui-user-questions
82
+ disabled: true
83
+
84
+ - insert:
85
+ - id: dsh-better-ptc
86
+ name: dsh-better-ptc
87
+
88
+ - id: dsh-better-ask-user
89
+ name: dsh-better-ask-user
90
+
72
91
  # ── Apollo host row ─────────────────────────────────────────────────────────
73
92
  # The apollo-skills row is one row on purpose. The row's module specifier must
74
93
  # be this package's npm name (`@menghuan1918/dsh-apollo`), not a short alias,
@@ -90,6 +109,8 @@
90
109
  # 3. the one-time legacy `exitGuard` import and the one-time old-config
91
110
  # migration into dsh-better-subagent / dsh-better-compact (src/migrate.js);
92
111
  # 4. the `declareOwnsHost` index tap.
112
+ # The PTC deadline exemption is now owned by the independent dsh-better-ptc
113
+ # singleton row, not this package.
93
114
  # It no longer registers `/apollo/api/*` (dsh-better-subagent owns those read
94
115
  # aliases) and no longer implements CLI protocols, subagent routing, the wait
95
116
  # tool, tier/delegation prompt generation or the child exit guard.
@@ -2,7 +2,7 @@
2
2
 
3
3
  面向维护者、集成者和需要排查装配问题的用户。用户安装与配置请看 [README](../README.md);本文保留 DSH 插件的实现细节、旧配置迁移、组合安装边界与验证方法。
4
4
 
5
- 当前实现版本:`0.5.2`;npm 包名是 `@menghuan1918/dsh-apollo`。纯 ESM,无构建步骤;宿主浏览器半边是直接加载的闭包工厂。
5
+ 当前实现版本:`0.6.0`;npm 包名是 `@menghuan1918/dsh-apollo`。纯 ESM,无构建步骤;宿主浏览器半边是直接加载的闭包工厂。
6
6
 
7
7
  ## 目录
8
8
 
@@ -33,17 +33,18 @@
33
33
 
34
34
  | 依赖 | 版本 | 所有权 |
35
35
  |---|---|---|
36
- | `dsh-better-goal` | `0.2.0` | 目标域 |
37
- | `@menghuan1918/dsh-better-subagent` | `0.1.0` | 子代理 CLI/等待/child guard/分档/设置页 |
38
- | `dsh-better-compact` | `0.1.1` | 压缩 companion + engine + 设置页/诊断 |
36
+ | `dsh-better-goal` | `0.2.1` | 目标域(`questionTimeoutSeconds` 已弃用、仅保留读取) |
37
+ | `@menghuan1918/dsh-better-subagent` | `0.2.0` | 子代理 CLI/等待/child guard/分档/设置页;注册 `wait_subagent` PTC 豁免 |
38
+ | `dsh-better-compact` | `0.1.2` | 压缩 companion + engine + 设置页/诊断 |
39
+ | `dsh-better-ask-user` | `0.1.1` | 提问等待/输入续期/卡片/超时行为;注册 `ask_user_question` PTC 豁免;旧 goal 等待迁移 |
40
+ | `dsh-better-ptc` | `0.1.0` | 单例 PTC deadline 豁免服务 |
39
41
  | `@deepseek-ai/schemastery` | `~3.18.4` | Apollo 行 Config |
40
42
  | `react` | peer | 宿主提供 |
41
43
 
42
- 两个 0.1.0 版本在实现完成时尚未发布到 npm;验证使用本地 tarball:
44
+ 正式安装从 npm registry 获取上述精确版本,不需要本地 tarball 或 link:
43
45
 
44
46
  ```sh
45
47
  npm install
46
- npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/to/dsh-better-compact-0.1.0.tgz
47
48
  ```
48
49
 
49
50
  发布顺序必须是先两个子包、后 Apollo(见[发布](#实现入口打包与发布))。
@@ -81,7 +82,7 @@ npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/
81
82
 
82
83
  ## Apollo 行:保留与弃用
83
84
 
84
- `apollo-skills` 是 Apollo 唯一的宿主插件行,0.5.0 后只承担:
85
+ `apollo-skills` 是 Apollo 的主宿主插件行,0.5.0 后只承担:
85
86
 
86
87
  1. 向技能注册表的全局层注册包内 `skills/*/SKILL.md`(rank 600,用户技能根可覆盖);
87
88
  2. 挂载正式行 Config([src/config.js](../src/config.js))与最小浏览器行页;
@@ -90,6 +91,8 @@ npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/
90
91
 
91
92
  **不再负责**:CLI 协议/适配/身份 KV、`wait_subagent`、能力/模型/路由 HTTP 探针、子代理设置页、模型分档与委派提示词生成、运行中子代理退出判断。Apollo 不注册任何 HTTP 路由;`/apollo/api/*` 由 better-subagent 作为只读兼容别名拥有,重复注册同一 `(kind, path)` 会被 webServer 拒绝。
92
93
 
94
+ PTC deadline 豁免由独立的 `dsh-better-ptc` 单例行提供(无浏览器半边、无设置页):它包裹挂载的 PTC runtime 的 prototype `run`,在已注册工具(`wait_subagent` / `ask_user_question`)在途时暂停 `run_code` 的 elapsed budget,返回后继续剩余预算;普通嵌套工具仍消耗预算。ask-user 与 subagent 都只插入同一个稳定行并调用 `ctx.betterPtc.register(...)`,Loader 去重保证只有一份服务和一层包装。没有 `ptcRuntime` 或 runtime 不提供 numeric timeout 时注册为 no-op。
95
+
93
96
  ### 配置分类
94
97
 
95
98
  ```js
@@ -159,7 +162,8 @@ npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/
159
162
  - `tool-jobs.maxConsecutiveWakes = 5000`;
160
163
  - `tool-result-pruner` 阈值是上游 2 倍(16384/8192/2048);
161
164
  - 保留 `eng-exit-guard`、`eng-mode-instructions` 两个本地插件;
162
- - 原生 delegation 工具行、tool-subagent 行保持一份,作为宿主 registry 的消费者。
165
+ - 原生 delegation 工具行、tool-subagent 行保持一份,作为宿主 registry 的消费者;
166
+ - `tool-ask-user` 不再固定 `timeout`:基础行保留为 fallback/shadow 探针,新 ask-user 宿主行在每个 Agent 上注册 `ask_user_question` 影子,等待时长与超时行为来自 ask-user 行设置。
163
167
 
164
168
  `eng-ptc` 是 eng 的拷贝,差异恰为上游 `ptc` 相对 `standard` 的差异:`tool-presentation { mode: ptc }`、`workflow-ptc` / `tool-workflow` disabled。0.5.0 起不再有 `eng-mode-instructions` 的 `ptc` 开关;better-subagent 自己识别 `eng-ptc` 追加 fan-out 规则。
165
169
 
@@ -192,7 +196,7 @@ npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/
192
196
  1. 有 Loader 时等待装配完成(`loader.await()`,有界);
193
197
  2. 等待 `legacyImport`(旧 eng.json)完成;
194
198
  3. 有界轮询 `settings.describe()`,直到 `dsh-better-subagent` 与 `dsh-better-compact` 两个命名空间都出现(默认 5s,50ms 间隔);
195
- 4. 子代理迁移 → 压缩迁移,各自写入后重读验证;
199
+ 4. 子代理迁移 → 恢复 Apollo job 开关 → 压缩迁移,各自写入后重读验证;
196
200
  5. 返回状态并记录日志(complete / pending / blocked / error)。
197
201
 
198
202
  若目标命名空间一直不可读/不可写(例如双显式 bundle 导致写入被拒),日志明确要求重载 profile,迁移保持幂等,下次启动会再试;不宣告成功。迁移不删除旧值,也不创建迁移 sidecar。
@@ -205,6 +209,8 @@ npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/
205
209
 
206
210
  **0.5.1 修复**:DSH 把 profile 覆盖行的 `name` 当 guard。旧 profile 若保留历史 `name: dsh-apollo`,Loader 会跳过该覆盖,`describe().value` 只剩默认值(例如用户显式 `subagents.eng: false` 会被看成默认 `true`)。迁移现在额外读取 `configEditor.configuration()` 中该行的 `override`,递归合并到有效配置之上:普通对象逐键合并,数组与所有标量(含 `false`/`0`/`""`/`[]`)整体覆盖。这样被跳过的显式值仍会迁移。`preset-eng` / `preset-eng-ptc` 的预设覆盖也按同样方式合并(`plugins` 数组整体替换),再读取原生 `compaction-basic`。
207
211
 
212
+ 历史 name guard 同样会让仍活跃的 Apollo job `exitGuard` 静默回退到默认 false。现在迁移还会只把这个开关经 `settings.update` 恢复到 Apollo,并重读验证;不会激活其他已弃用字段。宿主 `describe().user` 会投影被跳过的旧覆盖,因此优先检查活动 Apollo entry 的 raw config:它已声明 `exitGuard` 时(尤其当前 scoped 行显式 false),不覆盖。child 目标仍按自己的 `user.childExitGuard` 存在性判断,不让两个开关互相覆盖。旧 unscoped 行保留,重复执行不重写。仅在子代理迁移完成/无需写入后恢复 job 开关,避免新 scoped 行遮蔽尚未导入的旧来源;写入拒绝为 pending,重读不一致为 error,均纳入顶层状态。
213
+
208
214
  目标:`dsh-better-subagent` 的 `settings.describe()`:
209
215
 
210
216
  - `target.user` 的存在性表示显式值(含 false/0/""/[]);
@@ -218,7 +224,7 @@ npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/
218
224
  | `tiers.fast` / `tiers.quality` | 同名 | 目标该档已有任一显式字段 → 整档不混写;否则来源档与目标默认不同才写 |
219
225
  | `subagents.eng` / `global` | 同名 | 目标缺字段且值不同才写;显式 false 是值 |
220
226
  | `subagents.codex/claude.*` | 同名 | 逐叶补齐:enabled、executable、profile(仅 codex)、permissionMode、defaultModel、models(含顺序/effort 元数据) |
221
- | `exitGuard` | `childExitGuard` | 只补目标缺失字段;Apollo 自己的 `exitGuard` 保留给 job 守卫 |
227
+ | `exitGuard` | Apollo `exitGuard` + `childExitGuard` | 两份相同初值,随后独立配置;当前目标显式值(含 false)各自优先 |
222
228
  | `trustedHosts` | `trustedHosts` | 非 volatile;目标 raw config 未声明且来源非空时用 `configEditor.edit` 写 |
223
229
 
224
230
  写入:volatile 用 `settings.update(ns, patch, revision)`;非 volatile 用 target entry 的 `configEditor.edit`。`settings.update` 会触发 reconcile,可能替换目标 entry 对象,因此写 `trustedHosts` 前重新从 `entries()` 取目标 entry。写后重新 `describe()`/读 raw config 校验,任何字段不一致按 `error` 报告。
@@ -240,6 +246,10 @@ npm install --no-save /path/to/menghuan1918-dsh-better-subagent-0.1.0.tgz /path/
240
246
 
241
247
  原生默认值不会变成目标显式值:只有来源行里显式存在的键才会被携带。压缩配置是 volatile 保存、引擎载入时冻结快照;迁移后的设置需要重载 profile 才会被运行中引擎采用(inspect 的 `requiresReload` 可观察)。
242
248
 
249
+ Apollo 在迁移挂载时先登记启动门禁:验证完成前,`agent/pre-step` 阻止任务步骤,`llm/stream` 同时阻止不经过 pre-step 的手动摘要调用。迁移 pending/blocked/error 不得按默认配置继续执行;应检查日志后修复来源或目标并重载。若本次迁移改变 compact 的载入快照,持久化验证完成后仍要求重载,重载后确认无差异才放行。普通后续设置保存不关闭已通过的启动门禁,仍按原有“当前快照继续、重载后应用新值”语义处理。
250
+
251
+ 宿主行明确 `disabled: true` 时,跳过该增强的迁移,不将其缺少 settings namespace 误判为全宿主故障;活动行缺失 namespace 仍报 pending。子代理增强被禁用时保留尚未迁出的旧来源,并暂缓会遮蔽该来源的旧 job 开关恢复,待重新启用后再迁移;已生效的 Apollo job 配置不变。停用 compact 增强时仍需按卸载说明启用原生 `compaction-basic`,以恢复 `/compact`;启动门禁不替用户改写禁用选择。
252
+
243
253
  ### 迁移测试
244
254
 
245
255
  - [tests/migrate.test.mjs](../tests/migrate.test.mjs):纯函数与 fake host 编排。覆盖显式 false/空值、目标优先(分档整档、models 空列表、保留方式二选一)、缺失来源、ambiguity、trustedHosts、写入拒绝、幂等、重读验证、有界等待。
@@ -308,10 +318,10 @@ DSH_PACKAGE_ROOT="/…/node_modules/@deepseek-ai/dsh" npm run prompts
308
318
  ### npm 安装
309
319
 
310
320
  ```sh
311
- dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.5.2
321
+ dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.6.0
312
322
  ```
313
323
 
314
- 包发布后即可用此命令安装,并带入三个精确依赖、应用三份 bundle patch。安装后需要重启对应 profile 的 DSH 进程。0.5.0 已发布;0.5.1 是迁移修复版本;0.5.2 依赖 compact 0.1.1,修复 Web 设置表单的 schema rehydrate。
324
+ 包发布后即可用此命令安装,并带入五个精确依赖、应用三份 bundle patch。安装后需要重启对应 profile 的 DSH 进程。0.6.0 目前尚未发布;本轮在本地 worktrees + 临时 profile 验证。
315
325
 
316
326
  ### GitHub 源码安装
317
327
 
@@ -319,20 +329,23 @@ dsh plugin --profile <profile> add @menghuan1918/dsh-apollo@0.5.2
319
329
  dsh plugin --profile <profile> add github:Menghuan1918/dsh-apollo
320
330
  ```
321
331
 
322
- GitHub main 仍是源码安装选项。它同样按 `package.json` 解析精确依赖;子包 0.1.0/0.1.1 在 registry 可取得前,源码安装会因依赖 404 失败。
332
+ GitHub main 仍是源码安装选项。它同样按 `package.json` 解析精确依赖;五个子包在 registry 可取得前,源码安装会因依赖 404 失败。
323
333
 
324
334
  ### 本地 link 安装
325
335
 
326
336
  ```sh
327
337
  cd /absolute/path/to/dsh-apollo
328
- npm install
329
- npm install --no-save /tmp/better-plugin-packs/menghuan1918-dsh-better-subagent-0.1.0.tgz /tmp/better-plugin-packs/dsh-better-compact-0.1.0.tgz
338
+ # 五个子包尚未发布时不要直接 npm install(会因精确依赖 404 失败)。
339
+ # 先把本地目录/tarball 装进 Apollo 的 node_modules;本工作区已经就绪。
340
+ npm install --no-save /absolute/path/to/dsh-better-ptc /absolute/path/to/dsh-better-ask-user \
341
+ /absolute/path/to/dsh-better-subagent /absolute/path/to/dsh-better-goal \
342
+ /absolute/path/to/dsh-better-compact
330
343
  dsh plugin --profile <profile> add link:/absolute/path/to/dsh-apollo
331
344
  ```
332
345
 
333
346
  注意:
334
347
 
335
- - `dsh plugin add link:` 只链接 Apollo 本身,不会代装它的依赖;必须先 `npm install`(子包未发布时用本地 tarball `--no-save` 仅作验证);
348
+ - `dsh plugin add link:` 只链接 Apollo 本身,不会代装它的依赖;必须先 `npm install`(子包未发布时用本地目录/tarball `--no-save` 仅作验证);
336
349
  - 不要提交 `file:` / `link:` / Git 依赖;发布版必须保持精确 npm 版本;
337
350
  - 不需要启动额外服务或构建 Web 资源;宿主提供 React。
338
351
 
@@ -342,7 +355,7 @@ dsh plugin --profile <profile> add link:/absolute/path/to/dsh-apollo
342
355
  dsh --profile <profile> --dump-config
343
356
  ```
344
357
 
345
- 应看到 `apollo-skills`、`dsh-better-goal`、`dsh-better-compact`、`dsh-better-compact-engine`、`dsh-better-subagent` 行和两个工程预设。若缺少独立插件的行,先确认依赖是否安装;若同时显式选中了独立 bundle,参考上面的只读限制。
358
+ 应看到 `apollo-skills`、`dsh-better-goal`、`dsh-better-compact`、`dsh-better-compact-engine`、`dsh-better-subagent`、`dsh-better-ptc`、`dsh-better-ask-user` 行和两个工程预设;`ui-user-questions` 应被禁用。若缺少独立插件的行,先确认依赖是否安装;若同时显式选中了独立 bundle,参考上面的只读限制。
346
359
 
347
360
  ### 升级
348
361
 
@@ -363,9 +376,9 @@ dsh plugin --profile <profile> remove @menghuan1918/dsh-apollo
363
376
 
364
377
  两个子仓和 Apollo 都用 GitHub Release 触发 `.github/workflows/release.yml`,通过 npm trusted publishing(OIDC)发布,仓库里不存 npm token。tag 必须是 `v<package.json 的 version>`,CI 会先校验再跑测试和 `npm publish --provenance`。
365
378
 
366
- 1. 先发布 `@menghuan1918/dsh-better-subagent@0.1.0` 与 `dsh-better-compact@0.1.1`(各自仓库的 Release);
367
- 2. 在官方 registry(`https://registry.npmjs.org/`)确认两个版本可取得、可安装;
368
- 3. 再把 Apollo 的精确依赖指向已发布版本,推 main 并发布 Apollo Release(0.5.0 已发布;本轮为 `v0.5.2`);
379
+ 1. 先发布 `dsh-better-ptc@0.1.0`、`dsh-better-ask-user@0.1.1`、`@menghuan1918/dsh-better-subagent@0.2.0`、`dsh-better-goal@0.2.1`、`dsh-better-compact@0.1.2`(各自仓库的 Release);
380
+ 2. 在官方 registry(`https://registry.npmjs.org/`)确认五个版本可取得、可安装;
381
+ 3. 再把 Apollo 的精确依赖指向已发布版本,推 main 并发布 Apollo Release(本轮为 `v0.6.0`);
369
382
  4. 子包版本可取得前,不要把指向它们的精确依赖推到 Apollo main,否则 `npm install` / GitHub 源码安装会 404。
370
383
 
371
384
  ## 测试与冒烟
@@ -424,7 +437,7 @@ DSH_PACKAGE_ROOT=/path/to/node_modules/@deepseek-ai/dsh node scripts/runtime-smo
424
437
  - 本仓库不运行真实 Codex/Claude CLI;两个子包各自的 `cli-smoke` / runtime smoke 负责真实 CLI 生命周期。
425
438
  - 真实模型摘要保真、缓存命中率、S3 可视化页面的 GUI 验收不在 Apollo 侧冒烟内。
426
439
  - 未修改用户当前运行的 profile,未验证当前 GUI;当前 URL 以执行时 runtime context 为准。
427
- - 单元测试通过不等于 registry 发布/安装路径通过;子包 0.1.0/0.1.1 已发布;registry 安装路径已用 fresh cache 验证。
440
+ - 单元测试通过不等于 registry 发布/安装路径通过;五个子包的目标版本尚未发布,registry 安装路径未验证。
428
441
 
429
442
  ## 历史兼容
430
443
 
@@ -456,11 +469,11 @@ DSH_PACKAGE_ROOT=/path/to/node_modules/@deepseek-ai/dsh node scripts/runtime-smo
456
469
  | 文件 | 职责 |
457
470
  |---|---|
458
471
  | [package.json](../package.json) | 包元数据、bundle patch 顺序、浏览器注入、精确依赖 |
459
- | [cordis.patch.yml](../cordis.patch.yml) | 原生 goal/compaction 行禁用;goal、compact companion/engine、subagent、`apollo-skills` 顶层行 |
472
+ | [cordis.patch.yml](../cordis.patch.yml) | 原生 goal/compaction/官方 Web 提问卡片禁用;goal、compact companion/engine、subagent、shared ptc、ask-user、`apollo-skills` 顶层行 |
460
473
  | [presets/eng.patch.yml](../presets/eng.patch.yml) / [presets/eng-ptc.patch.yml](../presets/eng-ptc.patch.yml) | 两套预设的行清单唯一事实源 |
461
474
  | [presets/eng/plugins/exit-guard/index.js](../presets/eng/plugins/exit-guard/index.js) | job-only 退出守卫 |
462
475
  | [presets/eng/plugins/mode-instructions/index.js](../presets/eng/plugins/mode-instructions/index.js) | 工程纪律 + job 守卫提示词 |
463
- | [src/index.js](../src/index.js) | 宿主行入口:技能 provider、settings、迁移 |
476
+ | [src/index.js](../src/index.js) | 主宿主行入口:技能 provider、settings、迁移 |
464
477
  | [src/config.js](../src/config.js) | 正式配置 schema、deprecated 字段、规整与活配置读取 |
465
478
  | [src/settings.js](../src/settings.js) | 行页面策略、旧文件导入、legacyImport 就绪 Promise |
466
479
  | [src/owns-host.js](../src/owns-host.js) | `declareOwnsHost` index tap(唯一 web helper) |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@menghuan1918/dsh-apollo",
3
- "version": "0.5.2",
4
- "description": "DSH plugin bundle: workflow skills, engineering presets, independent better-goal / better-subagent / better-compact integration, and a minimal job exit guard. Pure ESM, no build step.",
3
+ "version": "0.6.0",
4
+ "description": "DSH plugin bundle: workflow skills, engineering presets, independent better-goal / better-subagent / better-compact / better-ask-user integration, and a minimal job exit guard. Pure ESM, no build step.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
7
7
  "type": "git",
@@ -54,9 +54,11 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@deepseek-ai/schemastery": "~3.18.4",
57
- "@menghuan1918/dsh-better-subagent": "0.1.0",
58
- "dsh-better-compact": "0.1.1",
59
- "dsh-better-goal": "0.2.0"
57
+ "@menghuan1918/dsh-better-subagent": "0.2.0",
58
+ "dsh-better-ask-user": "0.1.1",
59
+ "dsh-better-compact": "0.1.2",
60
+ "dsh-better-goal": "0.2.1",
61
+ "dsh-better-ptc": "0.1.0"
60
62
  },
61
63
  "peerDependencies": {
62
64
  "react": "^18.2.0"
@@ -147,7 +147,8 @@
147
147
  # domain is owned by the independent dsh-better-goal plugin row in the
148
148
  # top-level patch: it registers one `goal` tool and one `/goal` command
149
149
  # per Agent, hides any legacy goal tools a preset still carries, and has
150
- # its own Plugins row for round limits and question timeouts. The two
150
+ # its own Plugins row for round limits. Question waits live in the
151
+ # independent dsh-better-ask-user row. The two
151
152
  # removed rows remain deliberate removals in tests/upstream-drift.test.mjs,
152
153
  # which asserts they still exist upstream before filtering them out.
153
154
 
@@ -0,0 +1,36 @@
1
+ import { isDeepStrictEqual } from 'node:util'
2
+
3
+ /** Do not run a turn or manual summary using pre-migration defaults. */
4
+ export function mountMigrationAdmission(ctx) {
5
+ let report = { status: 'pending' }
6
+ let requiresReload = false
7
+ const assertReady = () => {
8
+ if (report.status !== 'complete') {
9
+ const detail = report.error ?? report.compact?.error ?? report.compact?.reason ?? report.subagent?.error
10
+ const error = new Error('Apollo 配置迁移尚未完成(' + report.status + ')。请检查迁移诊断,修复配置后重载 profile。' + (detail ? ' ' + detail : ''))
11
+ error.code = 'APOLLO_MIGRATION_PENDING'
12
+ throw error
13
+ }
14
+ if (requiresReload) {
15
+ const error = new Error('Apollo 已迁移压缩配置,但当前引擎仍使用旧快照。请重载 profile 后继续;刷新网页不会应用压缩配置。')
16
+ error.code = 'APOLLO_MIGRATION_RELOAD_REQUIRED'
17
+ throw error
18
+ }
19
+ }
20
+ ctx.on('agent/pre-step', (_payload, next) => {
21
+ assertReady()
22
+ return next()
23
+ }, { prepend: true })
24
+ // Manual /compact does not enter pre-step. Guard its provider boundary too,
25
+ // without replacing the compaction service or changing any request messages.
26
+ ctx.on('llm/stream', async function* (_options, next) {
27
+ assertReady()
28
+ yield* next()
29
+ }, { prepend: true })
30
+ return (result) => {
31
+ report = result
32
+ const compact = ctx.get?.('betterCompactConfig')
33
+ requiresReload = result.status === 'complete' && result.compact?.fields?.length > 0
34
+ && compact !== undefined && !isDeepStrictEqual(compact.snapshot(), compact.current())
35
+ }
36
+ }
package/src/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Apollo's single host row registers bundled skills, its own minimal row
3
- * settings, and the one-time old-config migration into the independent
4
- * dsh-better-subagent / dsh-better-compact rows. Presets are still delivered by
5
- * bundle patches. Browser code remains a no-build closure factory.
3
+ * settings, the one-time old-config migration into the independent
4
+ * dsh-better-subagent / dsh-better-compact / dsh-better-ask-user rows. Presets
5
+ * are still delivered by bundle patches. Browser code remains a no-build
6
+ * closure factory. The PTC deadline exemption is owned by the independent
7
+ * dsh-better-ptc singleton row, not this package.
6
8
  *
7
9
  * Apollo no longer implements subagent CLI protocols/routing, the wait tool,
8
10
  * capability/model HTTP probes, tier/delegation prompt generation or the child
@@ -14,6 +16,7 @@ import { readFile, readdir } from 'node:fs/promises'
14
16
  import { dirname, join } from 'node:path'
15
17
  import { fileURLToPath } from 'node:url'
16
18
  import { mountMigration } from './migrate.js'
19
+ import { mountMigrationAdmission } from './admission.js'
17
20
  import { mountSettings } from './settings.js'
18
21
  export { Config } from './config.js'
19
22
 
@@ -33,8 +36,9 @@ export const inject = ['skills']
33
36
  export function apply(ctx, config) {
34
37
  ctx.skills.registerProvider(() => createProvider(ctx))
35
38
  if (typeof ctx.inject === 'function') {
39
+ const onResult = typeof ctx.on === 'function' ? mountMigrationAdmission(ctx) : undefined
36
40
  const settings = mountSettings(ctx, config)
37
- mountMigration(ctx, config, { legacyImport: settings.legacyImport })
41
+ mountMigration(ctx, config, { legacyImport: settings.legacyImport, onResult })
38
42
  }
39
43
  }
40
44
 
package/src/migrate.js CHANGED
@@ -7,8 +7,8 @@
7
7
  *
8
8
  * - `apollo-skills` 的 `tiers.fast/quality`、`subagents.{eng,global}`、
9
9
  * `subagents.{codex,claude}.*`、`trustedHosts` → `dsh-better-subagent`
10
- * 相同路径;`exitGuard` → `childExitGuard`。旧 `exitGuard` 仍留在 Apollo
11
- * 作为 job/通用守卫开关,不被删除。
10
+ * 相同路径;`exitGuard` → `childExitGuard`。旧 `exitGuard` 同时保留/恢复到
11
+ * Apollo 作为 job 守卫开关;两边目标显式配置(含 false)各自优先。
12
12
  * - 旧 `eng` / `eng-ptc` 预设里原生 `compaction-basic` 的显式配置 →
13
13
  * `dsh-better-compact` 同名原生键;目标缺失的 `mode` 采用默认 `goal`。
14
14
  *
@@ -524,6 +524,28 @@ export async function migrateSubagent({ settings, configEditor, descriptors, sou
524
524
  return { status: 'complete', fields }
525
525
  }
526
526
 
527
+ /** Restore only the job switch lost to a historical Apollo name guard. */
528
+ async function migrateJobExitGuard({ settings, descriptors, source, rawSource }) {
529
+ const target = descriptors.find((row) => row?.ns === APOLLO_NS)
530
+ // user also projects skipped overrides, so the active raw config is the
531
+ // authority for an already configured job switch (including explicit false).
532
+ if (hasOwn(rawSource, 'exitGuard')
533
+ || (hasOwn(target?.user, 'exitGuard') && target.user.exitGuard !== source.exitGuard)
534
+ || target?.value?.exitGuard === source.exitGuard) {
535
+ return { status: 'no-change' }
536
+ }
537
+ if (target === undefined) return { status: 'pending', error: 'Apollo namespace is not readable' }
538
+ try {
539
+ await settings.update(APOLLO_NS, { exitGuard: source.exitGuard }, target.revision)
540
+ } catch (error) {
541
+ return { status: 'pending', error: errorMessage(error) }
542
+ }
543
+ const after = (await settings.describe()).find((row) => row?.ns === APOLLO_NS)
544
+ return after?.value?.exitGuard === source.exitGuard
545
+ ? { status: 'complete' }
546
+ : { status: 'error', error: 'unverified: exitGuard' }
547
+ }
548
+
527
549
  /**
528
550
  * Apply the compaction migration.
529
551
  *
@@ -570,7 +592,8 @@ function errorMessage(error) {
570
592
  * Run the one-time (idempotent) migration.
571
593
  *
572
594
  * Order: Loader settlement → legacy `eng.json` import → bounded wait for both
573
- * target namespaces → subagent plan/write/verify → compact plan/write/verify.
595
+ * target namespaces → subagent plan/write/verify → job switch recovery →
596
+ * compact plan/write/verify.
574
597
  * A missing target namespace yields `pending`; errors are reported, never
575
598
  * silently claimed as success, and no source value is deleted.
576
599
  *
@@ -588,7 +611,12 @@ export async function runMigration(ctx, options = {}) {
588
611
  const loader = loaderOf(ctx)
589
612
  if (loader !== undefined) await bounded(Promise.resolve(loader.await()).catch(() => undefined), timeoutMs)
590
613
  if (options.legacyImport !== undefined) await bounded(options.legacyImport, timeoutMs)
591
- const descriptors = await waitForDescriptors(settings, [SUBAGENT_NS, COMPACT_NS], {
614
+ const configEditor = options.configEditor ?? serviceOf(ctx, 'configEditor')
615
+ // A deliberately disabled row is not an unloaded/failed active plugin. Keep
616
+ // its legacy source intact for a later re-enable instead of blocking native work.
617
+ const disabled = new Set([SUBAGENT_NS, COMPACT_NS].filter((ns) => targetEntryOf(configEditor, ns)?.options?.disabled === true))
618
+ const targets = [SUBAGENT_NS, COMPACT_NS].filter((ns) => !disabled.has(ns))
619
+ const descriptors = await waitForDescriptors(settings, targets, {
592
620
  timeoutMs,
593
621
  intervalMs: options.intervalMs,
594
622
  })
@@ -599,7 +627,6 @@ export async function runMigration(ctx, options = {}) {
599
627
  return { status: 'pending', error: message }
600
628
  }
601
629
  const sourceDescriptor = descriptors.find((row) => row?.ns === APOLLO_NS)
602
- const configEditor = options.configEditor ?? serviceOf(ctx, 'configEditor')
603
630
  const agentPresets = options.agentPresets ?? serviceOf(ctx, 'agentPresets')
604
631
  // describe().value only projects the *volatile* form, so read the Apollo
605
632
  // row's raw config for non-volatile fields (trustedHosts) and overlay the
@@ -617,23 +644,34 @@ export async function runMigration(ctx, options = {}) {
617
644
  effectiveSource,
618
645
  configurationOverrideOf(configEditor, APOLLO_NS),
619
646
  ))
620
- const subagent = await migrateSubagent({ settings, configEditor, descriptors, source, log })
621
- const compact = await migrateCompact({ settings, configEditor, descriptors, agentPresets, log })
622
- const status = [subagent.status, compact.status].some((value) => value === 'error')
647
+ const subagent = disabled.has(SUBAGENT_NS)
648
+ ? { status: 'disabled', fields: [] }
649
+ : await migrateSubagent({ settings, configEditor, descriptors, source, log })
650
+ // A scoped job write would become the last override and hide the skipped
651
+ // source from configuration().override. Wait until its child fields are safe.
652
+ const job = subagent.status === 'disabled'
653
+ ? { status: 'deferred', reason: 'subagent is disabled; preserve the legacy source until it is re-enabled' }
654
+ : ['complete', 'no-change'].includes(subagent.status)
655
+ ? await migrateJobExitGuard({ settings, descriptors, source, rawSource })
656
+ : { status: 'pending', error: 'waiting for the subagent migration before restoring the job switch' }
657
+ const compact = disabled.has(COMPACT_NS)
658
+ ? { status: 'disabled', fields: [] }
659
+ : await migrateCompact({ settings, configEditor, descriptors, agentPresets, log })
660
+ const status = [job.status, subagent.status, compact.status].some((value) => value === 'error')
623
661
  ? 'error'
624
- : [subagent.status, compact.status].some((value) => value === 'pending')
662
+ : [job.status, subagent.status, compact.status].some((value) => value === 'pending')
625
663
  ? 'pending'
626
664
  : ['ambiguous', 'invalid', 'source-missing'].includes(compact.status) ? 'blocked'
627
665
  : 'complete'
628
- for (const [name, section] of [['subagent', subagent], ['compact', compact]]) {
666
+ for (const [name, section] of [['job', job], ['subagent', subagent], ['compact', compact]]) {
629
667
  if (section.error !== undefined) {
630
668
  log?.warn?.(`apollo-migration: ${name} is ${section.status}: ${section.error}`)
631
669
  }
632
670
  }
633
671
  log?.[status === 'complete' ? 'info' : 'warn']?.(
634
- `apollo-migration: ${status} (subagent=${subagent.status}, compact=${compact.status})`,
672
+ `apollo-migration: ${status} (job=${job.status}, subagent=${subagent.status}, compact=${compact.status})`,
635
673
  )
636
- return { status, source: sourceDescriptor === undefined ? 'apply-config' : 'settings', subagent, compact }
674
+ return { status, source: sourceDescriptor === undefined ? 'apply-config' : 'settings', job, subagent, compact }
637
675
  }
638
676
 
639
677
  /**
@@ -641,7 +679,8 @@ export async function runMigration(ctx, options = {}) {
641
679
  *
642
680
  * @param ctx - plugin context.
643
681
  * @param config - Apollo row config (fallback source when describe() is not ready).
644
- * @param options - `{ legacyImport, env, timeoutMs, intervalMs }`.
682
+ * @param options - `{ legacyImport, env, timeoutMs, intervalMs, onResult }`.
683
+ * `onResult` receives the settled report without blocking the Loader callback.
645
684
  */
646
685
  export function mountMigration(ctx, config, options = {}) {
647
686
  let started = false
@@ -656,7 +695,8 @@ export function mountMigration(ctx, config, options = {}) {
656
695
  log: settingsCtx.logger ?? ctx.logger,
657
696
  }).catch((error) => {
658
697
  settingsCtx.logger?.warn?.(`apollo-migration: skipped: ${errorMessage(error)}`)
659
- })
698
+ return { status: 'error', error: errorMessage(error) }
699
+ }).then((result) => options.onResult?.(result))
660
700
  }
661
701
  ctx.inject(['settings'], start)
662
702
  }