# 安装与配置 ## 装上 ```bash # 在 profile 目录里加一行 file: 依赖 cd ~/.dsh/profiles/desktop # package.json 的 dependencies 里加: # "dsh-shader-workbench": "file:D:/Projects/DSHP/dsh-shader-workbench" # dsh.profile.bundles 里也加一项 "dsh-shader-workbench" node install ``` 然后**重启 DSH**。侧栏会出现「Shader 工作台」。 ### 存储位置 **面板资源树最上面那一行就是**:显示当前存哪,旁边有「更改 / 复制路径 / 重置」。 改完立刻生效,落在 `/shader-workbench.json`。 优先级(高到低): 1. profile 配置里的 `workspaceRoot` 2. 环境变量 `DSH_SHADER_WORKBENCH_ROOT` 3. **面板里选的那个**(`/shader-workbench.json`) 4. 会话工作目录下的 `shader-workbench/`(工具)/ 最近一次工具用过的根(面板) 被第 1、2 条压过时,那一行会挂一个「来自插件配置 / 来自环境变量」的徽标 —— 否则你会以为"在面板里改了却没生效"。 > 第 3 条是**面板和 agent 共用**的一层。没有它的时候两边会算出不同的根: > agent 把材质写进 A,你在面板里看到的是 B(空的),资源树永远空, > 存图/快照/导出这些按钮点了也"没反应"。2026-10-10 报的就是这个。 > > 路径必须是**绝对路径**;留空串 = 清掉手动设置、回到自动推断。 --- ## 安全 - 所有路径操作限制在工作区内,解析后做前缀校验(不是过滤特殊字符)。 - 目录名走白名单:拒绝路径分隔符、`..`、Windows 保留名、控制字符。 - HTTP API 同源校验,请求体上限 256 KiB。 - 面板自己不碰磁盘,全部走 Host。 - `readOnly: true` 时所有写操作被拒。 --- --- ## 排查:插件好像没加载? DSH 只在插件加载失败时把原因写进**自己的 stderr**(桌面壳里没人看得见,也不落盘), 所以「插件为什么没生效」本来是个查不动的问题。本插件自己留了一份启动记录: ``` ~/.dsh/shader-workbench-boot.log ``` 每行一个 JSON。正常加载长这样(三行): ```json {"phase":"module-loaded","node":"v24.x","electron":"44.0.0"} {"phase":"apply-start","hasTools":true,"hasWebServer":true,"hasEffect":true} {"phase":"apply-ok","registered":["shader_node_library", "...共 9 个"],"failed":[],"route":"ok"} ``` 对着表看: | 日志里有 | 说明 | |---|---| | 什么都没有 | 模块压根没被 import —— 问题在 profile 的 `dsh.profile.bundles`,或包没装好 | | `config-schema-invalid` | `Config` 不是 Standard Schema。改回去就完事,别犹豫 | | `module-loaded` 但没有 `apply-start` | 两种成因,**第 ① 种是本项目最贵的坑,见下面两条**:① 模块有 `default` 导出,宿主只拿到裸 `apply`,`inject` / `Config` / `name` 全丢;② `Config` 不是 Standard Schema | | `apply-start` 但没有 `apply-ok` | `apply()` 抛了;紧接着会有一行 `apply-error` 带上 message 与 stack | | 有 `tool-register-failed` | 某个工具的 schema 被 DSH 拒了,那行写清了是哪个工具、什么原因 | | `route:"failed: ..."` | 工具能用,但 HTTP 路由没挂上(面板会连不上 Host) | | `route:"ok"`,但 `/plugins/dsh-shader-workbench/...` 全是 404 | **注册的前缀带了尾斜杠**,见下面第三条。boot log 一切正常,浏览器只报 `Failed to fetch dynamically imported module` | > ### ⚠️ 三条必须记住的契约(都是「静默失效」,都不报错) > > **① 不要写 `export default`。** `@deepseek-ai/cordis-plugin-include` 装载一个包时是 > `data = module.default || module`(`lib/index.js:157`)。只要模块有 default 导出, > 宿主拿到的就是那个裸函数,`inject` / `Config` / `name` 一个都读不到: > fiber 不等服务就激活,`apply()` 里第一句 `ctx.tools` 立刻抛 > `Error: cannot get property "tools" without inject`。 > **对照物:同 profile 里能正常工作的 `dsh-image-gen` 就没有 default 导出。** > 现在 `plugin-load.test.js` 里有一份逐字复刻的 `plugin.default || plugin` 断言守着。 > > **② `Config` 必须是 Standard Schema**(`Config['~standard'].validate`),不能是裸 > JSON Schema。Cordis 激活 fiber 时无条件调它(`resolveConfig`),裸对象抛 > `TypeError: Cannot read properties of undefined (reading 'validate')`。 > 注意:只有修好 ①、宿主真的读到 `Config` 之后,这条才会咬人 —— **两个都得修**。 > > ①② 的症状一模一样:**`module-loaded` 有了、`apply()` 不跑、侧栏什么都不出现、 > DSH 一声不吭**(错误只写进宿主自己的 stderr,桌面壳里没人看得见也不落盘, > 可选插件连 crash 日志都不写)。当时 70 个用例全绿也没拦住 —— > 因为测试用假 context 直接调 `apply()`,把宿主解析插件这一步整个绕过去了。 > > **③ 注册给 webServer 的前缀不能带尾斜杠。** 宿主的前缀匹配是 > `pathname !== prefix && !pathname.startsWith(prefix + '/')` —— **它自己会补 `/`**。 > 注册成 `/plugins/x/` 之后它去比 `/plugins/x//`,永远为假:`register()` 不抛错、 > boot log 写 `route:"ok"`,但整条前缀一个请求都进不来,面板只报 > `Failed to fetch dynamically imported module`。 > 所以 `host/routes.js` 里有两个常量:处理器内部切路径用的 `API_PREFIX`(带斜杠) > 和注册用的 `ROUTE_PREFIX`(不带),**别合并**。 > 现在 `http.test.js` 的测试服务器照抄了宿主那段匹配逻辑(以前是无条件进 handler, > 所以 14 个用例全绿也照样漏)。 > > 前两条由 `plugin-load.test.js` 逐字复刻宿主的装载语句 + `resolveConfig` + `apply` > 调用序列守着;第三条由 `plugin-load.test.js` 的前缀匹配用例与 `http.test.js` 的 > 真 HTTP 服务守着。 两条独立自检(都不用看日志): ```bash # 路由在不在:404 = 没加载;200 = 加载成功 curl http://127.0.0.1:19387/plugins/dsh-shader-workbench/ping # 包能不能从 profile 解析 cd ~/.dsh/profiles/desktop && node -e "import('dsh-shader-workbench').then(m=>console.log(m.name, m.inject))" ``` 日志路径可用环境变量 `DSH_SHADER_WORKBENCH_BOOTLOG` 覆盖 (测试用它避免污染用户目录)。 > 注册是**逐个 try/catch** 的:一个工具的 schema 不合规不会让另外八个也上不去。 > 路由注册也单独兜住 —— 路由失败时工具照常可用。 ---