# dsh-hotswap

DSH Web 插件热插拔管理器：在 **设置 → 插件 → 「插件管理」** 标签页里列出当前挂载的所有 Cordis 插件，支持**运行时热启用 / 热停用 / 热重启**，以及**监视 profile 的 `package.json` 自动热挂载 / 卸载 bundle**——全程无需重启 `dsh web`。

> 原名 `@dsh-external/dsh-plugin-manager`（未公开发布）。

## 功能

- **热停用**：调用 Loader 的 `entry.update({ disabled: true })`，插件当场卸载（服务、工具、监听器全部回收）。
- **热启用**：`entry.update({ disabled: false })`，当场导入并启动插件——包括启动时就处于停用状态的插件。
- **热重启**：卸载后重新加载，并尽力清除 Node ESM 模块缓存，让磁盘上的新代码生效（best-effort，见「已知限制」）。
- **Bundle 自动同步（热挂载）**：监视 profile 的 `package.json`，当 `dsh.profile.bundles` 增删条目时，通过 Include 根分组热挂载 / 卸载对应 bundle——`dsh plugin add/remove` 之后无需重启。
- **持久化**：每次开关都会把 `disabled: true` 写入 / 移除 profile 的 `cordis.patch.yml` 中一个由本插件维护的自动区块（`# === dsh-hotswap managed block ===`），因此重启 DSH 后开关状态依然生效。
- **安全护栏**：
  - 保护名单：`webserver`、`connection`、`api-gateway`、`modules`、`typert*`、`web*`、`hmr` 等界面赖以生存的条目以及本插件自身，不能被停用；
  - `!!js` 表达式控制、父分组停用的条目不允许切换；
  - 一键「全部恢复」重新启用所有由管理器停用的插件。
- **外部停用识别**：能识别 `cordis.patch.yml` 中手写的 `disabled: true` 行；启用时会同步移除该行，避免“现在能跑、重启又没了”的不一致。

## 安装

```sh
dsh plugin --profile web add dsh-hotswap
# 重启生效：
dsh web
```

本地源码 / tgz：

```sh
npm pack
dsh plugin --profile web add ./dsh-hotswap-0.1.0.tgz
```

## 配置

全部可选，通过 profile 的 `cordis.patch.yml` 覆盖：

```yaml
- id: dsh-hotswap
  config:
    profile: web              # 持久化目标 profile（仅作兜底；默认从 Loader baseUrl 推导）
    protected: [some-id]      # 追加不可停用的条目 id
```

## ⚠️ 安全须知（重要）

本插件暴露同源 HTTP 端点（`/_dsh/hotswap/*`），可让请求方**停用 / 启用 / 重启任意插件**。它沿用 DSH Web 的「受信任主机」模型，**不额外加鉴权**：

- 只要 Web 服务绑定在 `127.0.0.1`，就只有本机进程能调用，是安全的。
- **如果你把 `webserver` 的 `host` 设为 `0.0.0.0` 暴露到局域网，局域网内任何设备都能控制你的插件（进而驱动 agent）**。DSH Web 本身无鉴权 / 无 TLS，切勿暴露到不可信网络。
- 来源校验（`isSameOrigin`）只拦截带 `Origin` 头的跨站请求；无 `Origin` 头的本机程序（curl 等）会被放行——这是刻意的本机便利，不是漏洞，但意味着**不要与不信任的进程共享这台机器**。

## 兼容性

- 开发 / 测试于 **DSH `0.1.0-rc.6`**。
- 热重启与代码重载依赖 Cordis Loader 的**内部 API**（`entry._dispose` / `entry.refresh` / `loader.internal`），这些不是公开契约，可能在 DSH 升级后变化。升级 DSH 前请先确认本插件仍可用。

## 架构

- 主机端 `index.js`：注入 `webServer` + `loader`，注册 `/_dsh/hotswap/` 前缀路由（state / set / restart / reset）；通过 Loader 的公共条目 API 做运行时启停，用文本级“围栏区块”维护 `cordis.patch.yml`；`createBundleSyncer` 监视 `package.json` 做热挂载。
- 客户端 `client.js`：注册 `settings.plugins.tab` 插槽（id `manager`），React 渲染插件列表（搜索、筛选、状态灯、徽章、开关/重启按钮）。

## 已知限制

- 停用某个插件后，**已经打开的页面**里它注入的客户端 UI（如设置页标签）要到刷新页面后才会消失。
- 运行时启停只影响当前进程；「持久化」只覆盖本管理器经手的开关。手动写在围栏外的配置行不会被改动（除非点击“启用”时顺手移除同 id 的 `disabled: true` 行）。
- 热重启的「代码重载」是 best-effort：Node ESM 模块缓存无法被可靠失效，多数情况下只是卸载后重新 apply，未必读到磁盘新代码。
- 若误停关键插件导致界面无法操作：编辑 UI 上显示的持久化文件，删除 dsh-hotswap 自动区块后重启 `dsh web` 即可恢复。
- `headless` profile 没有 `webServer`，本插件只服务于 Web 界面。

## License

MIT
