dsh-model-switch 0.4.26 → 0.4.28

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/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## v0.4.28
2
+
3
+ Two fixes for the same DSH `0.2.1-rc.2` line: re-enabling the plugin on 0.2.1 made **every tool call fail before its body ran**, and the Subagent routing adapter that 0.4.26 had to fail closed is adapted to the release's activation API.
4
+
5
+ - **P0: every tool call failed under the Auto preset on DSH 0.2.1 (`src/auto-review.ts`).** With the plugin enabled and the approval policy on Auto, each tool call was rejected with `Auto review of tool "…" failed; its body was not executed: cannot get property "workingDirectory" without inject` — read, grep, pwsh, even exiting plan mode. Root cause: `model-switch-auto-review` called `official.apply(scope)` **directly inside its own fiber**, so every service the official gate reads had to be mirrored in this row's `inject`; 0.2.1's gate started reading `ctx.workingDirectory`, and the mirror list had gone stale. Fix: the official gate is now **mounted as a plugin** (`autoReviewGate` → `scope.plugin(...)`), so cordis resolves the official dependency list — including `workingDirectory` on 0.2.1 and its absence on 0.2.0 — and this row declares only the services its own code reads (`llm`, `modelSwitch`). The session-correlation listener is registered only after the gate's fiber reports that its `apply` actually ran, so it still lands ahead of the gate's own prepended listener; an already-active official row still fails loudly (single `registerAuto`), and a gate still waiting for host services is reported instead of silently mis-ordering.
6
+ - **Subagent routing adapted to the 0.2.1 activation API (`src/subagent-runtime.ts`).** 0.2.1 replaced `start()` / `startContinuable()` with `startActivation(spec)`. The routed runtime now recognizes **either published line** (`subagentSurfaceOf`), keeps both legacy overrides unchanged, and adds a `startActivation` override that rewrites `spec.request.agentOptions` through the same `provider group → flat subagent card → session model` resolver (a caller-named route still wins; a provider whose capability is not `agentOptions` is still never reinterpreted). Only a Host publishing neither surface falls back to the untouched official runtime with `routedRuntimeUnavailableNotice`, so the 0.2.1 fallback path is now unreachable in practice.
7
+ - **Correction to the v0.4.26 entry.** That entry claimed the official auto-review `apply` "runs unchanged under this row's scoped `llm`" on 0.2.1. The live desktop showed otherwise: it ran unchanged only until the reviewer read a service the mirrored `inject` did not carry. 0.4.26 and 0.4.27 therefore shipped that failure on 0.2.1; 0.4.28 is the first release where the gate actually runs there.
8
+ - **Verification.**
9
+ - `pnpm run check` green (52 test files / 460 tests + pack gate). New coverage: the inject trap reproduced with a real cordis `Service` (an unwrapped gate fails with the exact live message, the wrapped gate passes), the official dependency list surviving the mount, gate-failure reporting, `subagentSurfaceOf` for the activation line, the start line, and neither, activation-spec routing (session group, flat fixed pair, follow-main, explicit caller route, external provider), spec passthrough, and mounting the routed runtime on an activation-only Host.
10
+ - **Real published packages, `0.2.1-rc.2`** (lab CLI `node_modules`, a real `Service` registration so the trap is live, the real official gate): the pre-fix shape reproduces exactly `Auto review of tool "pwsh" failed; its body was not executed: cannot get property "workingDirectory" without inject`; the shipped shape reaches `workingDirectory.ensure` (one call), registers the Auto gate once, and never fails on inject. The real 0.2.1 `SubagentRuntime` classifies as the activation line and drops `start`/`startContinuable`; the adapter delegates to that exact method with `request.agentOptions` rewritten to the configured subagent route while `provider` / `label` / `delivery` / `signal` / `parent` identity pass through.
11
+ - **Real published packages, `0.2.0-rc.2`**: the row registers the Auto gate **without** a `workingDirectory` service, the gate never hits an inject failure, the runtime classifies as the `start` line, and `start()` delegation still rewrites the route and preserves parent identity.
12
+ - **Lab plane** (`DSH_HOME=~/.dsh-lab`, npm-installed `0.2.1-rc.2` runtime, packed `dsh-model-switch-0.4.28.tgz` installed in the `labh` profile): a headless turn ran a real tool (directory listing) with no inject failure and no `{{cwd}}` error; a second turn spawned a real subagent, and the child session's own `subagent/descriptor` records `mode: continuable`, `provider: spawn`, `agentProvider: apigoto`, `agentModel: mimo-v2.6-flash` — the fixed subagent route configured for that profile, not the session default `apigoto/deepseek-flash`. That is the end-to-end proof that the activation-line override reaches the child's route on 0.2.1.
13
+ - **Residual (honest).** The live desktop is the only place the Auto preset gate runs end to end with a real tool body: after updating, re-enable the plugin, switch the approval policy back from full-access mode to Auto, and run one tool call. The harness above exercises the real 0.2.1 packages and the real cordis `Service` trap, but not a full session snapshot — the fake session stops the reviewer right after `workingDirectory.ensure`, by design. 0.1.7 / 0.2.0 keep the same entries, the same route precedence, and the same fail-closed behavior.
14
+
15
+ ## v0.4.27
16
+
17
+ A fix for a regression v0.4.26 introduced on DSH `0.2.1-rc.2`, plus the architecture change that removes its cause: the plugin no longer ships static copies of DSH's agent presets.
18
+
19
+ - **What broke (v0.4.26, DSH 0.2.1 only).** Every turn failed before the first token with `unknown prompt variable "{{cwd}}" in section "deployment:persona-suffix"`, and the composer needed the plugin disabled to continue. Cause: `cordis.patch.yml` replaced `preset-standard` / `preset-ptc` / `preset-cordis` wholesale with copies captured from the **0.2.0** presets. Those copies carried `suffix: Your working directory is {{cwd}}.` in the `persona` row; DSH 0.2.1 validates prompt variables and registers only `provider` and `model`, and its own presets dropped the `cwd` variable — so composing the persona suffix threw for every new turn. The copies had also drifted: they lacked the `time-context` and `tool-schedule` rows that 0.2.1 moved into presets, so even without the fatal error those rows would have been missing.
20
+ - **Fix: bridge the nested compaction engine at Host startup instead of copying presets (`src/preset-sync.ts`, wired from `ModelSwitchRuntime`).** On startup the Host finds each `@deepseek-ai/dsh-agent-preset` declaring row in the loader, walks its nested entry lists, disables the nested `@deepseek-ai/dsh-compaction-basic` row, and inserts `model-switch-compaction` directly after it. Every other row, prompt text, and `!!js` expression is left exactly as the installed DSH (or a deployment that edited the preset in the Web editor) wrote it. An entry that has not started yet gets its raw config swapped in place; a started entry is reloaded through the public loader `entry.update`. A failure is logged once and the official preset stays mounted, so the turn still runs and compaction simply keeps the official default route.
21
+ - **Why the copies are gone for good.** The static copies were a pinned snapshot that silently hid every later DSH preset change — this regression is exactly that failure mode. Reading each installed release's own preset definition removes the per-release re-sync step and restored the `time-context` / `tool-schedule` rows on 0.2.1.
22
+ - **Verification.** `pnpm run check` green (52 test files / 450 tests + pack gate). `tests/preset-patch.spec.ts` was rewritten to pin the bridge instead of the copies: 0.2.0-shaped (persona `{{cwd}}` suffix) and 0.2.1-shaped preset configs, idempotency, a preset without a compaction group, matching by package + preset id, both bridge paths (raw-config swap and loader reload), reload-failure logging, and a check that the patch no longer replaces any preset row. Lab plane on `0.2.1-rc.2` (`DSH_HOME=~/.dsh-lab`, port 3082): the composed tree now carries the official presets (`persona` prefix only, no `{{cwd}}`, with `time-context` and `tool-schedule` restored) and a headless host with 0.4.27 ran a full turn end to end — the answer streamed normally, and the naming record shows the new chain: a failed group candidate (`apigoto/no-such-title-model`) fell through to the deployment pair, `session/title-llm-request` recorded `apigoto/mimo-v2.6-flash` with `reasoningEffort: off` on the 0.2.1 call shape, and the provider title replaced the fallback title.
23
+ - **Residual.** The desktop app updated to `0.2.1-rc.2` is still running v0.4.26 with the plugin disabled; re-enable it after updating to 0.4.27 to confirm the live turn on that machine. 0.1.7 / 0.2.0 compositions are untouched by this change: with no preset row replacement, they keep their own shipped presets, and only the nested compaction swap is applied by the bridge at startup.
24
+
1
25
  ## v0.4.26
2
26
 
3
27
  Two coordinated changes: automatic session naming now survives a failing title route (the auxiliary call is retried and then falls through the candidate routes instead of leaving the deterministic fallback title in place forever), and official DSH `0.2.1-rc.2` is declared compatible — that release replaced the shared title call with a provider-prepared one and replaced the Subagent start surface with its activation API, so both seams are now handled per installed line.
