hiwork-workbench
v0.0.6
Published
HiWork 工作台插件:中央内容区提供「工作台」应用货架(M0 只做目录展示与 link 类应用入口)。
Readme
hiwork-workbench
HiWork 的工作台插件:中央内容区提供「工作台」应用货架——一屏展示已上架且已分配给我的
内部应用,按分类分组、可搜索,link 类应用直接跳系统浏览器。
同一插件还提供 PMS 个人待办:右侧栏一个 pms-todo 页(主入口)+ 工作台顶部一张
「我的待办」卡(辅入口)。设计见
docs/superpowers/specs/2026-09-17-pms-todo-integration-design.md。
本仓库当前是 M0(应用目录 + 货架):只做展示与 link 入口,不含任务运行时
(提交 / 轮询 / 产物落工作区是 M1)。工作台设计见
hiwork-desktop/docs/superpowers/specs/2026-09-17-hiwork-workbench-design.md,
实施计划见同目录 plans/2026-09-17-hiwork-workbench-m0.md(T6 / T7)。
- 包名 / bundle id:
hiwork-workbench - client 插件名:
hiwork-workbench-client - 中央页 feature:
id = workbench,order = 10(排在定时任务 20 之前,spec D5) - 右栏待办 tab:
id = @hiwork/pms-todo、kind = pms-todo、page type(无patterns) - locale 命名空间:
hiwork-workbench(zh / en 词典,文案一律走词典)
目录
src/
index.ts Host 入口(只声明插件名 + 启动日志)
client/
index.ts Web 入口:中央 feature / 设置页降级 / 右栏待办 tab 注册
WorkbenchView.tsx 货架页(四态 + 搜索 + 分组 + 卡片 + 顶部待办卡)
workbenchModel.ts 纯逻辑:归一化、卡片形态判定、搜索、分组
PmsTodoPanel.tsx 右栏待办面板(五态 + 登录表单 + 按来源分派的勾选确认/回滚)
PmsTodoCard.tsx 工作台里的「我的待办」入口卡
todoTab.ts 待办 tab 的类型声明与打开路径(page type)
todoData.ts 待办取数钩子(待办 + 我的项目任务并行取数,两个来源互不牵连)
todoModel.ts 待办纯逻辑:归一化、合并/去重/排序、计数、文案键、错误码映射
contracts.ts 宿主 client 能力的结构契约(type-only)
locales.ts 命名空间 hiwork-workbench 的 zh/en 词典
styles.ts / styles.css 样式注入
scripts/
host-build.mjs Host 产物自包含打包(esbuild)
client-build.mjs Client 产物闭包工厂打包(esbuild)
tests/ 入口、feature 注册、纯逻辑、货架页、待办、词典、打包契约数据从哪来
工作台自己不碰网关。 网关的登录会话 cookie 与虚拟 Key 只存在于 hiwork-core 的 Host
半边,所以清单由 core 的 host 桥拉取,经 client 服务面交给本插件(spec D6):
const face = ctx.get('hiworkFeatureCenter') // cordis 服务名
const workbench = face?.managedWorkbench // **可选成员**,老 core 上没有
workbench?.snapshot() // 同步读镜像(可能陈旧)
await workbench?.warm(signal) // 命中 Host 预热缓存,零网络
if (workbench?.isStale()) await workbench?.refresh(signal)取值方式刻意是 ctx.get('hiworkFeatureCenter') + 结构契约:DSH 的 client 模块表禁止 feature
插件之间 runtime-import,所以本插件不 import hiwork-core,src/client/contracts.ts 里按
结构重声明它消费的那几个成员(字段名与 core 的 ManagedWorkbenchFace 逐字一致)。
降级(spec §10)
| 场景 | 表现 |
| --- | --- |
| 没有 hiwork-core(独立安装) | 注册设置页分区 settings.section#workbench |
| 有 core 但没有 managedWorkbench 面(老 core / 网关未启用工作台) | 同上;分区里渲染「本部署尚未启用工作台」 |
| core 后到 | 立刻撤下设置页分区,切成中央 feature(同一功能不会有两个主入口) |
| 清单还没拉到 | 骨架屏(空态会让人误以为「没有应用」) |
| 清单为空 | 「还没有可用的应用」 |
| 镜像陈旧 / 离线 / 拉取失败但有缓存 | 顶部一行「离线或使用的是缓存,这份清单可能不是最新」 |
| 拉取失败且无缓存 | 错误提示 + 重试按钮 |
| 未知 executor kind / 未知 input type | 该卡片置灰 + 「请升级 HiWork 客户端」,其余应用照常(绝不抛错) |
| link 类缺地址 | 该卡片置灰为「暂时无法打开」(不拿 undefined 去 window.open) |
| http-task 类 | 卡片显示「即将开放」并禁用(M1 才接运行时) |
| 应用没配图标(icon_url 留空) | 卡片左上角画显示名首字符占位(不留白——空方块会被当成「应用坏了」) |
| 图标地址拉不到(内网域名解析不到等) | 同样退回首字符占位;卡片本身照常可点 |
判定只在 src/client/workbenchModel.ts 收口一次(appTarget()),组件不重复判一遍。
卡片图标
清单里的 iconUrl(网关线名 icon,core 的 Host 半边已改好名)由卡片左上角的 <img> 渲染,
40×40 圆角方块、object-fit: contain(清单里多半是站点 favicon,裁切会切掉识别度最高的部分)。
图片是装饰性的(应用名就在旁边)→ alt="" + aria-hidden,读屏不会多念一遍。
拉不到就退回首字符占位;「拉不到」记的是具体那个地址,管理员改掉 icon_url 后新地址会重新加载
(不会背着上一次的失败结论)。图标地址是 http(s) 外链,宿主不设 CSP,浏览器直连即可(跨域图片不需要 CORS)。
入口
| 项 | 值 |
| --- | --- |
| feature id | workbench |
| order | 10 |
| 菜单文案 | 工作台(跟随语言切换,词典键 view.title) |
| 图标 | 内联 SVG(2×2 应用格),侧栏折叠成图标栏时只显示它 |
| render | 复用 WorkbenchView,数据面在渲染时从 core 的活对象上再读一次 |
搜索只按名称与标签过滤(spec §7 的口径);分组按分类 code 升序、组内按应用名升序
(都是确定性的 code unit 比较,不依赖 ICU/拼音;分类名改文案不会改变分组顺序),
运营侧要调顺序用 featured(管理员手工维护,spec §13.1)。
PMS 待办
主入口是右侧栏 tab(kind = pms-todo):待办要能「随时瞄一眼、顺手勾一下」,必须在
和聊天并存的前提下可用,而右栏面板折叠后仍保持挂载(中央区 takeover 退出时会卸载 React
根),所以座位是右栏。page type——待办没有资源地址,sidebarRightTabs.register 里
不带 patterns,只按 kind 打开;注册形状以桌面端运行时的 .d.ts
(@deepseek-ai/dsh-client-ui-sidebar-right)为准。
数据只来自 hiworkFeatureCenter.pmsTodo(可选成员,与 managedWorkbench 同款):
const face = ctx.get('hiworkFeatureCenter') // cordis 服务名
const todo = face?.pmsTodo // **可选成员**,老 core 上没有
todo?.status() // 同步读凭据状态
await todo?.today(signal); await todo?.statistics(signal)
await todo?.myTasks(signal) // 我的**未完成项目任务**(D7;也是可选成员)
await todo?.setStatus({ id, status: 'COMPLETED' }) // 待办
await todo?.completeTask({ id }) // 项目任务(需要 task:list:edit)| 场景 | 表现 |
| --- | --- |
| core 无 pmsTodo 面(老 core) | 右栏 tab 一句「请升级 HiWork 客户端」;工作台那张卡整张不渲染(都不报错) |
| 宿主没有右侧栏席位 | 右栏入口整体不注册,卡片按钮置灰;中央页与设置页降级入口照常 |
| 从未配置凭据 | 引导表单(账号 + 密码 + 记住密码)→ login,成功后直接转列表 |
| 凭据失效(UNAUTHORIZED / loggedIn === false) | 「登录已过期,请重新输入」;上次数据保留可见并标陈旧,勾选框禁用 |
| 还没拉到过数据 | 骨架屏(空态会让人以为「今天没待办」) |
| 拉取失败(有缓存) | 照常展示 + 「可能不是最新」;勾选框禁用(不吞操作) |
| 拉取失败(无缓存) | 错误提示 + 重试 |
| 只有一个来源拉不到 | 另一个来源照常渲染(一个失败不弄死另一个);缺的那半明说「这份列表可能少了待办 / 项目任务」 |
| 两类都空 | 「今天没有待办」(只有任务、没有待办时走正常列表,不算空) |
| 勾完成(待办) | 先给 D5 的副作用说明再确认:若这是该任务的最后一条待办,PMS 里的项目任务会被一并标记完成 |
| 勾完成(项目任务) | 另一句确认(直接改 PMS 任务状态)→ completeTask;无 task:list:edit 被拒时明说「在 HiWork 里没有完成任务的权限,请到 PMS 操作」并回滚(不说成网络故障) |
| 取消完成 | 明说不会恢复 PMS 里的项目任务状态(反向不成立,不许暗示恢复) |
| 勾选失败 | 该条回滚到原状态 + 按稳定 code 给原因(不做乐观更新的静默吞错) |
| 未知 priority / status 枚举 | 原样透传 / 按未完成渲染,不崩(将来 PMS 加 URGENT 不该让老客户端挂) |
口径(spec D7):面板与卡片展示的是 待办 ∪ 我的未完成项目任务(mergeRows 合并 / 去重 /
排序),每行带来源徽标(待办 / 项目任务),任务额外显示项目名。待办的逾期 / 临期只看服务端
的 isOverdue / isDueSoon 布尔,客户端不按日期重算(D4);任务PMS 不返回这两个布尔,
才按 status = 3 / deadline < 今天 本地判(只作用于任务来源)。顶部两个数字
「待处理 N」(合并后未完成条数)/「逾期 N」(其中被判逾期的条数)都从合并结果数出来,
服务端 statistics 只统计 todo_task、仅作交叉参考,不覆盖合并结果。
去重是启发式:createFromTask 生成的待办标题固定是 【任务】<taskName>,命中则保留待办
那条、隐藏对应任务(待办的软关联 source_type/source_id 没有暴露在 VO 里);根治办法是 PMS
在 TodoTaskVO 里补 sourceType/sourceId 后换成精确匹配。错误文案一律按稳定 code 映射
(服务端的 message 不上屏)。凭据(PMS 账号密码 / JWT)只在 core 的 Host 半边,本插件只消费
它交出来的状态与列表——任何响应里都不会有 token(测试把守)。
client bundle 纯度
src/client/** 对 @deepseek-ai/* 只允许 import type(打包时被擦除),运行时只 import
react / react/jsx-runtime。tests/packaging.spec.ts 会在构建后扫描 lib/client.js 的
require 说明符,越界即失败;Host 产物自包含(桌面端补种会清空 profile 的
node_modules/@deepseek-ai/*)。
命令
| 命令 | 作用 |
| --- | --- |
| pnpm typecheck | 严格类型检查 |
| pnpm build | 构建 lib/index.js(Host)与 lib/client.js(Web) |
| pnpm test | Vitest(DOM 用例首行 // @vitest-environment jsdom) |
| pnpm verify | typecheck → build → test(提交前跑这个) |
本地试用(不打包桌面端)
pnpm build && pnpm pack # 产出 hiwork-workbench-0.0.1.tgz在 DSH profile(如 ~/.dsh/profiles/web)的 package.json 里加依赖
"hiwork-workbench": "file:<绝对路径>/hiwork-workbench-0.1.0.tgz",并在
dsh.profile.bundles 追加 "hiwork-workbench",然后 pnpm install --force
并重启 dsh web(Host 半边变化必须重启,只改 client 时刷新页面即可)。
后续(M1)
- Host 半边注册
/hiwork-workbenchloopback RPC(提交 / 轮询 / 取消)与产物落工作区; http-task的运行页(按inputsschema 渲染表单)与右侧栏「任务」tab;- 产物目录
<工作区>/HiWork 应用产物/<应用显示名>/<yyyy-mm-dd>/(同名不覆盖,追加(2))。
