dsh-paoding 0.3.4 → 0.3.6
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 +4 -4
- package/README_en.md +4 -4
- package/docs/architecture.md +1 -1
- package/docs/architecture_en.md +1 -1
- package/docs/configuration.md +1 -1
- package/docs/installation.md +15 -9
- package/docs/installation_en.md +15 -9
- package/icon.svg +8 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +6 -2
- package/plugins/paoding-config-ui/api-core.mjs +64 -18
- package/plugins/paoding-config-ui/index.mjs +9 -4
- package/plugins/paoding-config-ui/lib/base.css +28 -0
- package/plugins/paoding-config-ui/lib/client.js +38 -4
- package/plugins/paoding-config-ui/package.json +1 -1
- package/tools/lib/preset-system.mjs +174 -7
- package/tools/lib/state.mjs +7 -43
- package/tools/lib/upgrade.mjs +13 -7
- package/tools/lib/version.mjs +58 -30
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ dsh-paoding(中文品牌「庖丁」,典出《庄子》——庖丁顺纹理
|
|
|
17
17
|
- **装了什么都能认出来**:MCP 服务器、本地插件、已装技能,安装与配置时全程检测;默认只装 dsh 基础工具,检测到的 host 工具在「庖丁配置」里按需勾选;停用哪个,重新应用后相关工具自动剔除。
|
|
18
18
|
- **按工作区各有各的班底**:不同项目可以配不同的主/子 agent——「庖丁配置」里选定工作区单独配置,每个工作区生成自己的预设(`orchestrator-<目录名>`),新建会话时选对应预设即可,全局默认不受影响。
|
|
19
19
|
- **图形界面配置**:不习惯改配置文件的话,点开左侧栏底部动作条(设置行上方)的「庖丁配置」入口,整页点选即可,「保存并应用」即时重生成预设——与首装自动化同一条生成管线。
|
|
20
|
-
- **版本更新有提醒,面板一键升级**:面板会对比 npm registry(GitHub release
|
|
20
|
+
- **版本更新有提醒,面板一键升级**:面板会对比 npm registry(npmmirror 镜像、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
|
+
- **两代宿主都兼容**:DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 / 0.1.7-rc.2 全支持——两代宿主发现预设的机制不同(目录扫描 vs patch 声明行),安装器自动检测宿主版本选对落点,宿主升级、降级后的首次保存/自愈自动迁移,全程无感。
|
|
23
23
|
|
|
24
24
|
## 插件截图
|
|
25
25
|
|
|
@@ -27,7 +27,7 @@ dsh-paoding(中文品牌「庖丁」,典出《庄子》——庖丁顺纹理
|
|
|
27
27
|
|
|
28
28
|
## 快速开始
|
|
29
29
|
|
|
30
|
-
前置:**Node.js ≥ 18**、**pnpm** 与一台已装好的 DSH host(**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.
|
|
30
|
+
前置:**Node.js ≥ 18**、**pnpm** 与一台已装好的 DSH host(**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.2** 全支持,预设落点自动适配两代宿主机制)。
|
|
31
31
|
|
|
32
32
|
**官方插件通道(唯一安装入口)**——不用克隆仓库,一条命令,装完重启即用:
|
|
33
33
|
|
|
@@ -80,7 +80,7 @@ dsh plugin --profile web add link:"$PWD/dsh-paoding"
|
|
|
80
80
|
| 文档 | 内容 |
|
|
81
81
|
|---|---|
|
|
82
82
|
| [架构](docs/architecture.md) | 为什么这样设计——概述、成本账、角色与工具、对应 DSH 原生机制、Token 治理、已知边界。 |
|
|
83
|
-
| [安装](docs/installation.md) | 官方插件通道一条命令安装、版本支持与预设落点双轨(DSH 0.1.5 – 0.1.7-rc.
|
|
83
|
+
| [安装](docs/installation.md) | 官方插件通道一条命令安装、版本支持与预设落点双轨(DSH 0.1.5 – 0.1.7-rc.2)、首装自动化与版本标记、升级、开发检出自装、host patch 检测机制、庖丁配置面板、卸载、排查。 |
|
|
84
84
|
| [编排](docs/orchestration.md) | 编排总览、失败如何呈现与三层处理、one-shot vs continuable、上下文隔离。 |
|
|
85
85
|
| [配置](docs/configuration.md) | 配置键全参考、内置角色微调、主 agent 工具与技能、自定义角色、按角色分模型、角色会话模式。 |
|
|
86
86
|
|
package/README_en.md
CHANGED
|
@@ -17,9 +17,9 @@ dsh-paoding (Chinese brand 「庖丁」/ Pao Ding, from the Zhuangzi parable of
|
|
|
17
17
|
- **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
18
|
- **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
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
|
-
- **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
|
|
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
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
|
+
- **Works on both host generations**: DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 / 0.1.7-rc.2 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.
|
|
23
23
|
|
|
24
24
|
## Screenshots
|
|
25
25
|
|
|
@@ -27,7 +27,7 @@ dsh-paoding (Chinese brand 「庖丁」/ Pao Ding, from the Zhuangzi parable of
|
|
|
27
27
|
|
|
28
28
|
## Quick Start
|
|
29
29
|
|
|
30
|
-
Prerequisites: **Node.js ≥ 18**, **pnpm**, and a working DSH host (**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.
|
|
30
|
+
Prerequisites: **Node.js ≥ 18**, **pnpm**, and a working DSH host (**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.2**, all supported — the preset landing adapts to the host generation automatically).
|
|
31
31
|
|
|
32
32
|
**The official plugin channel (the only install route)** — no clone needed; one command, then a restart:
|
|
33
33
|
|
|
@@ -80,7 +80,7 @@ All preferences live in `~/.dsh/dsh-paoding.config.yml`: role add/remove/tweak,
|
|
|
80
80
|
| Document | Contents |
|
|
81
81
|
|---|---|
|
|
82
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. |
|
|
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.
|
|
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.2), first-install automation and the version marker, upgrading, dev-checkout install, host patch detection, the Paoding Config panel, uninstalling, troubleshooting. |
|
|
84
84
|
| [Orchestration](docs/orchestration_en.md) | Orchestration overview, how failures surface and the three recovery layers, one-shot vs continuable, context isolation. |
|
|
85
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. |
|
|
86
86
|
|
package/docs/architecture.md
CHANGED
|
@@ -269,7 +269,7 @@ grep inline cap 与对照同设计)后,根因锁定为:**默认压缩从
|
|
|
269
269
|
触发),配合 retainRatio 默认保留约 16% 尾部(≈42K)+ 摘要 checkpoint,稳态平均回落到 ~80K 量级。
|
|
270
270
|
压缩每次触发调用一次 LLM 摘要——复用前缀缓存,成本极低——因此触发频繁一点也几乎不额外花钱;
|
|
271
271
|
0.45 是「省 token 与不频繁摘要」之间的平衡点。compaction-basic 另显式钉了 `maxTokens: 8192`:
|
|
272
|
-
DSH 0.1.7 起宿主默认升到 65536,显式值让 0.1.5 – 0.1.7-rc.
|
|
272
|
+
DSH 0.1.7 起宿主默认升到 65536,显式值让 0.1.5 – 0.1.7-rc.2 全系的摘要预算行为保持一致。
|
|
273
273
|
|
|
274
274
|
**参数与效果。**
|
|
275
275
|
|
package/docs/architecture_en.md
CHANGED
|
@@ -352,7 +352,7 @@ performs one LLM summarization call that reuses the prefix cache, so its cost is
|
|
|
352
352
|
somewhat more often costs almost nothing extra; 0.45 is the balance point between saving tokens and not
|
|
353
353
|
summarizing too frequently. compaction-basic also pins `maxTokens: 8192` explicitly: from DSH 0.1.7 the host
|
|
354
354
|
default rises to 65536, and the explicit value keeps the summary-budget behavior identical across
|
|
355
|
-
0.1.5 – 0.1.7-rc.
|
|
355
|
+
0.1.5 – 0.1.7-rc.2.
|
|
356
356
|
|
|
357
357
|
**Parameters and effect.**
|
|
358
358
|
|
package/docs/configuration.md
CHANGED
|
@@ -19,7 +19,7 @@ dsh-paoding 把「按角色分工的编排 preset」做成**配置驱动**:你
|
|
|
19
19
|
| 场景 | 行为 |
|
|
20
20
|
|---|---|
|
|
21
21
|
| Web 面板(左侧栏「庖丁配置」入口) | **主路径**:打开面板会把既有配置加载为当前值,点「保存并应用」写入 preset 并把分配保存到配置文件(默认 `~/.dsh/dsh-paoding.config.yml`,以 `0o600` 权限写入)。手编过配置文件的话,先开面板再应用即可 |
|
|
22
|
-
| 首装自动化 | 插件启动时发现 preset
|
|
22
|
+
| 首装自动化 | 插件启动时发现 preset 缺失、版本标记不符、或宿主预设轨道翻转(只升 dsh 不动插件),自动自愈一次:前者按配置文件重生成(无配置则写基础模板),轨道翻转只按磁盘产物补写缺失的声明行、不重生成内容——日常无需手动触发 |
|
|
23
23
|
| 兜底 CLI(仅限克隆仓库的开发者) | `node tools/install.mjs --auto`:有配置文件则幂等应用,没有则写基础模板;`--dry-run` 只打印检测报告与将生成的 allow / persona 变化,不写任何文件(含配置文件),适合先预览再动手;`--config <file>` 用指定文件替代默认位置 |
|
|
24
24
|
|
|
25
25
|
配置文件**存在但解析失败**是硬错误(直接报错退出);**不存在**则视为无配置(写基础模板)。`profile`、`roles` 等顶层键的语义见下节。
|
package/docs/installation.md
CHANGED
|
@@ -12,14 +12,14 @@
|
|
|
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 0.1.5 – 0.1.7-rc.
|
|
15
|
+
- **已安装 DSH host**(`dsh` 可用,含其 Web/agent 运行时;**@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.2** 全支持,安装器自动适配两代预设机制,详见第 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
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`
|
|
22
|
+
| 本预设安装目标 | `$DSH_HOME/.agent-presets/orchestrator` | 生成产物:`agent.cordis.yml`(重写后的角色配置)、`preset.yml`、`restrict.mjs`、`.generator-version` 版本与轨道标记,是一个静态目录;按工作区配置生成的预设落同一父目录下的 `orchestrator-<slug>` |
|
|
23
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 节 |
|
|
24
24
|
| 配置文件 | `$DSH_HOME/dsh-paoding.config.yml` | 「庖丁配置」面板「保存并应用」写入的角色与工具分配(`0o600`,可手编);首装自动化同样读写它 |
|
|
25
25
|
|
|
@@ -50,9 +50,9 @@ dsh plugin --profile web add dsh-paoding
|
|
|
50
50
|
|
|
51
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
52
|
|
|
53
|
-
### 版本支持与预设落点双轨(DSH 0.1.5 – 0.1.7-rc.
|
|
53
|
+
### 版本支持与预设落点双轨(DSH 0.1.5 – 0.1.7-rc.2)
|
|
54
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 不读这个字段,声明纯属元数据,不影响安装。
|
|
55
|
+
本插件兼容两代宿主:**DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 / 0.1.7-rc.2 全部支持**,安装与使用流程完全一致。插件 0.3.4 起,package.json 显式声明 `peerDependencies: @deepseek-ai/dsh >= 0.1.5`——0.1.7 起宿主装插件前做兼容性预检,无声明等于默认放行,显式声明后按真实兼容面把关;0.1.5 / 0.1.6 不读这个字段,声明纯属元数据,不影响安装。
|
|
56
56
|
|
|
57
57
|
两代宿主发现「本地 authored preset」的机制不同,安装器据此分双轨落点,宿主检测全自动、用户无感:
|
|
58
58
|
|
|
@@ -80,11 +80,17 @@ dsh plugin --profile web add dsh-paoding
|
|
|
80
80
|
|
|
81
81
|
插件启动时自动检测编排预设是否就位:`$DSH_HOME/.agent-presets/orchestrator` 目录缺失,或其中的 `.generator-version` 标记与插件包版本不符时,自动执行一次等价 `--auto` 的安装——`~/.dsh/dsh-paoding.config.yml` 已存在就按配置应用;没有就写基础模板(只含 DSH 自带的基础工具,见下)。它走的是与面板「保存并应用」同一条检测/生成管线(`collectState` / `generateAndInstall`)——同一管线,但启动自愈的检测是纯文件扫描、不带运行时检测事实,面板则带;host 上装有 bundle 形态插件时,两者的检测库存可能不同,产物随之略有差异。
|
|
82
82
|
|
|
83
|
-
所以 **`dsh plugin add` + 重启 = 完整安装**:预设生成、配置模板落盘、面板可点,一步到位。自动生成失败不会拖垮插件启动——只在 DSH 日志里记一条告警,可到面板手动「保存并应用」补上。启动自愈同时负责双轨落点的维护与跨版本迁移——宿主在 0.1.6 与 0.1.7 之间升级、降级后,首次自愈会自动补写或撤下 `$DSH_HOME/cordis.patch.yml`
|
|
83
|
+
所以 **`dsh plugin add` + 重启 = 完整安装**:预设生成、配置模板落盘、面板可点,一步到位。自动生成失败不会拖垮插件启动——只在 DSH 日志里记一条告警,可到面板手动「保存并应用」补上。启动自愈同时负责双轨落点的维护与跨版本迁移——宿主在 0.1.6 与 0.1.7 之间升级、降级后,首次自愈会自动补写或撤下 `$DSH_HOME/cordis.patch.yml` 里的托管块(见上节「版本支持与预设落点双轨」)。**只升级 dsh、不动插件**时同样如此:宿主轨道翻转后首次启动,自愈按磁盘上已有的预设产物补写缺失的声明行——preset 内容一字节不重生成,各工作区预设一并恢复。
|
|
84
84
|
|
|
85
85
|
### 版本标记与自动重生成
|
|
86
86
|
|
|
87
|
-
`.generator-version`
|
|
87
|
+
`.generator-version` 标记由插件启动自愈在生成 preset 后写入,内容两行:**生成器版本 + 预设安装轨道**(旧版只写版本一行,读到时按「轨道未知」处理,恰是宿主升级后的迁移触发态)。三类触发:
|
|
88
|
+
|
|
89
|
+
- **标记缺失 / 版本不符**(首装、插件升级)→ 整盘重生成。面板「保存并应用」与兜底 CLI 的生成走整目录重建、不写回标记——重建即移除旧标记,下次 DSH 启动自愈发现标记缺失会多补跑一次生成并重写标记(产物相同,只是多跑一轮)。插件升级后标记与包版本不符,下次 DSH 启动自动按新版重生成,「升级后 preset 还是旧版产物」的漂移就此消除。
|
|
90
|
+
- **版本一致、轨道翻转**(只升 dsh 不动插件——升级最常见路径)→ 轻量迁移,不重生成内容:声明轨上按磁盘产物补写缺失的声明行(含各工作区预设),directory 轨上若有残留声明行则整盘重生成撤下(残留会让 0.1.6 profile 启动失败)。
|
|
91
|
+
- **版本轨道都一致** → 对账声明行:缺行补行、目录已删的孤儿行撤行,齐则什么都不动。
|
|
92
|
+
|
|
93
|
+
面板不监视配置文件改动;想立刻按当前配置重新生成,点面板「保存并应用」。
|
|
88
94
|
|
|
89
95
|
### 基础模板与工具勾选
|
|
90
96
|
|
|
@@ -102,19 +108,19 @@ dsh plugin --profile web add dsh-paoding
|
|
|
102
108
|
|
|
103
109
|
## 3. 升级
|
|
104
110
|
|
|
105
|
-
**面板一键升级(推荐)**:面板自动对比 npm registry 上的最新版本(GitHub release
|
|
111
|
+
**面板一键升级(推荐)**:面板自动对比 npm registry 上的最新版本(npmmirror 镜像、GitHub release 依次兜底),发现新版就地提示并附上发布页链接。点「升级」按钮,面板就地执行:
|
|
106
112
|
|
|
107
113
|
```bash
|
|
108
114
|
dsh plugin --profile <name> update dsh-paoding
|
|
109
115
|
```
|
|
110
116
|
|
|
111
|
-
(profile 自动探测,通常无需手填。)升级只换 profile 里的插件包本体,当前进程的代码不变;成功后提示**重启 DSH**,重启时 preset 按 `.generator-version`
|
|
117
|
+
(profile 自动探测,通常无需手填。)升级只换 profile 里的插件包本体,当前进程的代码不变;成功后提示**重启 DSH**,重启时 preset 按 `.generator-version` 标记自动按新版重生成。只升级 dsh 本体(不动插件)同理:重启即触发轨道迁移自愈,无需重装或重新应用。
|
|
112
118
|
|
|
113
119
|
手动升级等价:直接在终端跑上面这条 `dsh plugin update` 命令,同样重启生效。
|
|
114
120
|
|
|
115
121
|
**开发 link 形态**(见第 4 节)版本比较照常(更新卡有新版照常提示),但无法就地升级——升级器要求包真实装在某个 profile 的 node_modules 下,link 直连仓库匹配不到;点「升级」会如实报这一点,并提示手动跑 `dsh plugin add dsh-paoding@latest` 切回 registry 版。
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
检测失败(断网、限流、代理不可用)时页头会留一行弱化的「版本检测失败 · 重试」,完整原因(npm registry / npmmirror / GitHub 三路各自的错误,以及代理排查提示)在悬浮提示里,不弹窗打扰;检测成功且确无新版时不显示任何内容。常见误区:浏览器能打开 npm 页面不代表 DSH 进程检测也能通——DSH 遵循启动环境里的 `HTTPS_PROXY` / `HTTP_PROXY` 等代理变量,浏览器不走它们;若靠代理上网,请确认代理可用后重启 DSH 再试。
|
|
118
124
|
|
|
119
125
|
## 4. 开发自装(link: 形态)
|
|
120
126
|
|
package/docs/installation_en.md
CHANGED
|
@@ -12,14 +12,14 @@ 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 0.1.5 – 0.1.7-rc.
|
|
15
|
+
- **A working DSH host** (the `dsh` command, including its Web/agent runtime; **@deepseek-ai/dsh 0.1.5 – 0.1.7-rc.2**, 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
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 |
|
|
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` version-and-track marker — a static directory; per-workspace presets land in sibling `orchestrator-<slug>` directories |
|
|
23
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 |
|
|
24
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 |
|
|
25
25
|
|
|
@@ -50,9 +50,9 @@ The orchestrator preset needs no separate install — the plugin fills it in aut
|
|
|
50
50
|
|
|
51
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
52
|
|
|
53
|
-
### Version support and the dual preset landing (DSH 0.1.5 – 0.1.7-rc.
|
|
53
|
+
### Version support and the dual preset landing (DSH 0.1.5 – 0.1.7-rc.2)
|
|
54
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.
|
|
55
|
+
The plugin supports two host generations: **DSH 0.1.5 / 0.1.6 / 0.1.7-rc.1 / 0.1.7-rc.2 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
56
|
|
|
57
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
58
|
|
|
@@ -80,11 +80,17 @@ Two cross-version behavior alignments happen the same automatic way, with no use
|
|
|
80
80
|
|
|
81
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.
|
|
82
82
|
|
|
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).
|
|
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). The same holds when **only dsh is upgraded and the plugin untouched**: after the host's preset system flips, the first startup backfills the missing declaration rows from the preset artifacts already on disk — no preset content is regenerated, and per-workspace presets are restored along with the base one.
|
|
84
84
|
|
|
85
85
|
### The version marker and auto-regeneration
|
|
86
86
|
|
|
87
|
-
The `.generator-version` marker
|
|
87
|
+
The `.generator-version` marker is written by the plugin's startup self-heal after it generates the preset. Its content is two lines: **the generator version and the preset system track** (older releases wrote only the version; such markers read back as "track unknown", which is exactly the migration trigger after a host upgrade). Three triggers:
|
|
88
|
+
|
|
89
|
+
- **Marker missing or version-mismatched** (first install, plugin upgrade) → full regeneration. The panel's Save & Apply (and the fallback CLI) do a full-directory rebuild and write no marker — the rebuild removes the old marker, so the next DSH start's self-heal finds it missing, runs one extra generation, and rewrites the marker (same output, just one extra round). After a plugin upgrade the marker no longer matches the package version, so the next DSH start regenerates the preset against the new version — the "upgraded the plugin but the preset is stale" drift is eliminated.
|
|
90
|
+
- **Version matching but the track flipped** (dsh upgraded, plugin untouched — the most common upgrade path) → lightweight migration with no content regeneration: on the declarative track, missing declaration rows are backfilled from the on-disk artifacts (per-workspace presets included); on the directory track, any leftover declaration rows force a full regeneration that strips them (leftovers would break 0.1.6 profile startup).
|
|
91
|
+
- **Version and track both matching** → declaration-row reconciliation: missing rows are backfilled, orphan rows whose directories are gone are removed; if everything lines up, nothing is touched.
|
|
92
|
+
|
|
93
|
+
The panel does not watch the config file; to regenerate from the current config right away, hit Save & Apply.
|
|
88
94
|
|
|
89
95
|
### Base template and tool opt-in
|
|
90
96
|
|
|
@@ -102,19 +108,19 @@ Once installed:
|
|
|
102
108
|
|
|
103
109
|
## 3. Upgrading
|
|
104
110
|
|
|
105
|
-
**One-click upgrade in the panel (recommended)**: the panel compares your local version against the latest npm registry publish (GitHub release as
|
|
111
|
+
**One-click upgrade in the panel (recommended)**: 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. Click the Upgrade button and the panel runs this in place:
|
|
106
112
|
|
|
107
113
|
```bash
|
|
108
114
|
dsh plugin --profile <name> update dsh-paoding
|
|
109
115
|
```
|
|
110
116
|
|
|
111
|
-
(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.
|
|
117
|
+
(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.
|
|
112
118
|
|
|
113
119
|
Manual equivalent: run the `dsh plugin update` command above yourself, restart, done.
|
|
114
120
|
|
|
115
121
|
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.
|
|
116
122
|
|
|
117
|
-
If the check fails (offline, rate-limited),
|
|
123
|
+
If the check fails (offline, rate-limited, proxy down), a subdued "version check failed · retry" row stays in the panel header with the full causes (per-source errors across npm registry / npmmirror / GitHub, plus proxy troubleshooting hints) in its tooltip; a successful check with no new version shows nothing. Common pitfall: being able to open the npm page in a browser does not mean the DSH process can reach it — DSH honors HTTPS_PROXY / HTTP_PROXY from its launch environment while browsers do not. If you rely on a proxy, make sure it is up and restart DSH before retrying.
|
|
118
124
|
|
|
119
125
|
## 4. Dev-checkout install (link: form)
|
|
120
126
|
|
package/icon.svg
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#4D6BFE"/>
|
|
3
|
+
<g transform="rotate(-45 32 32)">
|
|
4
|
+
<rect x="14" y="11" width="32" height="22" rx="3" fill="#FFFFFF"/>
|
|
5
|
+
<circle cx="41" cy="16.5" r="2" fill="#4D6BFE"/>
|
|
6
|
+
<rect x="44" y="17" width="13" height="10" rx="4" fill="#FFFFFF" opacity="0.85"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-paoding",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "庖丁 — DSH (DeepSeek Harness) 编排模式安装器:host 感知的角色工具过滤 + Web 设置页可视化配置器",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"icon": "./icon.svg",
|
|
7
8
|
"files": [
|
|
8
9
|
"bin/",
|
|
9
10
|
"tools/",
|
|
@@ -11,6 +12,8 @@
|
|
|
11
12
|
"plugins/",
|
|
12
13
|
"!plugins/paoding-config-ui/src",
|
|
13
14
|
"docs/",
|
|
15
|
+
"locale/",
|
|
16
|
+
"icon.svg",
|
|
14
17
|
"README.md",
|
|
15
18
|
"README_en.md",
|
|
16
19
|
"LICENSE",
|
|
@@ -43,7 +46,8 @@
|
|
|
43
46
|
"exports": {
|
|
44
47
|
".": "./plugins/paoding-config-ui/index.mjs",
|
|
45
48
|
"./client": "./plugins/paoding-config-ui/lib/client.js",
|
|
46
|
-
"./package.json": "./package.json"
|
|
49
|
+
"./package.json": "./package.json",
|
|
50
|
+
"./locale/*.json": "./locale/*.json"
|
|
47
51
|
},
|
|
48
52
|
"dsh": {
|
|
49
53
|
"client": {
|
|
@@ -12,6 +12,14 @@ import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'n
|
|
|
12
12
|
import { readFile } from 'node:fs/promises'
|
|
13
13
|
import { assignSlugs, collectState, generateAndInstall, presetIdOf } from '../../tools/install.mjs'
|
|
14
14
|
import { autoAssignments } from '../../tools/lib/alloc.mjs'
|
|
15
|
+
import {
|
|
16
|
+
backfillHomePatchRows,
|
|
17
|
+
detectPresetSystem,
|
|
18
|
+
formatGeneratorMarker,
|
|
19
|
+
listOrchestratorPresetIds,
|
|
20
|
+
parseGeneratorMarker,
|
|
21
|
+
readHomePatchRows,
|
|
22
|
+
} from '../../tools/lib/preset-system.mjs'
|
|
15
23
|
|
|
16
24
|
export function resolveDshHome() {
|
|
17
25
|
return process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
|
@@ -142,10 +150,11 @@ export function installAssignments(state, assignments, { dryRun = false, saveCon
|
|
|
142
150
|
// ── 首装 / 升级后 preset 自动重生成 ─────────────────────────────────────────
|
|
143
151
|
//
|
|
144
152
|
// 唯一官方安装通道是插件通道(dsh plugin add dsh-paoding),包不再携带 CLI:
|
|
145
|
-
// preset
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
153
|
+
// preset 的落盘改由插件启动时自愈。对账基准是 preset 目录里的 .generator-version
|
|
154
|
+
// 标记文件(v2 起内容 = 生成器版本 + 预设安装轨道两行):插件包版本变、或宿主
|
|
155
|
+
// 轨道翻转(只升 dsh 不动插件——升级最常见路径,旧标记只记版本时同样解析为
|
|
156
|
+
// 轨道不符)→ 下次启动自动自愈,一举解决「升级后 preset 还是旧版产物」与
|
|
157
|
+
// 「升 dsh 后 preset 静默消失」两类漂移。
|
|
149
158
|
|
|
150
159
|
// 生成器版本(标记对账基准):插件包根 package.json 读一次即缓存。读失败兜底
|
|
151
160
|
// '0.0.0'——最坏后果只是每次启动多重生成一次 preset,不会装坏任何东西。
|
|
@@ -165,23 +174,30 @@ export function pluginVersion() {
|
|
|
165
174
|
const PRESET_ENSURE_ABORTED = '插件已卸载(dispose),本次 preset 生成中止'
|
|
166
175
|
|
|
167
176
|
/**
|
|
168
|
-
* 首装 /
|
|
177
|
+
* 首装 / 升级后自动安装 orchestrator preset(等价 install --auto 语义)。
|
|
169
178
|
*
|
|
170
|
-
*
|
|
171
|
-
* 不符 →
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
179
|
+
* 判定(满足其一即动盘):
|
|
180
|
+
* - preset 目录 / 标记缺失,或标记版本与 generatorVersion 不符 → 整盘重生成;
|
|
181
|
+
* - 版本一致但标记轨道与当前宿主轨道不符(只升 dsh 不动插件 / 旧格式标记)→
|
|
182
|
+
* 轻量迁移:声明轨上按磁盘产物补写缺失声明行(内容字节不动,不重生成),
|
|
183
|
+
* directory 轨上落回整盘重生成(残留声明行会让 0.1.6 profile 启动失败,
|
|
184
|
+
* 撤块自愈由其 directory 分支完成);
|
|
185
|
+
* - 版本轨道都一致 → 对账声明行(缺行补行、孤儿行撤行),齐则跳过。
|
|
186
|
+
* 局部 collectState() 不带运行时事实(首装触发点在插件启动、宿主服务未必就绪,
|
|
187
|
+
* 且与 CLI --auto 语义对齐;预设安装轨道由 collectState 内部按 argv 宿主版本 /
|
|
188
|
+
* dsh --version / 文件探测兜底,state.presetSystem 随 state 一并进
|
|
189
|
+
* generateAndInstall 双轨收尾;不走 refreshState()——那会拿无事实的扫描覆盖
|
|
190
|
+
* 路由层的共享缓存)后按既有配置合成 assignments(无配置写基础模板),经
|
|
191
|
+
* generateAndInstall 落盘,最后写标记文件(版本 + 轨道)。
|
|
177
192
|
* shouldAbort 在各 await 点后检查:插件 dispose 后在途的生成不再继续写盘。
|
|
178
193
|
* 可脱离 ctx 独立调用(单测友好),任何异常自捕、绝不抛出。
|
|
179
194
|
*
|
|
180
195
|
* @param {{dshHome?: string, generatorVersion?: string, shouldAbort?: () => boolean}} opts
|
|
181
196
|
* 缺省取 resolveDshHome() 与 pluginVersion();shouldAbort 返回 true 时中止
|
|
182
197
|
* (按 reason 返回,不算 error)。
|
|
183
|
-
* @returns {Promise<{installed: boolean, reason?: string, error?: string}>}
|
|
184
|
-
* installed=true 表示本次实际重生成;
|
|
198
|
+
* @returns {Promise<{installed: boolean, backfilled?: string[], reason?: string, error?: string}>}
|
|
199
|
+
* installed=true 表示本次实际重生成;backfilled 非空表示本次补写了哪些
|
|
200
|
+
* preset 的声明行(内容未重生成);两者皆否时带 reason(无需动盘 / 已中止)
|
|
185
201
|
* 或 error(生成失败,调用方只记日志)。
|
|
186
202
|
*/
|
|
187
203
|
export async function ensurePresetInstalled({ dshHome = resolveDshHome(), generatorVersion = pluginVersion(), shouldAbort = null } = {}) {
|
|
@@ -190,10 +206,35 @@ export async function ensurePresetInstalled({ dshHome = resolveDshHome(), genera
|
|
|
190
206
|
const markerFile = path.join(presetDir, '.generator-version')
|
|
191
207
|
let marker = null
|
|
192
208
|
try {
|
|
193
|
-
marker = (await readFile(markerFile, 'utf8'))
|
|
209
|
+
marker = parseGeneratorMarker(await readFile(markerFile, 'utf8'))
|
|
194
210
|
} catch { /* 目录或标记缺失:按未生成处理 */ }
|
|
195
|
-
|
|
196
|
-
|
|
211
|
+
|
|
212
|
+
if (marker !== null && marker.version === generatorVersion) {
|
|
213
|
+
// 版本一致:内容无需重生成,但宿主轨道可能已随 dsh 升降级翻转(只升 dsh
|
|
214
|
+
// 不动插件是升级常态),声明行也可能被外部动过——这里做轻量对账。
|
|
215
|
+
const system = await detectPresetSystem({ dshHome })
|
|
216
|
+
if (shouldAbort?.()) return { installed: false, reason: PRESET_ENSURE_ABORTED }
|
|
217
|
+
const presetIds = listOrchestratorPresetIds(dshHome)
|
|
218
|
+
if (system === 'declarative') {
|
|
219
|
+
const backfilled = backfillHomePatchRows(dshHome, presetIds)
|
|
220
|
+
if (marker.track === 'declarative' && backfilled.length === 0) {
|
|
221
|
+
return { installed: false, reason: 'preset 已由当前版本生成器产出,无需重生成' }
|
|
222
|
+
}
|
|
223
|
+
writeFileSync(markerFile, formatGeneratorMarker(generatorVersion, 'declarative'), 'utf8')
|
|
224
|
+
return backfilled.length > 0
|
|
225
|
+
? { installed: false, backfilled, reason: `宿主已切换声明行轨,补写 ${backfilled.length} 条预设声明行(preset 内容未重生成)` }
|
|
226
|
+
: { installed: false, reason: '预设安装轨道标记已更新为声明行轨(内容无需重生成)' }
|
|
227
|
+
}
|
|
228
|
+
// directory 轨:声明行残留会让 0.1.6 profile 启动失败——有残留走整盘
|
|
229
|
+
// 重生成(directory 分支撤块自愈);行清白时只升级标记格式,不动内容。
|
|
230
|
+
if (readHomePatchRows(dshHome).length === 0) {
|
|
231
|
+
if (marker.track === 'directory') {
|
|
232
|
+
return { installed: false, reason: 'preset 已由当前版本生成器产出,无需重生成' }
|
|
233
|
+
}
|
|
234
|
+
writeFileSync(markerFile, formatGeneratorMarker(generatorVersion, 'directory'), 'utf8')
|
|
235
|
+
return { installed: false, reason: '预设安装轨道标记已升级为带轨道格式(内容无需重生成)' }
|
|
236
|
+
}
|
|
237
|
+
// 声明行残留 + directory 宿主:落到下方整盘重生成
|
|
197
238
|
}
|
|
198
239
|
|
|
199
240
|
if (shouldAbort?.()) return { installed: false, reason: PRESET_ENSURE_ABORTED }
|
|
@@ -220,9 +261,14 @@ export async function ensurePresetInstalled({ dshHome = resolveDshHome(), genera
|
|
|
220
261
|
// 标记最后写:generateAndInstall 会整目录重建(rmSync),先写必被删。
|
|
221
262
|
if (shouldAbort?.()) return { installed: false, reason: PRESET_ENSURE_ABORTED }
|
|
222
263
|
mkdirSync(presetDir, { recursive: true })
|
|
223
|
-
|
|
264
|
+
const track = result.presetSystem === 'declarative' ? 'declarative' : 'directory'
|
|
265
|
+
writeFileSync(markerFile, formatGeneratorMarker(generatorVersion, track), 'utf8')
|
|
266
|
+
// 声明轨上整盘重生成只重建本 preset 的行:其余工作区 preset 目录若在而
|
|
267
|
+
// 声明行缺(轨道翻转后首次触发本路径),一并补齐。
|
|
268
|
+
const backfilled = track === 'declarative' ? backfillHomePatchRows(dshHome, listOrchestratorPresetIds(dshHome)) : []
|
|
224
269
|
return {
|
|
225
270
|
installed: true,
|
|
271
|
+
...(backfilled.length > 0 ? { backfilled } : {}),
|
|
226
272
|
reason: state.existing
|
|
227
273
|
? 'preset 缺失或由旧版生成器产出,已按现有配置重新生成'
|
|
228
274
|
: 'preset 缺失或由旧版生成器产出,已写入基础模板',
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* upgrade/preview/apply/rescan + client.css 静态样式),经
|
|
7
7
|
* ./api-core.mjs 复用 tools/install.mjs 的检测与生成管线;workspaces 直连
|
|
8
8
|
* 宿主 workspaceRegistry 服务(list / create,幂等);version
|
|
9
|
-
* 复用 tools/lib/version.mjs 的版本检测(npm registry
|
|
10
|
-
*
|
|
9
|
+
* 复用 tools/lib/version.mjs 的版本检测(npm registry 优先,npmmirror
|
|
10
|
+
* 镜像、GitHub release 依次兜底);upgrade 经 tools/lib/upgrade.mjs
|
|
11
|
+
* spawn `dsh plugin update` 完成
|
|
11
12
|
* 一键升级。首装 / 升级后 preset 自动重生成(ensurePresetInstalled,等价
|
|
12
13
|
* install --auto 语义)在路由注册完成后异步触发,不阻塞启动。
|
|
13
14
|
* - Client 侧:lib/client.js 经宿主 sidebar.footer.action 键控槽在左侧栏
|
|
@@ -175,12 +176,16 @@ function triggerPresetEnsure(ctx, isDisposed) {
|
|
|
175
176
|
ctx.logger?.info?.(
|
|
176
177
|
`paoding-config-ui: orchestrator preset 已重生成(generator v${generatorVersion},${r.reason})`,
|
|
177
178
|
)
|
|
179
|
+
} else if (Array.isArray(r.backfilled) && r.backfilled.length > 0) {
|
|
180
|
+
// 轨道迁移 / 声明行对账补写(preset 内容未重生成):值得留一行轨迹,
|
|
181
|
+
// 方便「升级 dsh 后主子代理消失」这类问题定位。
|
|
182
|
+
ctx.logger?.info?.(`paoding-config-ui: ${r.reason ?? '预设声明行已补写'}(${r.backfilled.join('、')})`)
|
|
178
183
|
} else if (r.error) {
|
|
179
184
|
// 失败复位防抖闩:下次 activate(热重载 / 重注册)可重试
|
|
180
185
|
presetEnsureStarted = false
|
|
181
186
|
ctx.logger?.warn?.(`paoding-config-ui: preset 自动生成失败(可在侧栏底部「庖丁配置」入口手动应用): ${r.error}`)
|
|
182
187
|
}
|
|
183
|
-
//
|
|
188
|
+
// 版本轨道一致的常规跳过 / 纯标记格式升级不打日志:每次启动都刷一行纯噪音。
|
|
184
189
|
})
|
|
185
190
|
.catch((err) => {
|
|
186
191
|
presetEnsureStarted = false
|
|
@@ -526,7 +531,7 @@ export function apply(ctx) {
|
|
|
526
531
|
}
|
|
527
532
|
|
|
528
533
|
if (route === 'version' && req.method === 'GET') {
|
|
529
|
-
// 版本提示:npm registry
|
|
534
|
+
// 版本提示:npm registry 优先,npmmirror / GitHub 依次兜底(version.mjs,
|
|
530
535
|
// 带 1 小时内存缓存,命中不发外网请求)。绝不 5xx——检测失败也回
|
|
531
536
|
// 200 + error 字段,面板据此静默降级(不打扰使用)。timeoutMs 3000:
|
|
532
537
|
// 检测只是锦上添花,不值得让面板等更久。layout 告知客户端挂载形态
|
|
@@ -149,6 +149,34 @@
|
|
|
149
149
|
|
|
150
150
|
.omd-upgradeBtn:focus-visible { outline: 2px solid var(--dsw-alias-border-l4); outline-offset: 1px; }
|
|
151
151
|
|
|
152
|
+
/* 版本检测失败行(弱化):比更新警示卡低一档的存在感——三级文字色小字
|
|
153
|
+
⚠ + 重试小 pill,常态(检测成功且无新版)不渲染。完整失败原因(三路源
|
|
154
|
+
各自错误与代理排查提示)收在行上的 title 悬浮提示,不占版面。 */
|
|
155
|
+
.omd-versionFail { gap: 4px; }
|
|
156
|
+
|
|
157
|
+
.omd-retryBtn {
|
|
158
|
+
flex: none;
|
|
159
|
+
padding: 1px 10px;
|
|
160
|
+
border: 1px solid var(--dsw-alias-border-l3);
|
|
161
|
+
border-radius: 999px;
|
|
162
|
+
background: transparent;
|
|
163
|
+
color: var(--dsw-alias-label-secondary);
|
|
164
|
+
font: inherit;
|
|
165
|
+
font-size: 11px;
|
|
166
|
+
line-height: 16px;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
transition: color 0.12s ease, border-color 0.12s ease;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.omd-retryBtn:hover:not(:disabled) {
|
|
172
|
+
color: var(--dsw-alias-label-primary);
|
|
173
|
+
border-color: var(--dsw-alias-border-l4);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.omd-retryBtn:disabled { opacity: 0.6; cursor: default; }
|
|
177
|
+
|
|
178
|
+
.omd-retryBtn:focus-visible { outline: 2px solid var(--dsw-alias-border-l4); outline-offset: 1px; }
|
|
179
|
+
|
|
152
180
|
/* 升级进行中的补充说明(分钟级操作,管理预期)。 */
|
|
153
181
|
.omd-upgradeHint {
|
|
154
182
|
flex: none;
|
|
@@ -236,8 +236,8 @@ window.__ModuleLoader__.load({
|
|
|
236
236
|
// 版本提示(渲染在页头标题旁,由 PageShell 持有):常态版本号不进正文;
|
|
237
237
|
// updateAvailable 时这里渲染醒目提示卡:releaseUrl 新标签页打开 release 页、
|
|
238
238
|
// 「升级」按钮走 onUpgrade(一键 dsh plugin update 自升级),并保留当前版本号。
|
|
239
|
-
// latest 为 null
|
|
240
|
-
//
|
|
239
|
+
// latest 为 null 一律不显示新版部分;检测失败(error 非空)改渲染弱化失败行
|
|
240
|
+
// (⚠ 版本检测失败 · 重试,完整原因在 title 悬浮提示),不再整行静默。
|
|
241
241
|
// opts(可省):busy/busyOp 复用面板级互斥(任何操作进行中都禁用升级,
|
|
242
242
|
// 升级进行中也反过来禁用其他按钮),onUpgrade 为点击回调。
|
|
243
243
|
// ── 服务端 API 助手(PageShell 与 ConfigPanel 共用)──────────────────────
|
|
@@ -325,9 +325,28 @@ window.__ModuleLoader__.load({
|
|
|
325
325
|
// 渲染并持有升级动作,面板正文不再重复版本信息;当前版本号就在旁边的
|
|
326
326
|
// pd-versionTag 上,卡内不再赘述「(当前 vX)」。note / error 为升级
|
|
327
327
|
// 反馈行(成功提示 / 失败原因,含子进程输出尾部),也收在卡内。
|
|
328
|
+
// 检测失败(v.error 非空且无新版信息)不再全静默:弱化小字「版本检测
|
|
329
|
+
// 失败 · 重试」——否则「没新版」与「没测出来」无从区分(真实事故:用户
|
|
330
|
+
// 端 0.3.2 对 0.3.5 迟迟看不到升级卡,只能靠作者远程猜)。检测成功且确
|
|
331
|
+
// 无新版仍静默(常态不打扰)。完整失败原因(三路源各自错误 + 代理提示)
|
|
332
|
+
// 收在 title 悬浮提示里,不占版面。
|
|
328
333
|
function versionRowOf(v, opts) {
|
|
329
334
|
if (!v || typeof v.current !== "string" || !v.current) return null;
|
|
330
|
-
if (!(v.updateAvailable && v.latest))
|
|
335
|
+
if (!(v.updateAvailable && v.latest)) {
|
|
336
|
+
if (typeof v.error !== "string" || !v.error) return null;
|
|
337
|
+
var retryBusy = !!(opts && opts.retryBusy);
|
|
338
|
+
return h("span", { className: "omd-versionRow omd-versionFail", title: v.error },
|
|
339
|
+
h("span", { className: "omd-versionIcon" }, "⚠"),
|
|
340
|
+
h("span", { className: "omd-versionText" }, "版本检测失败"),
|
|
341
|
+
typeof (opts && opts.onRetry) === "function"
|
|
342
|
+
? h("button", {
|
|
343
|
+
type: "button",
|
|
344
|
+
className: "omd-retryBtn",
|
|
345
|
+
disabled: retryBusy,
|
|
346
|
+
onClick: opts.onRetry,
|
|
347
|
+
}, retryBusy ? "检测中…" : "重试")
|
|
348
|
+
: null);
|
|
349
|
+
}
|
|
331
350
|
var busy = !!opts && !!opts.busy;
|
|
332
351
|
var note = (opts && opts.note) || "";
|
|
333
352
|
var error = (opts && opts.error) || "";
|
|
@@ -3042,11 +3061,26 @@ window.__ModuleLoader__.load({
|
|
|
3042
3061
|
var _ub = useState(false), upgBusy = _ub[0], setUpgBusy = _ub[1];
|
|
3043
3062
|
var _un = useState(""), upgNote = _un[0], setUpgNote = _un[1];
|
|
3044
3063
|
var _ue = useState(""), upgErr = _ue[0], setUpgErr = _ue[1];
|
|
3064
|
+
// 版本检测重试进行中(页头失败行的「重试」按钮禁用态):与升级三态分开,
|
|
3065
|
+
// 检测失败重试不该锁升级(升级预检在服务端另做一次,不依赖这份 verInfo)。
|
|
3066
|
+
var _vb = useState(false), verRetryBusy = _vb[0], setVerRetryBusy = _vb[1];
|
|
3045
3067
|
|
|
3046
3068
|
// 一键升级逻辑在共享的 runUpgradeFlow(页头更新卡统一走这一份)。
|
|
3047
3069
|
function doUpgrade() {
|
|
3048
3070
|
runUpgradeFlow(verInfo, setVerInfo, setUpgBusy, setUpgNote, setUpgErr);
|
|
3049
3071
|
}
|
|
3072
|
+
|
|
3073
|
+
// 检测失败重试:直接再拉一次 /api/paoding/version(服务端失败结果不进
|
|
3074
|
+
// 缓存,重试即真实检测;成功则 error 随新响应自然清空)。仍失败维持
|
|
3075
|
+
// 原提示——失败行常驻,重试按钮永远可用。
|
|
3076
|
+
function doRetryVersion() {
|
|
3077
|
+
setVerRetryBusy(true);
|
|
3078
|
+
paodingApi("version").then(function (d) {
|
|
3079
|
+
if (d && typeof d.current === "string" && d.current) setVerInfo(d);
|
|
3080
|
+
}).catch(function () { /* 仍不通:维持失败行,不额外打扰 */ }).finally(function () {
|
|
3081
|
+
setVerRetryBusy(false);
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3050
3084
|
useEffect(function () {
|
|
3051
3085
|
var onKey = function (e) {
|
|
3052
3086
|
if (e && e.key === "Escape" && typeof onClose === "function") onClose();
|
|
@@ -3064,7 +3098,7 @@ window.__ModuleLoader__.load({
|
|
|
3064
3098
|
verInfo && typeof verInfo.current === "string" && verInfo.current
|
|
3065
3099
|
? h("span", { className: "pd-versionTag" }, versionTagOf(verInfo.current))
|
|
3066
3100
|
: null),
|
|
3067
|
-
versionRowOf(verInfo, { busy: upgBusy, onUpgrade: doUpgrade, note: upgNote, error: upgErr })),
|
|
3101
|
+
versionRowOf(verInfo, { busy: upgBusy, onUpgrade: doUpgrade, note: upgNote, error: upgErr, onRetry: doRetryVersion, retryBusy: verRetryBusy })),
|
|
3068
3102
|
h("p", { className: "pd-pageSubtitle" },
|
|
3069
3103
|
"为主 agent 与子 agent 编排工具、技能与角色,预览并应用生成的 agent.cordis.yml。")),
|
|
3070
3104
|
h("button", {
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* name: '@deepseek-ai/dsh-agent-preset' 的行(config = { id 必填, name?,
|
|
6
6
|
* description?, order?, plugins[] 必填 },宿主 agent-preset-registry 挂载
|
|
7
7
|
* plugins 子树;行 name 相对声明行 baseUrl 解析,绝对 file: URL 直接采用)。
|
|
8
|
-
*
|
|
8
|
+
* 本模块为双轨化提供四件事:
|
|
9
9
|
* 1) 轨道探测 detectPresetSystem:运行时探测(Web 插件对宿主服务的实时反射,
|
|
10
|
-
* 最权威)>
|
|
10
|
+
* 最权威)> 进程 argv 宿主版本(插件进程内零开销,反映正在运行的宿主而非
|
|
11
|
+
* PATH 上的 dsh)> `dsh --version` 版本号 > $DSH_HOME 单数包存在性 > 默认
|
|
11
12
|
* directory(安全侧:声明行误落 0.1.6 会炸 profile 启动,目录轨误落
|
|
12
13
|
* 0.1.7 只是预设不显示);
|
|
13
14
|
* 2) 声明行构造 buildPresetRowText:把生成的 agent.cordis.yml 全文内联成
|
|
@@ -17,11 +18,16 @@
|
|
|
17
18
|
* readHomePatchRows:对 $DSH_HOME/cordis.patch.yml 的 MANAGED 标记块做
|
|
18
19
|
* 行级增删查,块外用户内容一字节不动;托管块损坏(缺结束标记)时拒绝
|
|
19
20
|
* 写盘并明确报错,与仓库「配置不可信时拒绝落盘」口径一致。
|
|
20
|
-
*
|
|
21
|
+
* 4) 轨道迁移对账 backfillHomePatchRows / listOrchestratorPresetIds /
|
|
22
|
+
* parseGeneratorMarker / formatGeneratorMarker:宿主只升 dsh 不动插件时
|
|
23
|
+
* (标记版本一致、轨道翻转——升级最常见路径),按磁盘产物补写缺失声明行
|
|
24
|
+
* 而不整盘重生成;生成器标记记「版本 + 轨道」两行,任一不符即触发自愈。
|
|
25
|
+
* 零运行时依赖(node:child_process / node:fs / node:path / node:url);被 state 引用。
|
|
21
26
|
*/
|
|
22
27
|
import { spawnSync } from 'node:child_process'
|
|
23
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
24
29
|
import path from 'node:path'
|
|
30
|
+
import { pathToFileURL } from 'node:url'
|
|
25
31
|
import { warn } from './util.mjs'
|
|
26
32
|
|
|
27
33
|
/** 托管块起始标记:其后到结束标记之间是本工具自动生成的声明行,用户勿改。 */
|
|
@@ -62,14 +68,22 @@ export function isDeclarativeTriple(triple) {
|
|
|
62
68
|
/**
|
|
63
69
|
* 探测当前宿主的预设安装轨道,返回 'declarative' | 'directory'。逐级降级:
|
|
64
70
|
* 1) runtimeSystem 直通:Web 插件运行时对宿主服务的实时探测结果最权威;
|
|
65
|
-
* 2)
|
|
71
|
+
* 2) 进程 argv 宿主版本:插件与宿主同进程时零开销、且反映正在运行的宿主
|
|
72
|
+
* (PATH 上的 dsh 可能已换代而旧进程还在跑,此时以 argv 为准才不会把
|
|
73
|
+
* 声明行提前写出去);CLI / 测试进程不是 dsh,返回 null 落到下一级;
|
|
74
|
+
* 3) `dsh --version`(timeout 10s;spawn 失败 / 超时 / 输出不可解析一律吞掉
|
|
66
75
|
* 落到下一级);
|
|
67
|
-
*
|
|
76
|
+
* 4) 文件探测:单数包 @deepseek-ai/dsh-agent-preset 是 0.1.7 的声明行宿主
|
|
68
77
|
* 组件,在即声明轨(复数包时代没有它);
|
|
69
|
-
*
|
|
78
|
+
* 5) 默认 directory —— 安全侧兜底(理由见模块头注释)。
|
|
70
79
|
*/
|
|
71
80
|
export async function detectPresetSystem({ dshHome, runtimeSystem = null } = {}) {
|
|
72
81
|
if (runtimeSystem === 'declarative' || runtimeSystem === 'directory') return runtimeSystem
|
|
82
|
+
const argvVersion = hostVersionFromArgv()
|
|
83
|
+
if (argvVersion !== null) {
|
|
84
|
+
const triple = parseVersionTriple(argvVersion)
|
|
85
|
+
if (triple !== null) return isDeclarativeTriple(triple) ? 'declarative' : 'directory'
|
|
86
|
+
}
|
|
73
87
|
const versionText = dshVersionText()
|
|
74
88
|
if (versionText !== null) {
|
|
75
89
|
const triple = parseVersionTriple(versionText)
|
|
@@ -79,6 +93,32 @@ export async function detectPresetSystem({ dshHome, runtimeSystem = null } = {})
|
|
|
79
93
|
return 'directory'
|
|
80
94
|
}
|
|
81
95
|
|
|
96
|
+
/**
|
|
97
|
+
* 从当前进程 argv[1] 推断正在运行的宿主版本:沿脚本所在目录向上找最近的
|
|
98
|
+
* package.json,name 是 @deepseek-ai/dsh 即取其 version。插件跑在宿主进程里,
|
|
99
|
+
* 这比 PATH 上的 `dsh --version` 又快又准(无需子进程);找不到(CLI / 测试 /
|
|
100
|
+
* 任意非 dsh 进程)返回 null。找到的第一个 package.json 不是 dsh 即止——
|
|
101
|
+
* 向上穿越无关包目录没有意义。
|
|
102
|
+
*/
|
|
103
|
+
function hostVersionFromArgv() {
|
|
104
|
+
try {
|
|
105
|
+
const entry = process.argv[1]
|
|
106
|
+
if (typeof entry !== 'string' || entry === '') return null
|
|
107
|
+
let dir = path.dirname(path.resolve(entry))
|
|
108
|
+
for (let i = 0; i < 6 && dir !== path.dirname(dir); i++) {
|
|
109
|
+
const pkgPath = path.join(dir, 'package.json')
|
|
110
|
+
if (existsSync(pkgPath)) {
|
|
111
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
112
|
+
return pkg?.name === '@deepseek-ai/dsh' ? String(pkg.version ?? '') : null
|
|
113
|
+
}
|
|
114
|
+
dir = path.dirname(dir)
|
|
115
|
+
}
|
|
116
|
+
return null
|
|
117
|
+
} catch {
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
82
122
|
/** `dsh --version` 取裸版本号文本;任何失败(ENOENT / 超时 / 非零退出)返回 null。 */
|
|
83
123
|
function dshVersionText() {
|
|
84
124
|
try {
|
|
@@ -388,3 +428,130 @@ export function readHomePatchRows(dshHome) {
|
|
|
388
428
|
return []
|
|
389
429
|
}
|
|
390
430
|
}
|
|
431
|
+
|
|
432
|
+
// ── 轨道迁移对账(宿主升降级自愈)────────────────────────────────────────────
|
|
433
|
+
//
|
|
434
|
+
// 只升 dsh 不动插件是升级的最常见路径:标记版本照旧吻合、自愈不会触发,但
|
|
435
|
+
// .agent-presets/ 目录轨产物在 0.1.7 下已经无人扫描——preset 静默消失。这一节
|
|
436
|
+
// 的原语补上这条缝:标记从「只记版本」升级为「版本 + 轨道」两行,轨道翻转即
|
|
437
|
+
// 触发自愈;且声明轨上的自愈可以只补 home patch 声明行(磁盘产物原样保留、
|
|
438
|
+
// 字节不动),不必整盘重生成。
|
|
439
|
+
|
|
440
|
+
/** 编排类 preset 目录名(orchestrator / orchestrator-<slug>)的形状过滤。 */
|
|
441
|
+
const ORCHESTRATOR_PRESET_ID_RE = /^orchestrator(-[a-z0-9-]+)?$/
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* 列出 $DSH_HOME/.agent-presets/ 下的编排类 preset 目录名(含基础 orchestrator
|
|
445
|
+
* 与各工作区专属 orchestrator-<slug>),按名排序。目录不存在 / 不可读一律返回
|
|
446
|
+
* 空列表。非编排类目录(liangshen 之类自制 preset)不在此列——迁移对账只认
|
|
447
|
+
* 本工具生成的产物形状。
|
|
448
|
+
*/
|
|
449
|
+
export function listOrchestratorPresetIds(dshHome) {
|
|
450
|
+
try {
|
|
451
|
+
return readdirSync(path.join(dshHome, '.agent-presets'))
|
|
452
|
+
.filter((name) => ORCHESTRATOR_PRESET_ID_RE.test(name))
|
|
453
|
+
.sort()
|
|
454
|
+
} catch {
|
|
455
|
+
return []
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 读任意 preset 目录下 preset.yml 的显示元数据(name / description / order),
|
|
461
|
+
* 与 generateAndInstall 写 preset.yml / 声明行 config 的取值口径同源:顶层
|
|
462
|
+
* name / order 单行标量 + description 多行纯量(续行按 YAML 折叠语义以空格
|
|
463
|
+
* 拼接成单值)。读不到 / 解析不出时返回空元数据——声明行的 name /
|
|
464
|
+
* description / order 都是宿主可选键,缺省不致命。
|
|
465
|
+
*/
|
|
466
|
+
export function readPresetDirMeta(dir) {
|
|
467
|
+
try {
|
|
468
|
+
const text = readFileSync(path.join(dir, 'preset.yml'), 'utf8')
|
|
469
|
+
const lines = text.split(/\r?\n/)
|
|
470
|
+
const firstMatch = (re) => {
|
|
471
|
+
for (const line of lines) {
|
|
472
|
+
const m = re.exec(line)
|
|
473
|
+
if (m) return m
|
|
474
|
+
}
|
|
475
|
+
return null
|
|
476
|
+
}
|
|
477
|
+
const name = firstMatch(/^name:[ \t]*(.+?)[ \t]*$/)?.[1] ?? null
|
|
478
|
+
const orderRaw = firstMatch(/^order:[ \t]*(\d+)[ \t]*$/)
|
|
479
|
+
const order = orderRaw ? Number(orderRaw[1]) : null
|
|
480
|
+
let description = null
|
|
481
|
+
const di = lines.findIndex((l) => /^description:/.test(l))
|
|
482
|
+
if (di !== -1) {
|
|
483
|
+
const parts = [lines[di].slice('description:'.length).trim()]
|
|
484
|
+
for (let i = di + 1; i < lines.length; i++) {
|
|
485
|
+
const line = lines[i]
|
|
486
|
+
if (line.trim() === '' || !/^[ \t]/.test(line)) break // 续行尽 / 撞上下一个顶层键
|
|
487
|
+
parts.push(line.trim())
|
|
488
|
+
}
|
|
489
|
+
description = parts.filter((p) => p !== '').join(' ') || null
|
|
490
|
+
}
|
|
491
|
+
return { name, description, order }
|
|
492
|
+
} catch {
|
|
493
|
+
return { name: null, description: null, order: null }
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* 把磁盘上已有的编排类 preset 目录补写进 home patch 托管块(缺行才补,已有行
|
|
499
|
+
* 原样不动);行在、目录没了的撤行——目录与行同生同灭。产物不全(缺
|
|
500
|
+
* agent.cordis.yml / restrict.mjs)的目录跳过并 warn,不拖垮其余补写。
|
|
501
|
+
* 返回实际补写成功的 presetId 列表(既没补也没撤时为空数组)。
|
|
502
|
+
*/
|
|
503
|
+
export function backfillHomePatchRows(dshHome, presetIds) {
|
|
504
|
+
const rowsOnDisk = readHomePatchRows(dshHome)
|
|
505
|
+
const wanted = new Set((Array.isArray(presetIds) ? presetIds : []).filter((id) => ORCHESTRATOR_PRESET_ID_RE.test(id)))
|
|
506
|
+
const rows = []
|
|
507
|
+
const backfilled = []
|
|
508
|
+
for (const presetId of wanted) {
|
|
509
|
+
if (rowsOnDisk.some((r) => r.presetId === presetId)) continue
|
|
510
|
+
const dir = path.join(dshHome, '.agent-presets', presetId)
|
|
511
|
+
try {
|
|
512
|
+
const agentYmlText = readFileSync(path.join(dir, 'agent.cordis.yml'), 'utf8')
|
|
513
|
+
const restrictFile = path.join(dir, 'restrict.mjs')
|
|
514
|
+
if (!existsSync(restrictFile)) throw new Error('restrict.mjs 缺失')
|
|
515
|
+
const meta = readPresetDirMeta(dir)
|
|
516
|
+
rows.push(
|
|
517
|
+
buildPresetRowText({
|
|
518
|
+
presetId,
|
|
519
|
+
name: meta.name,
|
|
520
|
+
description: meta.description,
|
|
521
|
+
order: meta.order,
|
|
522
|
+
agentYmlText,
|
|
523
|
+
restrictFileUrl: pathToFileURL(restrictFile).href,
|
|
524
|
+
}),
|
|
525
|
+
)
|
|
526
|
+
backfilled.push(presetId)
|
|
527
|
+
} catch (err) {
|
|
528
|
+
warn(`声明行补写跳过 ${presetId}(产物不全,可整盘重生成修复): ${err?.message ?? err}`)
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (rows.length > 0) upsertHomePatchRows(dshHome, rows)
|
|
532
|
+
// 反向对账:托管块里有行、磁盘上已无对应目录的(用户手删目录等),行同步撤下
|
|
533
|
+
const orphanRowIds = rowsOnDisk.filter((r) => r.presetId !== null && !wanted.has(r.presetId)).map((r) => r.rowId)
|
|
534
|
+
if (orphanRowIds.length > 0) stripHomePatchRows(dshHome, orphanRowIds)
|
|
535
|
+
return backfilled
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* 解析 .generator-version 标记文本为 { version, track }。v2 格式两行:首行
|
|
540
|
+
* 生成器版本、次行预设安装轨道(declarative / directory);旧格式(单行版本,
|
|
541
|
+
* 0.3.4 及以前)解析出 track: null —— 恰是「宿主轨道已翻转但产物版本未动」的
|
|
542
|
+
* 迁移触发态。空文本 / 全空白返回 null(按未生成处理)。
|
|
543
|
+
*/
|
|
544
|
+
export function parseGeneratorMarker(text) {
|
|
545
|
+
const lines = String(text ?? '')
|
|
546
|
+
.split(/\r?\n/)
|
|
547
|
+
.map((l) => l.trim())
|
|
548
|
+
.filter((l) => l !== '')
|
|
549
|
+
if (lines.length === 0) return null
|
|
550
|
+
const track = lines[1] === 'declarative' || lines[1] === 'directory' ? lines[1] : null
|
|
551
|
+
return { version: lines[0], track }
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** 生成 .generator-version 标记文本(版本 + 轨道两行,v2 格式)。 */
|
|
555
|
+
export function formatGeneratorMarker(version, presetSystem) {
|
|
556
|
+
return `${version}\n${presetSystem}\n`
|
|
557
|
+
}
|
package/tools/lib/state.mjs
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
copyFileSync,
|
|
17
17
|
existsSync,
|
|
18
18
|
mkdirSync,
|
|
19
|
-
readdirSync,
|
|
20
19
|
readFileSync,
|
|
21
20
|
rmSync,
|
|
22
21
|
statSync,
|
|
@@ -50,8 +49,10 @@ import { smartDefaults } from './alloc.mjs'
|
|
|
50
49
|
import {
|
|
51
50
|
buildPresetRowText,
|
|
52
51
|
detectPresetSystem,
|
|
52
|
+
listOrchestratorPresetIds,
|
|
53
53
|
presetRowId,
|
|
54
54
|
readHomePatchRows,
|
|
55
|
+
readPresetDirMeta,
|
|
55
56
|
stripHomePatchRows,
|
|
56
57
|
upsertHomePatchRows,
|
|
57
58
|
} from './preset-system.mjs'
|
|
@@ -236,14 +237,7 @@ export async function collectState({ dshHome, configFile, profile = null, patche
|
|
|
236
237
|
// 无读权限等)一律降级为空列表,绝不影响检测结果本体;generateAndInstall
|
|
237
238
|
// 成功落盘后以它为对账清单做孤儿工作区 preset 回收。两轨的产物目录同名同
|
|
238
239
|
// 位置(.agent-presets/<presetId>/),这一扫描天然覆盖两轨的 installed 判定。
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
installedPresets = readdirSync(path.join(dshHome, '.agent-presets'))
|
|
242
|
-
.filter((name) => /^orchestrator(-[a-z0-9-]+)?$/.test(name))
|
|
243
|
-
.sort()
|
|
244
|
-
} catch {
|
|
245
|
-
installedPresets = []
|
|
246
|
-
}
|
|
240
|
+
const installedPresets = listOrchestratorPresetIds(dshHome)
|
|
247
241
|
|
|
248
242
|
return {
|
|
249
243
|
dshHome,
|
|
@@ -283,43 +277,13 @@ export async function collectState({ dshHome, configFile, profile = null, patche
|
|
|
283
277
|
}
|
|
284
278
|
|
|
285
279
|
/**
|
|
286
|
-
* SRC preset.yml
|
|
287
|
-
*
|
|
280
|
+
* SRC preset.yml 显示元数据(name / description / order):声明行轨把这些值
|
|
281
|
+
* 写进 home patch 声明行的 config,取值口径与目录轨写 preset.yml 完全同源
|
|
288
282
|
* (name 可被 main_agent_display_name / 工作区派生名覆盖,见 generateAndInstall
|
|
289
|
-
*
|
|
290
|
-
* description 多行纯量(续行按 YAML 折叠语义以空格拼接成单值,落声明行时由
|
|
291
|
-
* JSON.stringify 转义)。读不到 / 解析不出时返回空元数据——声明行的
|
|
292
|
-
* name / description / order 都是宿主可选键,缺省不致命。
|
|
283
|
+
* 调用处)。解析实现已沉到 preset-system.readPresetDirMeta(迁移对账同源复用)。
|
|
293
284
|
*/
|
|
294
285
|
function readPresetMeta() {
|
|
295
|
-
|
|
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
|
-
}
|
|
286
|
+
return readPresetDirMeta(SRC_DIR)
|
|
323
287
|
}
|
|
324
288
|
|
|
325
289
|
/**
|
package/tools/lib/upgrade.mjs
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* 开发 link: 直连形态的真实路径在仓库里、匹配不到,如实报「无法就地升级」。
|
|
11
11
|
*
|
|
12
12
|
* 零依赖纯 Node(>=18):目标版本复用 version.mjs 的 fetchLatestFromRegistry
|
|
13
|
-
*
|
|
13
|
+
* / fetchLatestFromMirror(官方源失败再走 npmmirror 镜像——点「升级」的用户
|
|
14
|
+
* 网络本来就在出状况,多一路快路能实打实救回一次升级),子进程、时钟全部
|
|
15
|
+
* 可注入便于单测。升级后不做磁盘后验
|
|
14
16
|
* ——本进程代码不随升级变化,无从对账;成功与否以 dsh 子进程退出码为准,
|
|
15
17
|
* 新版本在重启 DSH 后生效(preset 由新包的启动自愈钩子自动重生成)。
|
|
16
18
|
*
|
|
@@ -22,7 +24,7 @@ import os from 'node:os'
|
|
|
22
24
|
import { spawn } from 'node:child_process'
|
|
23
25
|
import { realpath } from 'node:fs/promises'
|
|
24
26
|
import { fileURLToPath } from 'node:url'
|
|
25
|
-
import { currentVersion, compareVersions, fetchLatestFromRegistry } from './version.mjs'
|
|
27
|
+
import { currentVersion, compareVersions, fetchLatestFromMirror, fetchLatestFromRegistry } from './version.mjs'
|
|
26
28
|
|
|
27
29
|
// 子进程输出上限:合并 stdout+stderr 后只保留最后 ~8KB(安装进度刷屏无排障
|
|
28
30
|
// 价值,报错结论都在尾部);累积过程中超过 1MB 就先截一版,防止异常刷爆内存。
|
|
@@ -124,13 +126,17 @@ async function doRunUpgrade({ profile, fetchImpl, spawnImpl, timeoutMs, now }) {
|
|
|
124
126
|
const elapsedMs = () =>
|
|
125
127
|
Math.max(0, (typeof now === 'function' ? now() : typeof now === 'number' ? now : Date.now()) - startedAt)
|
|
126
128
|
|
|
127
|
-
// ① 版本预检:拿不到
|
|
128
|
-
//
|
|
129
|
-
//
|
|
129
|
+
// ① 版本预检:拿不到 latest 就不做半截升级(宁可不动现有安装)。官方源
|
|
130
|
+
// 失败再试 npmmirror 镜像。超时给 10s:面板静默检测用 3s 够了,这里是
|
|
131
|
+
// 用户显式点了「升级」、本来就要等,多花几秒换一次成功率是划算的;
|
|
132
|
+
// 仍超时就如实报失败。
|
|
130
133
|
const { version: current } = await currentVersion()
|
|
131
134
|
const reg = await fetchLatestFromRegistry({ fetchImpl, timeoutMs: 10000 })
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
const manifest = reg.ok ? reg : await fetchLatestFromMirror({ fetchImpl, timeoutMs: 10000 })
|
|
136
|
+
if (!manifest.ok) {
|
|
137
|
+
return { ok: false, error: `无法确定最新版本:官方源 ${reg.error};镜像 ${manifest.error}` }
|
|
138
|
+
}
|
|
139
|
+
const target = manifest.latest // 形如 v0.2.3
|
|
134
140
|
if (compareVersions(target, current) <= 0) return { ok: true, upToDate: true, current }
|
|
135
141
|
const targetVersion = target.replace(/^v/, '')
|
|
136
142
|
|
package/tools/lib/version.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 版本提示 —— 检测线上是否有比本地更新的版本(Web 面板 + CLI 共用)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 可见,不像 GitHub release 需要另行打 tag
|
|
4
|
+
* 检测源三级:npm registry 的 dist-tags/latest 优先(无认证无限流,发布即
|
|
5
|
+
* 可见,不像 GitHub release 需要另行打 tag);失败再试 npmmirror 镜像(数据
|
|
6
|
+
* 同步自 npm registry,官方源超时 / 被墙时国内几秒内可达,分钟级同步延迟对
|
|
7
|
+
* 「有没有新版」这个判断无伤大雅);最后 GitHub releases/latest 兜底。
|
|
6
8
|
*
|
|
7
9
|
* 零依赖纯 Node(>=18):不引 cordis / npm 依赖,fetch 可注入(缺省用
|
|
8
10
|
* globalThis.fetch),超时用 AbortSignal.timeout()。所有失败路径都不抛错,
|
|
@@ -22,6 +24,9 @@ const RELEASE_API = 'https://api.github.com/repos/lifangjin/dsh-paoding/releases
|
|
|
22
24
|
const RELEASE_PAGE = 'https://github.com/lifangjin/dsh-paoding/releases/latest'
|
|
23
25
|
// npm registry 的 latest 元数据端点(等价 dist-tags.latest,无认证无限流)。
|
|
24
26
|
const REGISTRY_LATEST = 'https://registry.npmjs.org/dsh-paoding/latest'
|
|
27
|
+
// npmmirror 镜像的同名端点:manifest 形状与官方源一致(顶层 version 即
|
|
28
|
+
// latest),同步自 npm registry,官方源不可达时的国内快路。
|
|
29
|
+
const MIRROR_LATEST = 'https://registry.npmmirror.com/dsh-paoding/latest'
|
|
25
30
|
|
|
26
31
|
// 读不到 package.json 时的版本兜底。当前版本不可知(source: 'fallback')时
|
|
27
32
|
// updateAvailable 恒 false —— 拿「0.0.0 恒小于任何真实版本」去比较,反而必然
|
|
@@ -141,65 +146,87 @@ export async function fetchLatestRelease({ fetchImpl, timeoutMs = 3000 } = {}) {
|
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
149
|
+
* 请求一个 npm 系 manifest 端点(官方源与 npmmirror 镜像共用同一形状:完整
|
|
150
|
+
* manifest,顶层 version 即 latest)。任何失败(网络 / 超时 / 解析 / 形状不对)
|
|
151
|
+
* 都不抛出,返回 { ok: false, error };成功返回 { ok: true, latest: 'vX.Y.Z',
|
|
152
|
+
* releaseUrl, source }(端点的 version 不带 v 前缀,这里统一补齐,与 GitHub
|
|
153
|
+
* tag 的显示口径一致)。
|
|
148
154
|
*/
|
|
149
|
-
|
|
155
|
+
async function fetchLatestManifest(url, label, source, { fetchImpl, timeoutMs = 3000 } = {}) {
|
|
150
156
|
const doFetch = typeof fetchImpl === 'function' ? fetchImpl : globalThis.fetch
|
|
151
157
|
if (typeof doFetch !== 'function') return { ok: false, error: 'fetch 不可用(当前运行时无 fetch 实现)' }
|
|
152
158
|
try {
|
|
153
|
-
const res = await doFetch(
|
|
159
|
+
const res = await doFetch(url, {
|
|
154
160
|
headers: { accept: 'application/vnd.npm+json' },
|
|
155
161
|
signal: AbortSignal.timeout(timeoutMs),
|
|
156
162
|
})
|
|
157
|
-
if (!res.ok) return { ok: false, error:
|
|
163
|
+
if (!res.ok) return { ok: false, error: `${label} HTTP ${res.status}` }
|
|
158
164
|
let data = null
|
|
159
165
|
try {
|
|
160
166
|
data = await res.json()
|
|
161
167
|
} catch (err) {
|
|
162
|
-
return { ok: false, error:
|
|
168
|
+
return { ok: false, error: `${label} 响应解析失败: ${err?.message ?? err}` }
|
|
163
169
|
}
|
|
164
170
|
// /<name>/latest 返回该版本的完整 manifest,顶层 version 即 latest 版本号;
|
|
165
|
-
// CDN
|
|
171
|
+
// CDN 缓存抖动可能回形状不对的正文,按失败处理走下一级检测源。
|
|
166
172
|
const ver = typeof data?.version === 'string' && data.version !== '' ? data.version : null
|
|
167
|
-
if (!ver) return { ok: false, error:
|
|
168
|
-
return { ok: true, latest: `v${ver}`, releaseUrl: RELEASE_PAGE, source
|
|
173
|
+
if (!ver) return { ok: false, error: `${label} 响应缺少 version` }
|
|
174
|
+
return { ok: true, latest: `v${ver}`, releaseUrl: RELEASE_PAGE, source }
|
|
169
175
|
} catch (err) {
|
|
170
176
|
return { ok: false, error: String(err?.message ?? err) }
|
|
171
177
|
}
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
/**
|
|
181
|
+
* 请求 npm registry(官方源)的 latest 元数据——版本检测首选源,runUpgrade
|
|
182
|
+
* 的目标版本解析也复用。
|
|
183
|
+
*/
|
|
184
|
+
export async function fetchLatestFromRegistry(opts) {
|
|
185
|
+
return await fetchLatestManifest(REGISTRY_LATEST, 'npm registry', 'npm', opts)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 请求 npmmirror 镜像的 latest 元数据——官方源不可达(超时 / 被墙 / 代理
|
|
190
|
+
* 黑洞)时的国内快路:manifest 形状与官方源一致,同步延迟分钟级,对「有
|
|
191
|
+
* 没有新版」这个判断无伤大雅。
|
|
192
|
+
*/
|
|
193
|
+
export async function fetchLatestFromMirror(opts) {
|
|
194
|
+
return await fetchLatestManifest(MIRROR_LATEST, 'npmmirror', 'mirror', opts)
|
|
195
|
+
}
|
|
196
|
+
|
|
174
197
|
/**
|
|
175
198
|
* 组合结果(面板路由与 CLI 收尾共用):带 1 小时内存缓存,过期才重新请求;
|
|
176
199
|
* 请求失败时若上次成功结果在 lastGood 窗口(7 天,见 LASTGOOD_TTL_MS)内则
|
|
177
200
|
* 原样复用(stale-if-error),否则返回带 error 的降级结果(latest 置 null、
|
|
178
201
|
* updateAvailable 恒 false——调用方静默)。当前版本不可知(FALLBACK_VERSION
|
|
179
202
|
* 兜底)时 updateAvailable 恒 false:0.0.0 对任何 latest 都「更小」,比较必
|
|
180
|
-
*
|
|
181
|
-
* release
|
|
182
|
-
*
|
|
203
|
+
* 误报新版,升级提示整体抑制。检测源三级:npm registry 优先,失败再试
|
|
204
|
+
* npmmirror 镜像,最后 GitHub release;三路皆败才算本次检测失败,error 里
|
|
205
|
+
* 带上各路失败原因与代理排查提示便于排障。成功结果的 source 字段记录命中源。
|
|
183
206
|
* @param {{fetchImpl?: Function, timeoutMs?: number, now?: Function|number}} opts
|
|
184
207
|
* now 可注入函数或时间戳(测试用);缺省 Date.now()。
|
|
185
208
|
* @returns {Promise<{current: string, latest: string|null, updateAvailable: boolean,
|
|
186
209
|
* releaseUrl: string|null, checkedAt: string, error: string|null,
|
|
187
|
-
* source: 'npm'|'github'|null}>}
|
|
210
|
+
* source: 'npm'|'mirror'|'github'|null}>}
|
|
188
211
|
*/
|
|
189
212
|
export async function getVersionInfo({ fetchImpl, timeoutMs = 3000, now } = {}) {
|
|
190
213
|
const nowMs = typeof now === 'function' ? now() : typeof now === 'number' ? now : Date.now()
|
|
191
214
|
if (cache.info && nowMs - cache.at < CACHE_TTL_MS) return cache.info
|
|
192
215
|
const { version: current, source: currentSource } = await currentVersion()
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
let
|
|
197
|
-
if (
|
|
198
|
-
hit
|
|
199
|
-
|
|
216
|
+
// 三级链:① npm registry(官方源);② npmmirror 镜像(官方源不可达时的
|
|
217
|
+
// 国内快路);③ GitHub releases/latest(镜像也挂时的独立数据源)。
|
|
218
|
+
const failures = []
|
|
219
|
+
let hit = await fetchLatestFromRegistry({ fetchImpl, timeoutMs })
|
|
220
|
+
if (!hit.ok) {
|
|
221
|
+
failures.push(hit.error)
|
|
222
|
+
hit = await fetchLatestFromMirror({ fetchImpl, timeoutMs })
|
|
223
|
+
}
|
|
224
|
+
if (!hit.ok) {
|
|
225
|
+
failures.push(hit.error)
|
|
200
226
|
const rel = await fetchLatestRelease({ fetchImpl, timeoutMs })
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
hit = rel.ok
|
|
228
|
+
? { ok: true, latest: rel.tag, releaseUrl: rel.url, source: 'github' }
|
|
229
|
+
: { ok: false, error: rel.error }
|
|
203
230
|
}
|
|
204
231
|
if (hit && hit.ok) {
|
|
205
232
|
const info = {
|
|
@@ -216,10 +243,11 @@ export async function getVersionInfo({ fetchImpl, timeoutMs = 3000, now } = {})
|
|
|
216
243
|
cache = { at: nowMs, info }
|
|
217
244
|
return info
|
|
218
245
|
}
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
246
|
+
// 三路皆败:优先复用 lastGood 窗口(7 天)内的上次成功结果;超窗才给降级
|
|
247
|
+
// 结果(不写缓存)。error 把各路原因都带上(谁挂了一目了然),并附代理排查
|
|
248
|
+
// 提示——最常见的「浏览器能上网、DSH 进程检测失败」是启动环境的代理变量
|
|
249
|
+
// 指向了不可用的代理(DSH 遵循 HTTPS_PROXY 等变量,浏览器不走它们)。
|
|
250
|
+
const combined = `三路检测均失败(npm registry: ${failures[0]};npmmirror: ${failures[1]};GitHub: ${hit.error})。若本机经代理访问外网,请确认代理可用后重启 DSH 再试——DSH 进程遵循 HTTPS_PROXY / HTTP_PROXY 环境变量,浏览器不走它们,两者连通性可能不同`
|
|
223
251
|
if (cache.info && nowMs - cache.at < LASTGOOD_TTL_MS) return cache.info
|
|
224
252
|
return {
|
|
225
253
|
current,
|