dsh-paoding 0.3.7 → 0.3.9

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
@@ -11,13 +11,14 @@ dsh-paoding(中文品牌「庖丁」,典出《庄子》——庖丁顺纹理
11
11
  - **高频小事不过手他人**:找代码、查引用、读文件这类顺手的事,主 agent 自己直接办,不绕委派的圈子。
12
12
  - **可招募专属帮手**:内置四角色只是起点——给新角色配好工具和技能(比如装上 `html-ppt` 技能的 PPT 帮手),之后一句「把这份大纲做成 PPT」就有人接活。配置文件、图形界面两个入口都能建。
13
13
  - **帮手还能指定专用模型**:写代码的用强档、查资料的用轻快档,各配各的模型;不配就跟随主 agent 当前会话的模型。
14
+ - **帮手能继承对话开工**:把某个角色的「启动方式」切成继承对话(fork),帮手带着主 agent 已完成的对话轮次直接开工,省得在委派时把上下文复述一遍;默认全员全新启动,不开启就一切照旧。
14
15
  - **帮手能就地续修**:把某个角色的「会话模式」切成可续(continuable),活干砸了或干得不满意,主 agent 用 `send_message` 在同一个子会话接着修,不用从头重派;默认全员一次性(one-shot)用完即弃,不开启就一切照旧。
15
16
  - **省钱省上下文**:帮手的工具只在被叫到时才加载,用一次付一次;主 agent 每次请求的工具开销约降三分之二,上下文只收摘要,不再被搜索结果和代码改动撑爆。
16
17
  - **角色随改随用**:删掉用不上的内置角色、给角色改显示名、给主 agent 配显示名,都是配置里一行的事;改完在面板点一次「保存并应用」,预设即时重生成,重启 DSH / 新建会话后生效。
17
18
  - **装了什么都能认出来**:MCP 服务器、本地插件、已装技能,安装与配置时全程检测;默认只装 dsh 基础工具,检测到的 host 工具在「庖丁配置」里按需勾选;停用哪个,重新应用后相关工具自动剔除。
18
19
  - **按工作区各有各的班底**:不同项目可以配不同的主/子 agent——「庖丁配置」里选定工作区单独配置,每个工作区生成自己的预设(`orchestrator-<目录名>`),新建会话时选对应预设即可,全局默认不受影响。
19
20
  - **图形界面配置**:不习惯改配置文件的话,点开左侧栏底部动作条(设置行上方)的「庖丁配置」入口,整页点选即可,「保存并应用」即时重生成预设——与首装自动化同一条生成管线。
20
- - **版本更新有提醒,面板一键升级**:面板会对比 npm registry(npmmirror 镜像、GitHub release 依次兜底)上的最新版本,发现新版就地提示并附上发布页链接;点「升级」就地执行 `dsh plugin update dsh-paoding`(profile 自动探测)换上新版,重启 DSH 后生效,编排预设会在重启时按新版自动重生成。开发检出的 link 形态无法就地升级,面板会提示切回 registry 版的办法;检测失败时页头留一行「版本检测失败 · 重试」(完整原因与代理排查提示在悬浮提示里),不弹窗打扰。
21
+ - **版本更新有提醒,面板一键升级**:面板会对比 npm registry(npmmirror 镜像、GitHub release 依次兜底)上的最新版本,发现新版就地提示并附上发布页链接;点「升级」就地换上新版(profile 自动探测:普通 profile 走 `dsh plugin update dsh-paoding`;desktop profile 被 dsh CLI 的 Electron 专属守卫挡住,改在 profile 目录直跑 `pnpm add dsh-paoding@<版本>`),重启 DSH 后生效,编排预设会在重启时按新版自动重生成。开发检出的 link 形态无法就地升级,面板会提示切回 registry 版的办法;检测失败时页头留一行「版本检测失败 · 重试」(完整原因与代理排查提示在悬浮提示里),不弹窗打扰。
21
22
  - **来去自由**:不改 DSH 一行源码;装完新会话即用,卸载即删目录。
22
23
  - **两代宿主都兼容**:DSH 0.1.5 – 0.2.0-rc.2 全支持(0.1.5 / 0.1.6 / 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1 / 0.2.0-rc.2)——两代宿主发现预设的机制不同(目录扫描 vs patch 声明行),安装器自动检测宿主版本选对落点;编排组合里的 workflow worker 行也按宿主版本自动适配(0.1.5 用 workflow-worker-thread,0.1.6+ 换 workflow-ptc——旧包 0.1.6 起已删除,残留行正是 0.2.0 面板上「加载失败」的元凶),宿主升级、降级后的首次保存/自愈自动迁移,全程无感。
23
24
 
@@ -56,7 +57,7 @@ git clone https://github.com/lifangjin/dsh-paoding.git
56
57
  dsh plugin --profile web add link:"$PWD/dsh-paoding"
57
58
  ```
58
59
 
59
- 升级走面板「升级」按钮(等价 `dsh plugin update dsh-paoding`)。详细步骤、升级与排查见 [安装文档](docs/installation.md)。
60
+ 升级走面板「升级」按钮(普通 profile 等价 `dsh plugin update dsh-paoding`;desktop profile 则在 profile 目录直跑 `pnpm add` 精确版本)。详细步骤、升级与排查见 [安装文档](docs/installation.md)。
60
61
 
61
62
  ## 怎么用
62
63
 
@@ -73,7 +74,7 @@ dsh plugin --profile web add link:"$PWD/dsh-paoding"
73
74
 
74
75
  ## 配置
75
76
 
76
- 所有偏好都写在 `~/.dsh/dsh-paoding.config.yml`:角色增删改、技能分配、专用模型、会话模式(one-shot / continuable)、主 agent 的工具与技能……改完打开左侧栏底部的「庖丁配置」入口,点「保存并应用」即同步生效。预设每次由「静态源 + 配置」重新生成,不攒手工补丁。全部配置键说明见 [配置文档](docs/configuration.md);背后机制与成本账见 [架构文档](docs/architecture.md)。
77
+ 所有偏好都写在 `~/.dsh/dsh-paoding.config.yml`:角色增删改、技能分配、专用模型、会话模式(one-shot / continuable)、启动方式(fresh / fork)、主 agent 的工具与技能……改完打开左侧栏底部的「庖丁配置」入口,点「保存并应用」即同步生效。预设每次由「静态源 + 配置」重新生成,不攒手工补丁。全部配置键说明见 [配置文档](docs/configuration.md);背后机制与成本账见 [架构文档](docs/architecture.md)。
77
78
 
78
79
  ## 文档
79
80
 
package/README_en.md CHANGED
@@ -11,13 +11,14 @@ dsh-paoding (Chinese brand 「庖丁」/ Pao Ding, from the Zhuangzi parable of
11
11
  - **Everyday tasks never leave the desk**: finding code, checking references, reading files — the main agent handles those itself, no delegation round-trips.
12
12
  - **Recruit your own specialists**: the four built-ins are just the start. Give a new role its tools and skills (say, an `html-ppt`-equipped slide maker), and "turn this outline into a PPT" is someone else's job from then on. Create roles in the config file or the 庖丁配置 panel.
13
13
  - **A dedicated model per helper**: pin the coder to a stronger model and the researcher to a lighter, faster one; unset, they follow the main agent's current session model.
14
+ - **Helpers that inherit the conversation**: flip a role's start mode to fork and the helper starts from the main agent's already-completed turns — no restating the context inside every delegation prompt. Every role defaults to a fresh start, so nothing changes until you flip it.
14
15
  - **Helpers that finish the job in place**: flip a role's session mode to continuable and, when a run fails or disappoints, the main agent resumes the same child conversation with `send_message` instead of re-delegating from scratch. Every role defaults to one-shot — run and discard — so nothing changes until you flip it.
15
16
  - **Cheaper requests, cleaner context**: a helper's tools load only when it is called — pay per use. The main agent's per-request tool overhead drops by roughly two-thirds, and it keeps only summaries instead of accumulating search dumps and diffs.
16
17
  - **Roles bend to your setup**: delete built-in roles you don't need, give roles display names, set a display name for the main agent — all one line of config; hit Save & Apply in the panel once and the preset regenerates on the spot, taking effect after a DSH restart or a new session.
17
18
  - **Whatever you've installed, it's recognized**: MCP servers, local plugins, installed skills — detected throughout install and configuration; the default install carries only DSH base tools, and detected host tools are opted into per tool in **Paoding Config**; disable one, re-apply, and its tools drop out on their own.
18
19
  - **A different lineup per workspace**: each project can carry its own main/sub-agent setup — pick the workspace in 庖丁配置, configure it separately, and it gets its own preset (`orchestrator-<basename>`); choose that preset when starting a session. The global default stays untouched.
19
20
  - **A visual configurator**: prefer clicking to editing YAML? the 庖丁配置 (Paoding Config) entry in the sidebar bottom action bar (above the Settings row) covers everything; Save & Apply regenerates the preset on the spot — the same generation pipeline as first-install automation.
20
- - **Notified of new releases, one-click upgrade in the panel**: the panel compares your local version against the latest npm registry publish (npmmirror and GitHub release as successive fallbacks) and points to the release page when a newer one is out. The Upgrade button runs `dsh plugin update dsh-paoding` in place (profile auto-detected) to swap in the new version; a DSH restart applies it, and the orchestrator preset regenerates itself against the new version on startup. A dev checkout in link: form cannot be upgraded in place — the panel tells you how to switch back to the registry version. If the check fails (offline, rate-limited), a subdued "version check failed · retry" row stays in the header (full causes and proxy hints in its tooltip) without popping anything up.
21
+ - **Notified of new releases, one-click upgrade in the panel**: the panel compares your local version against the latest npm registry publish (npmmirror and GitHub release as successive fallbacks) and points to the release page when a newer one is out. The Upgrade button swaps in the new version in place (profile auto-detected): regular profiles go through `dsh plugin update dsh-paoding`, while the Electron-owned desktop profile — which the dsh CLI guards against — gets a direct `pnpm add dsh-paoding@<version>` run in the profile directory; a DSH restart applies it, and the orchestrator preset regenerates itself against the new version on startup. A dev checkout in link: form cannot be upgraded in place — the panel tells you how to switch back to the registry version. If the check fails (offline, rate-limited), a subdued "version check failed · retry" row stays in the header (full causes and proxy hints in its tooltip) without popping anything up.
21
22
  - **Installs and uninstalls cleanly**: no DSH source changes; active in a new session right after install, gone when you delete one directory.
22
23
  - **Works on both host generations**: DSH 0.1.5 through 0.2.0-rc.2 are all supported (0.1.5 / 0.1.6 / 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1 / 0.2.0-rc.2) — the two generations discover presets differently (directory scan vs patch declaration rows), and the installer picks the right landing automatically; from 0.2.0 the workflow worker row in the orchestration composition also adapts to the host version at generation time (0.1.5 uses workflow-worker-thread, 0.1.6+ swaps to workflow-ptc). Migration happens on the first save/self-heal after a host upgrade or downgrade, all without user action.
23
24
 
@@ -56,7 +57,7 @@ git clone https://github.com/lifangjin/dsh-paoding.git
56
57
  dsh plugin --profile web add link:"$PWD/dsh-paoding"
57
58
  ```
58
59
 
59
- Upgrades go through the panel's Upgrade button (equivalent to `dsh plugin update dsh-paoding`). Full steps, upgrading, and troubleshooting are in the [installation guide](docs/installation_en.md).
60
+ Upgrades go through the panel's Upgrade button (regular profiles: equivalent to `dsh plugin update dsh-paoding`; the desktop profile: a direct `pnpm add` of the exact version in the profile directory). Full steps, upgrading, and troubleshooting are in the [installation guide](docs/installation_en.md).
60
61
 
61
62
  ## How It Works in Practice
62
63
 
@@ -73,7 +74,7 @@ While a helper runs, the main agent can send follow-ups, check progress, or stop
73
74
 
74
75
  ## Configuration
75
76
 
76
- All preferences live in `~/.dsh/dsh-paoding.config.yml`: role add/remove/tweak, skill assignment, per-role models, per-role session modes (one-shot / continuable), the main agent's tools and skills… Edit it, open the 庖丁配置 (Paoding Config) entry at the bottom of the sidebar, and hit Save & Apply — the preset syncs. It is regenerated from "static source + config" every time, so no manual patches pile up. The full key reference is in the [configuration guide](docs/configuration_en.md); the rationale and the cost ledger are in the [architecture guide](docs/architecture_en.md).
77
+ All preferences live in `~/.dsh/dsh-paoding.config.yml`: role add/remove/tweak, skill assignment, per-role models, per-role session modes (one-shot / continuable), per-role start modes (fresh / fork), the main agent's tools and skills… Edit it, open the 庖丁配置 (Paoding Config) entry at the bottom of the sidebar, and hit Save & Apply — the preset syncs. It is regenerated from "static source + config" every time, so no manual patches pile up. The full key reference is in the [configuration guide](docs/configuration_en.md); the rationale and the cost ledger are in the [architecture guide](docs/architecture_en.md).
77
78
 
78
79
  ## Documentation
79
80
 
@@ -33,7 +33,7 @@ dsh-paoding 把「按角色分工的编排 preset」做成**配置驱动**:你
33
33
  | 顶层键 | 类型 | 语义 |
34
34
  |---|---|---|
35
35
  | `profile` | string | 检测时纳入哪个 profile 的 patch 层(`$DSH_HOME/profiles/<profile>/cordis.patch.yml`);缺省 `web` |
36
- | `roles` | map | `toolName` → `{ persona?, name?, tools[], model?, provider?, background_mode? }`。对内置三角色(`search_external` / `design` / `implement`):覆盖 persona 与 allow 意图,并可配 `name` 显示名(只替换该角色默认 persona 首行身份句的主语,见 2.4)、专用模型(`model` / `provider`,见 2.5)与会话模式(`background_mode`,见 2.6);其它键名 = **自定义角色**:生成全新 `delegation-<toolName>` 块并把 toolName 注入主 agent 的 `config.allow`(详见第 2、6 节) |
36
+ | `roles` | map | `toolName` → `{ persona?, name?, tools[], model?, provider?, start_mode?, background_mode? }`。对内置三角色(`search_external` / `design` / `implement`):覆盖 persona 与 allow 意图,并可配 `name` 显示名(只替换该角色默认 persona 首行身份句的主语,见 2.4)、专用模型(`model` / `provider`,见 2.5)、会话模式(`background_mode`,见 2.6)与启动方式(`start_mode`,见 2.7);其它键名 = **自定义角色**:生成全新 `delegation-<toolName>` 块并把 toolName 注入主 agent 的 `config.allow`(详见第 2、6 节) |
37
37
  | `roles_remove` | string[] | 整条删除的内置角色 toolName 列表(只认 `search_external` / `design` / `implement`,其它名字忽略并告警;缺省 = `[]`)。删除 = 委派行 / 工具面 / 角色 persona / 主 agent 侧委派指引全部不再生成(详见 2.3) |
38
38
  | `skills` | map | `skill` → 分配给该技能的角色 toolName 列表;安装时把 `Available skills: …` 引导句写进这些角色 persona 的尾部(详见第 7 节) |
39
39
  | `main_agent_extra` | string[] | 追加到主 agent 白名单的工具(host 工具,如 `mcp__codegraph__codegraph_explore`、`memory_search`、`mnemon_*`);按「preset 自带工具面 ∪ 检测库存」对账,两头都不占的名字不注入 |
@@ -52,6 +52,7 @@ dsh-paoding 把「按角色分工的编排 preset」做成**配置驱动**:你
52
52
  - `roles.<toolName>.model`:该角色的专用模型 id(可选 `string`)。配置后,该角色派出的每个子 agent 都固定用这个模型,不再随主 agent 会话当前模型走;缺省 = 不注入 `agentOptions`,子 agent 继承主 agent 的模型(详见 2.5)。
53
53
  - `roles.<toolName>.provider`:该角色模型所在的 provider 路由(可选 `string`)。须与 `model` 成对配置才生效,只配 `provider` 会被安装器告警并忽略;只配 `model` 时沿用主 agent 的 provider 路由(详见 2.5)。
54
54
  - `roles.<toolName>.background_mode`:该角色的会话模式(可选 `'one-shot' | 'continuable'`,缺省 `one-shot`)——一次性用完即弃,或可续:子会话跨轮保留,主 agent 用 `send_message` 就地续修。内置角色与自定义角色同语义;continuable 的前置、代价与面板路径见 2.6。
55
+ - `roles.<toolName>.start_mode`:该角色的启动方式(可选 `'fresh' | 'fork'`,缺省 `fresh`)——全新启动,或以主 agent 已完成的对话轮次为初始内容启动(继承对话)。内置角色与自定义角色同语义;fork 的语义、与专用模型的互斥及面板路径见 2.7。
55
56
  - `roles` 下键名不在三角色内 = 自定义角色(见第 6 节);请勿把静态角色 `search_internal_deep` 写进 `roles`(见 2.2)。
56
57
  - 内置角色可**整体删除**:`roles_remove` 键只认三角色名,删除效果、与 `roles` 键的一致性及恢复路径见 2.3。
57
58
 
@@ -242,6 +243,7 @@ roles:
242
243
  规则与边界:
243
244
 
244
245
  - **provider 须与 model 成对**:只写 `provider` 不生效——安装器告警并忽略;只写 `model` 合法,此时沿用主 agent 的 provider 路由。
246
+ - **与 fork 启动方式互斥**:该角色配了 `start_mode: fork`(见 2.7)时,专用模型不生效——fork 的子 agent 继承主 agent 的 provider 路由与模型(KV cache 复用的硬约束),安装器告警并忽略 `model`(连同 `agentOptions` 不注入)。
245
247
  - **路由与模型 id 必须真实存在**:可选路由取决于你的 DSH 部署注册了哪些 LLM 适配器——官方安装默认 `deepseek-official`(模型 `deepseek-v4-flash` / `deepseek-v4-pro` / `deepseek-v4-flash-vision-exp`);`pi-ai` 网关可在 DSH 设置里配 anthropic / openai / google 等 profile(需先备好对应凭据)。`model` 必须属于所选路由,否则该角色**每次委派**都会在运行时报错。
246
248
  - **被删除的角色不涉及**:`roles_remove` 整条删除的角色,条目里残留的 `model` / `provider` 被忽略并告警(见 2.3)。
247
249
  - **静态角色不在列**:`search_internal_deep` 无这两个子键(见 2.2)。
@@ -279,6 +281,38 @@ continuable 的前置与代价(如实说明):
279
281
 
280
282
  生效方式与其他配置键相同:改完在「庖丁配置」点「保存并应用」,再重启 DSH / 新会话。
281
283
 
284
+ ### 2.7 角色启动方式:roles.\<toolName>.start_mode
285
+
286
+ 角色条目的可选子键 `start_mode`,决定该角色派出的子 agent 怎么开局:`'fresh'`(全新启动,独立上下文)或 `'fork'`(继承对话,以主 agent 已完成的对话轮次为初始内容启动)。缺省 `fresh`;内置角色与自定义角色条目同语义,没有角色差异。
287
+
288
+ ```yaml
289
+ roles:
290
+ implement:
291
+ start_mode: fork # 帮手以主 agent 已完成的对话为底子开工;其余配置(persona / tools 等)照常生效
292
+ ```
293
+
294
+ fork 的语义(如实说明):
295
+
296
+ - **初始内容 = 主 agent 已完成的轮次**:截至发起委派时已收尾的对话(含主 agent 的指令与产出);进行中的那一轮不在其中。主 agent 还没有已完成轮次时,fork 出来的子 agent 与全新启动没有区别。
297
+ - **一次性快照,不是实时共享**:fork 发生在委派那一刻,之后主 agent 的新进展不会自动流进子会话,子 agent 的产出也不回写主会话——两边各自推进,靠返回结果交汇。要「持续跟着主 agent 走」的帮手,那是 `background_mode: continuable`(见 2.6)加 `send_message` 续修的用法;两者正交,可同时配置。
298
+ - **工具面照常收敛**:fork 只继承对话内容,不继承主 agent 的工具权限;子 agent 仍按该角色的 `toolFilter.allow` 收敛工具面(`tools` 配置照常生效)。
299
+ - **与专用模型互斥**:fork 的子 agent 继承主 agent 的 provider 路由与模型(KV cache 复用的硬约束),角色配了 `model` 时安装器告警并忽略、不注入 `agentOptions`(见 2.5)。
300
+
301
+ 典型用例:主 agent 已经做完调研、写完大纲,把「把这份大纲做成 PPT」这类活交给帮手时,fork 让帮手直接带着结论与大纲开工,省去把上下文复述一遍的成本。
302
+
303
+ 生成语义:基础模板在四个内置角色的委派块里各预置一行块级 `provider: spawn`(委派块 config 的首行);配成 `fork` 的角色,生成器把该行**原位改写**为 `provider: fork`,`fresh` / 缺省原样保留(全默认配置的生成产物与源模板逐字节一致)。自定义角色块同样按启动方式落 `provider: fork` / `provider: spawn`。fork 帮手对编排流程的影响见[编排](orchestration.md)。
304
+
305
+ 面板操作路径:左侧栏「庖丁配置」→ 角色卡「启动方式」单选(「全新(fresh)」/「继承对话(fork)」);选中继承对话且该角色配了专用模型时,面板就地提示互斥(不拦截保存)。
306
+
307
+ 边界:
308
+
309
+ - **与 `roles.<toolName>.provider` 无关**:那个 provider 是 LLM 路由(与 `model` 配对,见 2.5),落在委派块的 `agentOptions` 子行里;本键决定的是委派块的块级 `provider` 行(spawn / fork),两者分属两处、互不相干。
310
+ - **非法值告警回落**:`start_mode` 只认 `'fresh'` / `'fork'`,其他值安装器告警并按 `fresh` 处理。
311
+ - **被删除的角色不涉及**:`roles_remove` 整条删除的角色,条目里残留的 `start_mode` 被忽略并告警(见 2.3)。
312
+ - **静态角色不在列**:`search_internal_deep` 不经 `roles` 键(见 2.2);要改它的启动方式,直接改静态源里那个委派块的 `provider` 行再重新生成(生成器不重写该行,原样保留)。
313
+
314
+ 生效方式与其他配置键相同:改完在「庖丁配置」点「保存并应用」,再重启 DSH / 新会话。
315
+
282
316
  ## 3. 主 agent 工具面
283
317
 
284
318
  ### 3.1 机制与 21 项白名单基线
@@ -600,7 +634,7 @@ skills:
600
634
 
601
635
  DSH 用工作区(workspace,即项目目录)组织会话。顶层 `workspaces` 键让每个工作区有自己的主/子 agent 配置:全局条目照旧生成共享的 `orchestrator` 预设;工作区条目各自生成 `orchestrator-<slug>` 专属预设,预设列表里并存、互不覆盖。
602
636
 
603
- 每个工作区条目是**完整一套配置**,不是增量差异:字段与顶层同构(`profile` 除外,检测层全局共享)。角色子键照搬——`background_mode`(见 2.6)同样按工作区生效:某个项目想让 implement 可续、别的项目保持一次性,各配各的。在面板里第一次配置某个工作区时,以当前全局配置为起点,改完点「保存并应用」即生成该工作区的预设。
637
+ 每个工作区条目是**完整一套配置**,不是增量差异:字段与顶层同构(`profile` 除外,检测层全局共享)。角色子键照搬——`background_mode`(见 2.6)、`start_mode`(见 2.7)同样按工作区生效:某个项目想让 implement 可续或继承对话、别的项目保持一次性全新开局,各配各的。在面板里第一次配置某个工作区时,以当前全局配置为起点,改完点「保存并应用」即生成该工作区的预设。
604
638
 
605
639
  ```yaml
606
640
  workspaces:
@@ -33,7 +33,7 @@ The key names below match `normalizeConfig` in `tools/lib/config.mjs` — the si
33
33
  | Top-level key | Type | Semantics |
34
34
  |---|---|---|
35
35
  | `profile` | string | Which profile's patch layer (`$DSH_HOME/profiles/<profile>/cordis.patch.yml`) is scanned during detection; default `web` |
36
- | `roles` | map | `toolName` → `{ persona?, name?, tools[], model?, provider?, background_mode? }`. For the three built-in roles (`search_external` / `design` / `implement`): overrides the persona and the allow intent, and may carry a `name` display name (rewrites only the subject of that role's default persona identity sentence — see 2.4), a dedicated model (`model` / `provider`, see 2.5) and a session mode (`background_mode`, see 2.6). Any other key is a **custom role**: the installer emits a fresh `delegation-<toolName>` block and injects the toolName into the main agent's `config.allow` (sections 2 and 6) |
36
+ | `roles` | map | `toolName` → `{ persona?, name?, tools[], model?, provider?, start_mode?, background_mode? }`. For the three built-in roles (`search_external` / `design` / `implement`): overrides the persona and the allow intent, and may carry a `name` display name (rewrites only the subject of that role's default persona identity sentence — see 2.4), a dedicated model (`model` / `provider`, see 2.5), a session mode (`background_mode`, see 2.6) and a start mode (`start_mode`, see 2.7). Any other key is a **custom role**: the installer emits a fresh `delegation-<toolName>` block and injects the toolName into the main agent's `config.allow` (sections 2 and 6) |
37
37
  | `roles_remove` | string[] | toolNames of built-in roles deleted wholesale (only `search_external` / `design` / `implement` are accepted; other names are ignored with a warning; default `[]`). Deleting means the delegation block / tool surface / role persona / main-agent delegation guidance are no longer generated (see 2.3) |
38
38
  | `skills` | map | `skill` → the list of role toolNames the skill is assigned to; the installer appends an `Available skills: …` guidance sentence to those roles' personas (section 7) |
39
39
  | `main_agent_extra` | string[] | Tools appended to the main-agent allow list (host tools such as `mcp__codegraph__codegraph_explore`, `memory_search`, `mnemon_*`); names are checked against the preset's own tool face ∪ the detection inventory — a name in neither is not injected |
@@ -52,6 +52,7 @@ The key names below match `normalizeConfig` in `tools/lib/config.mjs` — the si
52
52
  - `roles.<toolName>.model`: a dedicated model id for the role (optional `string`). When set, every sub-agent the role spawns runs on that model instead of following the main agent's current session model; absent = nothing is injected and the child inherits the main agent's model (see 2.5).
53
53
  - `roles.<toolName>.provider`: the provider route the role's model lives on (optional `string`). It only takes effect paired with `model`; a lone `provider` is warned about and ignored by the installer, while a lone `model` rides the main agent's provider route (see 2.5).
54
54
  - `roles.<toolName>.background_mode`: the role's session mode (optional `'one-shot' | 'continuable'`; default `one-shot`) — run-and-discard, or continuable: the child conversation is kept across turns so the main agent can resume in place with `send_message`. Built-in and custom role entries share the semantics; the continuable prerequisites, costs and panel path are in 2.6.
55
+ - `roles.<toolName>.start_mode`: the role's start mode (optional `'fresh' | 'fork'`; default `fresh`) — a brand-new child, or one that starts with the main agent's completed turns as its initial content (inherit the conversation). Built-in and custom role entries share the semantics; fork semantics, the dedicated-model mutual exclusion and the panel path are in 2.7.
55
56
  - Keys under `roles` outside the built-in trio are **custom roles** (section 6). Do not put the static role `search_internal_deep` under `roles` (see 2.2).
56
57
  - Built-in roles can be **deleted wholesale**: the `roles_remove` key accepts only the trio's names — deletion effect, consistency with the `roles` key, and the restore paths are in 2.3.
57
58
 
@@ -242,6 +243,7 @@ roles:
242
243
  Rules and boundaries:
243
244
 
244
245
  - **`provider` pairs with `model`**: a lone `provider` has no effect — the installer warns and ignores it. `model` alone is valid and rides the main agent's provider route.
246
+ - **Mutually exclusive with the fork start mode**: when the role also sets `start_mode: fork` (see 2.7), the dedicated model has no effect — a forked child inherits the main agent's provider route and model (a hard KV-cache reuse constraint), and the installer warns, ignores `model` and skips the `agentOptions` injection.
245
247
  - **Routes and model ids must actually exist**: the available routes depend on which LLM adapters your DSH deployment registers — an official install defaults to `deepseek-official` (models `deepseek-v4-flash` / `deepseek-v4-pro` / `deepseek-v4-flash-vision-exp`); the `pi-ai` gateway accepts anthropic / openai / google etc. profiles configured in DSH settings (set up the matching credentials first). The `model` id must belong to the chosen route, otherwise **every delegation** of that role errors at runtime.
246
248
  - **Deleted roles are not involved**: for a role deleted wholesale by `roles_remove`, any `model` / `provider` left in its entry is ignored with a warning (see 2.3).
247
249
  - **The static role is out of scope**: `search_internal_deep` has neither sub-key (see 2.2).
@@ -279,6 +281,38 @@ Boundaries:
279
281
 
280
282
  Applying works exactly like every other config key: after editing, hit「保存并应用」(save & apply) in 庖丁配置, then restart DSH or open a new session.
281
283
 
284
+ ### 2.7 Role start mode: roles.\<toolName>.start_mode
285
+
286
+ The optional `start_mode` sub-key of a role entry decides how that role's child agents start out: `'fresh'` (a brand-new, independent context) or `'fork'` (inherit the conversation — the child starts with the main agent's completed turns as its initial content). Default `fresh`; built-in and custom role entries share the semantics, with no per-role differences.
287
+
288
+ ```yaml
289
+ roles:
290
+ implement:
291
+ start_mode: fork # the helper starts from what the main agent has already done; every other setting (persona / tools etc.) still applies
292
+ ```
293
+
294
+ Fork semantics (stated plainly):
295
+
296
+ - **Initial content = the main agent's completed turns**: the conversation as finished up to the moment of delegation (the main agent's instructions and outputs included); the turn currently in progress is not part of it. If the main agent has not completed any turn yet, a forked child is indistinguishable from a fresh spawn.
297
+ - **A one-shot snapshot, not live sharing**: the fork happens at delegation time. Later progress on the main-agent side never flows into the child session, and the child's output is not written back into the main session — the two advance independently and only meet through the returned result. For a helper that should "keep following the main agent", that is `background_mode: continuable` (see 2.6) plus `send_message` resumption; the two keys are orthogonal and can be combined.
298
+ - **The tool surface still converges**: fork inherits conversation content only, never the main agent's tool permissions; the child still narrows its tools to the role's `toolFilter.allow` (the `tools` setting applies as usual).
299
+ - **Mutually exclusive with the dedicated model**: a forked child inherits the main agent's provider route and model (a hard KV-cache reuse constraint); when the role also sets `model`, the installer warns, ignores it and skips the `agentOptions` injection (see 2.5).
300
+
301
+ Typical use case: the main agent has finished its research and written an outline, and hands "turn this outline into a deck" to a helper — fork lets the helper start directly from the conclusions and the outline instead of having the context restated to it.
302
+
303
+ Generation semantics: the base template ships a block-level `provider: spawn` row in each of the four built-in roles' delegation blocks (the first config row of the block); a role configured as `fork` has that row **rewritten in place** to `provider: fork`, while `fresh` / absent keeps it as is (with an all-default config the generated output stays byte-identical to the source template). Custom-role blocks likewise emit `provider: fork` / `provider: spawn` according to the start mode. For what a forked helper means at the orchestration level, see [Orchestration](orchestration_en.md).
304
+
305
+ Panel path: 庖丁配置 in the left sidebar → the「启动方式」("start mode") radio on a role card (「全新(fresh)」/「继承对话(fork)」). When inherit-conversation is selected and the role also has a dedicated model, the panel warns about the mutual exclusion in place (saving is not blocked).
306
+
307
+ Boundaries:
308
+
309
+ - **Unrelated to `roles.<toolName>.provider`**: that provider is the LLM route (paired with `model`, see 2.5) and lands in the block's `agentOptions` sub-rows; this key decides the delegation block's block-level `provider` row (spawn / fork). Two different places, no interaction.
310
+ - **Invalid values fall back with a warning**: `start_mode` only accepts `'fresh'` / `'fork'`; anything else is warned about and treated as `fresh`.
311
+ - **Deleted roles are not involved**: for a role deleted wholesale by `roles_remove`, any `start_mode` left in its entry is ignored with a warning (see 2.3).
312
+ - **The static role is out of scope**: `search_internal_deep` is not configured through the `roles` key (see 2.2); to change its start mode, edit that delegation block's `provider` row in the static source and re-apply (the generator never rewrites that row, it carries over verbatim).
313
+
314
+ Applying works exactly like every other config key: after editing, hit「保存并应用」(save & apply) in 庖丁配置, then restart DSH or open a new session.
315
+
282
316
  ## 3. Main-agent tool surface
283
317
 
284
318
  ### 3.1 Mechanism and the 21-name base allow list
@@ -610,7 +644,7 @@ When you disable tavily / codegraph / magic-memory (comment out the entry in `~/
610
644
 
611
645
  DSH organizes sessions by workspace (the project directory). The top-level `workspaces` key gives each workspace its own main/sub-agent configuration: the global entry keeps generating the shared `orchestrator` preset, while each workspace entry generates its own `orchestrator-<slug>` preset. They coexist in the preset roster and never overwrite each other.
612
646
 
613
- Each workspace entry is a **complete configuration set**, not a delta: same fields as the top level (except `profile` — detection layers stay global). Role sub-keys carry over as-is — `background_mode` (see 2.6) works per workspace too: let `implement` run continuable in one project while every other project stays one-shot. The first time you configure a workspace in the panel, it starts from the current global config; edit, then hit "Save & Apply" to generate that workspace's preset.
647
+ Each workspace entry is a **complete configuration set**, not a delta: same fields as the top level (except `profile` — detection layers stay global). Role sub-keys carry over as-is — `background_mode` (see 2.6) and `start_mode` (see 2.7) work per workspace too: let `implement` run continuable or conversation-inheriting in one project while every other project stays one-shot and fresh. The first time you configure a workspace in the panel, it starts from the current global config; edit, then hit "Save & Apply" to generate that workspace's preset.
614
648
 
615
649
  ```yaml
616
650
  workspaces:
@@ -117,7 +117,7 @@ dsh plugin --profile <name> update dsh-paoding
117
117
 
118
118
  (profile 自动探测,通常无需手填。)升级只换 profile 里的插件包本体,当前进程的代码不变;成功后提示**重启 DSH**,重启时 preset 按 `.generator-version` 标记自动按新版重生成。只升级 dsh 本体(不动插件)同理:重启即触发轨道迁移自愈,无需重装或重新应用。
119
119
 
120
- 手动升级等价:直接在终端跑上面这条 `dsh plugin update` 命令,同样重启生效。
120
+ 手动升级等价:普通 profile 直接在终端跑上面这条 `dsh plugin update` 命令;desktop profile 被 dsh CLI 独占守卫挡住,等价命令是 `cd ~/.dsh/profiles/desktop && pnpm add dsh-paoding@latest`,同样重启生效。
121
121
 
122
122
  **开发 link 形态**(见第 4 节)版本比较照常(更新卡有新版照常提示),但无法就地升级——升级器要求包真实装在某个 profile 的 node_modules 下,link 直连仓库匹配不到;点「升级」会如实报这一点,并提示手动跑 `dsh plugin add dsh-paoding@latest` 切回 registry 版。
123
123
 
@@ -190,7 +190,7 @@ patch 与配置文件用 **`yaml` 包**解析——通过 `createRequire` 从 `$
190
190
 
191
191
  ### 数据接口与安全
192
192
 
193
- 面板数据走**同源 `/api/paoding/*`**:Node 侧注册前缀路由——`GET /api/paoding/state`(带缓存的检测状态)、`GET /api/paoding/version`(最新版检测,版本/更新卡的数据源)、`GET /api/paoding/models`(宿主模型运行时的模型清单)、`GET` + `POST /api/paoding/workspaces`(工作区列表 / 添加工作区)、`POST /api/paoding/rescan`(强制重检测,含 MCP 握手)、`POST /api/paoding/preview`(不写盘生成)、`POST /api/paoding/apply`(安装并保存配置)、`POST /api/paoding/upgrade`(就地 `dsh plugin update`)、`GET /api/paoding/client.css`(面板样式静态下发)——复用 `tools/` 的检测与生成管线(`plugins/paoding-config-ui/api-core.mjs`)。路由自带**浏览器信任围栏**:Host 必须回环或落在 `webRuntime.trustedHosts`,并拒绝 cross-site 请求(复刻 DSH `/api` 网关围栏语义,因为 `/api/paoding` 前缀更长会命中本插件而绕开网关)。面板**没有独立服务端/端口**:DSH Web 端口只监听 `127.0.0.1`,不对外网暴露。
193
+ 面板数据走**同源 `/api/paoding/*`**:Node 侧注册前缀路由——`GET /api/paoding/state`(带缓存的检测状态)、`GET /api/paoding/version`(最新版检测,版本/更新卡的数据源)、`GET /api/paoding/models`(宿主模型运行时的模型清单)、`GET` + `POST /api/paoding/workspaces`(工作区列表 / 添加工作区)、`POST /api/paoding/rescan`(强制重检测,含 MCP 握手)、`POST /api/paoding/preview`(不写盘生成)、`POST /api/paoding/apply`(安装并保存配置)、`POST /api/paoding/upgrade`(就地升级:普通 profile 走 `dsh plugin update`,desktop profile 直跑 `pnpm add`)、`GET /api/paoding/client.css`(面板样式静态下发)——复用 `tools/` 的检测与生成管线(`plugins/paoding-config-ui/api-core.mjs`)。路由自带**浏览器信任围栏**:Host 必须回环或落在 `webRuntime.trustedHosts`,并拒绝 cross-site 请求(复刻 DSH `/api` 网关围栏语义,因为 `/api/paoding` 前缀更长会命中本插件而绕开网关)。面板**没有独立服务端/端口**:DSH Web 端口只监听 `127.0.0.1`,不对外网暴露。
194
194
 
195
195
  ### 面板能力
196
196
 
@@ -117,7 +117,7 @@ dsh plugin --profile <name> update dsh-paoding
117
117
 
118
118
  (the profile is auto-detected; usually nothing to fill in). The upgrade only swaps the plugin package inside the profile — the running process's code is untouched; on success it asks you to **restart DSH**, and at restart the preset regenerates itself against the new version via the `.generator-version` marker. Upgrading only the dsh host (plugin untouched) works the same way: the restart triggers the track-migration self-heal — no reinstall or manual re-apply needed.
119
119
 
120
- Manual equivalent: run the `dsh plugin update` command above yourself, restart, done.
120
+ Manual equivalent: run the `dsh plugin update` command above yourself (for the desktop profile, which the dsh CLI guards: `cd ~/.dsh/profiles/desktop && pnpm add dsh-paoding@latest`), restart, done.
121
121
 
122
122
  A **dev link: checkout** (see §4) still gets version checks (the update card appears when a newer version exists) but cannot be upgraded in place — the upgrader requires the package to really live under a profile's node_modules, which a link straight into the repo never matches. Hitting Upgrade reports exactly that and suggests running `dsh plugin add dsh-paoding@latest` to switch back to the registry version.
123
123
 
@@ -190,7 +190,7 @@ If you'd rather not hand-edit `dsh-paoding.config.yml`, open the **庖丁配置
190
190
 
191
191
  ### Data interface and security
192
192
 
193
- Panel data goes over the **same-origin `/api/paoding/*`**: the Node side registers prefix routes — `GET /api/paoding/state` (cached detection state), `GET /api/paoding/version` (latest-version check, the data source of the version/update card), `GET /api/paoding/models` (model list from the host LLM runtime), `GET` + `POST /api/paoding/workspaces` (list / add workspaces), `POST /api/paoding/rescan` (forced re-detection, MCP handshakes included), `POST /api/paoding/preview` (generate without writing), `POST /api/paoding/apply` (install and save config), `POST /api/paoding/upgrade` (in-place `dsh plugin update`), `GET /api/paoding/client.css` (statically served panel stylesheet) — reusing the detection/generation pipeline in `tools/` (`plugins/paoding-config-ui/api-core.mjs`). The routes carry a **browser trust fence**: the Host must be loopback or listed in `webRuntime.trustedHosts`, and cross-site requests are rejected (mirroring DSH's `/api` gateway fence semantics, since the longer `/api/paoding` prefix would otherwise hit this plugin and bypass the gateway). The panel has **no separate server or port**: the DSH Web port listens on `127.0.0.1` only and is never exposed to the outside.
193
+ Panel data goes over the **same-origin `/api/paoding/*`**: the Node side registers prefix routes — `GET /api/paoding/state` (cached detection state), `GET /api/paoding/version` (latest-version check, the data source of the version/update card), `GET /api/paoding/models` (model list from the host LLM runtime), `GET` + `POST /api/paoding/workspaces` (list / add workspaces), `POST /api/paoding/rescan` (forced re-detection, MCP handshakes included), `POST /api/paoding/preview` (generate without writing), `POST /api/paoding/apply` (install and save config), `POST /api/paoding/upgrade` (in-place upgrade: `dsh plugin update` for regular profiles, a direct `pnpm add` for the desktop profile), `GET /api/paoding/client.css` (statically served panel stylesheet) — reusing the detection/generation pipeline in `tools/` (`plugins/paoding-config-ui/api-core.mjs`). The routes carry a **browser trust fence**: the Host must be loopback or listed in `webRuntime.trustedHosts`, and cross-site requests are rejected (mirroring DSH's `/api` gateway fence semantics, since the longer `/api/paoding` prefix would otherwise hit this plugin and bypass the gateway). The panel has **no separate server or port**: the DSH Web port listens on `127.0.0.1` only and is never exposed to the outside.
194
194
 
195
195
  ### Panel capabilities
196
196
 
@@ -99,6 +99,10 @@ Delegation failure handling (SOP):
99
99
  and fix in place — resuming keeps the child's accumulated context, while
100
100
  re-delegating starts a cold task and repeats finished work. Only re-delegate
101
101
  when the child session is gone or the task needs a different role.
102
+ - A send_message that returned a delivery confirmation has been stored by the child
103
+ session — never resend it while awaiting the reply; collect the result via the
104
+ completion notification or job_output instead. Resend only when the send itself
105
+ failed (nothing was stored) or the child session is confirmed gone.
102
106
  - If the same task fails twice, stop retrying: report to the user what failed, why,
103
107
  and what you already tried. Never loop a failing delegation.
104
108
  - When re-delegating (fresh task or continuation), always include your integration
@@ -120,13 +124,15 @@ Delegation failure handling (SOP):
120
124
 
121
125
  continuable 分支:失败或产出不满、**且该角色已配成 continuable** 时,先 `send_message` 在同一子会话续修——子 agent 记得自己做到哪,就地补完;子会话已不在、或任务需要换角色时才重委派(即 persona SOP 中 Continuable roles 条目的口径);续修无效再回到上表按停止原因重委派(重委派从首选退为退路)。
122
126
 
127
+ 「排队消息不重发」:`send_message` 的工具结果是**送达确认,不是子 agent 的回答**。已返回确认的后续消息就进了子会话,等回音期间**绝不重发**——重发等于同一指令投两遍,子 agent 会把一份活当两份干;结果由完成通知或 `job_output` 收取。只有两种情况才再发:发送本身失败(消息从未入队),或子会话确认已不在(`list_agents` 查无此人)。拿不准时先查状态,别拿重发当探针。
128
+
123
129
  设计要点:SOP 是 persona 文本而非代码逻辑,因此可以按需编辑——直接改 `presets/orchestrator/agent.cordis.yml` 中 persona 段落(YAML 块标量,内容行缩进 6 空格,注意保持缩进),再到「庖丁配置」点「保存并应用」重新生成。例如想让失败任务更频繁地退回主 agent 自己做、或对某个角色采用不同的重试上限,都可在此调整。
124
130
 
125
131
  ## one-shot 与多轮迭代
126
132
 
127
133
  ### 默认:one-shot(用完即弃)
128
134
 
129
- 四个角色委派实例(`delegation-search-external` / `delegation-design` / `delegation-implement` / `delegation-search-internal-deep`)的会话模式都是 one-shot——基础模板把四个内置角色显式写明(委派块里的 `backgroundMode: one-shot` 行,纯为可见性,省得打开文件猜默认值),语义与 `dsh-tool-subagent` 的配置默认值一致。会话模式按角色可配:任一角色(含自定义角色)经 `roles.<toolName>.background_mode` 切成 `continuable`(见[配置](configuration.md) 2.6);还可经 `roles.<toolName>.model` / `.provider` 固定专用模型——配置后该角色固定用自己的模型,不随主 agent 会话切换模型而变(见[配置](configuration.md) 2.5)。含义:
135
+ 四个角色委派实例(`delegation-search-external` / `delegation-design` / `delegation-implement` / `delegation-search-internal-deep`)的会话模式都是 one-shot——基础模板把四个内置角色显式写明(委派块里的 `backgroundMode: one-shot` 行,纯为可见性,省得打开文件猜默认值),语义与 `dsh-tool-subagent` 的配置默认值一致。会话模式按角色可配:任一角色(含自定义角色)经 `roles.<toolName>.background_mode` 切成 `continuable`(见[配置](configuration.md) 2.6);还可经 `roles.<toolName>.model` / `.provider` 固定专用模型——配置后该角色固定用自己的模型,不随主 agent 会话切换模型而变(见[配置](configuration.md) 2.5);开局方式也可按角色切换——`roles.<toolName>.start_mode` 让帮手继承主 agent 已完成的对话启动(见本章末节与[配置](configuration.md) 2.7)。含义:
130
136
 
131
137
  - 一次委派 = 一个用完即弃的子会话。调用默认等子 agent 跑完、把结果交回主 agent(也可按工具参数 `run_in_background` 转为后台 job,用 `job_output` 收结果、`job_kill` 叫停)。
132
138
  - 子会话在任务结束时即弃,其工具面与上下文只为该次运行而加载(**按需加载 + 上下文隔离**,这是本 preset 的核心设计)。
@@ -156,6 +162,19 @@ continuable 不是免费的,启用前请确认以下四点:
156
162
  | 长期成本 | 每轮只付新任务 | 每轮携带累积上下文,成本上升 |
157
163
  | 适用 | 一切委派(默认) | 多轮打磨类委派(按角色开启,见[配置](configuration.md) 2.6) |
158
164
 
165
+ ### 启动方式:全新(spawn)与继承对话(fork)
166
+
167
+ 会话模式管「怎么收场」,启动方式(`roles.<toolName>.start_mode`,见[配置](configuration.md) 2.7)管「怎么开局」,两者正交——fork 帮手可以一次性用完即弃,也可以再配 continuable 跨轮续修。生成层把角色委派块的块级 `provider` 行写成 `spawn`(缺省,全新启动)或 `fork`(继承对话);preset 自带的 `subagent_fork` 通用委派工具就是同一个 fork 提供方,主 agent 白名单刻意不向其暴露,fork 只经角色配置按键生效。
168
+
169
+ fork 的语义(如实说明):
170
+
171
+ - **初始内容 = 主 agent 已完成的轮次。** 委派发生时,子 agent 拿到的是主 agent 到此刻已经收尾的对话(指令与产出都在);进行中的那一轮不算数。主 agent 一个已完成轮次都没有时,fork 与全新启动没有区别。
172
+ - **一次性快照,两边各自推进。** fork 之后主 agent 的新进展不会流进子会话,子 agent 的产出也不回写主会话;交汇点只有「子 agent 返回结果」。要持续跟着主 agent 走的帮手,用 continuable + `send_message`(见上节),两者可叠加。
173
+ - **只继承对话,不继承工具与权限。** fork 帮手的工具面仍按该角色 `toolFilter.allow` 收敛,DSH 权限语义(被拒操作不重试)原样适用。
174
+ - **模型跟随主 agent。** fork 子 agent 继承主 agent 当前的 provider 路由与模型(KV cache 复用的硬约束),角色配的专用模型此时不生效(安装器告警并忽略,见[配置](configuration.md) 2.5)。
175
+
176
+ 什么时候值得 fork:主 agent 已经做完一轮有分量的产出(调研结论、大纲、方案),接下来的活需要「知道这些」但又不想把这些内容在委派 prompt 里复述一遍——比如把写好的大纲做成 PPT、按已定的方案改代码。fork 让帮手直接从已完成上下文开工;反过来,任务与主 agent 上下文无关时不必 fork,全新启动上下文更干净。
177
+
159
178
  ## 进阶:免面板手编 implement_cont
160
179
 
161
180
  路径 B 的主路径在面板与配置键——角色卡「会话模式」一键可达(见[配置](configuration.md) 2.6)。本节是等价的免面板手编路径,共 5 步,供想看清生成层到底写了什么、或在不起面板的环境里操作时使用。默认 preset **不含** `implement_cont`——需要时自行启用,用完可整体移除。所有 YAML 均为配置原文(与 preset 内 delegation 块结构一致,已与源码核对)。
@@ -99,6 +99,10 @@ Delegation failure handling (SOP):
99
99
  and fix in place — resuming keeps the child's accumulated context, while
100
100
  re-delegating starts a cold task and repeats finished work. Only re-delegate
101
101
  when the child session is gone or the task needs a different role.
102
+ - A send_message that returned a delivery confirmation has been stored by the child
103
+ session — never resend it while awaiting the reply; collect the result via the
104
+ completion notification or job_output instead. Resend only when the send itself
105
+ failed (nothing was stored) or the child session is confirmed gone.
102
106
  - If the same task fails twice, stop retrying: report to the user what failed, why,
103
107
  and what you already tried. Never loop a failing delegation.
104
108
  - When re-delegating (fresh task or continuation), always include your integration
@@ -120,13 +124,15 @@ Behavior by stop reason (complete coverage, one-to-one with the persona):
120
124
 
121
125
  The continuable branch: when a run fails or the output disappoints **and the role is configured continuable**, first resume the same child conversation with `send_message` — the child remembers where it left off and finishes in place; re-delegate only when the child session is gone or the task needs a different role (the same wording as the Continuable roles entry in the persona SOP); otherwise fall back to the table above (re-delegation drops from first choice to fallback).
122
126
 
127
+ The "never resend a delivered message" rule: a `send_message` result is a **delivery confirmation, not the child's answer**. Once a send is confirmed, the message is stored in the child session — never resend it while awaiting the reply (a resend delivers the same instruction twice, and the child does the work twice); collect the result via the completion notice or `job_output`. Send again only when the send itself failed (nothing was stored) or the child session is confirmed gone (`list_agents` no longer lists it). When unsure, check status first — don't probe with resends.
128
+
123
129
  Design note: the SOP is persona text, not code, so it is tunable: edit the persona block in `presets/orchestrator/agent.cordis.yml` (YAML block scalar; content lines are indented 6 spaces — keep the indentation), then hit Save & Apply in 庖丁配置 to regenerate. For example, you can make failures bounce back to the main agent more often, or give a specific role a different retry ceiling.
124
130
 
125
131
  ## One-shot vs continuable
126
132
 
127
133
  ### Default: one-shot (run-and-discard)
128
134
 
129
- The four role delegation instances (`delegation-search-external` / `delegation-design` / `delegation-implement` / `delegation-search-internal-deep`) all run in `one-shot` mode — the base template writes it out explicitly for the four built-ins (a `backgroundMode: one-shot` row in each delegation block, purely for visibility so you never have to guess the default), which matches `dsh-tool-subagent`'s config default. The session mode is per-role configurable: any role (custom roles included) can be switched to `continuable` via `roles.<toolName>.background_mode` (see [Configuration](configuration_en.md) 2.6). Any role can additionally be pinned to a dedicated model via `roles.<toolName>.model` / `.provider` — once configured, the role runs on its own model and no longer follows the main agent's session model switches (see [Configuration](configuration_en.md) 2.5). Meaning:
135
+ The four role delegation instances (`delegation-search-external` / `delegation-design` / `delegation-implement` / `delegation-search-internal-deep`) all run in `one-shot` mode — the base template writes it out explicitly for the four built-ins (a `backgroundMode: one-shot` row in each delegation block, purely for visibility so you never have to guess the default), which matches `dsh-tool-subagent`'s config default. The session mode is per-role configurable: any role (custom roles included) can be switched to `continuable` via `roles.<toolName>.background_mode` (see [Configuration](configuration_en.md) 2.6). Any role can additionally be pinned to a dedicated model via `roles.<toolName>.model` / `.provider` — once configured, the role runs on its own model and no longer follows the main agent's session model switches (see [Configuration](configuration_en.md) 2.5). How a child starts can also be switched per role — `roles.<toolName>.start_mode` makes the helper start from the main agent's completed conversation (see the last section of this chapter and [Configuration](configuration_en.md) 2.7). Meaning:
130
136
 
131
137
  - One delegation = one run-and-discard child session. The call waits for the child to finish and hands the result back to the main agent by default (or becomes a background job via the tool's `run_in_background` parameter, collected with `job_output` and stopped with `job_kill`).
132
138
  - The child session is discarded when the task settles; its tool surface and context are paid for only for that one run (**pay-per-use + context isolation** — the preset's core design).
@@ -156,6 +162,19 @@ Quick comparison:
156
162
  | Long-term cost | each turn pays only the new task | each turn carries the accumulated context, cost grows |
157
163
  | Fit | every delegation (default) | multi-round polishing delegations, per role (see [Configuration](configuration_en.md) 2.6) |
158
164
 
165
+ ### Start mode: fresh (spawn) vs inherit-the-conversation (fork)
166
+
167
+ The session mode decides "how the child ends"; the start mode (`roles.<toolName>.start_mode`, see [Configuration](configuration_en.md) 2.7) decides "how it begins". The two are orthogonal — a forked helper can stay run-and-discard, and the same role can also pair fork with continuable for cross-turn resumption. At the generation layer the role delegation block's block-level `provider` row becomes `spawn` (default, brand-new start) or `fork` (inherit the conversation); the preset's `subagent_fork` generic delegation tool rides the same fork provider, but the main-agent allow list deliberately hides it — fork reaches the model only through the per-role config key.
168
+
169
+ Fork semantics (stated plainly):
170
+
171
+ - **Initial content = the main agent's completed turns.** At delegation time the child receives the main agent's conversation as finished up to that moment (instructions and outputs included); the turn currently in progress does not count. If the main agent has completed no turns yet, fork is indistinguishable from a fresh spawn.
172
+ - **A one-shot snapshot; the two sides advance independently.** After the fork, new main-agent progress never flows into the child session, and the child's output is not written back into the main session; the only meeting point is the child's returned result. For a helper that should keep following the main agent, use continuable + `send_message` (previous section) — the two combine fine.
173
+ - **Conversation only, never tools or permissions.** A forked helper's tool surface is still narrowed to its role's `toolFilter.allow`, and DSH's permission semantics (a denied operation is never retried) apply unchanged.
174
+ - **The model follows the main agent.** A forked child inherits the main agent's current provider route and model (a hard KV-cache reuse constraint); a dedicated model configured for the role has no effect in that case (the installer warns and ignores it, see [Configuration](configuration_en.md) 2.5).
175
+
176
+ When fork is worth it: the main agent has already produced something substantial (research conclusions, an outline, a plan), and the next job needs to "know all that" but should not have it restated inside the delegation prompt — turning a finished outline into a deck, or implementing code changes against an agreed plan. Fork lets the helper start straight from the completed context. Conversely, when the task has nothing to do with the main agent's context, there is no need to fork — a fresh start keeps the child context cleaner.
177
+
159
178
  ## Advanced: hand-editing implement_cont (no panel)
160
179
 
161
180
  The main path for Path B is the panel or the config key — one toggle on the role card's「会话模式」("session mode") control (see [Configuration](configuration_en.md) 2.6). This section is the equivalent panel-free route, in 5 steps — read it when you want to see exactly what the generation layer writes, or when working without the panel. The default preset does **not** ship `implement_cont`; enable it when you need it and remove it entirely when you no longer do. All YAML below is verbatim configuration (structured like the preset's delegation blocks and checked against the source).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-paoding",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "庖丁 — DSH (DeepSeek Harness) 编排模式安装器:host 感知的角色工具过滤 + Web 设置页可视化配置器",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -7,9 +7,10 @@
7
7
  * ./api-core.mjs 复用 tools/install.mjs 的检测与生成管线;workspaces 直连
8
8
  * 宿主 workspaceRegistry 服务(list / create,幂等);version
9
9
  * 复用 tools/lib/version.mjs 的版本检测(npm registry 优先,npmmirror
10
- * 镜像、GitHub release 依次兜底);upgrade 经 tools/lib/upgrade.mjs
11
- * spawn `dsh plugin update` 完成
12
- * 一键升级。首装 / 升级后 preset 自动重生成(ensurePresetInstalled,等价
10
+ * 镜像、GitHub release 依次兜底);upgrade 经 tools/lib/upgrade.mjs 完成
11
+ * 一键升级(常规 profile spawn dsh 插件转发器;desktop profile 被 dsh CLI
12
+ * 的 Electron 专属守卫硬拒,改在 profile 目录直跑 pnpm add 精确版本)。
13
+ * 首装 / 升级后 preset 自动重生成(ensurePresetInstalled,等价
13
14
  * install --auto 语义)在路由注册完成后异步触发,不阻塞启动。
14
15
  * - Client 侧:lib/client.js 经宿主 sidebar.footer.action 键控槽在左侧栏
15
16
  * 底部(设置行上方)注册「庖丁配置」入口,点击后在会话中栏打开全页配置
@@ -254,7 +255,30 @@ async function stateWithRuntimeFacts(ctx, { force = false } = {}) {
254
255
  // apply in-flight 闸(模块级):apply 落盘期间再来的 apply 直接 409,防并发写配置。
255
256
  let applyInFlight = false
256
257
 
257
- export function apply(ctx) {
258
+ // —— 宿主热切换竞态兜底 ————————————————————————————————————————————
259
+ // dsh 0.1.7-rc.2 / 0.2.0-rc.x 插件列表「禁用→启用」会重建条目纤维:新 apply()
260
+ // 先执行,旧 incarnation 的路由注销走异步结算,注册瞬间旧路由常仍在表里,
261
+ // webServer.register 对同 (kind, path) 一律抛 duplicate(组合层契约)。
262
+ // 兜底:只对 duplicate prefix route 这一种错误短重试,等旧注销落地;其余
263
+ // 错误原样抛出,真组合冲突不掩盖。宿主修复后此段可整体移除。
264
+ const DUPLICATE_PREFIX_ROUTE_RE = /^webserver: duplicate prefix route "/
265
+
266
+ /** 带兜底重试的路由注册:仅对 duplicate prefix route 错误重试,其余立即抛出。 */
267
+ export async function registerPrefixRouteWithRetry (webServer, route, { attempts = 8, delayMs = 30 } = {}) {
268
+ let lastError
269
+ for (let attempt = 0; attempt < attempts; attempt++) {
270
+ try {
271
+ return webServer.register(route)
272
+ } catch (err) {
273
+ if (!DUPLICATE_PREFIX_ROUTE_RE.test(String(err?.message ?? ''))) throw err
274
+ lastError = err
275
+ if (attempt < attempts - 1) await new Promise(resolve => setTimeout(resolve, delayMs))
276
+ }
277
+ }
278
+ throw lastError
279
+ }
280
+
281
+ export async function apply(ctx) {
258
282
  const { webServer } = ctx
259
283
  // dispose 探针:插件卸载后,在途异步任务(preset 自愈)不得再写盘。
260
284
  let disposed = false
@@ -265,7 +289,7 @@ export function apply(ctx) {
265
289
  try { return ctx.get('webRuntime')?.trustedHosts ?? [] } catch { return [] }
266
290
  }
267
291
 
268
- const disposer = webServer.register({
292
+ const disposer = await registerPrefixRouteWithRetry(webServer, {
269
293
  kind: 'prefix',
270
294
  path: '/api/paoding',
271
295
  handler: async (req, res) => {