dsh-paoding 0.3.3 → 0.3.4
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 +6 -3
- package/README_en.md +6 -3
- package/docs/architecture.md +4 -1
- package/docs/architecture_en.md +8 -2
- package/docs/configuration.md +2 -2
- package/docs/configuration_en.md +2 -2
- package/docs/installation.md +36 -7
- package/docs/installation_en.md +36 -7
- package/package.json +4 -2
- package/plugins/paoding-config-ui/api-core.mjs +8 -3
- package/plugins/paoding-config-ui/lib/base.css +1 -1
- package/plugins/paoding-config-ui/lib/client.js +32 -0
- package/plugins/paoding-config-ui/package.json +1 -2
- package/plugins/paoding-config-ui/runtime-inventory.mjs +20 -0
- package/presets/orchestrator/agent.cordis.yml +3 -0
- package/tools/lib/preset-system.mjs +390 -0
- package/tools/lib/state.mjs +125 -14
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ dsh-paoding(中文品牌「庖丁」,典出《庄子》——庖丁顺纹理
|
|
|
19
19
|
- **图形界面配置**:不习惯改配置文件的话,点开左侧栏底部动作条(设置行上方)的「庖丁配置」入口,整页点选即可,「保存并应用」即时重生成预设——与首装自动化同一条生成管线。
|
|
20
20
|
- **版本更新有提醒,面板一键升级**:面板会对比 npm registry(GitHub release 兜底)上的最新版本,发现新版就地提示并附上发布页链接;点「升级」就地执行 `dsh plugin update dsh-paoding`(profile 自动探测)换上新版,重启 DSH 后生效,编排预设会在重启时按新版自动重生成。开发检出的 link 形态无法就地升级,面板会提示切回 registry 版的办法;检测失败(比如断网)就静默跳过,不影响任何使用。
|
|
21
21
|
- **来去自由**:不改 DSH 一行源码;装完新会话即用,卸载即删目录。
|
|
22
|
+
- **两代宿主都兼容**:DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 全支持——两代宿主发现预设的机制不同(目录扫描 vs patch 声明行),安装器自动检测宿主版本选对落点,宿主升级、降级后的首次保存/自愈自动迁移,全程无感。
|
|
22
23
|
|
|
23
24
|
## 插件截图
|
|
24
25
|
|
|
@@ -26,7 +27,7 @@ dsh-paoding(中文品牌「庖丁」,典出《庄子》——庖丁顺纹理
|
|
|
26
27
|
|
|
27
28
|
## 快速开始
|
|
28
29
|
|
|
29
|
-
前置:**Node.js ≥ 18**、**pnpm** 与一台已装好的 DSH host(**@deepseek-ai/dsh
|
|
30
|
+
前置:**Node.js ≥ 18**、**pnpm** 与一台已装好的 DSH host(**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.1** 全支持,预设落点自动适配两代宿主机制)。
|
|
30
31
|
|
|
31
32
|
**官方插件通道(唯一安装入口)**——不用克隆仓库,一条命令,装完重启即用:
|
|
32
33
|
|
|
@@ -36,7 +37,7 @@ dsh plugin --profile web add dsh-paoding
|
|
|
36
37
|
|
|
37
38
|
等价捷径:`npx dsh-paoding@latest`(内部转成上面这条命令,默认 profile web,需本机已装 dsh)。npx 拉到哪版就装哪版:实际安装的是 `dsh-paoding@<npx 拉取到的版本>`。
|
|
38
39
|
|
|
39
|
-
`dsh plugin` 是 pnpm 的透明转发器:包被 pnpm 装进 profile,包内声明的 bundle patch 把「庖丁配置」面板挂进 DSH Web 左侧栏。**重启 DSH(`dsh web`)**即完成全部安装——插件启动时会自动检测,编排预设缺失或版本不符就自动生成一次(有 `~/.dsh/dsh-paoding.config.yml`
|
|
40
|
+
`dsh plugin` 是 pnpm 的透明转发器:包被 pnpm 装进 profile,包内声明的 bundle patch 把「庖丁配置」面板挂进 DSH Web 左侧栏。**重启 DSH(`dsh web`)**即完成全部安装——插件启动时会自动检测,编排预设缺失或版本不符就自动生成一次(有 `~/.dsh/dsh-paoding.config.yml` 按配置应用,没有则写基础模板),无需再点任何按钮。DSH 0.1.7 起宿主改用 patch 声明行发现预设,插件会自动改写登记方式,装法不变。新会话的预设选择器里选「编排模式 (Orchestrator)」即可;要设为默认,在 Settings → Agent Presets 里选。
|
|
40
41
|
|
|
41
42
|
开箱默认只带 DSH 自带的基础工具,检测到的 MCP/host 工具不会自动写入——装完点开左侧栏的**「庖丁配置」**按需勾选工具与角色,「保存并应用」即生效。不想要了,三条命令卸干净(按工作区配置生成的各工作区预设也一并删除):
|
|
42
43
|
|
|
@@ -46,6 +47,8 @@ rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets/orchestrator"
|
|
|
46
47
|
rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets"/orchestrator-*
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
宿主是 DSH 0.1.7+ 时再补一步:删去 `~/.dsh/cordis.patch.yml` 里的 dsh-paoding 托管块(带 auto-generated 起止标记),免得留下指向已删目录的声明行(详见[安装文档](docs/installation.md)第 7 节)。
|
|
51
|
+
|
|
49
52
|
**开发检出自装**——想改插件或预设源码时,用 link 形态直连仓库(改动经 HMR/重启生效):
|
|
50
53
|
|
|
51
54
|
```bash
|
|
@@ -77,7 +80,7 @@ dsh plugin --profile web add link:"$PWD/dsh-paoding"
|
|
|
77
80
|
| 文档 | 内容 |
|
|
78
81
|
|---|---|
|
|
79
82
|
| [架构](docs/architecture.md) | 为什么这样设计——概述、成本账、角色与工具、对应 DSH 原生机制、Token 治理、已知边界。 |
|
|
80
|
-
| [安装](docs/installation.md) |
|
|
83
|
+
| [安装](docs/installation.md) | 官方插件通道一条命令安装、版本支持与预设落点双轨(DSH 0.1.5 – 0.1.7-rc.1)、首装自动化与版本标记、升级、开发检出自装、host patch 检测机制、庖丁配置面板、卸载、排查。 |
|
|
81
84
|
| [编排](docs/orchestration.md) | 编排总览、失败如何呈现与三层处理、one-shot vs continuable、上下文隔离。 |
|
|
82
85
|
| [配置](docs/configuration.md) | 配置键全参考、内置角色微调、主 agent 工具与技能、自定义角色、按角色分模型、角色会话模式。 |
|
|
83
86
|
|
package/README_en.md
CHANGED
|
@@ -19,6 +19,7 @@ dsh-paoding (Chinese brand 「庖丁」/ Pao Ding, from the Zhuangzi parable of
|
|
|
19
19
|
- **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
20
|
- **Notified of new releases, one-click upgrade in the panel**: the panel compares your local version against the latest npm registry publish (GitHub release as fallback) 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), it stays silent and nothing is interrupted.
|
|
21
21
|
- **Installs and uninstalls cleanly**: no DSH source changes; active in a new session right after install, gone when you delete one directory.
|
|
22
|
+
- **Works on both host generations**: DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 are all supported — the two generations discover presets differently (directory scan vs patch declaration rows), and the installer picks the right landing automatically, then migrates on the first save/self-heal after a host upgrade or downgrade, all without user action.
|
|
22
23
|
|
|
23
24
|
## Screenshots
|
|
24
25
|
|
|
@@ -26,7 +27,7 @@ dsh-paoding (Chinese brand 「庖丁」/ Pao Ding, from the Zhuangzi parable of
|
|
|
26
27
|
|
|
27
28
|
## Quick Start
|
|
28
29
|
|
|
29
|
-
Prerequisites: **Node.js ≥ 18**, **pnpm**, and a working DSH host (**@deepseek-ai/dsh
|
|
30
|
+
Prerequisites: **Node.js ≥ 18**, **pnpm**, and a working DSH host (**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.1**, all supported — the preset landing adapts to the host generation automatically).
|
|
30
31
|
|
|
31
32
|
**The official plugin channel (the only install route)** — no clone needed; one command, then a restart:
|
|
32
33
|
|
|
@@ -36,7 +37,7 @@ dsh plugin --profile web add dsh-paoding
|
|
|
36
37
|
|
|
37
38
|
Equivalent shortcut: `npx dsh-paoding@latest` (delegates to the command above; default profile web; requires dsh on PATH). Whichever version npx pulls is what gets installed: the actual install is `dsh-paoding@<the version npx resolved>`.
|
|
38
39
|
|
|
39
|
-
`dsh plugin` is a transparent pass-through to pnpm: the package is installed into the profile by pnpm, and the bundle patch declared inside it mounts the 庖丁配置 (Paoding Config) page into the DSH Web left sidebar. **Restart DSH (`dsh web`)** and the install is complete — on startup the plugin checks itself, and if the orchestrator preset is missing or carries an outdated version marker it regenerates it once (applying `~/.dsh/dsh-paoding.config.yml` when present, writing the base template otherwise). Nothing else to click. Pick "编排模式 (Orchestrator)" in the new-session preset selector; to make it the default, choose it in Settings → Agent Presets.
|
|
40
|
+
`dsh plugin` is a transparent pass-through to pnpm: the package is installed into the profile by pnpm, and the bundle patch declared inside it mounts the 庖丁配置 (Paoding Config) page into the DSH Web left sidebar. **Restart DSH (`dsh web`)** and the install is complete — on startup the plugin checks itself, and if the orchestrator preset is missing or carries an outdated version marker it regenerates it once (applying `~/.dsh/dsh-paoding.config.yml` when present, writing the base template otherwise). Nothing else to click. From DSH 0.1.7 the host discovers presets through patch declaration rows instead, and the plugin switches the registration over by itself — the install steps stay the same. Pick "编排模式 (Orchestrator)" in the new-session preset selector; to make it the default, choose it in Settings → Agent Presets.
|
|
40
41
|
|
|
41
42
|
Out of the box only DSH's built-in base tools are enabled — detected MCP/host tools are not written in automatically. Open **庖丁配置 (Paoding Config)** in the sidebar, opt into the tools and roles you need, and hit Save & Apply. When you no longer want it, three commands uninstall it for good — the per-workspace presets created by workspace-specific configs are removed too:
|
|
42
43
|
|
|
@@ -46,6 +47,8 @@ rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets/orchestrator"
|
|
|
46
47
|
rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets"/orchestrator-*
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
On a DSH 0.1.7+ host there is one extra step: delete the dsh-paoding managed block from `~/.dsh/cordis.patch.yml` (the auto-generated marker comments), so no declaration rows are left pointing at the removed directories (see §7 of the [installation guide](docs/installation_en.md)).
|
|
51
|
+
|
|
49
52
|
**Dev checkout install** — to hack on the plugin or the preset sources, link the repository directly (changes go live via HMR or a restart):
|
|
50
53
|
|
|
51
54
|
```bash
|
|
@@ -77,7 +80,7 @@ All preferences live in `~/.dsh/dsh-paoding.config.yml`: role add/remove/tweak,
|
|
|
77
80
|
| Document | Contents |
|
|
78
81
|
|---|---|
|
|
79
82
|
| [Architecture](docs/architecture_en.md) | Why it is designed this way — overview, the cost ledger, roles and tools, mapping to DSH's native mechanisms, token governance, known limits. |
|
|
80
|
-
| [Installation](docs/installation_en.md) | The single official channel (`dsh plugin add`), first-install automation and the version marker, upgrading, dev-checkout install, host patch detection, the Paoding Config panel, uninstalling, troubleshooting. |
|
|
83
|
+
| [Installation](docs/installation_en.md) | The single official channel (`dsh plugin add`), version support and the dual preset landing (DSH 0.1.5 – 0.1.7-rc.1), first-install automation and the version marker, upgrading, dev-checkout install, host patch detection, the Paoding Config panel, uninstalling, troubleshooting. |
|
|
81
84
|
| [Orchestration](docs/orchestration_en.md) | Orchestration overview, how failures surface and the three recovery layers, one-shot vs continuable, context isolation. |
|
|
82
85
|
| [Configuration](docs/configuration_en.md) | The full key reference, tuning built-in roles, main-agent tools and skills, custom roles, per-role models, per-role session modes. |
|
|
83
86
|
|
package/docs/architecture.md
CHANGED
|
@@ -246,6 +246,8 @@ SOP(按 stop reason 分流:error 重委派一次 / max-tokens 拆小 / refus
|
|
|
246
246
|
|
|
247
247
|
**⑦ 热插拔 = preset 是静态目录 + 插件通道挂载。** 插件本体经 `dsh plugin add`(pnpm + 包内 bundle patch)装入 profile,侧栏面板即挂即卸;编排预设本身是一个静态目录——生成器把静态源复制/改写为
|
|
248
248
|
`$DSH_HOME/.agent-presets/orchestrator`,编辑 YAML / mjs、面板「保存并应用」重新生成、重启 host 或新建会话即生效;
|
|
249
|
+
产物路径两代宿主一致,登记机制按宿主版本双轨——DSH ≤ 0.1.6 目录扫描即注册,DSH ≥ 0.1.7 由 `$DSH_HOME/cordis.patch.yml`
|
|
250
|
+
托管块里的 `@deepseek-ai/dsh-agent-preset` 声明行注册,安装器自动适配与迁移(见[安装](installation.md)第 2 节);
|
|
249
251
|
卸载 = `dsh plugin remove` 加删目录(见[安装](installation.md))。DSH 源码零改动,preset 可整体移除、不残留。
|
|
250
252
|
|
|
251
253
|
## Web 配置页 client bundle:源头多文件,产物单文件
|
|
@@ -266,7 +268,8 @@ grep inline cap 与对照同设计)后,根因锁定为:**默认压缩从
|
|
|
266
268
|
**已落地配置。** `agent.cordis.yml` 的 compaction-basic 已设 `thresholdRatio: 0.45`(≈117,965 tokens
|
|
267
269
|
触发),配合 retainRatio 默认保留约 16% 尾部(≈42K)+ 摘要 checkpoint,稳态平均回落到 ~80K 量级。
|
|
268
270
|
压缩每次触发调用一次 LLM 摘要——复用前缀缓存,成本极低——因此触发频繁一点也几乎不额外花钱;
|
|
269
|
-
0.45 是「省 token 与不频繁摘要」之间的平衡点。
|
|
271
|
+
0.45 是「省 token 与不频繁摘要」之间的平衡点。compaction-basic 另显式钉了 `maxTokens: 8192`:
|
|
272
|
+
DSH 0.1.7 起宿主默认升到 65536,显式值让 0.1.5 – 0.1.7-rc.1 全系的摘要预算行为保持一致。
|
|
270
273
|
|
|
271
274
|
**参数与效果。**
|
|
272
275
|
|
package/docs/architecture_en.md
CHANGED
|
@@ -318,7 +318,11 @@ integration summary) — see [docs/orchestration_en.md](orchestration_en.md).
|
|
|
318
318
|
into a profile via `dsh plugin add` (pnpm plus the in-package bundle patch), so the sidebar panel mounts and
|
|
319
319
|
unmounts with it; the orchestrator preset is a static directory — the generator copies/rewrites the static
|
|
320
320
|
sources into `$DSH_HOME/.agent-presets/orchestrator`; editing the YAML / mjs, hitting Save & Apply in the
|
|
321
|
-
panel to regenerate, and restarting the host or opening a new session takes effect.
|
|
321
|
+
panel to regenerate, and restarting the host or opening a new session takes effect. The artifact path is the
|
|
322
|
+
same on both host generations, while registration is dual-track by host version — DSH ≤ 0.1.6 registers by
|
|
323
|
+
directory scan, DSH ≥ 0.1.7 registers through `@deepseek-ai/dsh-agent-preset` declaration rows in the managed
|
|
324
|
+
block of `$DSH_HOME/cordis.patch.yml`, with the installer adapting and migrating automatically (see §2 of the
|
|
325
|
+
[Installation guide](installation_en.md)). Uninstall = `dsh plugin
|
|
322
326
|
remove` plus deleting the directory (see [Installation](installation_en.md)). No DSH source changes; the
|
|
323
327
|
preset leaves nothing behind when removed.
|
|
324
328
|
|
|
@@ -346,7 +350,9 @@ history periodically compressed back down.
|
|
|
346
350
|
(≈42K) plus a summary checkpoint; the steady-state average falls to the ~80K range. Each compaction run
|
|
347
351
|
performs one LLM summarization call that reuses the prefix cache, so its cost is negligible — triggering
|
|
348
352
|
somewhat more often costs almost nothing extra; 0.45 is the balance point between saving tokens and not
|
|
349
|
-
summarizing too frequently.
|
|
353
|
+
summarizing too frequently. compaction-basic also pins `maxTokens: 8192` explicitly: from DSH 0.1.7 the host
|
|
354
|
+
default rises to 65536, and the explicit value keeps the summary-budget behavior identical across
|
|
355
|
+
0.1.5 – 0.1.7-rc.1.
|
|
350
356
|
|
|
351
357
|
**Parameters and effect.**
|
|
352
358
|
|
package/docs/configuration.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# 配置
|
|
6
6
|
|
|
7
|
-
dsh-paoding 把「按角色分工的编排 preset」做成**配置驱动**:你手编(或在「庖丁配置」面板里点选)一个 YAML 配置文件,生成器(`tools/`,面板「保存并应用」与仓库内兜底 CLI 共用同一生成管线 `collectState` / `generateAndInstall`)在应用时据此重写目标 preset —— `~/.dsh/.agent-presets/orchestrator/agent.cordis.yml`,覆盖角色 allow、角色 persona、主 agent 白名单(`config.allow` 注入)、主 agent persona
|
|
7
|
+
dsh-paoding 把「按角色分工的编排 preset」做成**配置驱动**:你手编(或在「庖丁配置」面板里点选)一个 YAML 配置文件,生成器(`tools/`,面板「保存并应用」与仓库内兜底 CLI 共用同一生成管线 `collectState` / `generateAndInstall`)在应用时据此重写目标 preset —— `~/.dsh/.agent-presets/orchestrator/agent.cordis.yml`,覆盖角色 allow、角色 persona、主 agent 白名单(`config.allow` 注入)、主 agent persona 的技能行/内联与人设追加。产物路径在两代宿主上一致;两代宿主发现 preset 的机制不同(≤ 0.1.6 目录扫描 / ≥ 0.1.7 patch 声明行托管块),安装器自动适配,见[安装](installation.md)的「版本支持与预设落点双轨」。
|
|
8
8
|
|
|
9
9
|
配置文件描述的是**声明式意图**。目标文件每次都由「源码 preset + 本配置文件」重新生成:改配置 → 在面板点「保存并应用」= 幂等同步,不会累积手工补丁。
|
|
10
10
|
|
|
@@ -632,7 +632,7 @@ workspaces:
|
|
|
632
632
|
### 9.4 生效方式与边界
|
|
633
633
|
|
|
634
634
|
- **预设选择发生在新建会话时**:新建会话界面选好工作区后,在预设选择器里选对应的 `orchestrator-<slug>`。全局默认预设不受任何影响。预设生效需要新会话(或 DSH 重启后可见)。
|
|
635
|
-
- 预设目录落在 `$DSH_HOME/.agent-presets/` 下,全机共享:同一台机器上不同项目各起的 DSH 实例,因 slug 不同互不覆盖。
|
|
635
|
+
- 预设目录落在 `$DSH_HOME/.agent-presets/` 下,全机共享:同一台机器上不同项目各起的 DSH 实例,因 slug 不同互不覆盖。DSH ≥ 0.1.7 上这些预设另经 `$DSH_HOME/cordis.patch.yml` 托管块里的声明行登记(`config.id` 即 `orchestrator` / `orchestrator-<slug>`),同一套 slug 命名两代宿主通用,见[安装](installation.md)的「版本支持与预设落点双轨」。
|
|
636
636
|
- 兜底 CLI 本次仍只作用于全局条目;`workspaces` 段在 CLI 应用后原样保留,不会被冲掉。
|
|
637
637
|
- 运行中的会话不会换预设——子 agent 的角色与工具面在会话创建时随预设组装定型,会话内不可变;要换套班底,开新会话选新预设。
|
|
638
638
|
|
package/docs/configuration_en.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Configuration
|
|
6
6
|
|
|
7
|
-
dsh-paoding makes the role-based orchestration preset **configuration-driven**: you hand-write (or click together in the 庖丁配置 panel) a YAML config file, and the generator (`tools/`; the panel's Save & Apply and the in-repo fallback CLI share the same generation pipeline, `collectState` / `generateAndInstall`) rewrites the target preset — `~/.dsh/.agent-presets/orchestrator/agent.cordis.yml` — when it is applied, covering the role allows, role personas, the main-agent allow list (`config.allow` injection) and the main-agent persona's skill rows / inlines and persona extra.
|
|
7
|
+
dsh-paoding makes the role-based orchestration preset **configuration-driven**: you hand-write (or click together in the 庖丁配置 panel) a YAML config file, and the generator (`tools/`; the panel's Save & Apply and the in-repo fallback CLI share the same generation pipeline, `collectState` / `generateAndInstall`) rewrites the target preset — `~/.dsh/.agent-presets/orchestrator/agent.cordis.yml` — when it is applied, covering the role allows, role personas, the main-agent allow list (`config.allow` injection) and the main-agent persona's skill rows / inlines and persona extra. The artifact path is the same on both host generations; the generations discover the preset differently (≤ 0.1.6 directory scan / ≥ 0.1.7 declaration managed block in the patch), and the installer adapts automatically — see "Version support and the dual preset landing" in the [installation guide](installation_en.md).
|
|
8
8
|
|
|
9
9
|
The config file describes **declarative intent**. The target file is regenerated from scratch every time — "source preset + this config file": edit the config → hit Save & Apply in the panel = idempotent sync, with no accumulating hand patches.
|
|
10
10
|
|
|
@@ -642,7 +642,7 @@ With a workspace selected, preview and apply target it alone; the success note r
|
|
|
642
642
|
### 9.4 How it takes effect, and boundaries
|
|
643
643
|
|
|
644
644
|
- **Preset choice happens at session creation**: pick the workspace on the new-session screen, then pick the matching `orchestrator-<slug>` in the preset selector. The global default preset is untouched. A new session (or a DSH restart) is needed for a freshly generated preset to appear.
|
|
645
|
-
- Preset directories live under `$DSH_HOME/.agent-presets/` and are shared machine-wide: separate DSH instances launched from different projects never clobber each other, because their slugs differ.
|
|
645
|
+
- Preset directories live under `$DSH_HOME/.agent-presets/` and are shared machine-wide: separate DSH instances launched from different projects never clobber each other, because their slugs differ. On DSH ≥ 0.1.7 these presets are additionally registered through declaration rows in the managed block of `$DSH_HOME/cordis.patch.yml` (`config.id` being `orchestrator` / `orchestrator-<slug>`); the same slug naming works on both generations — see "Version support and the dual preset landing" in the [installation guide](installation_en.md).
|
|
646
646
|
- The fallback CLI still targets the global entry only; the `workspaces` section survives a CLI apply unchanged.
|
|
647
647
|
- A running session never switches presets — a session's roles and tool surfaces are assembled at creation and fixed for its lifetime; to change the lineup, start a new session with the new preset.
|
|
648
648
|
|
package/docs/installation.md
CHANGED
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
- **Node.js ≥ 18**。DSH host 与插件链路都跑在 Node 上;版本过低时 `dsh` 本身就起不来。可用 `node --version` 自查。
|
|
14
14
|
- **pnpm 可用**。`dsh plugin` 是 pnpm 的透明转发器:插件包装进 profile 时由 pnpm 完成。缺失时安装命令会报 pnpm 相关错误,装好 pnpm(`npm i -g pnpm` 或 `corepack enable`)后重试。
|
|
15
|
-
- **已安装 DSH host**(`dsh` 可用,含其 Web/agent 运行时;**@deepseek-ai/dsh
|
|
15
|
+
- **已安装 DSH host**(`dsh` 可用,含其 Web/agent 运行时;**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.1** 全支持,安装器自动适配两代预设机制,详见第 2 节「版本支持与预设落点双轨」)。本插件不改 DSH 源码,只往 DSH 的 preset roster 写入编排预设、并把「庖丁配置」面板挂进 Web 左侧栏,因此先要有可用的 DSH 家目录。
|
|
16
16
|
- **路径事实**(生成管线内建常量,写死前先了解它们):
|
|
17
17
|
|
|
18
18
|
| 项 | 默认值 | 说明 |
|
|
19
19
|
|---|---|---|
|
|
20
20
|
| `$DSH_HOME` | `~/.dsh` | DSH 家目录(认环境变量 `DSH_HOME`,缺省 `$HOME/.dsh`)。host 层配置 `cordis.patch.yml`、`node_modules`、`profiles/<name>/cordis.patch.yml`、插件 profile 目录都在其下 |
|
|
21
|
-
| preset roster 根目录 | `$DSH_HOME/.agent-presets/` | `dsh-agent-presets`
|
|
22
|
-
| 本预设安装目标 | `$DSH_HOME/.agent-presets/orchestrator` | 生成产物:`agent.cordis.yml`(重写后的角色配置)、`preset.yml`、`restrict.mjs`、`.generator-version`
|
|
21
|
+
| preset roster 根目录 | `$DSH_HOME/.agent-presets/` | DSH ≤ 0.1.6 由 `dsh-agent-presets` 扫描此目录发现本地 authored presets;DSH ≥ 0.1.7 宿主不再扫目录,此目录退为预设文件存放处(见第 2 节「版本支持与预设落点双轨」) |
|
|
22
|
+
| 本预设安装目标 | `$DSH_HOME/.agent-presets/orchestrator` | 生成产物:`agent.cordis.yml`(重写后的角色配置)、`preset.yml`、`restrict.mjs`、`.generator-version` 版本标记,是一个静态目录;按工作区配置生成的预设落同一父目录下的 `orchestrator-<slug>` |
|
|
23
|
+
| 声明行托管块(仅 DSH ≥ 0.1.7) | `$DSH_HOME/cordis.patch.yml` 内的托管块 | 以 `# --- dsh-paoding presets (auto-generated; do not edit) ---` 起始、配套 end 标记收尾,块内 `- insert:` 写 `@deepseek-ai/dsh-agent-preset` 声明行;安装器自动维护,详见第 2 节 |
|
|
23
24
|
| 配置文件 | `$DSH_HOME/dsh-paoding.config.yml` | 「庖丁配置」面板「保存并应用」写入的角色与工具分配(`0o600`,可手编);首装自动化同样读写它 |
|
|
24
25
|
|
|
25
26
|
- **环境变量**:`DSH_HOME` 覆盖 DSH 家目录(默认 `~/.dsh`);`DSH_PAODING_CONFIG` 覆盖配置文件路径(面板认它;仓库内兜底 CLI 则用 `--config <file>` 指定)。
|
|
@@ -47,13 +48,39 @@ dsh plugin --profile web add dsh-paoding
|
|
|
47
48
|
|
|
48
49
|
### profile 语义
|
|
49
50
|
|
|
50
|
-
`--profile web` 决定插件装进哪个 profile:「庖丁配置」面板只在以该 profile 启动的 DSH Web 里出现。`web` 是 `dsh web` 的默认 profile,多数机器无需改动;多 profile 用户对每个需要的 profile 各执行一次 `dsh plugin add`。编排预设本体落在 `$DSH_HOME/.agent-presets/`,全机共享、不随 profile
|
|
51
|
+
`--profile web` 决定插件装进哪个 profile:「庖丁配置」面板只在以该 profile 启动的 DSH Web 里出现。`web` 是 `dsh web` 的默认 profile,多数机器无需改动;多 profile 用户对每个需要的 profile 各执行一次 `dsh plugin add`。编排预设本体落在 `$DSH_HOME/.agent-presets/`,全机共享、不随 profile 走——DSH ≥ 0.1.7 的声明行写在 home 层 `$DSH_HOME/cordis.patch.yml`,同样与 profile 无关——在任何 profile 的面板里「保存并应用」,写的都是同一份预设。
|
|
52
|
+
|
|
53
|
+
### 版本支持与预设落点双轨(DSH 0.1.5 – 0.1.7-rc.1)
|
|
54
|
+
|
|
55
|
+
本插件兼容两代宿主:**DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 全部支持**,安装与使用流程完全一致。插件 0.3.4 起,package.json 显式声明 `peerDependencies: @deepseek-ai/dsh >= 0.1.5`——0.1.7 起宿主装插件前做兼容性预检,无声明等于默认放行,显式声明后按真实兼容面把关;0.1.5 / 0.1.6 不读这个字段,声明纯属元数据,不影响安装。
|
|
56
|
+
|
|
57
|
+
两代宿主发现「本地 authored preset」的机制不同,安装器据此分双轨落点,宿主检测全自动、用户无感:
|
|
58
|
+
|
|
59
|
+
- **目录轨(DSH ≤ 0.1.6)**:宿主扫描 `$DSH_HOME/.agent-presets/` 下的目录来发现 preset,目录即注册。安装器照旧把产物写成 `$DSH_HOME/.agent-presets/orchestrator/`(`agent.cordis.yml` + `preset.yml` + `restrict.mjs`);按工作区配置生成的预设落同目录下的 `orchestrator-<slug>`。
|
|
60
|
+
- **声明行轨(DSH ≥ 0.1.7)**:宿主不再扫目录,改为读取 patch 里的 `@deepseek-ai/dsh-agent-preset` 声明行。安装器在 `$DSH_HOME/cordis.patch.yml` 里维护一个托管块——以 `# --- dsh-paoding presets (auto-generated; do not edit) ---` 起始、以配套的 end 标记收尾——块内用 `- insert:` 写声明行:全局预设 `config.id: orchestrator`,工作区预设 `config.id: orchestrator-<slug>`,各自的 `plugins` 内联全部插件行;`restrict.mjs` 以绝对 `file:` URL 引用。产物三件套仍落在 `.agent-presets/<id>/`,只是从「目录即注册」变成「文件存放处」。托管块之外的用户内容一字节不动;块内带 do not edit 标记,手改块内内容会在下次保存时被覆盖。
|
|
61
|
+
|
|
62
|
+
安装器按下面的顺序判定走哪条轨:
|
|
63
|
+
|
|
64
|
+
1. Web 面板运行时探测优先——`agentPresets` 服务的 `register` 方法 0.1.7 才有,探测到即在声明行轨;
|
|
65
|
+
2. CLI 侧 `dsh --version`;
|
|
66
|
+
3. 兜底探测 `$DSH_HOME/node_modules/@deepseek-ai/dsh-agent-preset`(这个单数包在,即为 0.1.7+);
|
|
67
|
+
4. 都失败时默认按 ≤ 0.1.6 处理(安全侧)。
|
|
68
|
+
|
|
69
|
+
宿主版本变化后的首次保存/自愈会自动迁移,双向都不用手动干预:
|
|
70
|
+
|
|
71
|
+
- **降级自愈(0.1.7 → 0.1.6)**:0.1.6 不认识声明行,声明行留在 patch 里会让 profile 启动失败——安装器自动撤下 home patch 里的托管块;目录轨产物本就齐全,预设无缝回到目录扫描。
|
|
72
|
+
- **升级迁移(0.1.6 → 0.1.7)**:安装器自动补写托管块;`.agent-presets/` 里按旧轨生成的 preset 会话(`agentPreset: orchestrator`)在新轨下按 `config.id` 继续对上,老会话恢复不断档。
|
|
73
|
+
|
|
74
|
+
另有两处跨版本行为对齐,同样自动完成、无需用户动作:
|
|
75
|
+
|
|
76
|
+
- **摘要预算**:0.1.7 起宿主把 compaction 的 `maxTokens` 默认升到 65536;本 preset 对 `dsh-compaction-basic` 显式钉 `maxTokens: 8192`,0.1.5 / 0.1.6 的摘要预算行为原样保持,两代宿主上一致。
|
|
77
|
+
- **面板图标**:0.1.7 改了 ui-primitives 的图标命名,面板图标已做跨版本兜底,同一 bundle 在两代宿主上都能正常渲染;面板功能在两代宿主上一致。
|
|
51
78
|
|
|
52
79
|
### 首装自动化:重启即完整安装
|
|
53
80
|
|
|
54
81
|
插件启动时自动检测编排预设是否就位:`$DSH_HOME/.agent-presets/orchestrator` 目录缺失,或其中的 `.generator-version` 标记与插件包版本不符时,自动执行一次等价 `--auto` 的安装——`~/.dsh/dsh-paoding.config.yml` 已存在就按配置应用;没有就写基础模板(只含 DSH 自带的基础工具,见下)。它走的是与面板「保存并应用」同一条检测/生成管线(`collectState` / `generateAndInstall`)——同一管线,但启动自愈的检测是纯文件扫描、不带运行时检测事实,面板则带;host 上装有 bundle 形态插件时,两者的检测库存可能不同,产物随之略有差异。
|
|
55
82
|
|
|
56
|
-
所以 **`dsh plugin add` + 重启 = 完整安装**:预设生成、配置模板落盘、面板可点,一步到位。自动生成失败不会拖垮插件启动——只在 DSH
|
|
83
|
+
所以 **`dsh plugin add` + 重启 = 完整安装**:预设生成、配置模板落盘、面板可点,一步到位。自动生成失败不会拖垮插件启动——只在 DSH 日志里记一条告警,可到面板手动「保存并应用」补上。启动自愈同时负责双轨落点的维护与跨版本迁移——宿主在 0.1.6 与 0.1.7 之间升级、降级后,首次自愈会自动补写或撤下 `$DSH_HOME/cordis.patch.yml` 里的托管块(见上节「版本支持与预设落点双轨」)。
|
|
57
84
|
|
|
58
85
|
### 版本标记与自动重生成
|
|
59
86
|
|
|
@@ -164,7 +191,7 @@ patch 与配置文件用 **`yaml` 包**解析——通过 `createRequire` 从 `$
|
|
|
164
191
|
- **Agent 工具分配卡片**:主 agent 一张固定卡(基础工具取消勾选 = 加入 `main_agent_remove` 从主 agent 剔除;MCP / 插件工具勾选 = 加入 `main_agent_extra`;技能「read 按需」与「内联全文」两组勾选;另有「人设追加」编辑区,可改、清空、恢复默认)+ `search_external` / `design` / `implement` 三张内置角色卡 + 任意自定义角色卡(带删除按钮);每张角色卡把工具按「基础工具 / MCP 工具 / 插件工具」三组呈现、可逐项勾选,并可编辑 persona。底部「新建自定义 agent 角色」。
|
|
165
192
|
- **技能分配**:每个 skill 一行,勾选分配给哪些 agent(写入对应 persona 的 `Available skills` 软引导;角色技能软引导,主 agent 技能另有 read 按需/内联两组)。
|
|
166
193
|
- **预览生成**:不写盘生成 `agent.cordis.yml`,显示每个角色 allow 的 intent → kept 计数与生成全文。
|
|
167
|
-
- **保存并应用**:确认后写入 preset(覆盖 `$DSH_HOME/.agent-presets/orchestrator
|
|
194
|
+
- **保存并应用**:确认后写入 preset(覆盖 `$DSH_HOME/.agent-presets/orchestrator`)并按宿主版本同步登记——≤ 0.1.6 目录扫描即生效,≥ 0.1.7 同步维护 `$DSH_HOME/cordis.patch.yml` 里的声明行托管块(见第 2 节)——再把当前分配保存到 `$DSH_HOME/dsh-paoding.config.yml`;应用成功后提示重启 DSH 或新建会话生效(检测到 patch 比生成结果新时提示重启后生效)。打开面板时会加载既有配置文件作为当前值——手编过的选择直接可见、可改。
|
|
168
195
|
|
|
169
196
|
## 7. 卸载
|
|
170
197
|
|
|
@@ -178,6 +205,8 @@ rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets"/orchestrator-*
|
|
|
178
205
|
|
|
179
206
|
第一条摘掉插件与侧栏面板(只影响指定 profile),第二条删掉编排预设目录,第三条清掉按工作区配置时生成的各工作区预设目录(`orchestrator-<目录名>`;没用过按工作区配置就没有这类目录,通配不命中、无害)——基础 `orchestrator` 目录已由上一条命令删掉,不含在这个通配里。配置文件 `~/.dsh/dsh-paoding.config.yml` 不随之上删——留着或删掉都不影响 DSH 其它配置,重装时首装自动化会再次按它应用。
|
|
180
207
|
|
|
208
|
+
宿主为 DSH ≥ 0.1.7 时再多看一眼:`$DSH_HOME/cordis.patch.yml` 里可能还留着 dsh-paoding 托管块(起止标记注释包着 `- insert:` 声明行)。预设目录删掉后,块内声明行就成了指向已删目录的悬空引用,请把整个托管块(从 `# --- dsh-paoding presets (auto-generated; do not edit) ---` 起始行到 end 标记行)一并删去——块外内容不受影响。
|
|
209
|
+
|
|
181
210
|
卸载不影响 host 的其它内容(MCP、插件、会话等),与 DSH 源码零耦合;之后新建会话时「编排模式 (Orchestrator)」不再出现在预设选择器里。
|
|
182
211
|
|
|
183
212
|
## 8. 常见问题与排查
|
|
@@ -188,7 +217,7 @@ rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets"/orchestrator-*
|
|
|
188
217
|
|
|
189
218
|
**装了插件,预设列表里却没有「编排模式 (Orchestrator)」**
|
|
190
219
|
|
|
191
|
-
预设由插件启动时自动生成:先确认装完后**重启过 DSH**;预设在新会话创建时装载,重启后还要新建一个会话才会出现在选择器里。仍没有就查 DSH 日志里 `paoding-config-ui` 的告警(自动生成失败会记日志,可到面板手动「保存并应用」补上),再检查 `ls "${DSH_HOME:-$HOME/.dsh}/.agent-presets/orchestrator"`——目录为空多半是插件运行时的 `$DSH_HOME` 与你以为的家目录不一致(启动 DSH 前显式 `export DSH_HOME
|
|
220
|
+
预设由插件启动时自动生成:先确认装完后**重启过 DSH**;预设在新会话创建时装载,重启后还要新建一个会话才会出现在选择器里。仍没有就查 DSH 日志里 `paoding-config-ui` 的告警(自动生成失败会记日志,可到面板手动「保存并应用」补上),再检查 `ls "${DSH_HOME:-$HOME/.dsh}/.agent-presets/orchestrator"`——目录为空多半是插件运行时的 `$DSH_HOME` 与你以为的家目录不一致(启动 DSH 前显式 `export DSH_HOME=…`)。目录在、宿主又是 0.1.7+ 而选择器里仍没有时,再查 `$DSH_HOME/cordis.patch.yml` 里有无 dsh-paoding 托管块——0.1.7 靠块内声明行发现预设,缺块就到面板点一次「保存并应用」补写(见第 2 节「版本支持与预设落点双轨」)。
|
|
192
221
|
|
|
193
222
|
**`tools.restrict()` 报 unknown tools / 子 agent 创建被拒**
|
|
194
223
|
|
package/docs/installation_en.md
CHANGED
|
@@ -12,14 +12,15 @@ Check the following before installing:
|
|
|
12
12
|
|
|
13
13
|
- **Node.js ≥ 18**. The DSH host and the plugin chain both run on Node; with an older version `dsh` won't start in the first place. Check with `node --version`.
|
|
14
14
|
- **pnpm available**. `dsh plugin` is a transparent pass-through to pnpm: installing a plugin package into a profile is done by pnpm. If pnpm is missing the install command fails with a pnpm error — install pnpm (`npm i -g pnpm` or `corepack enable`) and retry.
|
|
15
|
-
- **A working DSH host** (the `dsh` command, including its Web/agent runtime; **@deepseek-ai/dsh
|
|
15
|
+
- **A working DSH host** (the `dsh` command, including its Web/agent runtime; **@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.1**, all supported — the installer adapts to the two preset mechanisms automatically; see §2 "Version support and the dual preset landing"). This plugin does not modify DSH source code; it only writes the orchestrator preset into DSH's preset roster and mounts the Paoding Config page into the Web sidebar, so a usable DSH home directory must exist first.
|
|
16
16
|
- **Path facts** (constants built into the generation pipeline; know them before customizing):
|
|
17
17
|
|
|
18
18
|
| Item | Default | Description |
|
|
19
19
|
|---|---|---|
|
|
20
20
|
| `$DSH_HOME` | `~/.dsh` | The DSH home directory (the `DSH_HOME` environment variable is honored, defaulting to `$HOME/.dsh`). The home-layer `cordis.patch.yml`, `node_modules`, `profiles/<name>/cordis.patch.yml`, and plugin profile directories live under it |
|
|
21
|
-
| preset roster root | `$DSH_HOME/.agent-presets/` |
|
|
22
|
-
| Install target of this preset | `$DSH_HOME/.agent-presets/orchestrator` | The generated artifact: `agent.cordis.yml` (rewritten role config), `preset.yml`, `restrict.mjs`, and the `.generator-version` marker — a static directory |
|
|
21
|
+
| preset roster root | `$DSH_HOME/.agent-presets/` | Under DSH ≤ 0.1.6, `dsh-agent-presets` scans this directory for locally authored presets; under DSH ≥ 0.1.7 the host no longer scans it and the directory is just where the preset files live (see §2 "Version support and the dual preset landing") |
|
|
22
|
+
| Install target of this preset | `$DSH_HOME/.agent-presets/orchestrator` | The generated artifact: `agent.cordis.yml` (rewritten role config), `preset.yml`, `restrict.mjs`, and the `.generator-version` marker — a static directory; per-workspace presets land in sibling `orchestrator-<slug>` directories |
|
|
23
|
+
| Declaration managed block (DSH ≥ 0.1.7 only) | a managed block inside `$DSH_HOME/cordis.patch.yml` | Opens with `# --- dsh-paoding presets (auto-generated; do not edit) ---` and closes with the matching end marker; the `- insert:` rows inside carry `@deepseek-ai/dsh-agent-preset` declarations, maintained by the installer — see §2 |
|
|
23
24
|
| Config file | `$DSH_HOME/dsh-paoding.config.yml` | Role/tool assignments written by the Paoding Config panel's Save & Apply (`0o600`, hand-editable); first-install automation reads and writes it too |
|
|
24
25
|
|
|
25
26
|
- **Environment variables**: `DSH_HOME` overrides the DSH home directory (default `~/.dsh`); `DSH_PAODING_CONFIG` overrides the config-file path (honored by the panel; the in-repo fallback CLI uses `--config <file>` instead).
|
|
@@ -47,13 +48,39 @@ The orchestrator preset needs no separate install — the plugin fills it in aut
|
|
|
47
48
|
|
|
48
49
|
### Profile semantics
|
|
49
50
|
|
|
50
|
-
`--profile web` decides which profile the plugin is installed into: the Paoding Config page appears only in a DSH Web launched with that profile. `web` is the default profile of `dsh web`, so most machines need no change; multi-profile users run `dsh plugin add` once per profile they want it in. The orchestrator preset itself lives under `$DSH_HOME/.agent-presets/` and is shared machine-wide, independent of profiles — Save & Apply from any profile's panel writes the same preset.
|
|
51
|
+
`--profile web` decides which profile the plugin is installed into: the Paoding Config page appears only in a DSH Web launched with that profile. `web` is the default profile of `dsh web`, so most machines need no change; multi-profile users run `dsh plugin add` once per profile they want it in. The orchestrator preset itself lives under `$DSH_HOME/.agent-presets/` and is shared machine-wide, independent of profiles — under DSH ≥ 0.1.7 the declaration rows go into the home-layer `$DSH_HOME/cordis.patch.yml`, equally profile-independent — Save & Apply from any profile's panel writes the same preset.
|
|
52
|
+
|
|
53
|
+
### Version support and the dual preset landing (DSH 0.1.5 – 0.1.7-rc.1)
|
|
54
|
+
|
|
55
|
+
The plugin supports two host generations: **DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 are all supported**, with an identical install and usage flow. As of plugin 0.3.4, package.json declares `peerDependencies: @deepseek-ai/dsh >= 0.1.5` explicitly — from 0.1.7 the host runs a plugin-compatibility precheck before installing: no declaration meant a default pass, an explicit one gets checked against the real compatibility surface; 0.1.5 / 0.1.6 never read the field, where the declaration is pure metadata and changes nothing about the install.
|
|
56
|
+
|
|
57
|
+
The two generations discover "local authored presets" differently, so the installer maintains a dual landing, detecting the host automatically — nothing for the user to do:
|
|
58
|
+
|
|
59
|
+
- **Directory track (DSH ≤ 0.1.6)**: the host discovers presets by scanning directories under `$DSH_HOME/.agent-presets/` — the directory is the registration. The installer writes the artifacts to `$DSH_HOME/.agent-presets/orchestrator/` (`agent.cordis.yml` + `preset.yml` + `restrict.mjs`) as before; workspace-specific presets land in sibling `orchestrator-<slug>` directories.
|
|
60
|
+
- **Declaration track (DSH ≥ 0.1.7)**: the host no longer scans directories; it reads `@deepseek-ai/dsh-agent-preset` declaration rows from the patch instead. The installer maintains a managed block in `$DSH_HOME/cordis.patch.yml` — opening with `# --- dsh-paoding presets (auto-generated; do not edit) ---` and closing with the matching end marker — whose `- insert:` rows write the declarations: the global preset gets `config.id: orchestrator`, a workspace preset gets `config.id: orchestrator-<slug>`, each with all plugin rows inlined under `plugins`; `restrict.mjs` is referenced by an absolute `file:` URL. The artifact trio still lands in `.agent-presets/<id>/` — demoted from "the directory is the registration" to "a place where the files live". User content outside the block is left untouched, byte for byte; the block carries a do-not-edit marker, and anything hand-edited inside it is overwritten by the next save.
|
|
61
|
+
|
|
62
|
+
The installer decides which track to take in this order:
|
|
63
|
+
|
|
64
|
+
1. runtime probe from the Web panel first — the `agentPresets` service's `register` method exists only since 0.1.7; finding it means the declaration track;
|
|
65
|
+
2. `dsh --version` on the CLI side;
|
|
66
|
+
3. a last-resort probe of `$DSH_HOME/node_modules/@deepseek-ai/dsh-agent-preset` (that singular package being present means 0.1.7+);
|
|
67
|
+
4. if all fail, default to ≤ 0.1.6 (the safe side).
|
|
68
|
+
|
|
69
|
+
The first save/self-heal after the host version changes migrates automatically, in both directions, with no manual steps:
|
|
70
|
+
|
|
71
|
+
- **Downgrade self-heal (0.1.7 → 0.1.6)**: 0.1.6 does not understand declaration rows — leaving them in the patch breaks profile startup — so the installer removes the managed block from the home patch automatically; the directory-track artifacts are already complete, and the preset slips back to directory scanning seamlessly.
|
|
72
|
+
- **Upgrade migration (0.1.6 → 0.1.7)**: the installer writes the managed block automatically; sessions on presets generated under the old track (`.agent-presets/`, `agentPreset: orchestrator`) keep matching under the new track by `config.id`, so old sessions resume without a gap.
|
|
73
|
+
|
|
74
|
+
Two cross-version behavior alignments happen the same automatic way, with no user action:
|
|
75
|
+
|
|
76
|
+
- **Summary budget**: from 0.1.7 the host raises the compaction `maxTokens` default to 65536; this preset pins `maxTokens: 8192` on `dsh-compaction-basic` explicitly, so the 0.1.5 / 0.1.6 summary-budget behavior is preserved identically on both generations.
|
|
77
|
+
- **Panel icons**: 0.1.7 changed the ui-primitives icon naming; the panel's icons carry a cross-version fallback, so one bundle renders correctly on both host generations and the panel behaves identically on either.
|
|
51
78
|
|
|
52
79
|
### First-install automation: restart = full install
|
|
53
80
|
|
|
54
81
|
At startup the plugin checks whether the orchestrator preset is in place: if the `$DSH_HOME/.agent-presets/orchestrator` directory is missing, or its `.generator-version` marker does not match the plugin package version, it automatically performs one install equivalent to `--auto` — applying `~/.dsh/dsh-paoding.config.yml` when it exists, writing the base template otherwise (DSH base tools only; see below). It runs the same detection/generation pipeline as the panel's Save & Apply (`collectState` / `generateAndInstall`) — the same pipeline, but the startup self-heal detects with a pure file scan and no runtime detection facts while the panel detects with them; when a bundle-form plugin is installed on the host, the two detection inventories may differ, and the outputs differ slightly with them.
|
|
55
82
|
|
|
56
|
-
So **`dsh plugin add` + restart = a complete install**: preset generated, config template on disk, panel ready — all in one step. If the automatic generation fails it never takes the plugin down — a warning is logged, and you can apply manually from the panel.
|
|
83
|
+
So **`dsh plugin add` + restart = a complete install**: preset generated, config template on disk, panel ready — all in one step. If the automatic generation fails it never takes the plugin down — a warning is logged, and you can apply manually from the panel. The startup self-heal also maintains the dual landing and the cross-version migration: after the host moves between 0.1.6 and 0.1.7, the first self-heal writes or removes the managed block in `$DSH_HOME/cordis.patch.yml` automatically (see "Version support and the dual preset landing" above).
|
|
57
84
|
|
|
58
85
|
### The version marker and auto-regeneration
|
|
59
86
|
|
|
@@ -164,7 +191,7 @@ Panel data goes over the **same-origin `/api/paoding/*`**: the Node side registe
|
|
|
164
191
|
- **Agent tool-assignment cards**: one fixed card for the main agent (unchecking a base tool adds it to `main_agent_remove`, dropping it from the main agent; checking an MCP / plugin tool adds it to `main_agent_extra`; skills in two groups, "read on demand" and "inline full text"; plus a "persona extra" editor — edit, clear, or restore the default) + three built-in role cards (`search_external` / `design` / `implement`) + any number of custom role cards (with a delete button); every role card presents its tools in three groups — base / MCP / plugin — with per-item checkboxes, and the persona is editable. "Create a custom agent role" at the bottom.
|
|
165
192
|
- **Skill assignment**: one row per skill, check which agents it goes to (written into the matching personas as `Available skills` soft guidance; role skills are soft guidance, main-agent skills have the read-on-demand/inline groups).
|
|
166
193
|
- **Preview**: generates `agent.cordis.yml` without writing, showing each role's intent → kept counts plus the full generated text.
|
|
167
|
-
- **Save & Apply**: after confirmation, writes the preset (overwriting `$DSH_HOME/.agent-presets/orchestrator`) and saves the current assignments to `$DSH_HOME/dsh-paoding.config.yml`; success asks you to restart DSH or start a new session (and flags when a patch is newer than the generated output). Opening the panel loads the existing config file as the current values — hand-edited choices are visible and editable right away.
|
|
194
|
+
- **Save & Apply**: after confirmation, writes the preset (overwriting `$DSH_HOME/.agent-presets/orchestrator`) and registers it per host generation — on ≤ 0.1.6 the directory scan picks it up as-is, on ≥ 0.1.7 the declaration managed block in `$DSH_HOME/cordis.patch.yml` is maintained in the same pass (see §2) — then saves the current assignments to `$DSH_HOME/dsh-paoding.config.yml`; success asks you to restart DSH or start a new session (and flags when a patch is newer than the generated output). Opening the panel loads the existing config file as the current values — hand-edited choices are visible and editable right away.
|
|
168
195
|
|
|
169
196
|
## 7. Uninstalling
|
|
170
197
|
|
|
@@ -178,6 +205,8 @@ rm -rf "${DSH_HOME:-$HOME/.dsh}/.agent-presets"/orchestrator-*
|
|
|
178
205
|
|
|
179
206
|
The first removes the plugin and the sidebar panel (scoped to the given profile), the second deletes the orchestrator preset directory, and the third removes the per-workspace preset directories (`orchestrator-<basename>`) created when you configured individual workspaces (if you never did, the glob matches nothing and it's harmless) — the base `orchestrator` directory is already gone with the previous command and is not covered by that glob. The config file `~/.dsh/dsh-paoding.config.yml` is not removed with them — keep it or delete it, it does not affect any other DSH configuration, and a reinstall's first-install automation would apply it again.
|
|
180
207
|
|
|
208
|
+
On a DSH ≥ 0.1.7 host, check one more place: `$DSH_HOME/cordis.patch.yml` may still carry the dsh-paoding managed block (the `- insert:` declaration rows wrapped in marker comments). Once the preset directories are gone those declaration rows dangle, pointing at deleted directories — remove the whole managed block (from the `# --- dsh-paoding presets (auto-generated; do not edit) ---` opening line through the end marker) as well; content outside the block is untouched.
|
|
209
|
+
|
|
181
210
|
Uninstalling touches nothing else on the host (MCP, plugins, sessions), and stays decoupled from DSH source code; "编排模式 (Orchestrator)" disappears from the preset selector for new sessions.
|
|
182
211
|
|
|
183
212
|
## 8. Troubleshooting
|
|
@@ -188,7 +217,7 @@ Uninstalling touches nothing else on the host (MCP, plugins, sessions), and stay
|
|
|
188
217
|
|
|
189
218
|
**The plugin is installed but "编排模式 (Orchestrator)" is not in the preset list**
|
|
190
219
|
|
|
191
|
-
The preset is generated automatically when the plugin starts: first make sure you **restarted DSH** after installing; presets load when a session is created, so after the restart you also need a new session for it to appear in the selector. Still nothing? Check the DSH log for `paoding-config-ui` warnings (a failed auto-generation is logged; apply manually from the panel), then check `ls "${DSH_HOME:-$HOME/.dsh}/.agent-presets/orchestrator"` — an empty directory usually means the plugin's runtime `$DSH_HOME` differs from the home directory you assumed (export `DSH_HOME=…` explicitly before launching DSH).
|
|
220
|
+
The preset is generated automatically when the plugin starts: first make sure you **restarted DSH** after installing; presets load when a session is created, so after the restart you also need a new session for it to appear in the selector. Still nothing? Check the DSH log for `paoding-config-ui` warnings (a failed auto-generation is logged; apply manually from the panel), then check `ls "${DSH_HOME:-$HOME/.dsh}/.agent-presets/orchestrator"` — an empty directory usually means the plugin's runtime `$DSH_HOME` differs from the home directory you assumed (export `DSH_HOME=…` explicitly before launching DSH). If the directory is there, the host is 0.1.7+, and the selector still shows nothing, check `$DSH_HOME/cordis.patch.yml` for the dsh-paoding managed block — 0.1.7 discovers presets through the declaration rows inside it; if the block is missing, hit Save & Apply once in the panel to write it (see §2 "Version support and the dual preset landing").
|
|
192
221
|
|
|
193
222
|
**`tools.restrict()` reports unknown tools / subagent creation is rejected**
|
|
194
223
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-paoding",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "庖丁 — DSH (DeepSeek Harness) 编排模式安装器:host 感知的角色工具过滤 + Web 设置页可视化配置器",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=18"
|
|
21
21
|
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@deepseek-ai/dsh": ">=0.1.5"
|
|
24
|
+
},
|
|
22
25
|
"scripts": {
|
|
23
26
|
"prepublishOnly": "node scripts/build-client.mjs --check"
|
|
24
27
|
},
|
|
@@ -46,7 +49,6 @@
|
|
|
46
49
|
"client": {
|
|
47
50
|
"platform": "web",
|
|
48
51
|
"inject": [
|
|
49
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
50
52
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
51
53
|
"@deepseek-ai/dsh-client-connection"
|
|
52
54
|
]
|
|
@@ -118,6 +118,9 @@ export function serializeState(s) {
|
|
|
118
118
|
suggested: s.suggested,
|
|
119
119
|
installedPresets,
|
|
120
120
|
workspaceMeta,
|
|
121
|
+
// 预设安装轨道(declarative = 0.1.7+ 声明行 / directory = ≤0.1.6 目录扫描),
|
|
122
|
+
// 面板可据此展示;旧版 collectState 产出的 state 无此字段时按目录轨兜底
|
|
123
|
+
presetSystem: s.presetSystem ?? 'directory',
|
|
121
124
|
// 主 persona 尾部追加的默认常量(供 UI「人设追加」默认展示 / 恢复默认)
|
|
122
125
|
mainPersonaExtraDefault: s.mainPersonaExtraDefault ?? null,
|
|
123
126
|
// 运行时识别结果透传(字段缺失视为未启用运行时检测,与旧版 UI 行为一致)
|
|
@@ -166,9 +169,11 @@ const PRESET_ENSURE_ABORTED = '插件已卸载(dispose),本次 preset 生
|
|
|
166
169
|
*
|
|
167
170
|
* 判定:preset 目录缺失,或 .generator-version 标记缺失 / 版本与 generatorVersion
|
|
168
171
|
* 不符 → 执行一次自动安装:局部 collectState()(不带运行时事实的纯文件扫描,
|
|
169
|
-
* 首装触发点在插件启动、宿主服务未必就绪,且与 CLI --auto
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
+
* 首装触发点在插件启动、宿主服务未必就绪,且与 CLI --auto 语义对齐;预设安装
|
|
173
|
+
* 轨道由 collectState 内部按 dsh --version / 文件探测兜底,state.presetSystem
|
|
174
|
+
* 随 state 一并进 generateAndInstall 双轨收尾;不走 refreshState()——那会拿
|
|
175
|
+
* 无事实的扫描覆盖路由层的共享缓存)后按既有配置合成 assignments(无配置写
|
|
176
|
+
* 基础模板),经 generateAndInstall 落盘,最后写标记文件。
|
|
172
177
|
* shouldAbort 在各 await 点后检查:插件 dispose 后在途的生成不再继续写盘。
|
|
173
178
|
* 可脱离 ctx 独立调用(单测友好),任何异常自捕、绝不抛出。
|
|
174
179
|
*
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
flex: none;
|
|
45
45
|
padding: 1px 8px;
|
|
46
46
|
border-radius: 999px;
|
|
47
|
-
background: var(--dsw-alias-accent
|
|
47
|
+
background: var(--dsw-alias-interactive-bg-hover-accent, var(--dsw-alias-bg-layer-2));
|
|
48
48
|
font-size: 11px;
|
|
49
49
|
line-height: 16px;
|
|
50
50
|
font-weight: 500;
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
* - factory 内 require() 只解析平台 seed 模块(react / cordis /
|
|
25
25
|
* ui-slots / ui-primitives 与已物化模块);本 bundle 依赖 react 与
|
|
26
26
|
* @deepseek-ai/dsh-client-ui-primitives。
|
|
27
|
+
* - 图标跨版本兜底:DSH ≤0.1.6 图标导出名带尺寸后缀(IconCheckOutline16),
|
|
28
|
+
* 0.1.7-rc.1 起改粗细命名 + size prop,factory 内把旧名别名到 Regular 版。
|
|
27
29
|
* - 挂载点:左侧栏底部动作位入口(sidebar.footer.action 键控槽,与插件
|
|
28
30
|
* 广场 cordis 徽章同槽,设置行上方)+ 会话中栏全页 overlay
|
|
29
31
|
* (data-dsh-paoding-view / html[data-dsh-paoding-active]);
|
|
@@ -51,6 +53,36 @@ window.__ModuleLoader__.load({
|
|
|
51
53
|
var useState = React.useState, useEffect = React.useEffect, useCallback = React.useCallback, useRef = React.useRef;
|
|
52
54
|
var P = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
53
55
|
|
|
56
|
+
// 图标跨版本兜底:DSH ≤0.1.6 的 ui-primitives 图标导出名带尺寸后缀
|
|
57
|
+
// (IconCheckOutline16 / IconChevronDownOutline14,IconProps { size,
|
|
58
|
+
// className });0.1.7-rc.1 起改粗细命名(…OutlineRegular / …Medium,
|
|
59
|
+
// size 变 prop、默认 16),12 个旧名全部消失。同一份 bundle 要在两代
|
|
60
|
+
// 宿主上都能跑:旧名缺、新名在,才把旧名别名到对应 Regular 版——新
|
|
61
|
+
// 组件接受 size prop,ic() 的 size 传参照常生效,旧 14px 名 → Regular
|
|
62
|
+
// + size 14 视觉等价(同为 1px 描边)。旧宿主上有原生实现就用原生,
|
|
63
|
+
// Button / Pill 等两代都有的导出一律不动。
|
|
64
|
+
P = Object.assign({}, P); // 复制一份导出面再补别名:不往共享模块对象上写属性(避免污染宿主里其他 ui-primitives 消费者);P 是 factory 作用域的 var,重赋值后所有后续片段看到的就是这份副本
|
|
65
|
+
(function () {
|
|
66
|
+
var ICON_ALIAS = {
|
|
67
|
+
IconCheckOutline16: "IconCheckOutlineRegular",
|
|
68
|
+
IconChevronDownOutline14: "IconChevronDownOutlineRegular",
|
|
69
|
+
IconCordisPluginOutline14: "IconCordisPluginOutlineRegular",
|
|
70
|
+
IconDataOutline16: "IconDataOutlineRegular",
|
|
71
|
+
IconEditOutline16: "IconEditOutlineRegular",
|
|
72
|
+
IconLoadingOutline16: "IconLoadingOutlineRegular",
|
|
73
|
+
IconPlusOutline16: "IconPlusOutlineRegular",
|
|
74
|
+
IconRefreshOutline16: "IconRefreshOutlineRegular",
|
|
75
|
+
IconSkillOutline16: "IconSkillOutlineRegular",
|
|
76
|
+
IconTrashOutline16: "IconTrashOutlineRegular",
|
|
77
|
+
IconUserOutline16: "IconUserOutlineRegular",
|
|
78
|
+
IconWarningOutline16: "IconWarningOutlineRegular",
|
|
79
|
+
};
|
|
80
|
+
Object.keys(ICON_ALIAS).forEach(function (oldName) {
|
|
81
|
+
var newName = ICON_ALIAS[oldName];
|
|
82
|
+
if (P[oldName] === undefined && P[newName] !== undefined) P[oldName] = P[newName];
|
|
83
|
+
});
|
|
84
|
+
})();
|
|
85
|
+
|
|
54
86
|
// ── 样式注入(幂等)─────────────────────────────────────────────────
|
|
55
87
|
//
|
|
56
88
|
// 样式正文在 lib/base.css,由 Node 半经 GET /api/paoding/client.css 下发
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paoding-config-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"client": {
|
|
10
10
|
"platform": "web",
|
|
11
11
|
"inject": [
|
|
12
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
13
12
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
14
13
|
"@deepseek-ai/dsh-client-connection"
|
|
15
14
|
]
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
* MCP 工具由 dsh-mcp-client 以 `mcp__<server>__<tool>` 公开名注册,
|
|
19
19
|
* 故 schemas()/entries() 等能枚举出形如 mcp__tavily__tavily_search 的
|
|
20
20
|
* 全名,与 tools/install.mjs 的 inventory 命名约定一致。
|
|
21
|
+
* - 预设安装轨道(presetSystem):agentPresets 服务有无 register 方法——
|
|
22
|
+
* 0.1.7+ 的 agent-preset-registry(声明行轨)有,≤0.1.6 的目录扫描服务
|
|
23
|
+
* 没有;collectState 拿它免掉 dsh --version 子进程探测。
|
|
21
24
|
*
|
|
22
25
|
* 约定:本模块只负责“取数”,任何一步失败都不抛出——错误记入 error 字符串
|
|
23
26
|
* (多条用 '; ' 连接、整体截断),返回可用的部分结果;列表带截断上限防爆。
|
|
@@ -187,6 +190,22 @@ function readRegisteredToolNames(ctx, errors) {
|
|
|
187
190
|
return { names: out, ok: acquired }
|
|
188
191
|
}
|
|
189
192
|
|
|
193
|
+
/**
|
|
194
|
+
* 预设安装轨道探测:0.1.7+ 宿主的 agentPresets 服务是 agent-preset-registry
|
|
195
|
+
* (带 register 方法,声明行轨);≤0.1.6 的同名服务只做 .agent-presets 目录
|
|
196
|
+
* 扫描、没有 register。ctx.reflect.get(name, false) 读不到服务返回 undefined、
|
|
197
|
+
* 不触发 inject 护栏,整个探测自捕——任何异常都按 directory(安全侧)处理,
|
|
198
|
+
* 永不抛出。
|
|
199
|
+
*/
|
|
200
|
+
function detectPresetSystemOf(ctx) {
|
|
201
|
+
try {
|
|
202
|
+
const svc = ctx?.reflect?.get?.('agentPresets', false)
|
|
203
|
+
return svc != null && typeof svc.register === 'function' ? 'declarative' : 'directory'
|
|
204
|
+
} catch {
|
|
205
|
+
return 'directory'
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
190
209
|
/**
|
|
191
210
|
* 采集运行时事实(供 collectState 合并 / UI 展示)。
|
|
192
211
|
* 永不抛出:任何异常都会落到 error 里,返回部分结果。
|
|
@@ -209,5 +228,6 @@ export function collectRuntimeFacts(ctx) {
|
|
|
209
228
|
...(error !== '' ? { error } : {}),
|
|
210
229
|
pluginEntries: plugin.entries,
|
|
211
230
|
toolNames: tools.names,
|
|
231
|
+
presetSystem: detectPresetSystemOf(ctx),
|
|
212
232
|
}
|
|
213
233
|
}
|
|
@@ -147,6 +147,9 @@
|
|
|
147
147
|
name: '@deepseek-ai/dsh-compaction-basic'
|
|
148
148
|
config:
|
|
149
149
|
thresholdRatio: 0.45
|
|
150
|
+
# 钉住摘要预算:0.1.7 起该键默认从 8192 升到 65536,显式 8192 保持
|
|
151
|
+
# 0.1.5/0.1.6 的实际行为不变(摘要单次预算不随宿主版本漂移)。
|
|
152
|
+
maxTokens: 8192
|
|
150
153
|
|
|
151
154
|
- id: command-compact
|
|
152
155
|
name: '@deepseek-ai/dsh-command-compact'
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 预设安装机制双轨 —— DSH ≤0.1.6 靠宿主 dsh-agent-presets(复数)扫描
|
|
3
|
+
* $DSH_HOME/.agent-presets/<id>/ 目录成 preset(目录扫描轨);0.1.7 起目录扫描
|
|
4
|
+
* 机制删除,改为声明行:bundle/profile/home 任一 patch 层里 - insert: 一条
|
|
5
|
+
* name: '@deepseek-ai/dsh-agent-preset' 的行(config = { id 必填, name?,
|
|
6
|
+
* description?, order?, plugins[] 必填 },宿主 agent-preset-registry 挂载
|
|
7
|
+
* plugins 子树;行 name 相对声明行 baseUrl 解析,绝对 file: URL 直接采用)。
|
|
8
|
+
* 本模块为双轨化提供三件事:
|
|
9
|
+
* 1) 轨道探测 detectPresetSystem:运行时探测(Web 插件对宿主服务的实时反射,
|
|
10
|
+
* 最权威)> `dsh --version` 版本号 > $DSH_HOME 单数包存在性 > 默认
|
|
11
|
+
* directory(安全侧:声明行误落 0.1.6 会炸 profile 启动,目录轨误落
|
|
12
|
+
* 0.1.7 只是预设不显示);
|
|
13
|
+
* 2) 声明行构造 buildPresetRowText:把生成的 agent.cordis.yml 全文内联成
|
|
14
|
+
* 一条声明行的 config.plugins(restrict 行 name 换成 restrict.mjs 的绝对
|
|
15
|
+
* file: URL——声明轨没有「相对 preset 目录」语义,必须绝对化);
|
|
16
|
+
* 3) home patch 托管块手术 upsertHomePatchRows / stripHomePatchRows /
|
|
17
|
+
* readHomePatchRows:对 $DSH_HOME/cordis.patch.yml 的 MANAGED 标记块做
|
|
18
|
+
* 行级增删查,块外用户内容一字节不动;托管块损坏(缺结束标记)时拒绝
|
|
19
|
+
* 写盘并明确报错,与仓库「配置不可信时拒绝落盘」口径一致。
|
|
20
|
+
* 零运行时依赖(node:child_process / node:fs / node:path);被 state 引用。
|
|
21
|
+
*/
|
|
22
|
+
import { spawnSync } from 'node:child_process'
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
24
|
+
import path from 'node:path'
|
|
25
|
+
import { warn } from './util.mjs'
|
|
26
|
+
|
|
27
|
+
/** 托管块起始标记:其后到结束标记之间是本工具自动生成的声明行,用户勿改。 */
|
|
28
|
+
export const MANAGED_BEGIN = '# --- dsh-paoding presets (auto-generated; do not edit) ---'
|
|
29
|
+
/** 托管块结束标记。两个标记成对出现;缺一即视为托管块损坏,拒绝写盘。 */
|
|
30
|
+
export const MANAGED_END = '# --- end dsh-paoding presets ---'
|
|
31
|
+
|
|
32
|
+
/** 声明行挂载的宿主 preset 包名(0.1.7+ 的 agent-preset-registry 组件包)。 */
|
|
33
|
+
export const PRESET_ROW_NAME = '@deepseek-ai/dsh-agent-preset'
|
|
34
|
+
|
|
35
|
+
/** preset 目录名(orchestrator / orchestrator-<slug>)→ home patch 声明行 id。 */
|
|
36
|
+
export function presetRowId(presetId) {
|
|
37
|
+
return `preset-${presetId}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── 版本解析与轨道判定 ──────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 解析裸版本号文本为 [major, minor, patch] 数字三元组;解析不了返回 null。
|
|
44
|
+
* prerelease / build 标签忽略(0.1.7-rc.1 → [0, 1, 7])。
|
|
45
|
+
*/
|
|
46
|
+
export function parseVersionTriple(text) {
|
|
47
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(String(text ?? '').trim())
|
|
48
|
+
if (!m) return null
|
|
49
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 三元组是否达到声明行轨(0.1.7+):逐段数值比较,prerelease 已在上游剥掉。 */
|
|
53
|
+
export function isDeclarativeTriple(triple) {
|
|
54
|
+
if (!Array.isArray(triple) || triple.length !== 3 || triple.some((n) => !Number.isInteger(n))) return false
|
|
55
|
+
const base = [0, 1, 7]
|
|
56
|
+
for (let i = 0; i < 3; i++) {
|
|
57
|
+
if (triple[i] !== base[i]) return triple[i] > base[i]
|
|
58
|
+
}
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 探测当前宿主的预设安装轨道,返回 'declarative' | 'directory'。逐级降级:
|
|
64
|
+
* 1) runtimeSystem 直通:Web 插件运行时对宿主服务的实时探测结果最权威;
|
|
65
|
+
* 2) `dsh --version`(timeout 10s;spawn 失败 / 超时 / 输出不可解析一律吞掉
|
|
66
|
+
* 落到下一级);
|
|
67
|
+
* 3) 文件探测:单数包 @deepseek-ai/dsh-agent-preset 是 0.1.7 的声明行宿主
|
|
68
|
+
* 组件,在即声明轨(复数包时代没有它);
|
|
69
|
+
* 4) 默认 directory —— 安全侧兜底(理由见模块头注释)。
|
|
70
|
+
*/
|
|
71
|
+
export async function detectPresetSystem({ dshHome, runtimeSystem = null } = {}) {
|
|
72
|
+
if (runtimeSystem === 'declarative' || runtimeSystem === 'directory') return runtimeSystem
|
|
73
|
+
const versionText = dshVersionText()
|
|
74
|
+
if (versionText !== null) {
|
|
75
|
+
const triple = parseVersionTriple(versionText)
|
|
76
|
+
if (triple !== null) return isDeclarativeTriple(triple) ? 'declarative' : 'directory'
|
|
77
|
+
}
|
|
78
|
+
if (existsSync(path.join(dshHome, 'node_modules', '@deepseek-ai', 'dsh-agent-preset'))) return 'declarative'
|
|
79
|
+
return 'directory'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `dsh --version` 取裸版本号文本;任何失败(ENOENT / 超时 / 非零退出)返回 null。 */
|
|
83
|
+
function dshVersionText() {
|
|
84
|
+
try {
|
|
85
|
+
const res = spawnSync('dsh', ['--version'], { timeout: 10_000, encoding: 'utf8' })
|
|
86
|
+
if (res.error || res.status !== 0) return null
|
|
87
|
+
const text = String(res.stdout ?? '').trim()
|
|
88
|
+
return text === '' ? null : text
|
|
89
|
+
} catch {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── 声明行构造 ──────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 构造一条 preset 声明行(insert 列表元素)的 YAML 文本:
|
|
98
|
+
*
|
|
99
|
+
* - id: preset-orchestrator
|
|
100
|
+
* name: '@deepseek-ai/dsh-agent-preset'
|
|
101
|
+
* config:
|
|
102
|
+
* id: orchestrator
|
|
103
|
+
* name: "编排模式 (Orchestrator)"
|
|
104
|
+
* description: "..."
|
|
105
|
+
* order: 5
|
|
106
|
+
* plugins:
|
|
107
|
+
* <agent.cordis.yml 全文,每非空行加 10 空格>
|
|
108
|
+
*
|
|
109
|
+
* 缩进基准:4 空格起排(`- insert:` 列表项层级由 upsertHomePatchRows 拼),
|
|
110
|
+
* plugins: 落 8 空格、插件条目行落 10 空格。name/description 用 JSON.stringify
|
|
111
|
+
* 落值——JSON 字符串即合法 YAML 双引号标量,换行/引号自动转义。agentYmlText
|
|
112
|
+
* 先把 restrict 行的 `name: ./restrict.mjs` 替换为 restrictFileUrl(绝对 file:
|
|
113
|
+
* URL),再整体 +10 缩进;空行保持空行(块标量相对缩进不变),`!!js` 表达式
|
|
114
|
+
* 行原样保留(宿主 patch schema 支持,求值仍由 cordis 装载时完成)。
|
|
115
|
+
*/
|
|
116
|
+
export function buildPresetRowText({ presetId, name = null, description = null, order = null, agentYmlText, restrictFileUrl }) {
|
|
117
|
+
if (typeof presetId !== 'string' || !/^[a-z0-9][a-z0-9-]*$/.test(presetId)) {
|
|
118
|
+
throw new Error(`buildPresetRowText: 非法 presetId: ${presetId}`)
|
|
119
|
+
}
|
|
120
|
+
if (typeof agentYmlText !== 'string' || agentYmlText === '') {
|
|
121
|
+
throw new Error('buildPresetRowText: agentYmlText 不能为空')
|
|
122
|
+
}
|
|
123
|
+
if (typeof restrictFileUrl !== 'string' || restrictFileUrl === '') {
|
|
124
|
+
throw new Error('buildPresetRowText: restrictFileUrl 不能为空')
|
|
125
|
+
}
|
|
126
|
+
const lines = [
|
|
127
|
+
` - id: ${presetRowId(presetId)}`,
|
|
128
|
+
` name: '${PRESET_ROW_NAME}'`,
|
|
129
|
+
' config:',
|
|
130
|
+
` id: ${presetId}`,
|
|
131
|
+
]
|
|
132
|
+
if (typeof name === 'string' && name !== '') lines.push(` name: ${JSON.stringify(name)}`)
|
|
133
|
+
if (typeof description === 'string' && description !== '') lines.push(` description: ${JSON.stringify(description)}`)
|
|
134
|
+
if (Number.isInteger(order)) lines.push(` order: ${order}`)
|
|
135
|
+
lines.push(' plugins:')
|
|
136
|
+
for (const line of String(agentYmlText).split(/\r?\n/)) {
|
|
137
|
+
if (line.trim() === '') {
|
|
138
|
+
lines.push('')
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
// restrict 行换绝对 file: URL(按行精确内容匹配,缩进原样保留)
|
|
142
|
+
const replaced = line.trim() === 'name: ./restrict.mjs'
|
|
143
|
+
? `${line.match(/^[ \t]*/)[0]}name: ${restrictFileUrl}`
|
|
144
|
+
: line
|
|
145
|
+
lines.push(` ${replaced}`)
|
|
146
|
+
}
|
|
147
|
+
while (lines.length > 0 && lines[lines.length - 1] === '') lines.pop() // 去尾部空行,段拼接才紧凑
|
|
148
|
+
return lines.join('\n')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── home patch 托管块手术 ───────────────────────────────────────────────────
|
|
152
|
+
//
|
|
153
|
+
// 托管块落在 $DSH_HOME/cordis.patch.yml(home 层 patch,作用于所有 profile),
|
|
154
|
+
// 形态:
|
|
155
|
+
//
|
|
156
|
+
// # --- dsh-paoding presets (auto-generated; do not edit) ---
|
|
157
|
+
// - insert:
|
|
158
|
+
// - id: preset-orchestrator
|
|
159
|
+
// name: '@deepseek-ai/dsh-agent-preset'
|
|
160
|
+
// config:
|
|
161
|
+
// ...
|
|
162
|
+
// # --- end dsh-paoding presets ---
|
|
163
|
+
//
|
|
164
|
+
// 每条声明行各自包一层 0 缩进的 `- insert:`(宿主约定「自定义【新增】条目必须
|
|
165
|
+
// 用 - insert: 包一层」,顶层 - id: 是修改已有条目语义),声明行本体按
|
|
166
|
+
// buildPresetRowText 的 4 空格基准缩进,作 insert 列表元素。块外用户内容除
|
|
167
|
+
// strip 的空行收敛外一字节不动。
|
|
168
|
+
|
|
169
|
+
/** home 层 patch 文件路径。 */
|
|
170
|
+
function patchFilePath(dshHome) {
|
|
171
|
+
return path.join(dshHome, 'cordis.patch.yml')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 读 home patch 为行数组;文件缺失当空文件。行尾换行符风格随原文件保留。 */
|
|
175
|
+
function readPatchLines(dshHome) {
|
|
176
|
+
let text = ''
|
|
177
|
+
try {
|
|
178
|
+
text = readFileSync(patchFilePath(dshHome), 'utf8')
|
|
179
|
+
} catch (err) {
|
|
180
|
+
if (err?.code !== 'ENOENT') throw err
|
|
181
|
+
return { lines: [], eol: '\n' }
|
|
182
|
+
}
|
|
183
|
+
if (text === '') return { lines: [], eol: '\n' }
|
|
184
|
+
return { lines: text.split(/\r?\n/), eol: text.includes('\r\n') ? '\r\n' : '\n' }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function writePatchLines(dshHome, lines, eol) {
|
|
188
|
+
mkdirSync(dshHome, { recursive: true })
|
|
189
|
+
writeFileSync(patchFilePath(dshHome), lines.join(eol), 'utf8')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 在行数组里定位托管块 [begin, end](闭区间下标)。无起始标记返回 null;
|
|
194
|
+
* 有起始无结束(托管块损坏,多半是用户手删了半截)throw 明确错误——后续写盘
|
|
195
|
+
* 一律拒绝,绝不覆盖手工内容。
|
|
196
|
+
*/
|
|
197
|
+
function findManagedSpan(lines) {
|
|
198
|
+
const begin = lines.findIndex((l) => l.trim() === MANAGED_BEGIN)
|
|
199
|
+
if (begin === -1) return null
|
|
200
|
+
let end = -1
|
|
201
|
+
for (let i = begin + 1; i < lines.length; i++) {
|
|
202
|
+
if (lines[i].trim() === MANAGED_END) {
|
|
203
|
+
end = i
|
|
204
|
+
break
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (end === -1) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`cordis.patch.yml 的 dsh-paoding 托管块已损坏(只有起始标记、找不到结束标记 "${MANAGED_END}"):` +
|
|
210
|
+
'请手工补回结束标记,或把整个托管块删除后重试;已拒绝写盘以免覆盖手工内容',
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
return { begin, end }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** 托管块内声明行段的起始行:4 空格缩进的 `- id: preset-...`。 */
|
|
217
|
+
const ROW_ID_LINE_RE = /^ {4}- id: (preset-\S+)[ \t]*$/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 把托管块内容行解析成行段数组 [{ rowId, lines }]:每个段由 `- insert:` 包裹行
|
|
221
|
+
* (若紧邻在 `- id:` 行上方)加声明行本体组成,到下一段或块尾止。段尾空行归
|
|
222
|
+
* 所属段,重写时裁掉保持块紧凑。
|
|
223
|
+
*/
|
|
224
|
+
function parseRowSegments(blockLines) {
|
|
225
|
+
const marks = []
|
|
226
|
+
for (let i = 0; i < blockLines.length; i++) {
|
|
227
|
+
const m = ROW_ID_LINE_RE.exec(blockLines[i])
|
|
228
|
+
if (!m) continue
|
|
229
|
+
// 段首含紧邻的 `- insert:` 包裹行;包裹行不在(手工摆弄过)则从行本体起段
|
|
230
|
+
const segStart = i > 0 && blockLines[i - 1].trim() === '- insert:' ? i - 1 : i
|
|
231
|
+
marks.push({ rowId: m[1], segStart })
|
|
232
|
+
}
|
|
233
|
+
return marks.map((mk, idx) => ({
|
|
234
|
+
rowId: mk.rowId,
|
|
235
|
+
lines: blockLines.slice(mk.segStart, idx + 1 < marks.length ? marks[idx + 1].segStart : blockLines.length),
|
|
236
|
+
}))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** 裁掉行数组尾部的空行(不含末尾那个代表「文件以换行结束」的空元素)。 */
|
|
240
|
+
function trimTrailingBlankLines(lines) {
|
|
241
|
+
const out = [...lines]
|
|
242
|
+
while (out.length > 1 && out[out.length - 1].trim() === '') out.pop()
|
|
243
|
+
return out
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** 3+ 连续空行收敛为一个空行(strip 撤行后的兜底收拾;1-2 连续空行原样保留)。 */
|
|
247
|
+
function collapseBlankRuns(lines) {
|
|
248
|
+
const out = []
|
|
249
|
+
let blankRun = 0
|
|
250
|
+
for (const line of lines) {
|
|
251
|
+
if (line.trim() === '') {
|
|
252
|
+
blankRun++
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
if (blankRun > 0) {
|
|
256
|
+
if (blankRun >= 3) out.push('')
|
|
257
|
+
else for (let i = 0; i < blankRun; i++) out.push('')
|
|
258
|
+
blankRun = 0
|
|
259
|
+
}
|
|
260
|
+
out.push(line)
|
|
261
|
+
}
|
|
262
|
+
for (let i = 0; i < blankRun; i++) out.push('') // 文件尾空行原样保留
|
|
263
|
+
return out
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 把若干声明行(buildPresetRowText 产物)行级 upsert 进 home patch 托管块。
|
|
268
|
+
* 托管块不存在则文末新建(非空文件先补尾换行、块前空一行);已存在则块内按
|
|
269
|
+
* rowId 替换 / 追加,块外用户内容一字节不动。托管块损坏时 throw(不写盘)。
|
|
270
|
+
*/
|
|
271
|
+
export function upsertHomePatchRows(dshHome, rowTexts) {
|
|
272
|
+
const rows = (Array.isArray(rowTexts) ? rowTexts : [])
|
|
273
|
+
.filter((t) => typeof t === 'string' && t.trim() !== '')
|
|
274
|
+
if (rows.length === 0) return
|
|
275
|
+
const wanted = rows.map((text) => {
|
|
276
|
+
const rowLines = text.replace(/\r?\n$/, '').split('\n')
|
|
277
|
+
const m = ROW_ID_LINE_RE.exec(rowLines[0])
|
|
278
|
+
if (!m) {
|
|
279
|
+
throw new Error(`buildPresetRowText 产出的行文本缺少 4 空格缩进的 "- id: preset-..." 首行: ${rowLines[0]}`)
|
|
280
|
+
}
|
|
281
|
+
return { rowId: m[1], lines: ['- insert:', ...rowLines] }
|
|
282
|
+
})
|
|
283
|
+
// 同 rowId 传多条时后者生效(Map 去重,保证替换语义而非重复追加)
|
|
284
|
+
const wantedById = new Map(wanted.map((w) => [w.rowId, w]))
|
|
285
|
+
|
|
286
|
+
const { lines, eol } = readPatchLines(dshHome)
|
|
287
|
+
const span = findManagedSpan(lines) // 损坏块 throw,直接中止(未写任何盘)
|
|
288
|
+
if (span !== null) {
|
|
289
|
+
const segments = parseRowSegments(lines.slice(span.begin + 1, span.end))
|
|
290
|
+
const merged = []
|
|
291
|
+
for (const seg of segments) {
|
|
292
|
+
if (wantedById.has(seg.rowId)) merged.push(...wantedById.get(seg.rowId).lines)
|
|
293
|
+
else merged.push(...trimTrailingBlankLines(seg.lines)) // 块内其余 preset 的行原样保留
|
|
294
|
+
}
|
|
295
|
+
for (const w of wanted) {
|
|
296
|
+
if (!segments.some((s) => s.rowId === w.rowId)) merged.push(...w.lines) // 新行追加到块尾
|
|
297
|
+
}
|
|
298
|
+
const next = [...lines.slice(0, span.begin), MANAGED_BEGIN, ...merged, MANAGED_END, ...lines.slice(span.end + 1)]
|
|
299
|
+
writePatchLines(dshHome, next, eol)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
// 无托管块:文末新建。非空文件先补尾换行(若缺)。块前空一行不再单独推:
|
|
303
|
+
// split 出的末尾 '' 元素(文件尾换行)后面紧跟新内容时,join 天然渲染出一个
|
|
304
|
+
// 空行,正好兼任块前分隔——补的尾换行元素同理。额外再推一个空行会变成两个。
|
|
305
|
+
const out = [...lines]
|
|
306
|
+
const hasContent = out.some((l) => l.trim() !== '')
|
|
307
|
+
if (hasContent && out[out.length - 1].trim() !== '') out.push('')
|
|
308
|
+
out.push(MANAGED_BEGIN)
|
|
309
|
+
for (const w of wanted) out.push(...w.lines)
|
|
310
|
+
out.push(MANAGED_END, '')
|
|
311
|
+
writePatchLines(dshHome, out, eol)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 从 home patch 托管块撤掉指定 rowId 的声明行段;rowIdsOrAll 传 null 删整块
|
|
316
|
+
* (含起止标记与块前自加的空行分隔,用户内容回到插入前的字节形态)。
|
|
317
|
+
* 块不存在 / 指定行都不在场返回 false(未写盘);托管块损坏时 warn 后返回
|
|
318
|
+
* false(撤行是锦上添花的自愈路径,不值得为此中断安装,修复由 upsert 的
|
|
319
|
+
* 明确报错引导)。
|
|
320
|
+
*/
|
|
321
|
+
export function stripHomePatchRows(dshHome, rowIdsOrAll = null) {
|
|
322
|
+
let lines
|
|
323
|
+
let eol
|
|
324
|
+
try {
|
|
325
|
+
({ lines, eol } = readPatchLines(dshHome))
|
|
326
|
+
} catch (err) {
|
|
327
|
+
warn(`cordis.patch.yml 读取失败,声明行撤除跳过: ${err?.message ?? err}`)
|
|
328
|
+
return false
|
|
329
|
+
}
|
|
330
|
+
let span = null
|
|
331
|
+
try {
|
|
332
|
+
span = findManagedSpan(lines)
|
|
333
|
+
} catch (err) {
|
|
334
|
+
warn(`${err.message}(声明行撤除跳过)`)
|
|
335
|
+
return false
|
|
336
|
+
}
|
|
337
|
+
if (span === null) return false
|
|
338
|
+
|
|
339
|
+
const allSegments = parseRowSegments(lines.slice(span.begin + 1, span.end))
|
|
340
|
+
const targets = rowIdsOrAll === null
|
|
341
|
+
? allSegments
|
|
342
|
+
: allSegments.filter((s) => new Set(rowIdsOrAll).has(s.rowId))
|
|
343
|
+
if (targets.length === 0) return false
|
|
344
|
+
|
|
345
|
+
if (targets.length === allSegments.length) {
|
|
346
|
+
// 整块摘除:连同块前那行本工具自加的空行分隔一起拿掉(空行分隔是自己
|
|
347
|
+
// 加的,摘掉才算「用户内容回到插入前」)
|
|
348
|
+
let start = span.begin
|
|
349
|
+
if (start > 0 && lines[start - 1].trim() === '' && lines.slice(0, start).some((l) => l.trim() !== '')) start -= 1
|
|
350
|
+
const next = [...lines.slice(0, start), ...lines.slice(span.end + 1)]
|
|
351
|
+
// 文件撤空(只剩空行)就落空文件,不留一撮换行尾巴
|
|
352
|
+
const empty = next.every((l) => l.trim() === '')
|
|
353
|
+
writePatchLines(dshHome, empty ? [] : collapseBlankRuns(next), eol)
|
|
354
|
+
return true
|
|
355
|
+
}
|
|
356
|
+
// 块内还有其余 preset 的行:只摘指定段,标记与块结构保留
|
|
357
|
+
const removeIds = new Set(targets.map((t) => t.rowId))
|
|
358
|
+
const merged = []
|
|
359
|
+
for (const seg of allSegments) {
|
|
360
|
+
if (!removeIds.has(seg.rowId)) merged.push(...trimTrailingBlankLines(seg.lines))
|
|
361
|
+
}
|
|
362
|
+
const next = [...lines.slice(0, span.begin), MANAGED_BEGIN, ...merged, MANAGED_END, ...lines.slice(span.end + 1)]
|
|
363
|
+
writePatchLines(dshHome, collapseBlankRuns(next), eol)
|
|
364
|
+
return true
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* 读 home patch 托管块里现有的声明行 [{ rowId, presetId }](presetId 取自声明
|
|
369
|
+
* 行 config 的 id: 值,8 空格缩进;解析不出为 null)。文件缺失 / 无托管块 /
|
|
370
|
+
* 托管块损坏一律返回空数组——调用方只拿它做「块在不在」的判断。
|
|
371
|
+
*/
|
|
372
|
+
export function readHomePatchRows(dshHome) {
|
|
373
|
+
let lines
|
|
374
|
+
try {
|
|
375
|
+
({ lines } = readPatchLines(dshHome))
|
|
376
|
+
} catch {
|
|
377
|
+
return []
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const span = findManagedSpan(lines)
|
|
381
|
+
if (span === null) return []
|
|
382
|
+
const configIdRe = /^ {8}id: (\S+)[ \t]*$/
|
|
383
|
+
return parseRowSegments(lines.slice(span.begin + 1, span.end)).map((seg) => {
|
|
384
|
+
const idLine = seg.lines.find((l) => configIdRe.test(l))
|
|
385
|
+
return { rowId: seg.rowId, presetId: idLine ? configIdRe.exec(idLine)[1] : null }
|
|
386
|
+
})
|
|
387
|
+
} catch {
|
|
388
|
+
return []
|
|
389
|
+
}
|
|
390
|
+
}
|
package/tools/lib/state.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 公共状态管线:collectState(patch 分层扫描 / MCP 工具名解析 / 插件与技能检测 /
|
|
3
|
-
* smart defaults,含可选 runtimeFacts 合并 / 已落盘 preset
|
|
4
|
-
* generateAndInstall(compose + yaml 校验 + dst 目录落盘 +
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* smart defaults,含可选 runtimeFacts 合并 / 预设安装轨道探测 / 已落盘 preset
|
|
4
|
+
* 目录扫描)与 generateAndInstall(compose + yaml 校验 + dst 目录落盘 +
|
|
5
|
+
* saveConfig,支持按工作区落到 orchestrator-<slug> 独立 preset 目录并把条目
|
|
6
|
+
* 并进配置 workspaces 段;配置读取失败 / 状态不可信时拒绝破坏性落盘,成功后
|
|
7
|
+
* 回收孤儿工作区 preset;并按 presetSystem 双轨收尾——0.1.7+ 声明行轨把声明行
|
|
8
|
+
* upsert 进 home patch 托管块,≤0.1.6 目录扫描轨撤掉残留声明块自愈)。
|
|
7
9
|
* install.mjs 的公共导出(collectState / generateAndInstall)即来自本模块。
|
|
8
|
-
* 依赖 util / yaml / config / host / skills / spans / compose / alloc /
|
|
9
|
-
* 与 node:fs / node:path;被 cli 与
|
|
10
|
+
* 依赖 util / yaml / config / host / skills / spans / compose / alloc /
|
|
11
|
+
* workspaces / preset-system 与 node:fs / node:path / node:url;被 cli 与
|
|
12
|
+
* plugins/paoding-config-ui 引用。
|
|
10
13
|
*/
|
|
11
14
|
import {
|
|
12
15
|
chmodSync,
|
|
@@ -20,6 +23,7 @@ import {
|
|
|
20
23
|
writeFileSync,
|
|
21
24
|
} from 'node:fs'
|
|
22
25
|
import path from 'node:path'
|
|
26
|
+
import { pathToFileURL } from 'node:url'
|
|
23
27
|
import { DEFAULT_MAIN_AGENT_PERSONA_EXTRA, ROLES, SRC_DIR, warn } from './util.mjs'
|
|
24
28
|
import { flattenEntries, loadYaml, parsePatchFile } from './yaml.mjs'
|
|
25
29
|
import {
|
|
@@ -43,6 +47,14 @@ import { detectSkills, findSkillMeta } from './skills.mjs'
|
|
|
43
47
|
import { locateAllowBlocks, locatePersonaBlocks } from './spans.mjs'
|
|
44
48
|
import { composeGenerated, extractMainAgentAllow } from './compose.mjs'
|
|
45
49
|
import { smartDefaults } from './alloc.mjs'
|
|
50
|
+
import {
|
|
51
|
+
buildPresetRowText,
|
|
52
|
+
detectPresetSystem,
|
|
53
|
+
presetRowId,
|
|
54
|
+
readHomePatchRows,
|
|
55
|
+
stripHomePatchRows,
|
|
56
|
+
upsertHomePatchRows,
|
|
57
|
+
} from './preset-system.mjs'
|
|
46
58
|
|
|
47
59
|
/**
|
|
48
60
|
* Reusable state collection for the CLI and the visual config UI.
|
|
@@ -51,11 +63,13 @@ import { smartDefaults } from './alloc.mjs'
|
|
|
51
63
|
* without writing anything or prompting. Throws on hard errors.
|
|
52
64
|
*
|
|
53
65
|
* `runtimeFacts`(可选,仅 Web 配置器经 ctx 传入)是运行时优先的补充事实:
|
|
54
|
-
* { ok, error?, pluginEntries, toolNames },来自
|
|
55
|
-
* runtime-inventory.mjs
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
66
|
+
* { ok, error?, pluginEntries, toolNames, presetSystem },来自
|
|
67
|
+
* plugins/paoding-config-ui/runtime-inventory.mjs。不传时整段合并逻辑跳过,
|
|
68
|
+
* 行为与旧版完全一致;传入时把 preset 未覆盖的运行时工具并进 inventory /
|
|
69
|
+
* mcpReports / pluginReports,让 bundle 形态加载的插件(@hyzyn/dsh-codegraph、
|
|
70
|
+
* dsh-mnemon 等)也能被识别;presetSystem(预设安装轨道)直接采信,免去
|
|
71
|
+
* dsh --version 子进程探测。文件扫描路径(KNOWN_HOST_PLUGINS 白名单等)
|
|
72
|
+
* 始终保持为兜底,不动。
|
|
59
73
|
*/
|
|
60
74
|
export async function collectState({ dshHome, configFile, profile = null, patches = [], cwd = process.cwd(), runtimeFacts = null }) {
|
|
61
75
|
const dstDir = path.join(dshHome, '.agent-presets', 'orchestrator')
|
|
@@ -211,10 +225,17 @@ export async function collectState({ dshHome, configFile, profile = null, patche
|
|
|
211
225
|
}
|
|
212
226
|
}
|
|
213
227
|
|
|
228
|
+
// 预设安装轨道探测(declarative = 0.1.7+ 声明行轨 / directory = ≤0.1.6 目录
|
|
229
|
+
// 扫描轨)。运行时事实里带 presetSystem(Web 插件对宿主服务的实时探测)时
|
|
230
|
+
// 直接采信零开销;CLI / 首装链路没有运行时事实,走 dsh --version + 文件探测
|
|
231
|
+
//(见 preset-system.detectPresetSystem 的降级链)。
|
|
232
|
+
const presetSystem = await detectPresetSystem({ dshHome, runtimeSystem: runtimeFacts?.presetSystem ?? null })
|
|
233
|
+
|
|
214
234
|
// 已落盘 preset 目录扫描(编排类):.agent-presets 下的 orchestrator 基础
|
|
215
235
|
// preset 与 orchestrator-<slug> 工作区专属 preset。扫描失败(目录尚不存在、
|
|
216
236
|
// 无读权限等)一律降级为空列表,绝不影响检测结果本体;generateAndInstall
|
|
217
|
-
// 成功落盘后以它为对账清单做孤儿工作区 preset
|
|
237
|
+
// 成功落盘后以它为对账清单做孤儿工作区 preset 回收。两轨的产物目录同名同
|
|
238
|
+
// 位置(.agent-presets/<presetId>/),这一扫描天然覆盖两轨的 installed 判定。
|
|
218
239
|
let installedPresets = []
|
|
219
240
|
try {
|
|
220
241
|
installedPresets = readdirSync(path.join(dshHome, '.agent-presets'))
|
|
@@ -248,6 +269,8 @@ export async function collectState({ dshHome, configFile, profile = null, patche
|
|
|
248
269
|
// 已落盘的编排类 preset 目录名列表(含基础 orchestrator 与各工作区专属),
|
|
249
270
|
// 供 UI 的 workspaceMeta 对账「该工作区 preset 是否已生成」。
|
|
250
271
|
installedPresets,
|
|
272
|
+
// 预设安装轨道(generateAndInstall 据此双轨收尾;serializeState 透传给面板)
|
|
273
|
+
presetSystem,
|
|
251
274
|
// 主 persona 尾部追加的默认常量(配置键 main_agent_persona_extra 缺省值):
|
|
252
275
|
// 供 UI state(serializeState → /api/paoding/state)展示与「恢复默认」用。
|
|
253
276
|
mainPersonaExtraDefault: DEFAULT_MAIN_AGENT_PERSONA_EXTRA,
|
|
@@ -259,16 +282,64 @@ export async function collectState({ dshHome, configFile, profile = null, patche
|
|
|
259
282
|
}
|
|
260
283
|
}
|
|
261
284
|
|
|
285
|
+
/**
|
|
286
|
+
* SRC preset.yml 显示元数据提取(name / description / order):声明行轨把这些
|
|
287
|
+
* 值写进 home patch 声明行的 config,取值口径与目录轨写 preset.yml 完全同源
|
|
288
|
+
* (name 可被 main_agent_display_name / 工作区派生名覆盖,见 generateAndInstall
|
|
289
|
+
* 调用处)。preset.yml 是仓库内受控格式:顶层 name / order 单行标量 +
|
|
290
|
+
* description 多行纯量(续行按 YAML 折叠语义以空格拼接成单值,落声明行时由
|
|
291
|
+
* JSON.stringify 转义)。读不到 / 解析不出时返回空元数据——声明行的
|
|
292
|
+
* name / description / order 都是宿主可选键,缺省不致命。
|
|
293
|
+
*/
|
|
294
|
+
function readPresetMeta() {
|
|
295
|
+
try {
|
|
296
|
+
const text = readFileSync(path.join(SRC_DIR, 'preset.yml'), 'utf8')
|
|
297
|
+
const lines = text.split(/\r?\n/)
|
|
298
|
+
const firstMatch = (re) => {
|
|
299
|
+
for (const line of lines) {
|
|
300
|
+
const m = re.exec(line)
|
|
301
|
+
if (m) return m
|
|
302
|
+
}
|
|
303
|
+
return null
|
|
304
|
+
}
|
|
305
|
+
const name = firstMatch(/^name:[ \t]*(.+?)[ \t]*$/)?.[1] ?? null
|
|
306
|
+
const orderRaw = firstMatch(/^order:[ \t]*(\d+)[ \t]*$/)
|
|
307
|
+
const order = orderRaw ? Number(orderRaw[1]) : null
|
|
308
|
+
let description = null
|
|
309
|
+
const di = lines.findIndex((l) => /^description:/.test(l))
|
|
310
|
+
if (di !== -1) {
|
|
311
|
+
const parts = [lines[di].slice('description:'.length).trim()]
|
|
312
|
+
for (let i = di + 1; i < lines.length; i++) {
|
|
313
|
+
const line = lines[i]
|
|
314
|
+
if (line.trim() === '' || !/^[ \t]/.test(line)) break // 续行尽 / 撞上下一个顶层键
|
|
315
|
+
parts.push(line.trim())
|
|
316
|
+
}
|
|
317
|
+
description = parts.filter((p) => p !== '').join(' ') || null
|
|
318
|
+
}
|
|
319
|
+
return { name, description, order }
|
|
320
|
+
} catch {
|
|
321
|
+
return { name: null, description: null, order: null }
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
262
325
|
/**
|
|
263
326
|
* Compose, validate and (unless dry-run) install the generated preset from an
|
|
264
327
|
* assignments object. Shared by the CLI and the visual config UI.
|
|
265
|
-
* Returns { text, roleResults, staleNote, wrote, presetId }.
|
|
328
|
+
* Returns { text, roleResults, staleNote, wrote, presetId, presetSystem }.
|
|
266
329
|
*
|
|
267
330
|
* 破坏性落盘护栏:配置读取失败直接抛错中止(不静默降级 prev=null 继续装);
|
|
268
331
|
* 配置缺失(prev=null)而目标 preset 目录已存在时同样拒绝重建。落盘成功后对
|
|
269
332
|
* state.installedPresets 做孤儿工作区 preset 回收(不被 workspaces 引用的
|
|
270
333
|
* orchestrator-<slug> 目录 rmSync + warn;基础 preset 永不清)。
|
|
271
334
|
*
|
|
335
|
+
* 预设安装双轨收尾(wrote=true 才动,dry-run 零副作用):
|
|
336
|
+
* - declarative(0.1.7+,目录扫描机制已删):把本 preset 以一条 `- insert:`
|
|
337
|
+
* 声明行 upsert 进 $DSH_HOME/cordis.patch.yml 托管块(restrict.mjs 用绝对
|
|
338
|
+
* file: URL);孤儿工作区回收处同步撤对应声明行——目录与行同生同灭。
|
|
339
|
+
* - directory(≤0.1.6):上面落盘即完成;home patch 里若有残留托管声明块
|
|
340
|
+
* (宿主刚从 0.1.7 降级等场景)整块撤下自愈——残留声明行会让 profile 启动
|
|
341
|
+
* 失败。
|
|
342
|
+
*
|
|
272
343
|
* workspacePath(可选):传工作区绝对路径时生成落到 orchestrator-<slug> 独立
|
|
273
344
|
* preset 目录(slug 由磁盘配置现有 workspaces 键 ∪ 本路径统一分配,同名目录
|
|
274
345
|
* 冲突自动加 hash 后缀),preset 显示名按 main_agent_display_name >
|
|
@@ -283,6 +354,10 @@ export function generateAndInstall(state, assignments, { dryRun = false, saveCon
|
|
|
283
354
|
// 一律丢弃,防其混进生成的 preset 或覆盖配置文件里对应的段。
|
|
284
355
|
assignments = stripNonTargetKeys(assignments)
|
|
285
356
|
|
|
357
|
+
// 预设安装轨道(collectState 已探测;缺省按目录轨兜底——安全侧,理由见
|
|
358
|
+
// preset-system 模块头注释)
|
|
359
|
+
const presetSystem = state.presetSystem ?? 'directory'
|
|
360
|
+
|
|
286
361
|
// 工作区定位:路径归一(容忍尾斜杠 / 相对写法),空值一律视为全局。
|
|
287
362
|
const wsPath = workspacePath === null || workspacePath === undefined || workspacePath === ''
|
|
288
363
|
? null
|
|
@@ -452,9 +527,45 @@ export function generateAndInstall(state, assignments, { dryRun = false, saveCon
|
|
|
452
527
|
warn(`孤儿工作区 preset 已回收: ${dir}(配置 workspaces 已无引用)`)
|
|
453
528
|
} catch (err) {
|
|
454
529
|
warn(`孤儿工作区 preset 回收失败(已保留): ${dir}: ${err?.message ?? err}`)
|
|
530
|
+
continue
|
|
455
531
|
}
|
|
532
|
+
if (presetSystem === 'declarative') {
|
|
533
|
+
// 声明行轨:目录回收了,home patch 托管块里的对应声明行同步撤下——
|
|
534
|
+
// 目录与行必须同生同灭,否则 0.1.7 宿主会挂出一个指向已删目录的 preset。
|
|
535
|
+
try {
|
|
536
|
+
stripHomePatchRows(dshHome, [presetRowId(name)])
|
|
537
|
+
} catch (err) {
|
|
538
|
+
warn(`孤儿 preset 的声明行撤除失败(${presetRowId(name)}): ${err?.message ?? err}`)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ── 预设安装双轨收尾(wrote=true 才动;dry-run 零副作用)────────────────
|
|
545
|
+
// declarative(0.1.7+):目录扫描机制已删,必须把本 preset 以一条 `- insert:`
|
|
546
|
+
// 声明行写进 home patch 托管块才算安装完成(preset 元信息与写 preset.yml 同
|
|
547
|
+
// 源:显示名取 displayName ?? SRC preset.yml 的 name,description / order 取
|
|
548
|
+
// SRC;restrict.mjs 用绝对 file: URL——声明行没有「相对 preset 目录」语义)。
|
|
549
|
+
// directory(≤0.1.6):上面落盘即完成;home patch 里若有残留托管声明块
|
|
550
|
+
//(宿主刚从 0.1.7 降级等场景)整块撤下自愈——残留声明行会让 profile 启动失败。
|
|
551
|
+
if (wrote) {
|
|
552
|
+
if (presetSystem === 'declarative') {
|
|
553
|
+
const meta = readPresetMeta()
|
|
554
|
+
upsertHomePatchRows(dshHome, [
|
|
555
|
+
buildPresetRowText({
|
|
556
|
+
presetId,
|
|
557
|
+
name: displayName ?? meta.name,
|
|
558
|
+
description: meta.description,
|
|
559
|
+
order: meta.order,
|
|
560
|
+
agentYmlText: generatedText,
|
|
561
|
+
restrictFileUrl: pathToFileURL(path.join(dstDir, 'restrict.mjs')).href,
|
|
562
|
+
}),
|
|
563
|
+
])
|
|
564
|
+
} else if (readHomePatchRows(dshHome).length > 0) {
|
|
565
|
+
stripHomePatchRows(dshHome, null)
|
|
566
|
+
warn('cordis.patch.yml 检出 dsh-paoding 声明块,但当前宿主是目录扫描轨(DSH ≤ 0.1.6),已整块撤下(残留声明行会导致 profile 启动失败)')
|
|
456
567
|
}
|
|
457
568
|
}
|
|
458
569
|
|
|
459
|
-
return { text: generatedText, roleResults, staleNote, wrote, presetId }
|
|
570
|
+
return { text: generatedText, roleResults, staleNote, wrote, presetId, presetSystem }
|
|
460
571
|
}
|