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.
4.6 KiB
4.6 KiB
侧边栏插件操作规范(给其他插件的 Agent)
版本基线:DSH Desktop 2.0.17 / 宿主
@deepseek-ai/*0.2.0-rc.2 用途:任何往 DSH 右侧栏挂 tab 的插件都按这份规范做,避免重复踩坑(每条都有实测根因)
一、硬规则(违反 = 静默失效,不报错)
1. Config 必须用宿主的 schemastery ⭐ 头号坑
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
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() 产出的不透明引用:
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 宿主包——
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接口里,需结构化探测)。
四、自检清单(发版前逐条过)
pnpm typecheck && pnpm lint && pnpm build && pnpm test全绿(构建 ≠ typecheck,tsdown 跳过 tsc 严格性)。grep '^import z' lib/index.js→ 必须是@deepseek-ai/schemastery。grep -c 'schemastery' package.json→dependencies里没有公版 schemastery。- 设置开关实测:点两次,值不再弹回,且
cordis.patch.yml里的新值等于 UI 显示值。 - 改完配置不需要重启 DSH(volatile 就地生效);若需要重启才生效 = 第 1 条没做对。
- 产物 rsync 到
~/.dsh/profiles/<profile>/node_modules/<pkg>/lib/后逐文件 md5 比对(tsdown 输出非确定性,字节数相同≠内容相同)。