packages/dsh-task-board
zhu1090093659/dsh-web侧边栏多列任务看板:卡片交给真实 DSH 智能体会话执行,支持 cron 定时(Host 侧到点执行,关浏览器也生效)。
给 DeepSeek Harness 左侧栏的面板入口行加显示开关:隐藏的入口仍可从「更多」菜单打开,选择存在 localStorage 里,刷新和重启都保留。
English · 详细说明(现象 → 根因 → 方案)
项目 · 内容 界面显示名 · 左侧栏入口显示(locale/zh.json → meta.title) 包名 · @local/dsh-sidebar-entry-manager 版本 · 1.1.0 形态 · DSH Web 客户端插件(宿主半边为空实现,全部逻辑在 client.js) 依赖 · 只依赖宿主既有的 slots / locale / layout 服务;dsh.client.inject 为空数组 是否改动宿主或别的插件 · 不改。只注册自己的座位、只写自己的 CSS 类名(dsem_ 前缀)
左侧栏那一排面板入口(插件、自动化任务,以及今后任何插件 register 到 sidebar.panellist 的行) 会越装越长。本插件给这些行加了一层显示开关:
先说清边界,避免装上去才发现「和想的不一样」。三条做不到的都是宿主硬编码,不是工作量问题。 依据是宿主 @deepseek-ai/dsh-client-ui-sidebar@0.2.1-rc.3 的 lib/client.js:
· 能力 · 说明 ✅ · 隐藏任意非 plugins 的面板行 · 行过滤条件 id === "plugins" \ · \ · !unpinned.includes(id)(client.js:471) ✅ · 被隐藏的入口仍可打开 · 「更多」菜单列出全部可管理入口 ✅ · 偏好持久化 · localStorage 键 dsh-sidebar-entry-manager:prefs ✅ · 双入口管理界面 · 左栏面板 + 设置页,共用同一份偏好 ❌ · 藏掉「插件」行 · 行过滤对 plugins 短路;setPinned 首行对 plugins 直接 return(client.js:26) ❌ · 藏掉「更多」按钮 · 它的显隐 = sidebar.panellist.actions 是否有注册(client.js:587)。本插件注册该座位用来放「管理左侧栏入口…」菜单项,注册了它就会亮 ❌ · 把两行合并成一个入口 · 需要自己接管 sidebar 座位的渲染层,那是重写侧栏外壳,不在本插件范围内
所以左栏的最少形态是「插件 + 更多」两行;管理面板里「插件」显示为只读徽章「始终显示」。
也可以点菜单底部的「管理左侧栏入口…」回到管理面板。
设置页(DSH 设置 → 左侧栏入口)是同一个组件的另一处挂载,读写同一份偏好。
宿主把「哪一行被收起」存在一个内存快照里(createPanelPins(),client.js:17-30), 重载即重置,不读也不写 localStorage。第三方插件能碰到这个快照的位置只有两处:
sidebar 座位的 inject 工厂返回值(setPinned、hooks.entryPins)—— 该工厂被原样存进槽位账本(dsh-client-ui-slots/lib/index.js:214),不需要接管座位,直接调 entry.inject() 就能拿到;
sidebar.panellist.actions 座位的 ownerProps.onPinnedChange —— 就是 setPinned 本身(1.0.0 用的路,现已降级为兜底)。本插件两条都走:启动期用第 1 条(首屏即生效),菜单渲染时用第 2 条对账。数据流是:
localStorage ←→ 模块内 snapshot ──下发──> 宿主 setPinned(id, pinned) ──> 宿主内存快照 ──> 侧栏行
↑ ↑ ↑
│ │ └── 启动期从 sidebar 座位的 entry.inject 直接取出
└── 管理面板开关 ─────┘localStorage 是唯一真相源;宿主的 pin 态只是它的投影。ui-sidebar 的 apply 把 setPinned 藏在它的 injectProps 工厂返回值里,而这个工厂以 entry.inject 的形式存进槽位账本(dsh-client-ui-slots/lib/index.js:214)。 本插件在 apply 一开始就调 ctx.slots.entries('sidebar')[i].inject(),同步拿到 setPinned, 不需要用户点开任何菜单。宿主若晚于本插件注册,则退化为订阅 slots.subscribe('sidebar', …) 补抓。
setPinned 本身是幂等的:只有当传入值与当前状态矛盾时才真正改快照,所以重复下发没有副作用。requestAnimationFrame 一帧后再补抓一次,覆盖加载顺序抖动的极端情况。entries(含每行 pinned 真值)比对一次,但现在它只是兜底,不再是唯一时机。
1.0.0 的缺陷是「刷新后所有侧栏行都显示,必须点开一次『更多』才生效」。根因:唯一的通道来源 onPinnedChange 来自 sidebar.panellist.actions 座位的 ownerProps,而宿主用 primitives 的 Menu 包裹该座位,Menu 在 open === false 时不挂载 children(dsh-client-ui-primitives/lib/index.js:4373), 所以不打开菜单就永远拿不到通道。
1.1.0 改为启动期直接读 sidebar 座位的 entry.inject(root scope 无参,可同步调用),首屏即按偏好渲染。 onPinnedChange 兼容路径保留,作为宿主改版后的退路。
仍然存在的限制:若宿主将来不再把 setPinned 放进 sidebar 座位的 inject 返回值, 插件会自动退回 1.0.0 的行为(点开一次「更多」后生效),此时管理面板底部的提示行会重新出现。
产物:build/dsh-sidebar-entry-manager-1.1.0.tgz(npm pack 格式,包内只含运行所需文件,不含 build/ 与 docs/)。
也可以直接从 GitHub Release 下载同一个 tgz:v1.1.0 Release。
从 1.0.0 升级时:先把已装的 1.0.0 移除(插件页移除,或删掉dependencies那行 +bundles里那项),
再按下面任一方式装 1.1.0 并重启。不要两个版本同时留在 bundles 里。先拿到 tarball 的绝对路径(按你的克隆位置替换前缀):
<本仓库根目录>\build\dsh-sidebar-entry-manager-1.1.0.tgz仓库里build/是打包产物目录,已被.gitignore忽略。若你拿到的是源码而没有build/,
在仓库根目录执行 npm pack --pack-destination build 即可重新生成。DSH 桌面端 → 左侧栏「插件」→ 安装 → 填上面那个 tgz 的绝对路径。
装完完全退出并重新启动 DSH。
.plugin-manager/:C:\Users\lenovo\.dsh\profiles\desktop\.plugin-manager\dsh-sidebar-entry-manager-1.1.0.tgzC:\Users\lenovo.dsh\profiles\desktop\package.json,在 dependencies 里加一行:"@local/dsh-sidebar-entry-manager": "file:.plugin-manager/dsh-sidebar-entry-manager-1.1.0.tgz"dsh.profile.bundles 数组末尾追加包名(顺序即叠加顺序,放末尾最安全):"@local/dsh-sidebar-entry-manager"cd C:\Users\lenovo\.dsh\profiles\desktop
pnpm install3 和 4 都不能省:只加依赖不加入 bundles,包在磁盘上但不会被组合进 cordis 树。
cordis.patch.yml 会向插件树 insert 一个 sidebar-entry-manager 节点,引用本包的 dsh.client 入口;dsh.client.immediately: true 表示不需要用户手动打开面板,Web 客户端一启动就装载 client.js;重启后:
dsh.profile.bundles 里的包名与 dependencies 那行删掉后 pnpm install + 重启;localStorage 的 dsh-sidebar-entry-manager:prefs 键里(无副作用,键名唯一);想彻底清干净可在浏览器控制台执行:
localStorage.removeItem('dsh-sidebar-entry-manager:prefs')宿主的 plugins 行、Menu 组件、其它插件文件均未被改动,无需还原任何东西。
文件 · 作用 build/dsh-sidebar-entry-manager-1.1.0.tgz · 分发包(.gitignore 忽略,不放仓库里) docs/详细说明.md · 现象 → 根因 → 方案 → 验证的完整长文 LICENSE · MIT index.js · 宿主半边:空 apply()(全部工作在浏览器半边) client.js · 生效源:四个座位注册 + 偏好 store + 对账 + 两套 UI + 自绘 CSS cordis.patch.yml · 把本包挂进 Web 插件名册(insert: sidebar-entry-manager) locale/zh.json、locale/en.json · 插件管理页显示名与说明 icon.svg · 插件图标(96×96,包内) README.md / README.en.md · 中文 / 英文说明(均随包发布)
座位 · 注册选项 · 作用 sidebar.panellist · id: "sidebar-entry-manager", order: 20 · 左栏入口图标(排在 plugins 0 / schedules 10 之后) main · key: "sidebar-entry-manager" · 管理面板本体 sidebar.panellist.actions · id: "sidebar-entry-manager", order: 0 · 「更多」菜单内容 + 兜底对账(v1.1.0 起不再依赖它拿通道) settings.section · id: "sidebar-entry-manager", order: 25 · 设置页一节,与面板共用组件
组件全部走 ctx.slots.inject(...) 注册,随插件卸载一并回收。
浏览器控制台:
__dshSidebarEntryManager.get() // 当前偏好,如 { schedules: false }
__dshSidebarEntryManager.entries() // 当前注册到 sidebar.panellist 的 id
__dshSidebarEntryManager.set('schedules', false) // 等于在面板里关掉它
__dshSidebarEntryManager.reset()版本 · 变更 1.0.0 · 首版:四个座位、偏好持久化、「更多」菜单对账、双入口管理界面 1.1.0 · 修复首屏失效:改从 sidebar 座位的 entry.inject 启动期直取宿主 setPinned,不再依赖点开「更多」菜单;新增槽位订阅 + rAF 双兜底