全部插件

DSH / BUNDLE / BUNDLES

dsh-gov-workbench

v1.0.0zbzbhshs / dsh-gov-workbenchbb9d7716ba

可安装组合包组合包与其他模块社区 · Topic 自动分析

概览

dsh-gov-workbench

政务门户风格的 dsh Cordis 插件:在独立端口提供一个办事大厅界面,直接对接宿主的会话、模型与权限。

README / ZH

插件文档

目录摘要

政务门户风格的 dsh Cordis 插件:在独立端口提供一个办事大厅界面,直接对接宿主的会话、模型与权限。

dsh.pub 核对固定版本的组合包契约、运行时事实与分发语义;完整 README 请查看源仓库。

在 GitHub 阅读完整 README

LIMITATIONS

已知限制

1. 3091 端口已在真实 dsh 进程中挂载验证(0.2.0-rc.2 / Windows),插件随宿主进程启动后正常监听并返回页面。回归测试 `node test/plugin-boot.mjs` 用 mock ctx 与假网关覆盖装配路径,它先让内核分配一个空闲端口再写进配置,因此不会占用 3091(`port: 0` 会被 `mergeConfig` 当作非法值回落到默认 3091,所以测试不能用它)。 2. dsh 0.2.0-rc.2 上 `ctx.apiProxy` 不存在。`@deepseek-ai/dsh-host-apiproxy` 已从发行版移除,API 网关被重构为 `ctx.typertGateway`(`@deepseek-ai/dsh-api-gateway` 的 `TypertGatewayService`)+ 各域 controller(`sessionController` / `settingsController` / `workspaceController` / `agentPresets` / `permissionPresets` / `llm` / `userQuestions` / `approval` 等);端点清单由 `@deepseek-ai/dsh-typert-registry` 在运行时从各包的 `typert.host.js` 注册进 `ctx.typert.local`。因此 `lib/index.js` 的 `inject` 留空(原因见下面第 4 条),挂载由 `lib/host.js` 把两种宿主形态归一成同一内部接口(`invoke` / `stream` / `hostEvents` / `resolveEventResult` / `describeEndpoint`),同时兼容 0.2.0+ 的 `typertGateway` 与 0.1.x 的老形态 `apiProxy`(后者优先级更高)。两者都不存在时插件照常开页面,并在状态接口里报告 `hostAvailable: false`,而不是崩掉整个 dsh。 配套的惰性解析设计:因为插件是「立即激活」的,网关可能在 `apply()` 之后才 provide。所以 `lib/host.js` 不在构造时绑定一次,而是: - `kind` 是 getter,每次读取都重新探测,实时反映可用性; - `invoke` / `stream` / `resolveEventResult` / `describeEndpoint` 在每次调用时重新 `ctx.get('typertGateway', false)`; - `hostEvents(signal)` 是等待式生成器:网关未就绪时先等(响应式 `ctx.inject([...], cb)` + 轮询兜底),出现后再开始转发;流自然结束后若网关仍在且未 abort 会重新接续,因此 `MuxController` 不需要任何重启逻辑。 回归测试:`test/plugin-boot.mjs` 的「网关晚于插件 provide 时自动接上」与「网关缺席时 hostEvents 等待而不是立即结束」两项。 3. 会话事件不在 0.2.0-rc.2 的网关转发白名单里。`@deepseek-ai/dsh-api-remotes` 只把一份白名单事件转发给浏览器(`approval/request`、`user-questions/request` 以及各类配置变更),session 事件不在其中;0.1.x 的 `apiProxy.events.mux()` 也已不存在。所以 `lib/sse.js` 的 `MuxController` 改为在宿主进程内直接订阅 `ctx.on('session/event', ..., { global: true })`(并订阅 `session/created` / `session/disposed`),与 `dsh-session-controller` 自己 follow 会话时的做法一致;`{ global: true }` 拿全局可见性,老版本自动退回两参数形式。实时投影(统计 / 标题 / 待办)另经 `sessionProjections.onChanged` 获取。若宿主未挂该投影服务,插件告警但继续工作,统计与待办只随会话事件更新。 4. **`cordis.patch.yml` 的插件行不能写 `inject` 字段,尤其不能写 `!!js`。** 写错会让插件永不激活,启动日志停在「Plugins waiting for services」,3091 根本不监听。正确做法是**整行删掉 `inject:`**(不是写 `inject: []`,空数组同样会覆盖插件导出的值),让 `lib/index.js` 的 `export const inject = []` 生效。`inject: [apiProxy]` 这种普通数组写法在 0.2.0-rc.2 上同样会让插件永不激活,因为该版本已无 `apiProxy` service。两种写法都不能用,原因不同。完整的五步机制、复现输出与回归守卫(`test/patch-inject.mjs` 8 项,含对照组)见 [`docs/harness-integration.md` 第 14 节](docs/harness-integration.md);`test/syntax-check.mjs` 另有一条静态守卫。 5. `test/syntax-check.mjs` 在受限沙箱下会自动降级。该脚本用 `node --check` 逐文件校验;若沙箱拒绝子进程的管道 stdio(`EPERM`),脚本会自动退回 `stdio: 'inherit'` 模式仅取退出码,并在输出里注明降级(校验方式仍是同一个 `node --check`)。本机 danger-full-access 模式下不会触发降级。 6. 端口 / host 改动不热生效。`PUT /plugin/config` 会落盘并返回 `needsRestart: true` 与提示文案,但当前监听保持不变,需重启 dsh。 7. `public/` 资源不缓存但每次读盘。好处是改完刷新即生效、无需重启;代价是每个静态请求都有一次磁盘读。适合本机单用户场景。 8. `req.signal` 的版本差异(三版本对照)。参考实现把 `req.signal` 传给宿主方法,这在任何 Node 上都不可靠:Node 22 及以下没有 `.signal`,宿主帧队列的 `signal.addEventListener(...)` 没有 undefined 防御,会抛 `TypeError`,前端重连后再次抛错,形成死循环;v24.18.0 会 abort,但总在 `res.close` 之后;v24.21.0 正常结束根本不 abort。三者都不能用于流中途取消:v22.22.2 连信号都没有,v24.18.0 太晚,v24.21.0 连结束都不反映。因此 `lib/transport.js` 的 `trackAbort()` 一律自建 `AbortController`,在 `res.on('close')` 里 abort,并用 `res.writableEnded` 区分「客户端断开」与「正常结束」。`test/server-smoke.mjs` 第 5 组是可复现探针,只断言三版本都成立的部分(进入处理器时尚未 abort),不对「结束后一定 abort」做断言,否则换 Node 版本 CI 会红。逐版本对照表与探针脚本见 [`docs/harness-integration.md` 第 7 节](docs/harness-integration.md)。全部 5 个测试套件已在 v22.22.2 / v24.18.0 / v24.21.0 三个 Node 上跑通。 9. 关停必须用 `ctx.effect`,不能用 `ctx.on('dispose')`。cordis 4.x 上 `ctx.on('dispose', fn)` 的 `fn` 永远不会执行,该事件名从不被派发。若用它做清理,插件卸载后 http 服务会继续占着 3091 端口。cordis 的插件级清理语义是 `ctx.effect(() => () => cleanup)`,effect 回调返回的函数在该 fiber 销毁时调用(`@deepseek-ai/cordis-plugin-timer` 等官方插件都是这个写法)。`lib/index.js` 已改用 `ctx.effect`,并有两条回归断言守着(行为断言 + 源码模式断言,且忽略注释)。 10. cordis 的 `ctx` 是 Proxy:读未声明 service 的属性会抛错,不是返回 `undefined`。`ctx.apiProxy` 在 mock ctx 上完全正常,在真 cordis 下直接抛 `cannot get property "apiProxy" without inject`,插件启动即崩。因此 `lib/host.js` / `lib/sse.js` / `lib/bridge.js` 的 service 探测一律走 `ctx.get(key, false)`(`false` = 不要求已注入),