Files
apes-sidebar/docs/plugin-conventions.md
T
figmar 35d33c7f65
CI / ci (pull_request) Canceled after 0s
CI / ci-windows (pull_request) Canceled after 0s
CI / plugin-mount (pull_request) Canceled after 0s
fix(subagent): hydrate the catalog via the host's refreshProjections
0.2.0-rc.2 withdrew every catalog/subagent seam this fork was written
against, and all three calls degraded to silent no-ops:

  openSubagent(address)             -> gone
  setSubagentCatalogOpen(id, open)  -> gone
  refreshSubagents(id)              -> gone

The host's own subagent UI (dsh-client-ui-subagent) rebuilds a parent's
child catalog with `sessions.refreshProjections(parentSessionId)`, so
that is the only kick that still exists. Wire it into `observe()` and the
catalog retry button, keeping the legacy names as fallbacks for older
hosts. Without it the root catalog never hydrated: the page sat on
"loading" or degraded to synthesized summary rows.

Also document what the investigation settled:
- docs/subagent-state-enumeration.md: the full state model (catalog
  child / diagnostic / session / job / sidechat) and proof that this
  fork already renders it 1:1 with upstream.
- docs/plugin-conventions.md: the hard rules another sidebar plugin
  must follow (host schemastery, volatile prefs, ns = profile entry id,
  no colons in tab ids, never read a volatile field directly) plus the
  withdrawn-API replacement table and a pre-release checklist.
- docs/feedback-0.2.0-rc2-contract-gaps.md: gaps 2/3 reframed. The ask
  is documenting the official paths (open(childSessionId) /
  refreshProjections(id)), not adding new APIs.

Gates: typecheck 0, lint 0, build 0, test 131 files / 1385 passed.
2026-10-13 23:18:30 +08:00

71 lines
4.6 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.
# 侧边栏插件操作规范(给其他插件的 Agent)
> 版本基线:DSH Desktop 2.0.17 / 宿主 `@deepseek-ai/*` 0.2.0-rc.2
> 用途:任何往 DSH 右侧栏挂 tab 的插件都按这份规范做,避免重复踩坑(每条都有实测根因)
## 一、硬规则(违反 = 静默失效,不报错)
### 1. `Config` 必须用宿主的 schemastery ⭐ 头号坑
```ts
import z from '@deepseek-ai/schemastery' // ✅ 宿主 fork,含 volatile 支持
import z from 'schemastery' // ❌ 公版 3.18.0,无 volatile
```
- 后果:`settings.update` **写进 `cordis.patch.yml` 成功**,但宿主 loader 静默 no-op —— 不生效、不报错、不重启、值冻结。
- 机制:宿主 `cordis.resolveConfig` 走 `runtime.Config['~standard'].validate()`,用的是**插件自己那份 schemastery** 的解析器;无 volatile 分支 ⇒ 没有 volatile 引用 ⇒ `cordis-plugin-loader._commitVolatile()` 首行 `if (!volatileEntries(fiber.config).length) return true` ⇒ 视作已提交。
- `package.json`:`peerDependencies` 加 `"@deepseek-ai/schemastery": "^3.18.1"`(宿主 mount 时提供),`devDependencies` 钉 `3.18.4`。
### 2. 用户偏好字段必须标 volatile,且并入 `Config`
```ts
tabsEnabled: z.dict(z.boolean()).default({}).extra('volatile', true)
```
- 0.2.0+ 移除了 `settings.register()`,**loader 只注册插件导出的 `Config`**;独立 `PrefsSchema` 是死代码。
- `settings.update` **只放行 volatile 字段**(非 volatile 路径抛 `Config field "<x>" is not volatile`)。
- **部署字段**(limit / shell 之类)保持**非 volatile**——它们不该被设置页改写。
### 3. settings ns = **profile 条目 id**,不是包名
`cordis.patch.yml` 里 `- id: <ns>` 的那个 id。用包名会在 `settings.describe()` 里找不到自己(报 `No configurable plugin entry "<包名>"`)。
### 4. tab id / map key **不要用冒号**
`apesTasks` ✅ / `apes:tasks` ❌。冒号 key 在设置投影链路上出现过丢失。
(i18n key 不受影响,本来就是 camelCase。)
### 5. volatile 字段**不能直读**
修复后 `config.tabsEnabled` 是 `createVolatile()` 产出的**不透明引用**:
```js
Object.freeze({ get: () => current, [Symbol.for('cosmokit.volatile.write')]: v => { current = v } })
```
⇒ 没有数据属性,`config.tabsEnabled.git` 是 `undefined`(不是旧值),`JSON.stringify` 出 `{}`。
**必须走 `settings.get()` / `settings.describe()`**(宿主对 ref 调 `plainConfig()` 解包)。
## 二、0.2.0-rc.2 消失的宿主 API 与官方替代
| 老调用(会静默 no-op) | 官方替代(宿主自己的 UI 就是这么调的) |
|---|---|
| `sessions.openSubagent(address)` | `sessions.open(childSessionId)` |
| `sessions.setSubagentCatalogOpen(id, open)` | `sessions.refreshProjections(id)` |
| `sessions.refreshSubagents(id)` | `sessions.refreshProjections(id)` |
判定方法(别猜):在 app 内全量 grep 宿主包——
```bash
A="/Applications/DSH Desktop.app/Contents/Resources/app/node_modules/@deepseek-ai"
grep -rn "<apiName>" "$A"/*/lib/*.js | head
# 0 命中 = 宿主没实现;再看官方自己的插件怎么调的(如下):
sed -n '945,1005p' "$A/dsh-client-ui-subagent/lib/client.js"
```
`dsh-client-ui-subagent` 是**宿主自己的子代理 UI**,是找「官方正确姿势」的最权威样板。
## 三、注册/生命周期
- 服务注册必须等服务、不能等槽声明:`ctx.inject(['sidebarRightTabs'], cb)` 驱动类型注册(槽声明早于服务 provide,按槽注册会静默什么都不注册且不重试);槽只用来挂 tab 体。
- 原生 tab 体宿主 `.paneBody` 是**有确定高度的块级滚动容器**,native 适配层要给每个 tab 体包 `height:100%` 的列 flex 宿主。
- 跨会话操作原生栏用 `openTabIn/openResourceIn/closeIn`(不在 `ISidebarRight` 接口里,需结构化探测)。
## 四、自检清单(发版前逐条过)
1. `pnpm typecheck && pnpm lint && pnpm build && pnpm test` 全绿(构建 ≠ typecheck,tsdown 跳过 tsc 严格性)。
2. `grep '^import z' lib/index.js` → 必须是 `@deepseek-ai/schemastery`。
3. `grep -c 'schemastery' package.json` → `dependencies` 里**没有**公版 schemastery。
4. 设置开关实测:点两次,值**不再弹回**,且 `cordis.patch.yml` 里的新值等于 UI 显示值。
5. 改完配置**不需要重启 DSH**(volatile 就地生效);若需要重启才生效 = 第 1 条没做对。
6. 产物 rsync 到 `~/.dsh/profiles/<profile>/node_modules/<pkg>/lib/` 后**逐文件 md5 比对**(tsdown 输出非确定性,字节数相同≠内容相同)。