@@ -6,11 +30,12 @@ Two coordinated changes: automatic session naming now survives a failing title r
6
30
  - **DSH 0.2.1 naming line (`executeSessionTitleLlm`).** `0.2.1-rc.2` removed `generateSessionTitleWithLlm` / `registerSessionTitleLlmProvider` and split the shared policy: the new `executeSessionTitleLlm(ctx, config, request, id, prepared)` returns assembled blocks plus the terminal finish reason, each provider owns its system instruction, input framing, reasoning-effort selection, and output interpretation, and `targetWords` / `targetCjkCharacters` moved into the provider's own config. The row detects the installed line at runtime and mirrors the shipped 0.2.1 provider exactly: same system instruction (including the best-effort line and, for all-prompts, the current-title stability rules anchored on a provider-sourced `currentTitle`), same JSON framing, the route's least supported reasoning effort, and the same finish-reason / whole-line-emphasis / empty-output acceptance rules. `cordis.patch.yml` also disables the `optional-session-title-all-prompts` row that 0.2.1's experimental session-titles bundle inserts (unknown-row warning on profiles without it, like the optional auto-review row).
7
31
  - **DSH 0.2.1 compatibility review (0.2.0-rc.2 → 0.2.1-rc.2, public API only).**
8
32
  - **Naming:** the service keeps `register` / `ensureFallback` / the `first-prompt` gate and only adds an optional `currentTitle` to the provider request; `onMainRequest` dropped one boundary-freshness condition. The provider request shape this row returns is unchanged.
9
- - **Auto review:** the official gate now resolves the reviewing agent's directory through `ctx.workingDirectory.ensure(...)` and injects `workingDirectory`; `dsh-base` 0.2.1 mounts the `working-directory` row, so the official `apply` runs unchanged under this row's scoped `llm`.
33
+ - **Auto review:** the official gate now resolves the reviewing agent's directory through `ctx.workingDirectory.ensure(...)` and injects `workingDirectory`; `dsh-base` 0.2.1 mounts the `working-directory` row. **[Corrected in v0.4.28]** This entry originally claimed the official `apply` "runs unchanged under this row's scoped `llm`" on 0.2.1. It did not: the row called `apply` directly and mirrored the gate's services in its own `inject`, so the new `workingDirectory` read failed for every tool call. v0.4.28 mounts the gate as a plugin instead.
10
34
  - **Compaction and presets:** `dsh-compaction-basic` and `dsh-agent-preset` are byte-identical to 0.2.0, so the wholesale preset copies and the compaction engine need no re-sync.
11
35
  - **Unchanged surfaces this plugin calls:** `llm.stream(GenerateOptions)` (with `prepareCall` added beside it), `web.registerSearchProvider`, `tools/pre-execute` (`exec.agent` kept), session `snapshotEvents` / `requestHeader` / `requestContext` / `append`, and the composer's `conversation.input.model` seat.
12
36
  - **Subagents:** `@deepseek-ai/dsh-subagent` replaced `start()` / `startContinuable()` with `startActivation(spec)` plus a provider registry. The routed runtime therefore cannot override the new entry point; this row mounts the untouched official runtime (fail-closed) and logs one explicit notice. Recorded in `docs/seam-gap.md` for a later upstream proposal.
