# 插件开发注意事项(踩坑记录) > 面向本仓库及任意 dsh 插件的开发者。每条记录含机制与验证方式, > 避免同类问题再次发生。 ## 1. client bundle 的 `__ModuleLoader__.load` id 必须是裸包名 **症状**:浏览器控制台报 ``` client-modules: bundle /plugins/dsh-chat-log/client.js loaded without registering "dsh-chat-log" via __ModuleLoader__.load Failed to load plugins ``` 按钮/客户端增强不生效。 **根因**(`@deepseek-ai/dsh-client-modules`): - Host 端为每个插件的 browser half 生成 graph 行 `{ id, url }`,**id 取自裸包名** (`package.json` 的 `name`,如 `dsh-chat-log`;`stripClientSuffix` 只会剥离 以 `/client` 结尾的后缀,**不会**解析带 scope 的 `@owner/pkg` 为 alias)。 - `arrive(row)` 加载 `url` 后校验 `factories.has(id)`;bundle 内 `window.__ModuleLoader__.load({ id, factory })` 注册的 id 必须与之**逐字相等**。 - 写成 `@deepseek-ai/dsh-chat-log`(照抄官方包的 scope 风格)就会注册不进 预定的 id,校验失败。 **修复**:`lib/client.js` 中 ```js window.__ModuleLoader__.load({ id: "dsh-chat-log", // 必须是裸包名,不要带 @scope/ ... }); ``` 配套一致化(不强校验但保持整洁):`data-plugin`、`data-plugin-css`、样式 `tagId` 也统一用裸包名。 **验证**: ```bash # 重启后直接 curl bundle 首行 curl -s http://127.0.0.1:3080/plugins/dsh-chat-log/client.js | head -3 # 应输出:window.__ModuleLoader__.load({ id: "dsh-chat-log", ... # 浏览器打开 UI:控制台 0 错误;会话头部出现插件按钮即加载成功 ``` ## 2. `dsh plugin add file:` 是复制安装,不是符号链接 **现象**:改完 `lib/*.js` 源码,重启后插件行为不变(或仍报旧 bug)。 **事实**:`dsh plugin --profile web add file:/path/to/pkg` 经 pnpm 将包**复制** 到 `~/.dsh/profiles/web/node_modules//`(实测为真实目录,非 symlink)。 源码与运行副本是两份,改一处不会同步另一处。 **开发期推荐**:用 `link:` 协议做引用安装,源码改动即时生效: ```bash dsh plugin --profile web add link:/mnt/e/.../dsh-chat-log # 符号链接 ``` 若已用 `file:` 安装,改源码后需同步副本: ```bash diff /path/to/src/lib/client.js ~/.dsh/profiles/web/node_modules//lib/client.js # 不一致时: dsh plugin --profile web remove && dsh plugin --profile web add file: ``` **验证**:`ls -ld ~/.dsh/profiles/web/node_modules/` —— `lrwxrwxrwx` 是链接,`drwx` 是复制。 ## 3. 插件行合并到 profile 的冒烟检查(无需重启) 改 `cordis.patch.yml` / `package.json`(`dsh.bundle` 声明)后,合并结果可以 不启动服务器先验证: ```bash dsh --profile web --dump-config 2>/dev/null | grep -A3 '' # - id: # name: ``` 并用 `%` 验证 bundle 已注册进 profile: ```bash grep -A15 '"bundles"' ~/.dsh/profiles/web/package.json | grep ``` ## 4. 零依赖后端能力清单(省去重复调研) - 会话原始日志读取:`ctx.get("sessionPersistence").readRaw(id)`—— 内部完成 slug 目录定位、多帧 zstd 解压、抗撕裂读,返回 `{ meta, filename, content }` (content 为**已解压的纯文本 JSONL**),插件无需自备 zstd。 - 命令注册:`ctx.commands.register({ name, description, handler })`; handler 参数 `invocation` 含 `rawInput` / `signal` / `agent.session`(当前会话)。 - client 触发 host 命令:`ctx.get("remote").commands.execute(sessionId, "/cmd", [])`, 返回 `{ ok, value: { result: { kind, text } } }`(与输入框提交同通道)。 ## 5. 已记录的历史事故 | 时间 | 问题 | 根因 → 修复 | |---|---|---| | 2026-08-25 | 浏览器端插件 Failed to load plugins | client bundle 注册 id 用了 `@deepseek-ai/...` → 裸包名(本文件第 1 条) |