Pixel Office
一款给 DeepSeek Harness Web GUI 的 digital + pixel-art 工作区主题插件。
它把会话列表换成一间俯视的像素办公室:6×4(24 工位)"田"字形工位网格,每个工位对应一个工作区。空工位只有一把座椅;已建立的工作区在桌上摆一台电脑;工作区有会话在跑时显示器点亮并跳动。点进工位是桌面正视图——左边一块计划板贴着代表会话的便利贴,右边一台 CRT 显示器,里面是真正的对话界面:聊天、发送、工具调用全部照常工作。
整套主题使用 CRT 扫描线、马赛克瓦片消除、CRT 开机转场、像素猫眨眼、便利贴悬停抬起等动效;侧栏被裁切掉、控制台顶部 HUD 横贯、霓虹色板覆盖整套界面。
⚠️ 版本兼容性(安装前请先看)
本插件只支持 DeepSeek Harness 的最新版本(≥ 0.1.2-alpha.1,客户端 runtime 已拆分为 api/* + ui/*)。更早的基座版本一律不支持,也不再做向下兼容。
原因不是偷懒:不同基座的 dsh.client.inject 依赖清单互不兼容(当前基座已删除 @deepseek-ai/dsh-client-runtime,旧基座则缺少 session-controller / workspace-controller 等包),一份 manifest 无法通吃。解决办法只有一个:把 DeepSeek Harness 升到最新版。还有啥不对的,能动手就别吵吵
升级基座后务必重新挂载(dsh.client.inject 在插件加入 profile 时解析一次,重启 dsh web 不会重读):
dsh plugin --profile web remove dsh-client-pixel-office
dsh plugin --profile web add <本仓库绝对路径>
视觉
俯视图:24 工位网格
![]()
每张卡显示工位编号、IDLE/LIVE 状态、便利贴计数、运行时指示灯与一个重命名 / 清空按钮。空位点击新建工作区。
桌面正视图:计划板 + CRT 显示器
![]()
进入工位看到一张完整办公桌:左侧"任务矩阵 / MISSION MATRIX"计划板(每张便利贴 = 一个会话,显示标题、YOU/AI/SYS 角标和真实末条消息),右侧 CRT 显示器(待机黑屏,点便利贴才接入)。底部"新便利贴堆 / NEW STICKIES"等待被拖到计划板空位。
设置:宿主面板里的 Pixel Office 分区
![]()
插件在宿主原生设置面板中注册了一个独立分区,提供总开关、动效强度(CALM / OVERDRIVE)、网格开关三项偏好。
特性
| 区域 | 行为 |
|---|---|
| 侧栏 | 裁切隐藏,不是 display:none(避免牵连 React 子树被删除) |
| 俯视图工位 | 6×4 田字网格;拖拽可搬迁设备、交换位置;空位点击新建工作区;重命名 + 清空(常驻「未分组」工位固定第 1 格,不可拖拽) |
| 桌面计划板 | ResizeObserver 自适应行列数;便利贴按 156×168 排版,格子按 1fr 精确铺满,始终保留一个空位 |
| 便利贴 | 一张 = 一个会话;点击在 CRT 打开;拖拽交换位置;拖出计划板撕下(归档);显示末条消息 + YOU/AI/SYS 角标 |
| CRT 显示器 | 进入工位默认黑屏待机,点便利贴才接入当前会话;当前打开的便利贴带青色高亮 |
| 新会话 | 桌面左下一叠便利贴,拖到计划板空位,弹框填写展示文字 |
| 设置分区 | 注册进 settings.section;总开关 / 动效强度 / 网格开关三项 |
| 持久化 | 工位布局、便利贴摆放、自定义文字存进 localStorage,刷新后保留 |
| 外观适配 | 插件表面强制深色(DARK_TOKENS),不随宿主外观切换;插件范围外的宿主界面不受影响 |
| 背景 | 五层视差:漂移极光、透视地板与天顶网格、三层像素浮尘、暗角与地平线泛光 |
| 动画 | 便利贴不同步摆动与悬停抬起、指示灯呼吸、CRT 扫描线、CRT 开机转场、工位级联延迟入场 |
| 马赛克消除 | 开便利贴时,conversation slot 上盖主题色马赛克遮罩,每个小方块随机逐个 pop 消失,露出下面的真实对话 |
| 像素猫 | 仅在工位有会话运行时出现于显示器里;静止偶尔眨眼;待机时不显示 |
| 动效档位 | CALM(仅保留配色,停止环境动效)/ OVERDRIVE(全动效);尊重宿主 prefers-reduced-motion |
| 工位排序 | 顶视图工具栏「排序」分段控件:手动(默认,布局完全交给拖拽)/ 活跃度(按各工位最近会话活动时间重排,一次性应用,之后仍可继续拖拽微调)。「未分组」始终钉在第 1 格 |
| 事件响应 | 订阅 connection/reset(断线提示)、theme/change(外观信号) |
| 版本标识 | 顶视图底部状态条右端与设置页 hero 显示 POWERED BY DSH <基座版本号>(如 0.1.2-alpha.1-cd5ef81-dirty)。显示的是宿主基座的版本,不是本插件的——基座只在侧边栏品牌区把这串文本渲染出来(由 process.env.DSH_CLIENT_VERSION/COMMIT_HASH/GIT_DIRTY 构建期内联),没有 cordis 服务、没有 window 全局、也没有 meta 标签,因此插件从 [data-slot="sidebar"] 里读。读不到时只显示 POWERED BY DSH,绝不猜一个版本号 |
| 依赖的服务 | 通过 export const inject 声明 slots / theme / workspaces / uiWorkspace / sessions 五个服务——基座的插件守卫只把声明过的服务交给插件,未声明的一律解析为 undefined |
工程架构
为应对基座 DeepSeek Harness 的持续更新(含破坏性变更),插件在 v2 中引入三层隔离:
服务适配层 (adapters/)
所有对基座服务(slots、theme、workspaces、uiWorkspace、sessions)的调用全部封装在适配器中。插件其余部分(index.tsx、views.tsx)不直接接触基座 API,而是通过适配器接口操作。
基座 API 变更时,只需修改对应适配器,插件主体无需改动。
| 适配器 | 封装的服务 | 核心职责 |
|---|---|---|
workspace.ts |
workspaces + uiWorkspace |
工作区创建、删除、重命名、目录选择、会话归档 |
session.ts |
sessions |
会话打开、创建 |
theme.ts |
theme |
主题 token 覆盖 |
slots.ts |
slots |
插槽注册与注入 |
events.ts |
ctx.on / ctx.effect |
事件订阅(connection/reset、theme/change) |
dom.ts |
— | 基座 DOM 选择器集中管理(data-slot 属性变更时只改此处) |
能力探测 (probeAdapters)
插件启动时对所有适配器执行能力探测。硬依赖(slots)失败则阻止挂载;软依赖(theme、session 绑定)缺失则报告警告但继续运行。
版本兼容矩阵 (compat.ts)
从侧边栏品牌区读取基座版本号,解析并与已知兼容范围比较。不兼容的基座版本会打印明确错误,兼容但有已知差异的版本会打印警告。
代码规范化
constants.ts— 所有魔法数字、布局尺寸、时间延迟集中管理strings.ts— 所有用户可见文案集中管理(中英双语),便于未来 i18n- 类型定义集中在各模块顶部,避免散落
安装
Pixel Office 是标准的 DSH Profile Bundle。安装的本质是把一个声明了 dsh.bundle.patch 的包加为某个 dsh profile 的依赖,dsh 会把它激活进 dsh.profile.bundles 层序。命令背地里就是 pnpm add,跑在当前 profile 目录里。
前置条件
- Node.js 22+(
engines锁定) - pnpm 10+(
packageManager锁定,建议 corepack) - 最新版 DeepSeek Harness(≥
0.1.2-alpha.1);更早版本不支持,见上方「版本兼容性」
profile 目录:默认 $DSH_HOME/profiles/web,未设 DSH_HOME 时即 ~/.dsh/profiles/web。
装、升、卸之后都需重启当前 dsh web 进程并刷新浏览器页面——当前 Web Profile 不承诺对持久化 Bundle layer 热重载。
从 GitHub 仓库安装
npx @deepseek-ai/dsh plugin --profile web add github:Doozqoo/dsh-pixel-office
系统已装 @deepseek-ai/dsh 可简写 dsh plugin ...;在 Harness 源码仓库里可 pnpm dsh plugin ...。
Git 依赖会在安装时运行该包的 prepare(即 pnpm run build)就地构建 lib/。pnpm ≥10 默认拦截依赖的生命周期脚本:把 pnpm 打印的那条精确 key 加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds,再重跑安装命令(pnpm 11 配置键是 allowBuilds,旧文档写过的 onlyBuiltDependencies 已弃用)。
github:Doozqoo/...解析的是仓库名;装进 profile 后,update/remove 用的是包名dsh-client-pixel-office。
本地开发调试(Harness 源码 + 本地 checkout)
如果你同时拿着 DeepSeek Harness 源码仓库和这份插件的本地 checkout,用 path 依赖把它链进 profile:
# 1) 构建插件——注册表认 lib/client.js,不是 src/
cd D:\...\dsh-pixel-office
pnpm install
pnpm build
# 2) 从 Harness 源码根目录,把 checkout 加进 web profile(给绝对路径)
cd D:\...\deepseek-harness
pnpm dsh plugin --profile web add "D:\...\dsh-pixel-office"
path 依赖是链接到 checkout(不是拷贝):改完源码重新 pnpm build 就地刷新 lib/,然后重启 dsh web 并硬刷新页面即可。
Windows:路径别带空格。
dsh plugin在 Windows 上经 shell 转给 pnpm,含空格的路径会被截断,导致装上Program/dsh-pixel-office这类残缺依赖,之后remove <真包名>会报"no such dependency"。遇到含空格路径时建一个 junction 再 add:New-Item -ItemType Junction -Path "C:\Users\you\dsh-pixel-office" ` -Target "D:\...\dsh-pixel-office" -Force node --import tsx/esm apps\cli\src\bin.ts plugin --profile web add ` "C:\Users\you\dsh-pixel-office"
确认装没装进层序,可看 profile 的 package.json 依赖与 dsh.profile.bundles 列表,或运行 pnpm dsh --profile web --dump-config 查看合成入口树里是否出现 pixel-office。
DeepSeek Harness 工位协同注意事项
deepseek-harness 是官方仓库。你在本地该工位上所做的任何修改(例如工作区删除时的会话级联归档、布局 reconcile 逻辑等)只活在本地 checkout,既不会同步给其他协作者,也不会进入上游。
这意味着,基于本地改动开发 Pixel Office 时不可避免地会遇到兼容性问题:
- 其他协作者的 harness 仍是上游原版,没有你本地的级联归档 / 布局 reconcile 等修复,表现可能与你本地对不上;
- 插件所依赖的宿主行为(接口、slot、事件)以运行中 harness 为准,
contracts.ts只是结构性镜像,上游一旦变动就需要手动对齐; - 本地对 harness 的修复若没回馈上游,长期会随上游演进产生分叉,merge 成本只会越积越高。
遇到相关问题时请遵守以下约定:
- 优先提回上游。 把 harness 侧的修复走 PR / 提交回馈,而不是只在本地下游永久保留分叉。
- 明确「本地独有补丁」与「上游公共行为」。 与协作者保持同步沟通,避免把上游行为误判成插件 Bug,或反过来把本地补丁当成通用能力。
- 先确认对方 harness 是否含你的本地改动再归责。排查兼容性问题时,先对齐双方 harness 状态,再决定是否归结为 Pixel Office 的问题。
升级
npx @deepseek-ai/dsh plugin --profile web update dsh-client-pixel-office
GitHub 安装可重跑 add 命令,pnpm 会更新锁定的 Git revision。本地 path 开发模式没有"升级"概念——改源码 + pnpm build 就是新版本。
卸载
npx @deepseek-ai/dsh plugin --profile web remove dsh-client-pixel-office
remove 会删除该 profile 依赖并把插件移出层序。重启 dsh web 后 Cordis 撤销样式表、主题覆盖、slot 注册,原生界面完整恢复。localStorage 中的布局偏好默认保留,便于以后重装续用;需要清除数据时通过浏览器站点数据管理删除。
配置(设置面板)
插件在宿主原生设置面板中注册 settings.section(label "Pixel Office")。点右上角 SETTINGS → "Pixel Office" 进入:
| 选项 | 范围 | 说明 |
|---|---|---|
| 启用像素办公 / ENABLE SKIN | ON / OFF |
总开关。关掉后所有样式、token 覆盖、场景 overlay 三样一起撤,回到原生界面;面板本身仍可访问以再次启用 |
| 动效强度 / INTENSITY | CALM · 静默 / OVERDRIVE · 过载 |
CALM 保留配色,仅停止环境动效;OVERDRIVE 启用全部动效 |
| 网格地平线 / GRID FLOOR | SHOWN / HIDDEN |
切换俯视网格与透视地板 |
便利贴数量不再需要手调:计划板会自动铺满可用空间,1280×800 大约放 11 张,1920×1080 约 30 张,2560×1440 约 56 张;窗口变化时自动重排。
持久化
| 字段 | 存储位置 | 内容 | 跨浏览器 |
|---|---|---|---|
| 工位布局 | localStorage |
6×4 工位的渲染顺序 | 否 |
| 便利贴摆放 | localStorage |
每个工作区内便利贴格子的会话顺序 | 否 |
| 自定义文字 | localStorage |
便利贴上的展示文字 | 否 |
| 动效偏好 | localStorage |
intensity / grid |
否 |
opened |
不持久化 | 当前打开的便利贴 | —— |
opened("这次进工位点开了哪张")刻意不持久化,刷新后自动从黑屏待机开始。持久化写入已做引用比较:拖拽 50 次最多写 0 次 storage。隐私模式下 storage 抛错会静默降级为仅内存。
目录结构
src/index.ts node 半边(占位 apply;Loader 需要一个可导入行)
src/client/index.tsx 浏览器入口:服务读取、slot 注册、生命周期
src/client/views.tsx 俯视图、桌面正视图、弹窗、设置面板
src/client/styles.ts 样式表文本 + 注入 effect
src/client/tokens.ts 亮 / 暗两套调色板
src/client/placement.ts 摆放与呈现逻辑(无 React、无 DOM)
src/client/store.ts 场景状态与指针拖拽
src/client/persist.ts 摆放持久化(localStorage 读写与校验)
src/client/contracts.ts 所需 DSH 表面的结构化类型
src/client/version.ts 从侧边栏品牌区读取宿主基座版本号
src/client/constants.ts 集中常量:布局尺寸、颜色、时间延迟
src/client/strings.ts 集中 UI 文案(中英双语,便于未来 i18n)
src/client/compat.ts 版本兼容矩阵:解析基座版本、评估兼容性
src/client/adapters/ 服务适配层:隔离所有基座 API 调用
├── index.ts 适配器入口:创建、导出、能力探测
├── types.ts 适配器依赖与探测结果类型
├── workspace.ts 封装 workspaces + uiWorkspace 服务
├── session.ts 封装 sessions 服务
├── theme.ts 封装 theme 服务(token 覆盖)
├── slots.ts 封装 slots 服务(插槽注册与注入)
├── events.ts 封装事件订阅(统一事件名称)
└── dom.ts 封装 DOM 查询(统一选择器)
assets/ README 截图(top-view / desk-view / settings)
tsdown.config.ts 产物构建配置
placement.ts 故意不含 React 与 DOM,是唯一能脱离浏览器直接测的部分。contracts.ts 在本地声明所需类型而不是从 @deepseek-ai/* 导入——独立仓库要在没有那些包的环境里也能 install 和 typecheck。这些是结构性镜像而非上游 API 分叉,若与运行中 harness 不一致,以 harness 为准。
开发
pnpm install # 安装依赖
pnpm build # 类型检查 + tsdown 打包(产物 lib/)
pnpm typecheck # tsc -b --force
构建产物(lib/client.js、lib/index.js、.d.ts、sourcemap)由 tsdown 一次性产出。Plug-and-play:dev server 读 lib/client.js,不在 src/ 上跑。
改完 src/ 必须 pnpm build 并重启 dsh web + 浏览器硬刷新——第三方插件没有 HMR,否则仍在跑旧 bundle。
给二次开发者的两条硬约束
- 服务必须先声明才能拿到。 基座的
cordis-client-runner守卫只把插件 fiber 在export const inject中声明过的服务交给插件;未声明的服务ctx.get一律返回undefined。加新能力时先把服务名加进inject。排查技巧:临时加一条
console.log(typeof ctx.get('<服务名>')),按「哪个是undefined就补声明哪个」逐轮二分。不要只靠读cordis.patch.yml判断——bundle 里列了某个 controller 服务,不等于插件ctx能get到。 ctx.remote用不了。 Host Remote 属于@deepseek-ai/dsh-api-gateway,而api-gateway与typert都不在 web 编排里;守卫还会直接拦截,代码里出现ctx.get('remote')就会报cannot get property "remote.workspace" without inject。 所有写操作请走workspaces/uiWorkspace/sessions三个直接服务。
发布
npm login # 首次:登录 npm(有 2FA 备好 TOTP)
pnpm publish --dry-run # 先看打进包里哪些文件(确认 lib/ 与 cordis.patch.yml 都在)
pnpm publish # 正式发布(会自动跑 prepare 构建 lib/)
npm 同名包不能覆盖已发布版本,每次发布先 npm version patch|minor|major 升版本再 pnpm publish。包名 dsh-client-pixel-office 为非 scoped 包,默认 public,无需 --access public。
已知限制
- 持久化只在本浏览器生效。 换浏览器或清缓存会回到默认推导。
- 俯视图固定 24 工位,第 1 格(左上角)常驻为「未分组」工位,展示未被任何工作区收纳的会话(与官方 UI 的「未分组」分组一致)。其余 23 格用于真实工作区,超出的工作区仍存在、仍可从原生界面访问,只是没有工位可放。「未分组」工位可点击进入,其下会话可继续对话或拖出归档;但工位本身为只读:不可重命名 / 清空 / 删除,也不能在其下新建会话——官方不提供「无所属工作区」的建会能力,插件保持与官方一致,不自行扩展该功能。
- 宿主处于亮色模式时,会话内部(host 渲染的 conversation)仍按宿主主题显示,可能与插件强制深色的 CRT 边框不一致;代码块、表格、输入框有兜底规则,未必覆盖全。
- 模型 / Agent 预设分区只做了外观皮肤(直角 + 等宽字体),内部控件未逐项验证。
- 暂无自动化测试。
placement.ts抽象出来便于测试,但测试还没写,欢迎 PR。
License
MIT — 见 LICENSE。
No comments yet. Be the first to write one.