13
- - **Verification.** `pnpm run check` green (`tsc` + `tsdown`, 52 test files / 437 tests, pack gate). New coverage: both auxiliary-LLM lines end to end, route-candidate ordering and dedup, max-token widening, fall-through, all-candidate failure reporting, cancelled requests staying silent, provider-prepared prompt/framing/anchor/effort selection, and the replaced-Subagent notice. Both official lines were also exercised end to end against the **real published packages** (0.2.1 `executeSessionTitleLlm`, 0.1.7 `generateSessionTitleWithLlm`): registration through the real policy validator, route selection, the `session/title-llm-request` record, the official system prompt, the least reasoning effort (0.2.1), and title acceptance all agree. **Lab plane:** `DSH_HOME=~/.dsh-lab` with the npm-installed `0.2.1-rc.2` runtime and the packed `dsh-model-switch-0.4.26.tgz` — profile composition `exit 0` with the naming, compaction, auto-review, and subagent rows composed from this plugin, and the Web Host booted on port 3082 serving the auth-gated page. **Residual (honest):** the 3082 capability RPC was not exercised from a browser (no browser automation is available in this environment), the Ark route probe is blocked by that account's weekly quota (`AccountQuotaExceeded`; the API reports the reset at `2026-10-19 00:00 +0800`), and the live desktop is still `0.2.0-rc.2`, so the 0.2.1 claim rests on the package-surface review, the unit suite, the real-package line checks, and the lab boot; the naming differential on a live 0.2.1 desktop is re-checked after the app update.
37
+ - **Verification.** `pnpm run check` green (`tsc` + `tsdown`, 52 test files / 437 tests, pack gate). New coverage: both auxiliary-LLM lines end to end, route-candidate ordering and dedup, max-token widening, fall-through, all-candidate failure reporting, cancelled requests staying silent, provider-prepared prompt/framing/anchor/effort selection, and the replaced-Subagent notice. Both official lines were also exercised end to end against the **real published packages** (0.2.1 `executeSessionTitleLlm`, 0.1.7 `generateSessionTitleWithLlm`): registration through the real policy validator, route selection, the `session/title-llm-request` record, the official system prompt, the least reasoning effort (0.2.1), and title acceptance all agree. **Lab plane:** `DSH_HOME=~/.dsh-lab` with the npm-installed `0.2.1-rc.2` runtime and the packed `dsh-model-switch-0.4.26.tgz` — profile composition `exit 0` with the naming, compaction, auto-review, and subagent rows composed from this plugin, and the Web Host booted on port 3082 serving the auth-gated page. **Residual (honest):** the 3082 capability RPC was not exercised from a browser (no browser automation is available in this environment), and the live desktop is still `0.2.0-rc.2`, so the 0.2.1 claim rests on the package-surface review, the unit suite, the real-package line checks, and the lab boot; the naming differential on a live 0.2.1 desktop is re-checked after the app update.
38
+ - **Post-release probe (2026-10-19 ~00:08 +0800, after the pinned Ark account's weekly quota reset).** The route was probed with the exact recorded system prompt and framed input. Before the reset it answered `HTTP 429 AccountQuotaExceeded`; with quota available the same route returns `finish: stop` and a complete title in 2.6–5.0 s with thinking enabled (57–134 reasoning tokens) versus 0.5–0.7 s and 6 tokens with thinking disabled. Tight budgets (`max_tokens` 8/16/24) still return a complete title, so truncation was not the observed failure mode — the route fall-through, not the widened retry, is the primary fix on this account. Evidence table and fix mapping in `docs/session-title-failure-mode.md`.
14
39
 
15
40
  ## v0.4.25
16
41
 
package/PRODUCT.md CHANGED
@@ -27,10 +27,10 @@ The settings surface aggregates public Settings namespaces, reads and mutates wi
27
27
 
28
28
  ## Exclusions
29
29
 
30
- Send-time switch compaction (`compactOnSwitch`) was removed in 0.4.25: injecting plugin-authored compaction checkpoints and notices during model switches interacted with the official `compaction-basic` step-pressure loop when a session's working set stayed near the target window's pressure threshold. All compaction policy and triggers belong exclusively to DSH's official compaction engine (`/compact` and automatic pressure/overflow compaction). The Auto reviewer route override rides a name-matched replacement of the experimental auto-review row and changes only the review request's provider/model. The compaction summarization route override rides a name-matched replacement of the official compaction-basic engine and changes only the summarization request's provider/model, forwarding the row's own engine config unchanged. Because the Web surface runs session compaction inside each agent preset's isolated compaction group and profile patches cannot address rows inside a preset's plugins list, the plugin replaces the shipped preset rows wholesale (the Web preset editor's own save mechanism) to swap that group's engine; the copies are re-synced per DSH release during the existing compatibility verification, and a deployment that edits presets in the Web editor owns the merge from its own wholesale row. Model Switch does not own global compaction policy, titles, fetch routing, fallback, load balancing, provider credentials, CLI/API, config migration, or DSH Core modifications.
30
+ Send-time switch compaction (`compactOnSwitch`) was removed in 0.4.25: injecting plugin-authored compaction checkpoints and notices during model switches interacted with the official `compaction-basic` step-pressure loop when a session's working set stayed near the target window's pressure threshold. All compaction policy and triggers belong exclusively to DSH's official compaction engine (`/compact` and automatic pressure/overflow compaction). The Auto reviewer route override rides a name-matched replacement of the experimental auto-review row and changes only the review request's provider/model. The compaction summarization route override rides a name-matched replacement of the official compaction-basic engine and changes only the summarization request's provider/model, forwarding the row's own engine config unchanged. Because the Web surface runs session compaction inside each agent preset's isolated compaction group and profile patches cannot address rows inside a preset's plugins list, the Host bridges that nested row at startup (`src/preset-sync.ts`): it disables the group's official `compaction-basic` and inserts `model-switch-compaction` directly after it, leaving every other row exactly as the installed DSH (or a deployment's own Web-editor save) wrote it. The plugin ships no preset copies: a DSH preset change needs no re-sync, and a deployment's preset edits are preserved. Model Switch does not own global compaction policy, titles, fetch routing, fallback, load balancing, provider credentials, CLI/API, config migration, or DSH Core modifications.
31
31
 
32
32
  Official `settings.section` has no icon field. The Settings nav glyph is a label-matched DOM swap, the same published pattern as usage-monitor, until an official icon seam exists. The Plan Review takeover uses priority -5, and the composer model seat uses priority -10.
33
33
 
34
34
  ## Delivery architecture
35
35
 
36
- The package foundation and pure deep modules remain the policy/test surface. Runtime delivery disables the independent Subagent Loader row with its official name and inserts a uniquely identified replacement; the experimental auto-review row is disabled the same way and replaced by `dsh-model-switch/auto-review`, which runs the official gate's `apply` unchanged under a scoped `llm` shadow (one `registerAuto`, and only when the profile started with the optional auto-review bundle). Official AgentPresets stay mounted so they share the process `dsh-scope` identity. Search uses the released `ctx.web.registerSearchProvider()` seam so official tool ownership is preserved. Image uses a unique tool name and provider-owned authenticated adapters. Self-maintained Codex/Grok plugins register optional adapters through the public Model Switch service when present; standalone provider behavior is unchanged. Vision remains disabled. Replacement modules preserve the official public interfaces and fail closed when compatibility or capability validation fails.
36
+ The package foundation and pure deep modules remain the policy/test surface. Runtime delivery disables the independent Subagent Loader row with its official name and inserts a uniquely identified replacement; the experimental auto-review row is disabled the same way and replaced by `dsh-model-switch/auto-review`, which mounts the official gate's `apply` unchanged as a plugin under a scoped `llm` shadow — the Host resolves the gate's own `inject` list (including 0.2.1's `workingDirectory`), one `registerAuto` runs, and only when the profile started with the optional auto-review bundle. Official AgentPresets stay mounted so they share the process `dsh-scope` identity. Search uses the released `ctx.web.registerSearchProvider()` seam so official tool ownership is preserved. Image uses a unique tool name and provider-owned authenticated adapters. Self-maintained Codex/Grok plugins register optional adapters through the public Model Switch service when present; standalone provider behavior is unchanged. Vision remains disabled. Replacement modules preserve the official public interfaces and fail closed when compatibility or capability validation fails.
package/README.md CHANGED
@@ -111,7 +111,7 @@ Each summarization call (`options.purpose === 'compaction'`) also resolves its r
111
111
  summarizationModel: deepseek-reasoner
112
112
  ```
113
113
 
114
- How the route reaches the engine depends on where the deployment runs its agent plane. The Web surface moves compaction into each agent preset's isolated `compaction` group, and profile patches cannot address rows inside a preset's `plugins` list — so this plugin replaces each shipped preset row wholesale (the same id-targeted mechanism the Web preset editor saves with) and swaps that group's official `compaction-basic` for `model-switch-compaction`, which mounts the **same official `BasicCompactionEngine`** under `compactionScope`. Surfaces that compose the agent process-wide (the TUI) use the top-level row replacement instead. Two consequences: a DSH release that edits those presets needs the copies re-synced (this plugin's per-release verification covers it), and a deployment that edits presets in the Web editor writes its own wholesale row that then owns the merge.
114
+ How the route reaches the engine depends on where the deployment runs its agent plane. The Web surface moves compaction into each agent preset's isolated `compaction` group, and profile patches cannot address rows inside a preset's `plugins` list — so the Host bridges that nested row at startup (`src/preset-sync.ts`): it disables the group's official `compaction-basic` and inserts `model-switch-compaction` directly after it, which mounts the **same official `BasicCompactionEngine`** under `compactionScope`. Every other preset row is left exactly as the installed DSH (or a deployment's own Web-editor save) wrote it; the plugin ships no preset copies, so a DSH preset change needs no re-sync and a deployment's preset edits are preserved. Surfaces that compose the agent process-wide (the TUI) use the top-level row replacement instead.
115
115
 
116
116
  Known issue (upstream; recorded in `docs/seam-gap.md`): while the working set stays above the official engine's pressure threshold, automatic compaction can fire on every step and keep re-attempting ranges whose summaries fail the engine's own "summary must be smaller" guard — a visible loop of automatic compactions that this plugin does not control (verified in a session A/B with the plugin enabled and disabled). Mitigations on the forwarded row config: `auto: false` (keeps `/compact`), a higher `thresholdRatio`, a smaller mounted tool schema, or a pinned compaction model whose summaries are leaner.
117
117
 
package/README.zh.md CHANGED
@@ -109,7 +109,7 @@ Plan Review 拥有独立于会话当前路由的执行模型草稿。**确认执
109
109
  summarizationModel: deepseek-reasoner
110
110
  ```
111
111
 
112
- 路由如何到达压缩引擎取决于部署在哪一层组合 agent。Web 面把压缩放进每个 Agent 预设的隔离 `compaction` 组里,而 profile patch 无法寻址 preset `plugins` 列表内部的行——因此本插件整表替换各预设行(与 Web 预设编辑器保存修改用的是同一套 id 定位机制),把该组的官方 `compaction-basic` 换成 `model-switch-compaction`,由它在 `compactionScope` 下挂载**同一份官方 `BasicCompactionEngine`**。进程级组合 agent 的面(如 TUI)则走顶层行替换。两个后果:DSH 发布若改动这些预设,随包副本需要同步(本插件的逐版本核对覆盖这一步);部署若用 Web 编辑器改预设,会写出自己的整表行、并从那之后自行负责合并。
112
+ 路由如何到达压缩引擎取决于部署在哪一层组合 agent。Web 面把压缩放进每个 Agent 预设的隔离 `compaction` 组里,而 profile patch 无法寻址 preset `plugins` 列表内部的行——因此 Host 在启动时桥接那一行(`src/preset-sync.ts`):禁用该组的官方 `compaction-basic`,并在其后插入 `model-switch-compaction`,由它在 `compactionScope` 下挂载**同一份官方 `BasicCompactionEngine`**。其余预设行一律保持安装版 DSH(或部署在 Web 编辑器里保存的版本)的原样;插件不再随包携带预设副本,所以 DSH 改预设无需再同步副本,部署自己的预设修改也不会被覆盖。进程级组合 agent 的面(如 TUI)则走顶层行替换。
113
113
 
114
114
  已知问题(上游侧,见 `docs/seam-gap.md`):当工作集持续超过官方引擎的压力阈值时,自动压缩可能每步触发,并反复重试摘要过不了官方「摘要必须更小」守卫的小片段——表现为"不停自动压缩",本插件不控制该行为(已在同一会话做过插件开/关 A/B 对照)。可用的缓解(都在转发行配置上):`auto: false`(保留 `/compact`)、调高 `thresholdRatio`、减少挂载的工具 schema,或固定一个摘要更精炼的压缩模型。
115
115
 
@@ -180,7 +180,7 @@ DeepSeek 薄适配器调用官方公开 `DeepSeekSearchProvider`,复用用户
180
180
 
181
181
  DSH Host peer 和开发依赖接受 `>=0.1.7-alpha.2`,包括 rc.1 和后续版本。开发锁文件解析到 rc.2。Cordis 接受 `>=4.0.4 <5.0.0`。
182
182
 
183
- 已验证版本:`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.2`、`0.2.1-rc.2`。0.2.0 的声明覆盖三处差异:Host 改名的提问撤下动词(`cancel()` → `dismiss()`)、`session.selectModel` 不 await 的部署默认值写入、以返回值报出的 `ModelDirectory.select` 拒绝;三者均已在 lab(3082/3085)与真实桌面会话中逐一验证。0.2.1 的声明覆盖两处被替换的 seam:共享命名调用改为 provider 自备提示词与解析的 `executeSessionTitleLlm`(本插件按安装行在运行时适配,并镜像官方 0.2.1 的提示词、取最低 reasoning effort 与 `currentTitle` 锚定);Subagent 运行时以 `startActivation` 取代 `start()`/`startContinuable()`,路由版运行时在新 Host 上不可用,本插件改为挂载未经改动的官方运行时并输出一条明确提示(记录于 `docs/seam-gap.md`)。0.2.1 的 lab 证据为:npm 安装的 0.2.1 运行时 + 打包后的 0.4.26 组合 `exit 0`、命名/压缩/自动审查/子代理四行均来自本插件、Web Host 在 3082 启动并服务需要鉴权的页面;该次未经浏览器调用 3082 能力 RPC,真实桌面的 0.2.1 命名差分在应用升级后复验。
183
+ 已验证版本:`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.2`、`0.2.1-rc.2`。0.2.0 的声明覆盖三处差异:Host 改名的提问撤下动词(`cancel()` → `dismiss()`)、`session.selectModel` 不 await 的部署默认值写入、以返回值报出的 `ModelDirectory.select` 拒绝;三者均已在 lab(3082/3085)与真实桌面会话中逐一验证。0.2.1 的声明覆盖两处被替换的 seam:共享命名调用改为 provider 自备提示词与解析的 `executeSessionTitleLlm`(本插件按安装行在运行时适配,并镜像官方 0.2.1 的提示词、取最低 reasoning effort 与 `currentTitle` 锚定);Subagent 运行时以 `startActivation` 取代 `start()`/`startContinuable()`,本插件同时识别两条已发布线,并为激活入口覆写 `spec.request.agentOptions`(显式路由仍优先、非 agentOptions 能力的外部适配器不改写),路由语义与旧线一致(0.4.28,记录于 `docs/seam-gap.md`);0.2.1 还新增了自动审查门禁对 `ctx.workingDirectory` 的读取,旧实现因在自己的 fiber 里直调 `official.apply` 而需要手工镜像门禁的 `inject`,于是每个工具调用都被拒(`cannot get property "workingDirectory" without inject`),0.4.28 改为把官方门禁以插件方式挂载、由官方 `inject` 解析依赖,本行只声明自身需要的服务。0.4.28 的真实包证据:0.2.1 上旧形态精确复现该注入错误、新形态到达 `workingDirectory.ensure` 且成功注册门禁、激活入口的路由改写与 spec 透传通过;0.2.0 上无需 `workingDirectory` 服务即可注册门禁,`start()` 路由改写与父身份保持不变。0.2.1 的 lab 证据为:npm 安装的 0.2.1 运行时 + 打包后的 0.4.26 组合 `exit 0`、命名/压缩/自动审查/子代理四行均来自本插件、Web Host 在 3082 启动并服务需要鉴权的页面;该次未经浏览器调用 3082 能力 RPC,真实桌面的 0.2.1 命名差分在应用升级后复验。0.2.1 还暴露出一处预设层面的差异(0.2.1 校验提示词变量、只有 `provider`/`model`,且把 `time-context`/`tool-schedule` 移进预设):0.4.26 的静态预设副本因此让每一轮都报 `unknown prompt variable "{{cwd}}"`,0.4.27 删除副本改为启动期运行时桥接,并在 lab 的 0.2.1 headless 真实回合中验证通过(展开见 CHANGELOG 0.4.27)。
184
184
 
185
185
  `package.json#dsh.compatibility.dshReleases` 中的已验证版本是证据,不是允许列表。新增验证声明前要审查公开 API 变化并测试新 Host 版本。
186
186
 
package/cordis.patch.yml CHANGED
@@ -43,479 +43,16 @@
43
43
  name: '@deepseek-ai/dsh-session-title-all-prompts-llm'
44
44
  disabled: true
45
45
 
46
- # The Web surface moves the agent plane (tools, compaction, delegation) into
47
- # per-session agent presets: session /compact and official automatic compaction
48
- # run against the compaction service inside each preset's isolated `compaction`
49
- # group, not the top-level row disabled above. The Loader's patch index cannot
50
- # address rows inside a preset's `config.plugins` list, so Model Switch replaces
51
- # each preset row wholesale — the same id-targeted mechanism the Web preset
52
- # editor saves with — and swaps that group's official compaction-basic for
53
- # model-switch-compaction. The copies come from
54
- # @deepseek-ai/dsh-web-app/presets/*.patch.yml (minimal has no compaction
55
- # group); a DSH release that edits those presets needs the copies re-synced as
56
- # part of the per-release verification, and a deployment that edits presets in
57
- # the Web editor writes its own wholesale row that owns the merge from there.
58
- # standard: agent-preset row replaced wholesale so its compaction group runs
59
- # model-switch-compaction (configurable summarization route) instead of the
60
- # official compaction-basic. Source: @deepseek-ai/dsh-web-app/presets/standard.patch.yml.
61
- - id: preset-standard
62
- name: '@deepseek-ai/dsh-agent-preset'
63
- config:
64
- id: standard
65
- order: 1
66
- plugins:
67
- - id: persona
68
- name: '@deepseek-ai/dsh-persona'
69
- config:
70
- suffix: Your working directory is {{cwd}}.
71
- prefix: You are a coding agent powered by the {{model}} model.
72
- - id: agent-instructions
73
- name: '@deepseek-ai/dsh-agent-instructions'
74
- config:
75
- maxBytes: 65536
76
- - id: tool-bash
77
- name: '@deepseek-ai/dsh-tool-bash'
78
- disabled: !!js process.platform === 'win32'
79
- - id: tool-pwsh
80
- name: '@deepseek-ai/dsh-tool-pwsh'
81
- disabled: !!js process.platform !== 'win32'
82
- - id: tool-fs
83
- name: '@deepseek-ai/dsh-tool-fs'
84
- - id: tool-fs-search
85
- name: '@deepseek-ai/dsh-tool-fs-search'
86
- config:
87
- sampleOverCapGlobResults: false
88
- - id: tool-jobs
89
- name: '@deepseek-ai/dsh-tool-jobs'
90
- - id: skill-filesystem
91
- name: '@deepseek-ai/dsh-skill-filesystem'
92
- - id: tool-skill
93
- name: '@deepseek-ai/dsh-tool-skill'
94
- - id: command-goal
95
- name: '@deepseek-ai/dsh-command-goal'
96
- - id: tool-goal
97
- name: '@deepseek-ai/dsh-tool-goal'
98
- - id: planning
99
- name: cordis:group
100
- group: true
101
- isolate:
102
- planMode: true
103
- config:
104
- - id: plan-mode
105
- name: '@deepseek-ai/dsh-plan-mode'
106
- config:
107
- section: |
108
- You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement 鈥?including an answer confirming something you asked 鈥?approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
109
-
110
- Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
111
-
112
- The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
113
-
114
- Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
115
-
116
- Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
117
-
118
- When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
119
- - id: compaction
120
- name: cordis:group
121
- group: true
122
- isolate:
123
- compaction: true
124
- toolResultPruner: true
125
- config:
126
- - id: compaction-basic
127
- name: '@deepseek-ai/dsh-compaction-basic'
128
- disabled: true
129
- - id: model-switch-compaction
130
- name: 'dsh-model-switch/compaction'
131
- - id: command-compact
132
- name: '@deepseek-ai/dsh-command-compact'
133
- - id: tool-result-pruner
134
- name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
135
- config:
136
- thresholdChars: 8192
137
- headChars: 4096
138
- tailChars: 1024
139
- - id: delegation
140
- name: cordis:group
141
- group: true
142
- isolate:
143
- workflowEngine: true
144
- config:
145
- - id: tool-subagent-control
146
- name: '@deepseek-ai/dsh-tool-subagent-control'
147
- - id: tool-subagent-list-agents
148
- name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
149
- - id: tool-subagent
150
- name: '@deepseek-ai/dsh-tool-subagent'
151
- config:
152
- provider: spawn
153
- toolName: subagent
154
- modelSelectionSettings: true
155
- backgroundMode: continuable
156
- - id: tool-subagent-fork
157
- name: '@deepseek-ai/dsh-tool-subagent'
158
- config:
159
- provider: fork
160
- toolName: subagent_fork
161
- backgroundMode: continuable
162
- - id: tool-subagent-codex
163
- name: '@deepseek-ai/dsh-tool-subagent'
164
- disabled: true
165
- config:
166
- provider: codex
167
- toolName: subagent_codex
168
- backgroundMode: one-shot
169
- maxDepth: provider-managed
170
- - id: tool-subagent-claude-code
171
- name: '@deepseek-ai/dsh-tool-subagent'
172
- disabled: true
173
- config:
174
- provider: claude-code
175
- toolName: subagent_claude_code
176
- backgroundMode: one-shot
177
- maxDepth: provider-managed
178
- - id: workflow-ptc
179
- name: '@deepseek-ai/dsh-workflow-ptc'
180
- config:
181
- provider: spawn
182
- - id: tool-workflow
183
- name: '@deepseek-ai/dsh-tool-workflow'
184
- - id: tool-ralph
185
- name: '@deepseek-ai/dsh-tool-ralph'
186
- disabled: true
187
- config:
188
- subagentProvider: spawn
189
- maxRounds: 64
190
- - id: tool-ask-user
191
- name: '@deepseek-ai/dsh-tool-ask-user'
192
- - id: tool-todo
193
- name: '@deepseek-ai/dsh-tool-todo'
194
- config:
195
- allowParallelInProgress: true
196
- - id: tool-web
197
- name: '@deepseek-ai/dsh-tool-web'
198
- config:
199
- fetch: true
200
- searchTimeoutMs: 60000
201
- - id: present
202
- name: '@deepseek-ai/dsh-tool-present'
203
- - id: tool-plugin-manager
204
- name: '@deepseek-ai/dsh-plugin-manager/tools'
205
- disabled: true
206
-
207
- # ptc: agent-preset row replaced wholesale so its compaction group runs
208
- # model-switch-compaction (configurable summarization route) instead of the
209
- # official compaction-basic. Source: @deepseek-ai/dsh-web-app/presets/ptc.patch.yml.
210
- - id: preset-ptc
211
- name: '@deepseek-ai/dsh-agent-preset'
212
- config:
213
- id: ptc
214
- order: 2
215
- plugins:
216
- - id: persona
217
- name: '@deepseek-ai/dsh-persona'
218
- config:
219
- suffix: Your working directory is {{cwd}}.
220
- prefix: You are a coding agent powered by the {{model}} model.
221
- - id: agent-instructions
222
- name: '@deepseek-ai/dsh-agent-instructions'
223
- config:
224
- maxBytes: 65536
225
- - id: tool-bash
226
- name: '@deepseek-ai/dsh-tool-bash'
227
- disabled: !!js process.platform === 'win32'
228
- - id: tool-pwsh
229
- name: '@deepseek-ai/dsh-tool-pwsh'
230
- disabled: !!js process.platform !== 'win32'
231
- - id: tool-fs
232
- name: '@deepseek-ai/dsh-tool-fs'
233
- - id: tool-fs-search
234
- name: '@deepseek-ai/dsh-tool-fs-search'
235
- config:
236
- sampleOverCapGlobResults: false
237
- - id: tool-jobs
238
- name: '@deepseek-ai/dsh-tool-jobs'
239
- - id: skill-filesystem
240
- name: '@deepseek-ai/dsh-skill-filesystem'
241
- - id: tool-skill
242
- name: '@deepseek-ai/dsh-tool-skill'
243
- - id: command-goal
244
- name: '@deepseek-ai/dsh-command-goal'
245
- - id: tool-goal
246
- name: '@deepseek-ai/dsh-tool-goal'
247
- - id: planning
248
- name: cordis:group
249
- group: true
250
- isolate:
251
- planMode: true
252
- config:
253
- - id: plan-mode
254
- name: '@deepseek-ai/dsh-plan-mode'
255
- config:
256
- section: |
257
- You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement 鈥?including an answer confirming something you asked 鈥?approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
258
-
259
- Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
260
-
261
- The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
262
-
263
- Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
264
-
265
- Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
266
-
267
- When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
268
- - id: compaction
269
- name: cordis:group
270
- group: true
271
- isolate:
272
- compaction: true
273
- toolResultPruner: true
274
- config:
275
- - id: compaction-basic
276
- name: '@deepseek-ai/dsh-compaction-basic'
277
- disabled: true
278
- - id: model-switch-compaction
279
- name: 'dsh-model-switch/compaction'
280
- - id: command-compact
281
- name: '@deepseek-ai/dsh-command-compact'
282
- - id: tool-result-pruner
283
- name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
284
- config:
285
- thresholdChars: 8192
286
- headChars: 4096
287
- tailChars: 1024
288
- - id: delegation
289
- name: cordis:group
290
- group: true
291
- isolate:
292
- workflowEngine: true
293
- config:
294
- - id: tool-subagent-control
295
- name: '@deepseek-ai/dsh-tool-subagent-control'
296
- - id: tool-subagent-list-agents
297
- name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
298
- - id: tool-subagent
299
- name: '@deepseek-ai/dsh-tool-subagent'
300
- config:
301
- provider: spawn
302
- toolName: subagent
303
- modelSelectionSettings: true
304
- backgroundMode: continuable
305
- - id: tool-subagent-fork
306
- name: '@deepseek-ai/dsh-tool-subagent'
307
- config:
308
- provider: fork
309
- toolName: subagent_fork
310
- backgroundMode: continuable
311
- - id: tool-subagent-codex
312
- name: '@deepseek-ai/dsh-tool-subagent'
313
- disabled: true
314
- config:
315
- provider: codex
316
- toolName: subagent_codex
317
- backgroundMode: one-shot
318
- maxDepth: provider-managed
319
- - id: tool-subagent-claude-code
320
- name: '@deepseek-ai/dsh-tool-subagent'
321
- disabled: true
322
- config:
323
- provider: claude-code
324
- toolName: subagent_claude_code
325
- backgroundMode: one-shot
326
- maxDepth: provider-managed
327
- - id: workflow-ptc
328
- name: '@deepseek-ai/dsh-workflow-ptc'
329
- disabled: true
330
- config:
331
- provider: spawn
332
- - id: tool-workflow
333
- name: '@deepseek-ai/dsh-tool-workflow'
334
- disabled: true
335
- - id: tool-ralph
336
- name: '@deepseek-ai/dsh-tool-ralph'
337
- disabled: true
338
- config:
339
- subagentProvider: spawn
340
- maxRounds: 64
341
- - id: tool-ask-user
342
- name: '@deepseek-ai/dsh-tool-ask-user'
343
- - id: tool-todo
344
- name: '@deepseek-ai/dsh-tool-todo'
345
- config:
346
- allowParallelInProgress: true
347
- - id: tool-web
348
- name: '@deepseek-ai/dsh-tool-web'
349
- config:
350
- fetch: true
351
- searchTimeoutMs: 60000
352
- - id: tool-presentation
353
- name: '@deepseek-ai/dsh-agent-tool-presentation'
354
- config:
355
- mode: ptc
356
- - id: present
357
- name: '@deepseek-ai/dsh-tool-present'
358
- - id: tool-plugin-manager
359
- name: '@deepseek-ai/dsh-plugin-manager/tools'
360
- disabled: true
361
-
362
- # cordis: agent-preset row replaced wholesale so its compaction group runs
363
- # model-switch-compaction (configurable summarization route) instead of the
364
- # official compaction-basic. Source: @deepseek-ai/dsh-web-app/presets/cordis.patch.yml.
365
- - id: preset-cordis
366
- name: '@deepseek-ai/dsh-agent-preset'
367
- config:
368
- id: cordis
369
- order: 4
370
- plugins:
371
- # Same persona as `standard`: tool descriptions and the skill catalog carry
372
- # the operating detail, and the skills own the preset-authoring rules.
373
- - id: persona
374
- name: '@deepseek-ai/dsh-persona'
375
- config:
376
- suffix: Your working directory is {{cwd}}.
377
- prefix: >-
378
- You are a coding agent powered by the {{model}} model.
379
- - id: agent-instructions
380
- name: '@deepseek-ai/dsh-agent-instructions'
381
- config:
382
- maxBytes: 65536
383
- - id: tool-bash
384
- name: '@deepseek-ai/dsh-tool-bash'
385
- disabled: !!js process.platform === 'win32'
386
- - id: tool-pwsh
387
- name: '@deepseek-ai/dsh-tool-pwsh'
388
- disabled: !!js process.platform !== 'win32'
389
- - id: tool-fs
390
- name: '@deepseek-ai/dsh-tool-fs'
391
- - id: tool-fs-search
392
- name: '@deepseek-ai/dsh-tool-fs-search'
393
- config:
394
- sampleOverCapGlobResults: false
395
- - id: tool-jobs
396
- name: '@deepseek-ai/dsh-tool-jobs'
397
- - id: command-goal
398
- name: '@deepseek-ai/dsh-command-goal'
399
- - id: tool-goal
400
- name: '@deepseek-ai/dsh-tool-goal'
401
- - id: planning
402
- name: cordis:group
403
- group: true
404
- isolate:
405
- planMode: true
406
- config:
407
- - id: plan-mode
408
- name: '@deepseek-ai/dsh-plan-mode'
409
- config:
410
- section: |
411
- You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement 鈥?including an answer confirming something you asked 鈥?approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
412
-
413
- Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
414
-
415
- The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
416
-
417
- Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
418
-
419
- Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
420
-
421
- When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
422
- - id: compaction
423
- name: cordis:group
424
- group: true
425
- isolate:
426
- compaction: true
427
- toolResultPruner: true
428
- config:
429
- - id: compaction-basic
430
- name: '@deepseek-ai/dsh-compaction-basic'
431
- disabled: true
432
- - id: model-switch-compaction
433
- name: 'dsh-model-switch/compaction'
434
- - id: command-compact
435
- name: '@deepseek-ai/dsh-command-compact'
436
- - id: tool-result-pruner
437
- name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
438
- config:
439
- thresholdChars: 8192
440
- headChars: 4096
441
- tailChars: 1024
442
- - id: delegation
443
- name: cordis:group
444
- group: true
445
- isolate:
446
- workflowEngine: true
447
- config:
448
- - id: tool-subagent-control
449
- name: '@deepseek-ai/dsh-tool-subagent-control'
450
- - id: tool-subagent-list-agents
451
- name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
452
- - id: tool-subagent
453
- name: '@deepseek-ai/dsh-tool-subagent'
454
- config:
455
- provider: spawn
456
- toolName: subagent
457
- modelSelectionSettings: true
458
- backgroundMode: continuable
459
- - id: tool-subagent-fork
460
- name: '@deepseek-ai/dsh-tool-subagent'
461
- config:
462
- provider: fork
463
- toolName: subagent_fork
464
- backgroundMode: continuable
465
- - id: tool-subagent-codex
466
- name: '@deepseek-ai/dsh-tool-subagent'
467
- disabled: true
468
- config:
469
- provider: codex
470
- toolName: subagent_codex
471
- backgroundMode: one-shot
472
- maxDepth: provider-managed
473
- - id: tool-subagent-claude-code
474
- name: '@deepseek-ai/dsh-tool-subagent'
475
- disabled: true
476
- config:
477
- provider: claude-code
478
- toolName: subagent_claude_code
479
- backgroundMode: one-shot
480
- maxDepth: provider-managed
481
- - id: workflow-ptc
482
- name: '@deepseek-ai/dsh-workflow-ptc'
483
- config:
484
- provider: spawn
485
- - id: tool-workflow
486
- name: '@deepseek-ai/dsh-tool-workflow'
487
- - id: tool-ralph
488
- name: '@deepseek-ai/dsh-tool-ralph'
489
- disabled: true
490
- config:
491
- subagentProvider: spawn
492
- maxRounds: 64
493
- - id: tool-ask-user
494
- name: '@deepseek-ai/dsh-tool-ask-user'
495
- - id: tool-todo
496
- name: '@deepseek-ai/dsh-tool-todo'
497
- config:
498
- allowParallelInProgress: true
499
- - id: tool-web
500
- name: '@deepseek-ai/dsh-tool-web'
501
- config:
502
- fetch: true
503
- searchTimeoutMs: 60000
504
- - id: tool-cordis
505
- name: '@deepseek-ai/dsh-tool-cordis'
506
- - id: skill-filesystem
507
- name: '@deepseek-ai/dsh-skill-filesystem'
508
- config:
509
- customSkillDirs:
510
- - !!js process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:path').dirname(process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('@deepseek-ai/dsh-agent-preset/package.json')), 'skills')
511
- - id: tool-skill
512
- name: '@deepseek-ai/dsh-tool-skill'
513
- - id: present
514
- name: '@deepseek-ai/dsh-tool-present'
515
- - id: tool-plugin-manager
516
- name: '@deepseek-ai/dsh-plugin-manager/tools'
517
- disabled: !!js "!ctx.get('profileContext')"
518
-
46
+ # The Web surface runs session compaction inside each agent preset's isolated
47
+ # compaction group, and the Loader patch index cannot address rows inside a
48
+ # preset's config.plugins list. This plugin therefore bridges the nested
49
+ # compaction engine at Host startup (src/preset-sync.ts): the shipped (or a
50
+ # deployment-edited) preset definition stays authoritative, and only the
51
+ # official compaction-basic row is disabled and followed by
52
+ # model-switch-compaction. Static copies of the shipped presets are gone —
53
+ # they broke DSH 0.2.1 turns, whose prompt-variable validation rejects the
54
+ # older copies' persona suffix (unknown prompt variable {{cwd}}), and local
55
+ # copies would hide every later preset change from the deployment.
519
56
 
520
57
  - insert:
521
58
  # Register Model Switch Host Settings/Main runtime before routed Subagents.
@@ -32,15 +32,18 @@ import { AsyncLocalStorage } from "node:async_hooks";
32
32
  */
33
33
  /** Cordis plugin name used by loader diagnostics. */
34
34
  const name = "model-switch-auto-review";
35
- /** The official gate's host services plus Model Switch's settings owner. */
36
- const inject = [
37
- "modelSwitch",
38
- "approval",
39
- "llm",
40
- "permissionPresets",
41
- "sessions",
42
- "tools"
43
- ];
35
+ /**
36
+ * Only the services this row's own code touches.
37
+ *
38
+ * The official gate used to run inside this row's fiber, so this list had to
39
+ * mirror every service the official `apply` reads. DSH 0.2.1 added
40
+ * `workingDirectory` to the official gate and the mirror went stale, which made
41
+ * every tool call fail with `cannot get property "workingDirectory" without
42
+ * inject`. The gate is now mounted as its own plugin (see {@link autoReviewGate}),
43
+ * so cordis resolves the official dependency list — including future additions —
44
+ * and this row declares only what it reads itself.
45
+ */
46
+ const inject = ["llm", "modelSwitch"];
44
47
  /** Optional bundle whose composition opts a profile into Auto review. */
45
48
  const AUTO_REVIEW_BUNDLE = "@deepseek-ai/dsh-experimental-auto-review";
46
49
  /**
@@ -138,6 +141,35 @@ function autoReviewBundleActive(ctx) {
138
141
  return Array.isArray(bundles) && bundles.includes(AUTO_REVIEW_BUNDLE);
139
142
  }
140
143
  /**
144
+ * Wrap the official gate as a plugin that declares its own dependencies.
145
+ *
146
+ * Directly calling `official.apply(scope)` ran the gate's code on this row's
147
+ * fiber, so every service the gate reads had to be mirrored in this row's
148
+ * `inject`. Mounting it instead lets cordis resolve the official list, which is
149
+ * what keeps a released Host's new dependency (0.2.1's `workingDirectory`) from
150
+ * breaking every tool call. The hooks let the caller observe whether the gate
151
+ * actually ran before it binds the session-correlation listener.
152
+ *
153
+ * @param gate - the official module (or an equivalent structural gate).
154
+ * @param hooks - applied/failure observers; the failure is rethrown.
155
+ * @returns A cordis plugin mounting the official gate unchanged.
156
+ */
157
+ function autoReviewGate(gate, hooks = {}) {
158
+ return {
159
+ name: gate.name ?? "experimental-auto-review",
160
+ ...gate.inject === void 0 ? {} : { inject: [...gate.inject] },
161
+ apply(gateCtx) {
162
+ try {
163
+ gate.apply(gateCtx);
164
+ } catch (error) {
165
+ hooks.onFailure?.(error);
166
+ throw error;
167
+ }
168
+ hooks.onApplied?.();
169
+ }
170
+ };
171
+ }
172
+ /**
141
173
  * Mount the official gate under the reviewer-route scope.
142
174
  *
143
175
  * A profile that never enabled the optional bundle keeps no Auto preset and no
@@ -159,13 +191,31 @@ async function apply(ctx) {
159
191
  }
160
192
  const activeSession = new AsyncLocalStorage();
161
193
  const scope = reviewScope(ctx, activeSession);
194
+ let failure;
195
+ let applied = false;
196
+ const fiber = scope.plugin(autoReviewGate(official, {
197
+ onApplied: () => {
198
+ applied = true;
199
+ },
200
+ onFailure: (error) => {
201
+ failure = error;
202
+ }
203
+ }));
162
204
  try {
163
- official.apply(scope);
164
- bindAutoReviewSessionCorrelation(scope, activeSession);
205
+ await fiber;
165
206
  } catch (cause) {
207
+ failure ??= cause;
208
+ }
209
+ if (failure !== void 0) {
166
210
  ctx.logger.error("model-switch: the official auto-review row is already active; the reviewer route override is unavailable");
167
- ctx.logger.error(cause);
211
+ ctx.logger.error(failure);
212
+ return;
213
+ }
214
+ if (!applied) {
215
+ ctx.logger.info("model-switch: the official auto-review gate is waiting for its host services; the reviewer route override is not bound yet");
216
+ return;
168
217
  }
218
+ bindAutoReviewSessionCorrelation(scope, activeSession);
169
219
  }
170
220
  //#endregion
171
- export { apply, autoReviewRouteOf, bindAutoReviewSessionCorrelation, inject, name, resolveSessionAutoReviewRoute, reviewLlm, reviewScope };
221
+ export { apply, autoReviewGate, autoReviewRouteOf, bindAutoReviewSessionCorrelation, inject, name, resolveSessionAutoReviewRoute, reviewLlm, reviewScope };
package/lib/index.js CHANGED
@@ -612,6 +612,153 @@ function allowDshRuntime(logger, pluginName, candidates) {
612
612
  return shouldMountDshRuntime(logger, pluginName, version, new Set(Object.entries(compatibility?.dshReleases ?? {}).filter(([, status]) => status === "compatible" || status === "verified").map(([release]) => release)), compatibility?.blocklist);
613
613
  }
614
614
  //#endregion
615
+ //#region lib/types/preset-sync.js
616
+ /** The preset ids those rows declare (`preset-…` is only the row id). */
617
+ const PRESET_IDS = [
618
+ "standard",
619
+ "ptc",
620
+ "cordis"
621
+ ];
622
+ /** The presentation row every preset uses for its child plugin list. */
623
+ const PRESET_PACKAGE = "@deepseek-ai/dsh-agent-preset";
624
+ /** DSH's own compaction engine inside a preset's compaction group. */
625
+ const OFFICIAL_COMPACTION_NAME = "@deepseek-ai/dsh-compaction-basic";
626
+ /** This plugin's routed engine row. */
627
+ const BRIDGED_COMPACTION_ID = "model-switch-compaction";
628
+ const BRIDGED_COMPACTION_NAME = "dsh-model-switch/compaction";
629
+ function isRecord(value) {
630
+ return typeof value === "object" && value !== null && !Array.isArray(value);
631
+ }
632
+ function isOfficialCompaction(row) {
633
+ return row.name === OFFICIAL_COMPACTION_NAME;
634
+ }
635
+ function isBridgedCompaction(row) {
636
+ return row !== void 0 && row.id === BRIDGED_COMPACTION_ID && row.name === BRIDGED_COMPACTION_NAME;
637
+ }
638
+ /**
639
+ * Swap the nested official compaction engine for this plugin's routed engine.
640
+ *
641
+ * Recurses through every nested entry list, so a preset that wraps its
642
+ * compaction group differently is still bridged. A list that carries no
643
+ * official engine is returned untouched. The transform is idempotent: a list
644
+ * whose official row is already disabled and already followed by the routed row
645
+ * reports `changed: false`.
646
+ * @param rows - one plugin list (a preset's `plugins` or a nested group's `config`).
647
+ * @returns The new list and whether anything changed.
648
+ */
649
+ function bridgePresetPlugins(rows) {
650
+ let changed = false;
651
+ const bridged = [];
652
+ for (let index = 0; index < rows.length; index += 1) {
653
+ const source = rows[index];
654
+ let row = source;
655
+ if (Array.isArray(source.config)) {
656
+ const nested = bridgePresetPlugins(source.config);
657
+ if (nested.changed) {
658
+ row = {
659
+ ...source,
660
+ config: nested.rows
661
+ };
662
+ changed = true;
663
+ }
664
+ }
665
+ if (isOfficialCompaction(row)) {
666
+ const follower = rows[index + 1];
667
+ const already = isBridgedCompaction(follower);
668
+ if (!already || row.disabled !== true) {
669
+ bridged.push({
670
+ ...row,
671
+ disabled: true
672
+ });
673
+ if (!already) bridged.push({
674
+ id: BRIDGED_COMPACTION_ID,
675
+ name: BRIDGED_COMPACTION_NAME
676
+ });
677
+ changed = true;
678
+ continue;
679
+ }
680
+ }
681
+ bridged.push(row);
682
+ }
683
+ return {
684
+ rows: bridged,
685
+ changed
686
+ };
687
+ }
688
+ /**
689
+ * Bridge one preset definition's plugin list.
690
+ * @param config - the preset row's stored config (`{ id, order, plugins }`).
691
+ * @returns The bridged config and whether anything changed.
692
+ */
693
+ function bridgePresetConfig(config) {
694
+ if (!isRecord(config) || !Array.isArray(config.plugins)) return {
695
+ config,
696
+ changed: false
697
+ };
698
+ const { rows, changed } = bridgePresetPlugins(config.plugins);
699
+ if (!changed) return {
700
+ config,
701
+ changed: false
702
+ };
703
+ return {
704
+ config: {
705
+ ...config,
706
+ plugins: rows
707
+ },
708
+ changed: true
709
+ };
710
+ }
711
+ /**
712
+ * Locate one preset row in the loader tree.
713
+ * @param loader - the host loader.
714
+ * @param id - the preset id (`standard`, `ptc`, `cordis`).
715
+ * @returns The declaring entry, or undefined when this profile has no such preset.
716
+ */
717
+ function presetEntry(loader, id) {
718
+ if (!loader) return void 0;
719
+ for (const entry of loader.entries()) {
720
+ if (entry.options?.name !== PRESET_PACKAGE) continue;
721
+ const config = entry.options?.config;
722
+ if (isRecord(config) && config.id === id) return entry;
723
+ }
724
+ }
725
+ /**
726
+ * Bridge every shipped preset in the running composition.
727
+ *
728
+ * Runs at Host startup, before any session can use a preset, so the wrapped
729
+ * engine is in place for the first session. An entry that has not started yet
730
+ * has its raw config replaced (so the first activation already carries the
731
+ * bridge); a started entry is reloaded through the public loader update. A
732
+ * preset without a compaction group is skipped, and a bridge failure is logged
733
+ * without failing the row: the official presets keep working.
734
+ * @param ctx - the Host plugin context.
735
+ * @returns The preset ids that were bridged.
736
+ */
737
+ async function bridgeAgentPresets(ctx) {
738
+ let loader;
739
+ try {
740
+ loader = ctx.get("loader", false);
741
+ } catch {
742
+ loader = void 0;
743
+ }
744
+ if (loader === void 0) return [];
745
+ const bridged = [];
746
+ for (const id of PRESET_IDS) {
747
+ const entry = presetEntry(loader, id);
748
+ if (entry === void 0) continue;
749
+ const { config, changed } = bridgePresetConfig(entry.options.config);
750
+ if (!changed) continue;
751
+ try {
752
+ if (entry.fiber === void 0) entry.options.config = config;
753
+ else await entry.update({ config });
754
+ bridged.push(id);
755
+ } catch (error) {
756
+ ctx.logger.warn(`model-switch: failed to bridge agent preset ${id}: ${String(error)}`);
757
+ }
758
+ }
759
+ return bridged;
760
+ }
761
+ //#endregion
615
762
  //#region lib/types/host-runtime.js
616
763
  /** Host owner for Model Switch settings. */
617
764
  var ModelSwitchRuntime = class extends Service {
@@ -646,6 +793,9 @@ var ModelSwitchRuntime = class extends Service {
646
793
  ctx.emit("model-switch/settings-update");
647
794
  });
648
795
  ctx.emit("model-switch/settings-update");
796
+ bridgeAgentPresets(ctx).then((bridged) => {
797
+ if (bridged.length > 0) ctx.logger.info(`model-switch: bridged agent preset compaction rows (${bridged.join(", ")})`);
798
+ }).catch((error) => ctx.logger.warn(`model-switch: agent preset bridge failed: ${String(error)}`));
649
799
  }
650
800
  currentSettings() {
651
801
  return { ...this.source() };
@@ -49,6 +49,30 @@ function routeSubagentRequest(request, route) {
49
49
  function assertPublicMethod(value, surface, method) {
50
50
  if (value === null || typeof value !== "object" && typeof value !== "function" || typeof Reflect.get(value, method) !== "function") throw new StartupIncompatibilityError(surface, surface + " must expose public " + method + "()");
51
51
  }
52
+ /**
53
+ * Classify the runtime surface this Host publishes.
54
+ *
55
+ * DSH 0.2.1 replaced `start()` / `startContinuable()` with the activation API
56
+ * (`startActivation`), so a version-agnostic adapter has to recognize both
57
+ * lines instead of hard-coding one.
58
+ *
59
+ * @param value - a runtime class's prototype or a mounted runtime instance.
60
+ * @returns `activation` (0.2.1+), `start` (0.1.7/0.2.0), or undefined when neither is published.
61
+ */
62
+ function subagentSurfaceOf(value) {
63
+ if (value === null || typeof value !== "object" && typeof value !== "function") return void 0;
64
+ if (typeof Reflect.get(value, "startActivation") === "function") return "activation";
65
+ const hasStart = typeof Reflect.get(value, "start") === "function";
66
+ const hasContinuable = typeof Reflect.get(value, "startContinuable") === "function";
67
+ return hasStart && hasContinuable ? "start" : void 0;
68
+ }
69
+ /** Read one public method, failing with the typed startup error when it is absent. */
70
+ function publicMethod(prototype, surface, method) {
71
+ if (prototype === null || typeof prototype !== "object" && typeof prototype !== "function") throw new StartupIncompatibilityError(surface, surface + " must expose public " + method + "()");
72
+ const value = Reflect.get(prototype, method);
73
+ if (typeof value !== "function") throw new StartupIncompatibilityError(surface, surface + " must expose public " + method + "()");
74
+ return value;
75
+ }
52
76
  function constructible(value) {
53
77
  if (typeof value !== "function") return false;
54
78
  try {
@@ -62,9 +86,9 @@ function constructible(value) {
62
86
  function assertOfficialRuntimeSurface() {
63
87
  const runtime = OfficialSubagentRuntime;
64
88
  if (!constructible(runtime)) throw new StartupIncompatibilityError("OfficialSubagentRuntime", "OfficialSubagentRuntime must be constructible");
65
- const prototype = Reflect.get(runtime, "prototype");
66
- assertPublicMethod(prototype, "OfficialSubagentRuntime", "start");
67
- assertPublicMethod(prototype, "OfficialSubagentRuntime", "startContinuable");
89
+ const line = subagentSurfaceOf(Reflect.get(runtime, "prototype"));
90
+ if (line === void 0) throw new StartupIncompatibilityError("OfficialSubagentRuntime", "OfficialSubagentRuntime must expose public startActivation() (0.2.1+) or start()/startContinuable()");
91
+ return line;
68
92
  }
69
93
  function assertRoutingSurface(ctx) {
70
94
  const modelSwitch = ctx.modelSwitch;
@@ -74,9 +98,9 @@ function routingSurface(ctx) {
74
98
  return ctx.modelSwitch;
75
99
  }
76
100
  function assertMountedSurface(ctx) {
77
- assertPublicMethod(ctx.subagents, "mounted Subagent runtime", "start");
78
- assertPublicMethod(ctx.subagents, "mounted Subagent runtime", "startContinuable");
79
- assertPublicMethod(ctx.subagents, "mounted Subagent runtime", "resolveMaxDepth");
101
+ const service = ctx.subagents;
102
+ assertPublicMethod(service, "mounted Subagent runtime", "resolveMaxDepth");
103
+ if (subagentSurfaceOf(service) === void 0) throw new StartupIncompatibilityError("mounted Subagent runtime", "mounted Subagent runtime must expose public startActivation() (0.2.1+) or start()/startContinuable()");
80
104
  }
81
105
  function once(dispose) {
82
106
  let pending;
@@ -199,14 +223,29 @@ var ModelSwitchSubagentRuntime = class extends OfficialSubagentRuntime {
199
223
  return routeSubagentRequest(request, sessionSubagentRouteOf(this.ctx, request.parent.session, modelSwitch.currentSettings()));
200
224
  }
201
225
  start(name, request) {
202
- return super.start(name, this.routed(name, request));
226
+ return publicMethod(OfficialSubagentRuntime.prototype, "OfficialSubagentRuntime", "start").call(this, name, this.routed(name, request));
203
227
  }
204
228
  startContinuable(spec) {
205
- return super.startContinuable({
229
+ return publicMethod(OfficialSubagentRuntime.prototype, "OfficialSubagentRuntime", "startContinuable").call(this, {
230
+ ...spec,
231
+ request: this.routed(spec.provider, spec.request)
232
+ });
233
+ }
234
+ /**
235
+ * 0.2.1+ activation entry: delegation tools call this instead of
236
+ * `start()`/`startContinuable()`, and its spec carries the same
237
+ * `request.agentOptions` this adapter already rewrites. Every other spec
238
+ * field (label, childId, signal, delivery) passes through untouched.
239
+ *
240
+ * @param spec - the activation spec published by the installed Host.
241
+ * @returns The official activation result.
242
+ */
243
+ startActivation(spec) {
244
+ return publicMethod(OfficialSubagentRuntime.prototype, "OfficialSubagentRuntime", "startActivation").call(this, {
206
245
  ...spec,
207
246
  request: this.routed(spec.provider, spec.request)
208
247
  });
209
248
  }
210
249
  };
211
250
  //#endregion
212
- export { ModelSwitchSubagentRuntime, StartupIncompatibilityError, profileSubagentRuntime as default, profileSubagentRuntime, mountWithStartupFallback, routeSubagentRequest, routedRuntimeUnavailableNotice };
251
+ export { ModelSwitchSubagentRuntime, StartupIncompatibilityError, profileSubagentRuntime as default, profileSubagentRuntime, mountWithStartupFallback, routeSubagentRequest, routedRuntimeUnavailableNotice, subagentSurfaceOf };
@@ -33,7 +33,17 @@ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
33
33
  import type { Session } from '@deepseek-ai/dsh-session';
34
34
  /** Cordis plugin name used by loader diagnostics. */
35
35
  export declare const name = "model-switch-auto-review";
36
- /** The official gate's host services plus Model Switch's settings owner. */
36
+ /**
37
+ * Only the services this row's own code touches.
38
+ *
39
+ * The official gate used to run inside this row's fiber, so this list had to
40
+ * mirror every service the official `apply` reads. DSH 0.2.1 added
41
+ * `workingDirectory` to the official gate and the mirror went stale, which made
42
+ * every tool call fail with `cannot get property "workingDirectory" without
43
+ * inject`. The gate is now mounted as its own plugin (see {@link autoReviewGate}),
44
+ * so cordis resolves the official dependency list — including future additions —
45
+ * and this row declares only what it reads itself.
46
+ */
37
47
  export declare const inject: string[];
38
48
  /** Pinned reviewer route; both identifiers are non-empty. */
39
49
  export interface AutoReviewRoute {
@@ -93,6 +103,42 @@ export declare function bindAutoReviewSessionCorrelation(scope: Context, activeS
93
103
  * @returns A child context shadowing only the llm service.
94
104
  */
95
105
  export declare function reviewScope(ctx: Context, activeSession?: AsyncLocalStorage<Session | undefined>): Context;
106
+ /**
107
+ * Structural view of the official gate's plugin face.
108
+ *
109
+ * `name` and `inject` are part of the module's released plugin contract; the
110
+ * gate is mounted through cordis rather than called directly so its own
111
+ * dependency list is what the fiber resolves.
112
+ */
113
+ export interface OfficialAutoReviewGate {
114
+ readonly apply: (ctx: Context) => void;
115
+ readonly inject?: readonly string[];
116
+ readonly name?: string;
117
+ }
118
+ /** The plugin face handed to cordis, with the official dependency list kept. */
119
+ export interface MountedAutoReviewGate {
120
+ readonly name: string;
121
+ readonly inject?: string[];
122
+ readonly apply: (gateCtx: Context) => void;
123
+ }
124
+ /**
125
+ * Wrap the official gate as a plugin that declares its own dependencies.
126
+ *
127
+ * Directly calling `official.apply(scope)` ran the gate's code on this row's
128
+ * fiber, so every service the gate reads had to be mirrored in this row's
129
+ * `inject`. Mounting it instead lets cordis resolve the official list, which is
130
+ * what keeps a released Host's new dependency (0.2.1's `workingDirectory`) from
131
+ * breaking every tool call. The hooks let the caller observe whether the gate
132
+ * actually ran before it binds the session-correlation listener.
133
+ *
134
+ * @param gate - the official module (or an equivalent structural gate).
135
+ * @param hooks - applied/failure observers; the failure is rethrown.
136
+ * @returns A cordis plugin mounting the official gate unchanged.
137
+ */
138
+ export declare function autoReviewGate(gate: OfficialAutoReviewGate, hooks?: {
139
+ readonly onApplied?: () => void;
140
+ readonly onFailure?: (error: unknown) => void;
141
+ }): MountedAutoReviewGate;
96
142
  /**
97
143
  * Mount the official gate under the reviewer-route scope.
98
144
  *
@@ -0,0 +1,64 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { Entry, Loader } from '@deepseek-ai/cordis-plugin-loader';
3
+ /**
4
+ * Agent presets that ship their own isolated `compaction` group.
5
+ *
6
+ * The Web surface mounts preset-nested rows below a `cordis:group`, and the
7
+ * Loader's patch index cannot address rows inside a preset's `config.plugins`
8
+ * list, so this plugin bridges those rows at runtime instead of shipping copies
9
+ * of the shipped preset definitions: the official (or deployment-edited) preset
10
+ * stays authoritative, and only the nested compaction engine is swapped.
11
+ */
12
+ export declare const PRESET_ROW_IDS: readonly ["preset-standard", "preset-ptc", "preset-cordis"];
13
+ interface PresetRow {
14
+ readonly id?: unknown;
15
+ readonly name?: unknown;
16
+ readonly group?: unknown;
17
+ readonly disabled?: unknown;
18
+ readonly config?: unknown;
19
+ }
20
+ /**
21
+ * Swap the nested official compaction engine for this plugin's routed engine.
22
+ *
23
+ * Recurses through every nested entry list, so a preset that wraps its
24
+ * compaction group differently is still bridged. A list that carries no
25
+ * official engine is returned untouched. The transform is idempotent: a list
26
+ * whose official row is already disabled and already followed by the routed row
27
+ * reports `changed: false`.
28
+ * @param rows - one plugin list (a preset's `plugins` or a nested group's `config`).
29
+ * @returns The new list and whether anything changed.
30
+ */
31
+ export declare function bridgePresetPlugins(rows: readonly PresetRow[]): {
32
+ rows: PresetRow[];
33
+ changed: boolean;
34
+ };
35
+ /**
36
+ * Bridge one preset definition's plugin list.
37
+ * @param config - the preset row's stored config (`{ id, order, plugins }`).
38
+ * @returns The bridged config and whether anything changed.
39
+ */
40
+ export declare function bridgePresetConfig(config: unknown): {
41
+ config: unknown;
42
+ changed: boolean;
43
+ };
44
+ /**
45
+ * Locate one preset row in the loader tree.
46
+ * @param loader - the host loader.
47
+ * @param id - the preset id (`standard`, `ptc`, `cordis`).
48
+ * @returns The declaring entry, or undefined when this profile has no such preset.
49
+ */
50
+ export declare function presetEntry(loader: Loader | undefined, id: string): Entry | undefined;
51
+ /**
52
+ * Bridge every shipped preset in the running composition.
53
+ *
54
+ * Runs at Host startup, before any session can use a preset, so the wrapped
55
+ * engine is in place for the first session. An entry that has not started yet
56
+ * has its raw config replaced (so the first activation already carries the
57
+ * bridge); a started entry is reloaded through the public loader update. A
58
+ * preset without a compaction group is skipped, and a bridge failure is logged
59
+ * without failing the row: the official presets keep working.
60
+ * @param ctx - the Host plugin context.
61
+ * @returns The preset ids that were bridged.
62
+ */
63
+ export declare function bridgeAgentPresets(ctx: Context): Promise<readonly string[]>;
64
+ export {};
@@ -21,6 +21,19 @@ export interface MountedStartup<T> {
21
21
  readonly dispose: () => Promise<void>;
22
22
  }
23
23
  type RoutableSubagentRequest = Pick<SubagentStartRequest, 'parent' | 'agentOptions'>;
24
+ /**
25
+ * The 0.2.1+ activation spec, declared structurally.
26
+ *
27
+ * The pinned dependency typings predate the activation API, so the modern entry
28
+ * is described here and delegated through the base prototype. `request` carries
29
+ * the same `parent`/`agentOptions` pair the legacy start request does, which is
30
+ * why one route adapter serves both lines.
31
+ */
32
+ export interface SubagentActivationSpecLike {
33
+ readonly provider: string;
34
+ readonly request: Pick<SubagentStartRequest, 'parent' | 'agentOptions'> & Record<string, unknown>;
35
+ readonly [field: string]: unknown;
36
+ }
24
37
  /**
25
38
  * Inject one resolved route into a spawn that names none.
26
39
  *
@@ -34,6 +47,19 @@ type RoutableSubagentRequest = Pick<SubagentStartRequest, 'parent' | 'agentOptio
34
47
  * @returns The request to hand the official runtime.
35
48
  */
36
49
  export declare function routeSubagentRequest<T extends RoutableSubagentRequest>(request: T, route: SubagentRoute | undefined): T;
50
+ /** The public start surface one installed Subagent runtime publishes. */
51
+ export type SubagentSurfaceLine = 'activation' | 'start';
52
+ /**
53
+ * Classify the runtime surface this Host publishes.
54
+ *
55
+ * DSH 0.2.1 replaced `start()` / `startContinuable()` with the activation API
56
+ * (`startActivation`), so a version-agnostic adapter has to recognize both
57
+ * lines instead of hard-coding one.
58
+ *
59
+ * @param value - a runtime class's prototype or a mounted runtime instance.
60
+ * @returns `activation` (0.2.1+), `start` (0.1.7/0.2.0), or undefined when neither is published.
61
+ */
62
+ export declare function subagentSurfaceOf(value: unknown): SubagentSurfaceLine | undefined;
37
63
  /**
38
64
  * Mount a candidate and use the fallback only for typed startup incompatibility.
39
65
  *
@@ -68,5 +94,15 @@ export declare class ModelSwitchSubagentRuntime extends OfficialSubagentRuntime
68
94
  private routed;
69
95
  start(name: string, request: SubagentStartRequest): Promise<SubagentRun>;
70
96
  startContinuable(spec: ContinuableStartSpec): Promise<ContinuableStart>;
97
+ /**
98
+ * 0.2.1+ activation entry: delegation tools call this instead of
99
+ * `start()`/`startContinuable()`, and its spec carries the same
100
+ * `request.agentOptions` this adapter already rewrites. Every other spec
101
+ * field (label, childId, signal, delivery) passes through untouched.
102
+ *
103
+ * @param spec - the activation spec published by the installed Host.
104
+ * @returns The official activation result.
105
+ */
106
+ startActivation(spec: SubagentActivationSpecLike): Promise<unknown>;
71
107
  }
72
108
  export default profileSubagentRuntime;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-model-switch",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "description": "Explicit model routing plus composer/Plan picker for DeepSeek Harness",
5
5
  "repository": {
6
6
  "type": "git",