dsh-plugin-market
插件市场:浏览、安装、卸载、更新、启停插件。从独立页面 /plugin-market 管理本机与云端目录。
给 Agent: Markdown · JSON · tarball
在已有插件市场的机器上安装
curl -fsSL https://addon.dshdo.com/install-plugin-market.sh | sh
# then in /plugin-market/ install dsh-plugin-market and enable it
打开本机 /plugin-market/ 云端市场,安装该 id,再启用。安装只登记,启用才运行。
版本
0.2.3· 发布时间0.2.2· 发布时间 — 0.2.2:完整注册回滚(含 registration rollback 恢复 patch/dep/link);Config 仅 Cordis 装配且 settings 读取失败大声报错;git 通用流式输出;非 ENOENT unlink 错误不再吞掉。内置冻结资产 market-6t1ytd2v.js / market-rfzjgc40.css。0.2.1· 发布时间 — bundled dshdo 设备侧栏改走 tapIndex;安装脚本写入正在运行的 DSH_HOME。
文件
README
dsh-plugin-market
DeepSeek Harness 本机插件市场。在同一 dsh web 进程打开 /plugin-market/。dshdo 打包设备要装进正在运行的 $DSH_HOME(常见是 ~/.local/share/dshdo/dsh/home),不是 ~/.dsh。
- 从
~/dsh-plugins或云端目录 https://addon.dshdo.com 安装 - 安装把插件登记到 dsh 插件管理器;profile 层与启用/停用/卸载生命周期都由它统一管理
- 界面中英切换;插件简介保持发布时的那一条
- 哈希路由:
#/plugin/<id>(已安装 / 本机货架),#/catalog/<id>(云端详情) - 云端详情同时给 Agent:HTML
/plugins/<id>,Markdown/plugins/<id>.md,JSON/v1/plugins/<id>
curl -fsSL https://addon.dshdo.com/install-plugin-market.sh | sh
然后重启 dsh web,打开 http://127.0.0.1:3080/plugin-market/。
安全模型(v0.2.2)
对 /plugin-market 的每个请求(API 与静态资源)都必须通过回环信任栅栏 (lib/trust.mjs,独立复刻 harness /api 栅栏语义):
- 对端 socket 必须是回环(IPv4
127/8、::1或 IPv4-mapped); Host头必须是裸的回环 authority(防 DNS 重绑定);- 拒绝
Sec-Fetch-Site: cross-site;带Origin时必须是与 Host
authority 完全一致的源——scheme、host、port 缺一不可,其中 scheme 取 请求实际到达的方式(req.socket.encrypted;明文回环服务器上是 http:,TLS 后是 https:),不做部署假设;拒绝字面 null origin;
- 不输出任何 CORS 头。本机同源页面可以驱动市场;跨站浏览器页面不行。
远程 LAN 浏览器由 access-gateway 的 LAN 前端(带认证)终结后转发到 回环(Origin/Sec-Fetch 被剥离、Host 重写为回环 authority),这类转发 通过栅栏而不削弱对直连请求的防跨站语义。
管理类变更请求体必须是严格 application/json,且 ≤ 64 KiB。tarball 安装 从不调用系统 tar:由市场自带的解析器读取,拒绝符号链接 / 硬链接 / 设备成员与路径穿越,并在写入唯一 staging 目录前执行单文件 / 总量 / 条目数上限。
生命周期归属与重启语义
dsh 插件管理器(ctx.pluginManager)是唯一的生命周期权威:enable / disable 调用 setBundleEnabled(<包名>, true|false),uninstall 调用 removeBundle(<包名>),云端安装把刚 staging 好的目录交给 installBundle(stagedDir, { enabled: true })。市场自己绝不写 cordis.patch.yml、profile manifest 或 profile symlink,因此变更的 profile 结果就是管理器的 ChangeResult,原样返回(application、stage、 error、warnings、packageResult、bundle)。已解析的拒绝 (application: 'failed',如 not-bundle、not-removable、 management-required)以 HTTP 200 且 ok: true 返回,失败体现在 value 里;页面检查 application/error 并如实报错,不再假定成功。管理器本身 拒绝(服务缺失或抛错)则按 handler 的 HTTP 错误向上传递(不可用时 503)。
市场只保留管理器无法承担的两道栅栏:不允许停用/卸载市场自身(403); update 对运行副本解析到 harness checkout 内的插件(builtin-resolved) 在做任何云端工作前返回 409。其余保护——内置、可移除性、占用中、 management-required——都是管理器的判定,以其 ChangeResult 如实上报。
响应携带 restartRequired,表示该变更要等 dsh web 进程重启后才生效; 插件行随后保留该标记直到进程重启(标记按插件记录、进程生命周期有效, 绝不读取 harness 更新器的 update-status.json)。对云端目录插件的 更新在 catalog.mjs 内按已装树的完整载荷哈希(treeHashVersion 2)判定 consent:装好后本地文件被改过、或记录缺失/不含可信哈希时,更新必须显式 传 consent: true(否则 409 needs-consent)。更新就地替换货架目录, 不触碰管理器负责的 profile 登记;若替换目录失败且恢复原目录也失败,错误 响应会带货架外保留的更新前副本 backupPath 并在文案中告知。列表行的 运行时字段(runtimeVersion / runtimePath / runtimeSource)除非有 loader 记录证言正在运行的模块,否则一律为 null(cordis 对每个插件只 保存可执行回调,版本与路径绝不由磁盘推断);解析身份在 resolvedVersion (解析副本的 manifest 缺失/不可读时为 null,绝不回退到货架行自身的 版本)resolvedModulePath moduleSource 中如实上报。enabled / installed 取自管理器 listBundles() 的记录,与本机、云端事实合并。
管理器的拒绝或取消原样返回,市场不做自己的回退。本地 git git pull 的 原始诊断流不回传 HTTP。本地 Git 更新只允许快进,不提供云端更新的目录 快照回退。
API 契约见 docs/API.md。测试:node --test "tests/*.test.mjs"。
请求生命周期与插件配置
插件暴露五个 Config 字段:
enabled仅在 Cordis 配置中控制挂载,不是实时设置。无效的已保存市场设置会阻止挂载。- 经
dsh-plugin-marketsettings 命名空间实时同步:catalogUrl与
requestTimeoutMs(校验为 1..2147483647 的整数,默认 30000,每个请求 的整体时限)。
- 重启后生效、在 apply 时捕获(它们配置 runner 对象本身,实时写入无法
重新绑定):maxOutputBytes(1..16777216,默认 1048576,分别限制 子命令 stdout/stderr 每个流的上限)与 graceMs(1..60000,默认 1000, 终止升级的宽限)。改它们请在 cordis.yml 中修改并重启进程;settings 命名空间从不暴露它们,因此不会有任何 UI 谎称其实时生效。
handler 为每个 请求持有一个 AbortSignal:读取变更请求体、代理云端目录的 fetch、云端 安装/更新的下载与哈希校验都绑定它;变更操作在锁内排队、拿到锁后会再次 检查 signal——超过时限(408)或卸载中失去 handler(503)的请求不会继续 运行,也不会提交它排队等到的任何状态。
handler 拥有它接受的每个请求。卸载时 index.js 先注销 /plugin-market 路由(服务器停止向其派发请求),再 dispose handler:在途请求以 503 中止 并等待其全部落定,卸载后竞态到达的请求一律 503 拒绝。dispose 幂等—— 重复调用等待同一个落定 promise,卸载绝不在旧 HTTP 变更仍在触碰已拆除 状态时返回。超时/卸载是中止请求而非强制完成:已越过提交点、不可 取消的写入与已入队的结算会被 join 到其真实落定——dispose 等待它,绝不 打断已经开始的工作(held-write 探针见 tests/serve-lifecycle.test.mjs)。
插件运行的唯一命令——本地 git 更新的 git pull --ff-only——经注入的受管 子进程服务(ctx.subprocess)+ 本 apply 的 runner(lib/run.mjs)执行, 绝不使用阻塞式原生子进程:该次运行受请求时限约束、请求中止或 handler 卸载 时被取消,并被插件 teardown 等待排空。子进程环境只显式转发 DSH_HOME (dshChildEnv):provider 会擦除环境里的 DSH_* 名,插件目录就是运行的 cwd——测试开关应放进 fixture 的假可执行文件,绝不作为 DSH_* 环境变量 进入生产子进程。git pull 失败只返回通用错误(安全退出码)——子进程原始 stdout/stderr 绝不回显进 HTTP 错误或日志。
apply 必须是箭头函数(export const apply = …),不能写成可构造的 export function apply:cordis 对带 .prototype 的回调走 new 并丢弃 返回值,普通函数返回的卸载器永远不会被收集,卸载后路由仍会挂着。真实 fiber 探针在 tests/fiber-dispose.test.mjs:用真实的 vendored Loader 启动 插件,既验证箭头形态能排空在途请求,也验证可构造形态的失败模式。