Files
dsh_ai1net_server/dsh-server-docs/04-调整方案/82-R2管理面就地化-原生平台管理分区.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

2048 lines
146 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
---
## 十、追加(2026-09-13 19:3x–19:5x):**从「只读」升级为「就地全量管理」**(用户要求)
### 10.1 用户的三条要求
1. **dsh 设置里的「用户管理」改名为「用户设置」**,且**去掉里面的「打开管理平台」入口**
2. **「平台管理」不要再跳新页面** —— **把所有管理项都放进「系统管理」,每项点开是弹窗**
### 10.2 落地
| 项 | 改动 | 产物 |
|---|---|---|
| 「用户管理」→**「用户设置」** | `@dsh-local/portal-entry`:section `id: user-management → user-settings`、`label` 改名、**order 102 → 103**(让位给平台管理);**删除「打开管理台」按钮**;内容改为「账号:<用户名> 角色:<管理员/普通用户>」+ 退出登录 | **`portal-entry-0.5.2.tgz`**(已铺发到 admin+guest) |
| 「平台管理」→**「系统管理」+ 管理项 + 弹窗** | `@dsh-local/business-plugins`:**删掉 `window.open(portal.html)` 跳转按钮**;分区改为「**管理项列表**」,5 项(用户管理 / 候选池 / 运行时 / 存储 / 当前实例)**每项点开是原生弹窗**;**用户管理弹窗内可直接 通过 / 禁用 / 启用 / 删除**(调平台既有 admin API,不跳门户) | **`business-plugins-0.3.0.tgz`**(已铺发) |
### 10.3 验收(`npm run verify` 内)
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ admin:含分区标题「系统管理」 ✓ admin:5 个管理项齐全
✓ admin:不再是只读(提示改为弹窗内管理)
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何管理项
结论:全绿 ✅
```
另:**磁盘复核** —— 两 profile 的 `business-plugins` 均 **0.3.0**、含「系统管理」、**`window.open` 计数 = 0**(跳新页彻底移除)✓
### 10.4 尚未做的(下一批)
- 弹窗内**目前只有「用户管理」是可写**的;**候选池 / 运行时 / 存储 / 实例** 三项为**只读列表**(写操作待补)。
- **portal-entry 源码不在仓库**(只有铺发脚本 + artifacts 里的 tgz)⇒ 本次是**在产物上改**的。**建议**:把 `portal-entry`/`business-plugins` 源码纳入仓库(档案 75 · B5 的旧账)。
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
---
## 十、追加(2026-09-13 19:3x–19:5x):**从「只读」升级为「就地全量管理」**(用户要求)
### 10.1 用户的三条要求
1. **dsh 设置里的「用户管理」改名为「用户设置」**,且**去掉里面的「打开管理平台」入口**
2. **「平台管理」不要再跳新页面** —— **把所有管理项都放进「系统管理」,每项点开是弹窗**
### 10.2 落地
| 项 | 改动 | 产物 |
|---|---|---|
| 「用户管理」→**「用户设置」** | `@dsh-local/portal-entry`:section `id: user-management → user-settings`、`label` 改名、**order 102 → 103**(让位给平台管理);**删除「打开管理台」按钮**;内容改为「账号:<用户名> 角色:<管理员/普通用户>」+ 退出登录 | **`portal-entry-0.5.2.tgz`**(已铺发到 admin+guest) |
| 「平台管理」→**「系统管理」+ 管理项 + 弹窗** | `@dsh-local/business-plugins`:**删掉 `window.open(portal.html)` 跳转按钮**;分区改为「**管理项列表**」,5 项(用户管理 / 候选池 / 运行时 / 存储 / 当前实例)**每项点开是原生弹窗**;**用户管理弹窗内可直接 通过 / 禁用 / 启用 / 删除**(调平台既有 admin API,不跳门户) | **`business-plugins-0.3.0.tgz`**(已铺发) |
### 10.3 验收(`npm run verify` 内)
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ admin:含分区标题「系统管理」 ✓ admin:5 个管理项齐全
✓ admin:不再是只读(提示改为弹窗内管理)
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何管理项
结论:全绿 ✅
```
另:**磁盘复核** —— 两 profile 的 `business-plugins` 均 **0.3.0**、含「系统管理」、**`window.open` 计数 = 0**(跳新页彻底移除)✓
### 10.4 尚未做的(下一批)
- 弹窗内**目前只有「用户管理」是可写**的;**候选池 / 运行时 / 存储 / 实例** 三项为**只读列表**(写操作待补)。
- **portal-entry 源码不在仓库**(只有铺发脚本 + artifacts 里的 tgz)⇒ 本次是**在产物上改**的。**建议**:把 `portal-entry`/`business-plugins` 源码纳入仓库(档案 75 · B5 的旧账)。
### 10.5 视觉层补齐(2026-09-13 20:1x,用户反馈「像毛坯房、参考门户样式、把 UI 规范记脑袋里」)
**做法**:按 `06-工作台UI规范` 重做视觉层(此前只做了功能骨架、无样式):
| 元素 | 采纳的规范条目 | 取值 |
|---|---|---|
| 管理项卡片 | **§4.5 `.data-card`** | **r12** / `16px 18px` / `box-shadow: 0 1px 3px rgba(0,0,0,.08)` / **hover 上浮 `translateY(-2px)` + 蓝边 + `0 4px 12px rgba(47,111,237,.15)`** / `transition .15s cubic-bezier(.16,1,.3,1)` |
| 卡片布局 | §3 | `grid-template-columns: repeat(auto-fill, minmax(158px, 1fr))` + gap 12;`minHeight 104px` 保证等高 |
| 弹窗 | **§4.7 `.modal` / `.modal-mask`** | mask `rgba(0,0,0,.35)` + flex 居中;面板 **r14 / `22px 26px` / `width 520px` / `max-width 92vw` / `0 8px 30px`**;进场 `paIn .18s` |
| 按钮 | §4.1 `.btn-sm` 量级 | `3px 10px` / r6 / 12–13px;hover 蓝边蓝字 + 淡蓝底 |
| 数值 | **§5** | 计数**右对齐 + `font-variant-numeric: tabular-nums`** |
| 字号 | §2.2 阶梯 | 卡片标题 15/500、说明 13/次级色、计数 17/500 |
| 动效降级 | §4.5 | `@media (prefers-reduced-motion: reduce)` 关动画与位移 |
**色彩**:继续用 dsh 官方 token `--dsw-*`(**刻意不硬编码亮色**——本 section 嵌在 dsh 设置面板内,须自动跟随 dsh 主题;硬编码会在暗色主题下瞎)。规范里的硬色值是给**门户静态页**的。
**产物**:`business-plugins-0.3.1.tgz`(已铺发 admin+guest);`npm run verify` 全绿。
⚠️ **教训(已入库)**:上一轮**只做了功能、没做视觉**就交付,被指「像毛坯房」。**做页面的默认动作必须包含「查规范 → 落地 §4.5/§4.7 等具体条目」**,不能等用户来提。
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
---
## 十、追加(2026-09-13 19:3x–19:5x):**从「只读」升级为「就地全量管理」**(用户要求)
### 10.1 用户的三条要求
1. **dsh 设置里的「用户管理」改名为「用户设置」**,且**去掉里面的「打开管理平台」入口**
2. **「平台管理」不要再跳新页面** —— **把所有管理项都放进「系统管理」,每项点开是弹窗**
### 10.2 落地
| 项 | 改动 | 产物 |
|---|---|---|
| 「用户管理」→**「用户设置」** | `@dsh-local/portal-entry`:section `id: user-management → user-settings`、`label` 改名、**order 102 → 103**(让位给平台管理);**删除「打开管理台」按钮**;内容改为「账号:<用户名> 角色:<管理员/普通用户>」+ 退出登录 | **`portal-entry-0.5.2.tgz`**(已铺发到 admin+guest) |
| 「平台管理」→**「系统管理」+ 管理项 + 弹窗** | `@dsh-local/business-plugins`:**删掉 `window.open(portal.html)` 跳转按钮**;分区改为「**管理项列表**」,5 项(用户管理 / 候选池 / 运行时 / 存储 / 当前实例)**每项点开是原生弹窗**;**用户管理弹窗内可直接 通过 / 禁用 / 启用 / 删除**(调平台既有 admin API,不跳门户) | **`business-plugins-0.3.0.tgz`**(已铺发) |
### 10.3 验收(`npm run verify` 内)
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ admin:含分区标题「系统管理」 ✓ admin:5 个管理项齐全
✓ admin:不再是只读(提示改为弹窗内管理)
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何管理项
结论:全绿 ✅
```
另:**磁盘复核** —— 两 profile 的 `business-plugins` 均 **0.3.0**、含「系统管理」、**`window.open` 计数 = 0**(跳新页彻底移除)✓
### 10.4 尚未做的(下一批)
- 弹窗内**目前只有「用户管理」是可写**的;**候选池 / 运行时 / 存储 / 实例** 三项为**只读列表**(写操作待补)。
- **portal-entry 源码不在仓库**(只有铺发脚本 + artifacts 里的 tgz)⇒ 本次是**在产物上改**的。**建议**:把 `portal-entry`/`business-plugins` 源码纳入仓库(档案 75 · B5 的旧账)。
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。# 82 · R2 管理面就地化(原生「平台管理」分区)
- 日期:2026-09-13
- 状态:🚧 **代码完成、产物已出;待投放 + 启用 + 浏览器验收**
- 触发:档案 81 §四 R2(原「实例内弹窗 iframe」);形态经用户 2026-09-13 拍板改为**原生弹窗**(§9.2b)
- 原则:**只读优先**(写操作回跳平台域);**不改官方 dsh**;**不把门户 UI 复制进插件**
> **TL;DR**|① R2 的 **R5 权限影响评估**已在档案 81 §9.2 出齐并获批(扩大点=实例内出现可操作的管理入口 ⇒ **仅 admin 可见** + **只读** + 写操作回跳);
> ② 本轮把「**平台管理**」分区**写进 `@dsh-local/business-plugins` 插件**(原生渲染,非 iframe)—— 数据直接调平台只读 API;
> ③ 产物 `dsh-local-business-plugins-0.2.9.tgz` 已出(`node --check` 通过、包结构合规);
> ④ **未做**:投放候选池 / 实例启用 / 浏览器验收(**卡在需要 admin 登录态**,见 §五);`/admin` 路由族 + 删 3 桩页 亦未做。
---
## 一、形态(与档案 81 §9.2b 一致)
| 维度 | 取值 |
|---|---|
| 渲染 | **原生渲染**(复用插件自有 token `--dsw-*` + `06-工作台UI规范` 字号阶梯),**非 iframe** |
| 数据 | 直接调平台**只读** API(跨子域 fetch + `credentials: "include"`,门户 CORS 白名单已就绪) |
| 范围 | **只读子集**:实例 / 熔断 / 用户 / 存储 / 候选池 / 运行时 |
| 写操作 | **一律回跳平台管理台**(`portalHost() + "/portal.html"`)—— 不复制门户写 UI(否则双源维护 + 与档案 39「收窄执行面」相冲) |
| 可见性 | **仅 admin**:先 `GET /api/auth/me` 判 `role === "admin"`,否则只渲染一行说明 |
| 落点 | `poc/business-plugins/lib/client.js`(设置面板 `settings.section`,`id: platform-admin`, `order: 102`) |
## 二、改了哪些文件
| 文件 | 改动 |
|---|---|
| `poc/business-plugins/lib/client.js` | **895 → 1083 行**:① zh/en 词典各 +**29** 个 `pa.*` 键;② 新增 `PaCard`(指标卡)+ `PlatformAdminSection`;③ 新增一个 `settings.section` 注册(`platform-admin`) |
| `poc/business-plugins/package.json` | 版本 `0.2.8` → **`0.2.9`** |
| 产物 | **`dsh-local-business-plugins-0.2.9.tgz`**(16,712 B;顶层仅 `package/`;无 `.tgz`/`node_modules`/`.bak`) |
## 三、数据来源(均为平台既有路由,零新增后端)
| 卡片 | API | 权限 |
|---|---|---|
| 角色门禁 | `GET /api/auth/me` | requireAuth |
| 当前实例 / 熔断 | `GET /api/dsh/status` | requireAuth |
| 用户 | `GET /api/admin/users` | requireAdmin |
| 存储 | `GET /api/admin/storage` | requireAdmin |
| 候选池 | `GET /api/plugins/business` | requireAdmin |
| 运行时 | `GET /api/admin/runtime` | requireAdmin |
> 全部**只读**;任一失败降级为 `null` 并照常渲染其余卡片(不整页报错)。
## 四、已验证 / 未验证
| 项 | 状态 | 证据 |
|---|---|---|
| 语法 | ✅ | `node --check lib/client.js` 通过 |
| 包结构 | ✅ | 顶层仅 `package/`;4 条目;无违规文件;`lib/client.js` 在内 |
| 词条键集 | ✅ | zh/en 各 +29 键,键名一一对应 |
| 渲染 / 交互 / 权限门禁 | ⏸ **未验证** | 需投放 + 启用(见 §五) |
## 五、⛔ 未完成:投放与验收需要 **admin 登录态**
- 投放走 **`POST /api/plugins/business`**(`requireAdmin`)⇒ 必须有 admin 的会话。
- 平台 CLI 只有 `start` / `bootstrap-admin` / `file-service`,**无投放命令**;文档内**无**测试口令 ⇒ **执行会话拿不到凭据**。
- 三条可选路径(待用户定):① 用户自己在门户投放(2 分钟);② 给 admin 口令;③ 用 `bootstrap-admin` 建**临时 admin**,用完删(会动生产 auth)。
## 六、R2 剩余(本次**未**做,属同期的另一半)
1. **`/admin` 路由族 + 删 3 个跳转桩页**(`web/desktop.html` / `plugins.html` / `skills.html`,各 247–265 B,`title=跳转中…`)—— 档案 81 §四表格口径;⚠️ 删桩页**必须同时加 301 到 `/admin`**(否则既有书签断)。删后**静态页 9 → 6**,需同步改 `scripts/verify-static.mjs` 的页数断言。
2. **注入层 CSS 类统一为 `.dshs-*`**(档案 81 §四:`.__dsh-*` / `.wk-*` → `.dshs-*`)。
## 七、回滚
- 插件侧:把候选池里的 `business-plugins` **回落 0.2.8**(旧 tgz 若已删,可从本机 git 历史取 `lib/client.js` 重建)。
- 本轮改动**全部在 `poc/business-plugins/`**,未动 `src/`、未动官方 dsh、未动服务器 ⇒ 回滚只需还原该目录 + 重新投放。
## 八、口径提醒
> ⚠️ **不能两头都要**(档案 81 §9.2b):若日后要求「弹窗里能完成**全部**管理动作」,那就等于把整个门户重写进插件(双源维护 + 扩大执行面)⇒ 届时应把**门户收敛成 API 客户端**,而不是继续复制 UI。
---
## 九、追加(2026-09-13 18:1x–19:2x):**部署完成 + 无浏览器渲染验收通过 + 一并做掉档案 80 第 7 项**
### 9.1 部署链路(**关键:不走候选池**)
`@dsh-local/business-plugins` **不是池内插件** —— 它由 **`scripts/ensure-biz-plugins.cjs`** 铺发:从 **`/opt/dsh/artifacts/`** 取**版本号最大**的 `business-plugins-*.tgz`(`--all` 含 admin;`--restart` 让 bundle 生效)。
| 步 | 结果 |
|---|---|
| 投放产物 | `business-plugins-0.2.9.tgz` → `/opt/dsh/artifacts/`(前一版为 0.2.8) |
| 铺发 | `node scripts/ensure-biz-plugins.cjs --all --restart` ⇒ **admin 与 guest 的 profile 均 0.2.8 → 0.2.9**、`bundles` 均含该包 ✓ |
| 磁盘层断言 | 两实例 `node_modules/@dsh-local/business-plugins/lib/client.js` 含「平台管理」× **5** ✓ |
| 实例 | 两实例均已重启并 running;实例页 **HTTP 200** ✓ |
### 9.2 渲染验收:**改用无浏览器 harness**(因 `agent-browser` daemon 起不来)
- ⚠️ **本机 `[email protected]` 的 daemon 起不来**:`open` **挂住且零输出**(`--version` / `node -e` 均正常;`~/.agent-browser` 里留下的 `default.port` 实测**不通**、清掉僵尸状态后仍不通)。Chrome 153 已由 `agent-browser install` 装好,但 daemon 不通 ⇒ **浏览器截图验收受阻**(属环境问题,未解)。
- ⚠️ `06-工作台UI规范 §7.3` 的三段式在**按需动态加载的 bundle** 上拿不到带 `rev` 的完整 URL(该 bundle 不出现在实例首页 HTML 里)⇒ ②③ 段也不适用。
- ✅ **替代做法(已沉淀为可复跑脚本)**:`scripts/verify-platform-admin-section.mjs` —— 打桩 `react` / `react/jsx-runtime` + **最小 hooks 渲染循环** + **真执行** `client.js`,直接断言结构与门禁。
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ 存在 platform-admin 分区 ✓ 其 order = 102
✓ admin:含分区标题「平台管理」 ✓ admin:6 张卡齐全 ✓ admin:含只读声明
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何指标卡
结论:全绿 ✅ exit=0
```
admin 视角实际渲染文本(实测):
> 平台管理 | 只读 | 本分区为只读;写操作请在平台管理台完成。
> 当前实例 | 运行中 | 端口 43095 · 自动重启 2 | 熔断 | 正常 | —
> 用户 | 4 | 待审核 1 · 已禁用 1 | 存储 | 2 | 报告每小时刷新
> 候选池 | 3 | 已投放插件 | 运行时 | 5 | 条目 | 打开完整管理台
已接入 **`npm run verify`**(`&& node scripts/verify-platform-admin-section.mjs`)⇒ 今后 UI 改动可 CI 式防回归。
### 9.3 一并做掉:档案 80 第 7 项 / 档案 81 §四「删 3 个跳转桩页」
- 删 `web/{desktop,plugins,skills}.html` ⇒ **静态页 9 → 6** ✓;改 **5 处引用**(`admin.html` / `index.html` / `login.html`×2 / `dsh.ts` 注释)。`verify-static.mjs` 是**自动发现页面**的,无需改。
- nginx `alotbuy.com.conf` **两个 server 块**各加 3 条精确匹配 301 ⇒ 实测 `/desktop.html`→301 `/portal.html`、`/plugins.html`→`#/plugins`、`/skills.html`→`#/skills` ✓;`login.html` 仍 200 ✓。
- ⚠️ 本机 nginx 由**宝塔**托管:`systemctl reload nginx` **失效**,须用 **`nginx -s reload`**。
### 9.4 🔴 期间修掉一个**由本次改名引入的真回归**(如实记录)
- 症状:**admin 实例页 502**,journal = `plugin tree failed to load … corrupt session log`。
- 根因:会话日志是 **`session.jsonl.zstd`(zstd 压缩)**。改名只改了**目录名**与**纯文本文件**,**压缩流 header 里的 `cwd` 仍是旧路径** ⇒ dsh 启动做一致性校验(header cwd vs 目录推导 cwd)**不匹配 ⇒ 抛错 ⇒ 插件树加载失败 ⇒ 崩溃循环**。
- 修复:`_fix_sesslog.cjs`(node 内置 `zlib.zstdDecompressSync/zstdCompressSync`,22.15+)—— 解压 → 替换 → 重压 → **回读校验**。**26 个文件 24 个待修 → 全修完,复查 0 残留** ✓;实例恢复 200,`corrupt session log` 归零。
- 📌 **教训**:**压缩文件里的路径 `grep`/`sed` 够不到** —— 与「SQLite 改名必须带 `-wal`/`-shm`」同类。已写进项目 `MEMORY.md` 铁律。
### 9.5 期间发现的两个平台缺陷(**待修**)
1. **`op-lock` 不可用**:锁根 `/opt/dsh/state/.op-lock` **不存在**,且 `scripts/op-lock.sh` 自身解析 `bt-server` 失败 ⇒ 它报「已存在 → 停手」是**假警报**,无法用于互斥。
2. **`agent-browser` daemon 起不来**(见 §9.2)—— 浏览器验收链路的唯一可用工具受阻。
### 9.6 尚未做
- **D / J / Q**(T03 遗留)与**登录/注册/门户/设置面板的体验审计**(用户 18:1x 关注的第三块)。
---
## 十、追加(2026-09-13 19:3x–19:5x):**从「只读」升级为「就地全量管理」**(用户要求)
### 10.1 用户的三条要求
1. **dsh 设置里的「用户管理」改名为「用户设置」**,且**去掉里面的「打开管理平台」入口**
2. **「平台管理」不要再跳新页面** —— **把所有管理项都放进「系统管理」,每项点开是弹窗**
### 10.2 落地
| 项 | 改动 | 产物 |
|---|---|---|
| 「用户管理」→**「用户设置」** | `@dsh-local/portal-entry`:section `id: user-management → user-settings`、`label` 改名、**order 102 → 103**(让位给平台管理);**删除「打开管理台」按钮**;内容改为「账号:<用户名> 角色:<管理员/普通用户>」+ 退出登录 | **`portal-entry-0.5.2.tgz`**(已铺发到 admin+guest) |
| 「平台管理」→**「系统管理」+ 管理项 + 弹窗** | `@dsh-local/business-plugins`:**删掉 `window.open(portal.html)` 跳转按钮**;分区改为「**管理项列表**」,5 项(用户管理 / 候选池 / 运行时 / 存储 / 当前实例)**每项点开是原生弹窗**;**用户管理弹窗内可直接 通过 / 禁用 / 启用 / 删除**(调平台既有 admin API,不跳门户) | **`business-plugins-0.3.0.tgz`**(已铺发) |
### 10.3 验收(`npm run verify` 内)
```
✓ 注册了两个 settings.section -> business-plugins, platform-admin
✓ admin:含分区标题「系统管理」 ✓ admin:5 个管理项齐全
✓ admin:不再是只读(提示改为弹窗内管理)
✓ 非 admin:被门禁挡住 ✓ 非 admin:不出现任何管理项
结论:全绿 ✅
```
另:**磁盘复核** —— 两 profile 的 `business-plugins` 均 **0.3.0**、含「系统管理」、**`window.open` 计数 = 0**(跳新页彻底移除)✓
### 10.4 尚未做的(下一批)
- 弹窗内**目前只有「用户管理」是可写**的;**候选池 / 运行时 / 存储 / 实例** 三项为**只读列表**(写操作待补)。
- **portal-entry 源码不在仓库**(只有铺发脚本 + artifacts 里的 tgz)⇒ 本次是**在产物上改**的。**建议**:把 `portal-entry`/`business-plugins` 源码纳入仓库(档案 75 · B5 的旧账)。
### 10.5 视觉层补齐(2026-09-13 20:1x,用户反馈「像毛坯房、参考门户样式、把 UI 规范记脑袋里」)
**做法**:按 `06-工作台UI规范` 重做视觉层(此前只做了功能骨架、无样式):
| 元素 | 采纳的规范条目 | 取值 |
|---|---|---|
| 管理项卡片 | **§4.5 `.data-card`** | **r12** / `16px 18px` / `box-shadow: 0 1px 3px rgba(0,0,0,.08)` / **hover 上浮 `translateY(-2px)` + 蓝边 + `0 4px 12px rgba(47,111,237,.15)`** / `transition .15s cubic-bezier(.16,1,.3,1)` |
| 卡片布局 | §3 | `grid-template-columns: repeat(auto-fill, minmax(158px, 1fr))` + gap 12;`minHeight 104px` 保证等高 |
| 弹窗 | **§4.7 `.modal` / `.modal-mask`** | mask `rgba(0,0,0,.35)` + flex 居中;面板 **r14 / `22px 26px` / `width 520px` / `max-width 92vw` / `0 8px 30px`**;进场 `paIn .18s` |
| 按钮 | §4.1 `.btn-sm` 量级 | `3px 10px` / r6 / 12–13px;hover 蓝边蓝字 + 淡蓝底 |
| 数值 | **§5** | 计数**右对齐 + `font-variant-numeric: tabular-nums`** |
| 字号 | §2.2 阶梯 | 卡片标题 15/500、说明 13/次级色、计数 17/500 |
| 动效降级 | §4.5 | `@media (prefers-reduced-motion: reduce)` 关动画与位移 |
**色彩**:继续用 dsh 官方 token `--dsw-*`(**刻意不硬编码亮色**——本 section 嵌在 dsh 设置面板内,须自动跟随 dsh 主题;硬编码会在暗色主题下瞎)。规范里的硬色值是给**门户静态页**的。
**产物**:`business-plugins-0.3.1.tgz`(已铺发 admin+guest);`npm run verify` 全绿。
⚠️ **教训(已入库)**:上一轮**只做了功能、没做视觉**就交付,被指「像毛坯房」。**做页面的默认动作必须包含「查规范 → 落地 §4.5/§4.7 等具体条目」**,不能等用户来提。
### 10.6 修复:**普通用户不该看到「系统管理」**(2026-09-13 20:3x,用户指出)
**问题**:此前只对**内容**做了门禁 ⇒ **section 本身仍对所有人注册**,普通用户在设置里能看到「系统管理」这一栏(点进去只有一行「仅管理员」)。
**正解(不再注册,而非注册后藏内容)**:
```js
// apply() 里先异步判角色,非 admin 干脆不注册
fetch(portalHost() + "/api/auth/me", { credentials: "include" })
.then(r => r.ok ? r.json() : null)
.then(d => { if (d?.user?.role !== "admin") return; ctx.slots.inject(...register...) })
.catch(() => {}) // fail-closed:判角色失败按非 admin 处理
```
**依据(有实证,不是猜)**:官方 settings 外壳的导航是这样算的 ——
`dsh-client-ui-settings/lib/client.js:560`:`const version = ctx.slots.getVersion("settings.section")` ⇒ **版本变化会重算导航行** ⇒ **晚注册(异步 inject)会被正确反映** ✓。
**验收(已进 `npm run verify`)**:
```
✓ admin 视角下注册了两个 settings.section -> business-plugins, platform-admin
✓ 非 admin 视角下只注册 1 个分区(不含系统管理) -> business-plugins
结论:全绿 ✅
```
(验收脚本原先没等异步注册 ⇒ 一并修好:`apply` 后 `await 80ms` 再断言。)
**产物**:`business-plugins-0.3.2.tgz`(已铺发 admin+guest)。
### 10.7 视觉层第 2 轮:**「照抄门户源码」而不是「照抄规范条目」**(2026-09-13 20:4x,用户第 2 次反馈)
**用户原话**:「点击卡片后的弹窗样式还是没变呢,说了要用之前门户的里面的对应样式」。
#### 🔴 真因有两个(都查明并已修)
| # | 真因 | 证据 |
|---|---|---|
| **1** | **我上一步的改动根本没出厂** —— `lib/client.js` 的 mtime = `20:33`,而线上最新产物 `business-plugins-0.3.2.tgz` 打包于 `20:17` | `ls -la` 时间戳对比;解包 0.3.2 与工作副本 `diff` 显示 109 行差异全在「未打包」那一侧 |
| **2** | 更要命:**上一版只有「用户管理」1 个弹窗是门户表格**,另外 4 个(候选池 / 运行时 / 存储 / 当前实例)**仍是裸 `div` + 内联样式**(`padding:8px 10px; border:1px solid …; borderRadius:8px; fontSize:13px`)⇒ 点开就是「毛坯房」 | `git diff` 原文:4 个分支返回 `jsxRuntime.jsx("div", { style: { display:"flex" … } })`,无任何 `className` |
⇒ **我上一轮「照抄」抄的是 `06-工作台UI规范` 的抽象条目(§4.5 `.data-card` / §4.7 `.modal`),而用户要的是 `web/portal.html` 里那套已经跑着的实际组件。** 规范条目 ≠ 门户真实样式。
#### 本轮做法:**读门户源码,逐值对应**
| 「系统管理」元素 | 门户来源(`web/portal.html`) | 落地类名 |
|---|---|---|
| 卡片网格 | `.nav-grid`(`repeat(auto-fill,minmax(200px,1fr))` / gap `14px`) | `.pa-grid` |
| 管理项卡片 | `.nav-card`(**r10** / `18px 16px` / hover:蓝边 + `0 4px 12px rgba(47,111,237,.12)` + 上浮 `-2px`) | `.pa-card` |
| 卡片内三行 | `.nav-card .ic`(主色)/ `.t`(15-600)/ `.d`(12 dim) | `.pa-ic` / `.pa-t` / `.pa-d` |
| 卡片计数 | `.pg-tab .pg-cnt`(12-600 胶囊 / 主色底) | `.pa-cnt` |
| **弹窗正文(全部 5 个)** | `.table-wrap` + `table.tbl`(th 灰底 `8px 10px`-600 / td `7px 10px` / 行 hover 浅蓝 / 末行去下边框) | `.pa-wrap` + `.pa-tbl` |
| 表格空态 | 门户 `emptyRow()`(居中 + `40px 10px`) | `.pa-tbl td.pa-empty-cell` |
| 角色徽章 | `.badge` + 四色 `.admin/.active/.pending/.disabled` | `.pa-badge` |
| 操作按钮 | `.btn-sm`(`3px 10px` / r6 / 14px;`.danger` 红字红边) | `.pa-sm` / `.pa-sm.danger` |
| 弹窗头 | `.card-h`(15-600,可带 13 dim 副标题) | `.pa-card-h` |
| 标题 / 副标题 | `.page-title`(20-600)/ `.page-sub`(13 dim) | `.pa-hd` / `.pa-sub` |
| 分组小标题 | `.home-section-title`(13 dim 600) | `.pa-sec` |
| **弹窗外壳** | 门户**没有弹窗** ⇒ 面板取 `.card`(r10 / `18px`)+ 门户家族阴影(`design.css` `--shadow` = `0 18px 50px -18px rgba(9,24,58,.35)`) | `.pa-overlay` + `.pa-modal` |
**5 个弹窗逐个改完**:
| 弹窗 | 表头 | 说明 |
|---|---|---|
| 用户管理 | 用户名 / 角色 / 操作 | 可写:通过 · 启用 · 禁用 · 删除(`.btn-sm.danger`) |
| 候选池 | 插件 / 版本 | 只读 |
| 运行时 | 项目 / 版本 | 只读 |
| 存储 | 用户 / 已用(`.pa-num` 右对齐 tabular-nums) | 只读 |
| 当前实例 | 项目 / 值(键值表) | 只读 |
⚠️ **唯一的刻意差异**:颜色走 dsh 官方 token `--dsw-*`(本 section 嵌在 dsh 设置面板内、须自动跟随 dsh 主题);门户那套 `--bp-*` 是给亮色静态页的硬编码。**字号 / 间距 / 圆角 / 动效与门户逐值一致。**
#### 三层验收(这次不只看单测)
**① 单测层**(已进 `npm run verify`,脚本 `scripts/verify-platform-admin-section.mjs` 新增「结构层」断言):
```
✓ 首页:卡片网格取门户 .nav-grid 值
✓ 首页:卡片取门户 .nav-card 值(.pa-card)
✓ 首页:计数胶囊取门户 .pg-cnt 值(.pa-cnt)
✓ 首页:标题/副标题取门户 .page-title/.page-sub 值
✓ 5 个弹窗全部 = 门户弹窗外壳 + portal 表格 + .btn-sm -> users/plugins/runtime/storage/instance 均通过
✓ 用户弹窗:角色用门户 .badge 四色 -> 4 枚
✓ 用户弹窗:删除按钮用门户 .btn-sm.danger
✓ 实例弹窗:键值齐全
结论:全绿 ✅
```
(写这套断言时踩到校验脚本自身的坑:树遍历 `depth > 12` 上限会把 `表格>tbody>tr>td>文本` 这一支截断 ⇒ 表头有、行文本丢;**已放宽到 20 并加注释**。属校验脚本缺陷,不是产物缺陷。)
**② 服务器实装层**(md5 三处一致):
```
dep: business-plugins-0.3.3.tgz ← 两个 profile 都指向 0.3.3
实链: business-plugins@file+…+business-plugins-0.3.3.tgz
md5: 5800ba4f77e1c786063aed8a91f9f4b4 ← admin / guest / 本地产物 三方完全相同
旧版残留(.pa-n{|pa-go): 0
```
**③ 端到端层**(**真正下发到浏览器的那份**):临时会话 `mksess.cjs` → 取实例首页 → 解出模块 URL → 取回 bundle(`用完即删临时会话`):
```
实例首页 http=200
模块 URL: /plugins/??…,@dsh-local/business-plugins/client.js,…&rev=fd084d6aa33a
bundle http=200 bytes=4260682
pa-empty-cell 3 处 / pa-card-h 4 处 / pa-overlay 2 处 / pa-cnt 3 处
pa-grid 3 处 / pa-tbl 10 处 / border-radius:10px;padding:18px 16px 1 处
旧版残留:pa-go 0 / .pa-n{ 0 / 0 8px 30px rgba(0,0,0,.28) 0
清理临时会话: 1
```
#### 缓存问题(这次一并确认,不是它导致的)
`src/supervisor/proxy.ts:383` 对 `/plugins/` 与 `/assets/` 统一下发 `Cache-Control: no-cache`(**允许缓存但每次回源校验**);且 dsh 的模块 URL 自带 **内容 sha1 的 `rev`**。⇒ 本轮 `rev` 已从旧值变为 `fd084d6aa33a`,**用户无需清缓存 / 换无痕**。
#### 产物与部署
- `business-plugins-0.3.3.tgz`(本地 `dsh-local-business-plugins-0.3.3.tgz`)
- 铺发:`node /opt/dshs/scripts/ensure-biz-plugins.cjs --all --restart` ⇒ `admin: ✓ bundles=6` / `guest: ✓ bundles=7`,均已停实例(下次访问自动拉起新 bundle)
- ⚠️ **顺手清掉一个自作的地雷**:打包脚本第一次因断言锚点写错(description 不以 `v0.2.8` 开头)**在写文件前就抛错**,但我已先跑了 `npm pack` ⇒ 生成了一个**内容是新版、版本号却仍写 0.3.2** 的本地 tgz。**已删除该文件**并改用正确锚点重新升到 0.3.3(服务器侧的 `business-plugins-0.3.2.tgz` 是真正的旧版,未受影响)。
#### 🔴 教训(已入库)
**「按规范条目做样式」≠「用门户已经跑着的样式」。** 用户说「参考门户里的对应样式」时,正确动作是 **打开 `web/portal.html` / `admin.html` 的源码,把 `.nav-card` / `table.tbl` / `.badge` / `.btn-sm` 的**实际取值**逐条搬过来**,而不是去引规范里的抽象条目再自行演绎。另外:**交付前必须自问「这份改动出厂了吗」**——本轮真因 1 就是改完没打包没铺发。
---
## 十一、追加(2026-09-13 21:0x–21:3x):**功能名与内容全面改为「门户 6 个功能页」**(用户第 3 次反馈)
> 用户在铺发 0.3.3 之后当场反馈(原话):
> 「怎么设置系统管理里面还是老样子呢,和之前门户那个**功能名称**、**页面样式**完全不一样呢,你要不要把之前那个 portal 页面打开看看,**我要点开弹窗里面展示的内容是和 portal 点击功能后那种详细的表格和功能**」
### 11.1 先定位「还是老样子」的真因(**不是没出厂**)
| 检查 | 结果 |
|---|---|
| 线上实际装的版本 | 实例 profile → `.dsh-stage/business-plugins-0.3.3.tgz`,`package.json` 解析 `"version": "0.3.3"` ⇒ **第 2 轮确实已生效** |
| 那用户为什么说"老样子" | **第 2 轮只改了「视觉层」(class / 字号 / 颜色),没动「信息架构」** —— 管理项仍是自造的 5 项只读摘要(用户管理 / 候选池 / 运行时 / 存储 / 当前实例),而门户是 6 个功能页(服务管理 / 密钥管理 / 用户管理 / 技能管理 / 插件管理 / 运行环境)。**名字对不上、表对不上** ⇒ 用户一眼就看出"不一样" |
⇒ **教训**:说"照抄门户",只抄样式不算抄;**功能名 / 表格列 / 操作按钮**才是用户眼里的"是不是同一个东西"。
### 11.2 口径变更(**推翻 §一 表里的两行**)
| 维度 | §一(初版) | 本轮(现行) |
|---|---|---|
| 范围 | 只读子集(实例/用户/存储/候选池/运行时) | **门户 6 个功能页,同名同构** |
| 写操作 | 一律回跳门户管理台 | **就地调用平台 admin API**(跨子域 + `credentials:include`;门户 CORS 白名单已含 `GET,POST,DELETE,OPTIONS`) |
| 形态 | 原生弹窗(非 iframe) | **不变**(用户 09-13 已拍板;本轮仍不做 iframe) |
> ⚠️ §八「口径提醒」原文(*不能两头都要…届时应把门户收敛成 API 客户端*)**仍然成立**:本轮选择了「复制 UI」这条路,代价是**双源维护**。为把代价压到最低,移植采用**逐函数照抄**并逐条注释对应关系(将来门户改了,按同名函数对照即可)。若日后要收敛,方向仍是「门户与弹窗共用一份渲染」。
### 11.3 落地(`poc/business-plugins/lib/client.js`,1624 → 2218 行)
| 新增 | 内容 |
|---|---|
| `PA_PAGES` | **6 个功能页**,每页 = `{ icon, title, desc, sub, html(), init($, root) }`,其中 `html()` ≡ 门户 `renderXxx()`、`init()` ≡ 门户 `initXxx()` |
| `PA_GROUPS` | 首页分组 ← 门户 `renderHome()` 的 `sec1`(服务)+ `sec2`(管理),顺序一致 |
| `PortalPage` | 页面宿主:`dangerouslySetInnerHTML` 注入门户原文 + `useEffect` 里跑门户 `init`(`$` = 弹窗子树内的局部查询器) |
| 工具函数 | `pafmt`(`{占位符}`)/ `paesc` / `pafmtSize` / `pawhen` / `paemptyRow` / `pareadAsBase64` / `parelTime` / `paReq` / `papostJson` —— 均为门户同名函数移植 |
| 词典 | `pa.*` 由 **91 → 225 条**(zh/en 键集一一对应,新增断言把关);弹窗内 **182 条** 文案**逐字取自门户** |
| CSS | 门户页面级组件逐条译为 `.pa-*`:`.card→.pa-box`、`.btn/.btn-primary→.pa-btn*`、`.page-head→.pa-phead`、`.pg-tabs/.pg-tab/.pg-cnt→.pa-tabs/.pa-tab/.pa-tcnt`、`.dsh-bar/.dot→.pa-dshbar/.pa-dot`、`.pathbar/.crumb→.pa-pathbar/.pa-crumb`、`.upload-row/.hint→.pa-row/.pa-hint`、`.key-row→.pa-keyrow`、`.wl-*→.pa-wl*`、`.empty→.pa-empty`、`.toast→.pa-toast` |
**三处机械改写**(除此外结构 / 表格列 / 文案**逐字保持一致**):
1. `document.getElementById(x)` → `$("x")`(局部查询器,避免与 dsh 面板同名 id 撞车);
2. `fetch("/api/…")` → `paReq("/api/…")`(跨子域 + `credentials:"include"`);
3. `location.hash` 路由 → 弹窗内页签状态(门户靠 hash 深链,弹窗不需要)。
**弹窗尺寸**(用户要求「需要多大就多大,不用做的太小」):`.pa-modal.pa-wide` = `width:min(1440px,96vw)`(对齐门户功能页 `.page` 的 1440)+ `height:min(92vh,1040px)`;头固定(门户 `.page-head` = ← 返回 + 标题 + 副标题 + 关闭),体内部滚动。
**两处对门户的有意偏离**(均已在代码注释里标注):
- 🐛 **修掉门户 `readAsBase64()` 的缺陷**:门户原文 `return String(new Promise(…)).split(",")[1] ?? ""` **缺 `await`** ⇒ 恒得 `"[object Promise]"` ⇒ 返回空串 ⇒ **门户的「上传文件 / 上传技能 zip / 上传插件 tgz」实际传的是空数据**(三个上传入口全受影响)。弹窗侧改为返回真 Promise;**门户侧未动,待同修**。
- `.pa-dir` 补了手型 + 主色(门户的文件夹行**没有任何可点提示**,弹窗里若照抄会没法导航文件树)。
### 11.4 验收
```
node scripts/verify-platform-admin-section.mjs ⇒ 全绿(0 退出码)
node --check poc/business-plugins/lib/client.js ⇒ 通过
```
验收脚本本轮同步升级:新增**词典键集一一对应 / 占位符键一致**两条断言;断言口径从「5 项只读摘要」换成「门户 6 个功能页同名同构 + 6 个弹窗外壳 + 各页表头/按钮文本」;并新增 `clsAll()` —— **同时扫 React 节点与 `dangerouslySetInnerHTML` 里的 class**(只看 React 节点会漏掉全部表格/工具条类名,这是本轮踩到的验收盲区)。
### 11.5 产物与铺发
- `business-plugins-0.3.4.tgz`(本地 `dsh-local-business-plugins-0.3.4.tgz`,39,098 B;包内 4 条目:`package/{package.json,lib/index.js,lib/client.js,cordis.patch.yml}`,无 `.tgz` / 无 `.bak`)
- ⚠️ 打包前**删掉了 `lib/client.js.bak-*`** —— `.npmignore` **没有**排除 `.bak`,留着会被打进产物
- 铺发:`ssh bt-server 'cd /opt/dshs && node scripts/ensure-biz-plugins.cjs --all --restart'`(服务端脚本 md5 与本机不同,但选版逻辑一致:按 `business-plugins-<ver>.tgz` 前缀取最大版本)
### 11.6 ⚠️ 本轮发现、**未动手**的问题(先报告)
1. **门户 `readAsBase64()` 缺 `await`**(见 §11.3)—— 影响门户三个上传入口,属生产缺陷。修它要改 `web/portal.html`,**不在本次要求范围** ⇒ 未动。
2. **本档案自身严重重复**:`# 82 · R2 管理面就地化` 标题出现 **6 次**,`## 一`–`## 八` 前缀块重复 **6 份**(1937 行里大半是重复内容,疑似历史多会话并行各自追加所致)。**未做去重**(档案只增不改 + 去重需逐块比对确认无独有内容,应单独立项)。
### 11.7 追加(21:2x):**「官方推荐插件」列表拉高**(用户要求)
> 用户原话:「系统设置 插件管理里面官方插件列表 **高度可以增加,距离底部 100PX 就行**」
| 项 | 改前 | 改后 |
|---|---|---|
| 官方插件表容器 | `class="pa-wrap" style="max-height:420px"` | `class="pa-wrap pa-plist"` |
| 高度 | 固定 **420px** | **`max(280px, min(calc(92vh - 470px), 580px))`** |
取值依据(写进 CSS 注释,便于复核):弹窗高 = `min(92vh,1040px)`;表**上方**固定消耗 ≈**470px**(弹窗头 + 页签 + 工具条 + 说明 + 信息行),表**下方**还有按钮行 + 结果区 + 内边距 ⇒ 用 `calc(92vh - 470px)` 反推,使**列表底部距弹窗底部约 100px**。
- 下限 **280px**:小屏时防止被压成一条线;
- 上限 **580px**:对应弹窗触顶 `1040px` 时的可用高度(1040 − 470 − 下方 ≈ 90 − 100 ≈ 380…560 区间,取 580 留 20px 余量)。
产物:**`business-plugins-0.3.5.tgz`**(39,544 B,包内 4 条目)。铺发同上 ⇒ admin/guest 均 0.3.5;`sha256 = 1d29683770b4…` **本机 = artifacts = 两实例 `.dsh-stage` 三方一致**;实例内 `lib/client.js:654` 已含 `.pa-plist{…}`。
验收:`verify-platform-admin-section.mjs` 全绿(CSS 断言扩到 `.pa-plist`);`node --check` 通过;**防假产物自检**:源文件 21:21:41 < 产物 21:21:58 ✅。
⚠️ 门户原值 `calc(100vh - 520px)` 是**整页布局**下的取值,弹窗内不适用 —— 别直接照抄。
---
## 修正(2026-09-14)· 本档存在**严重内部重复**(已体检,原文按 L5 冻结保留)
**实测**(`python3 scripts/docs-dedupe.py "04-调整方案/82-R2管理面就地化-原生平台管理分区.md"`):
| 项 | 数 |
|---|---|
| 文件规模 | **2024 行 / 89.1 KB**(全库最大单篇;约定上限 30 KB) |
| 块(按 `#`/`##`/`###` 切) | 224 个 |
| **重复标题** | **22 个** |
| **冗余块** | **190 个(≈85%)** —— 同一份「§一–§八」重复 **16 次**;`# 82 · …` 与 §九(9.1–9.6)各重复 **8 次** |
| ⚠️ **内容有差异的** | **「八、口径提醒」两个变体**(各 8 次)⇒ **不是纯复制**,去重时必须两份都看 |
**处置(零改写,遵守 L5 冻结)**:
1. **原文一字未改**,仅追加本小节;
2. 生成**去重视图**(保留每组信息最全的一份,其余位置留一行占位注释):
`…/.workbuddy/cache/dedupe-view/04-调整方案__82-R2管理面就地化-原生平台管理分区.md`
—— **89.1 KB → 59.0 KB**(省略 192 块);
3. ⚠️ **视图仍 59 KB > 30 KB 约定** ⇒ **去重只是第一步**,根治要靠**拆分**(把 §九 追加节与本次 R2 主体拆成两篇);此项**待立项**。
4. 阅读建议:**先看视图**;需核对「八、口径提醒」两个变体时再回原文。
> 复跑体检:`python3 scripts/docs-dedupe.py <本档相对路径> --view-out`