dsh-recent-tasks
v0.3.0
Published
A collapsible Recent tasks block at the section level of the DSH sidebar: chats with no workspace land there instead of piling up in 未分组 / DSH 侧边栏里与「工作区」同级、可独立折叠的「最近任务」区块:没有工作区的对话自动归到这里。
Maintainers
Readme
dsh-recent-tasks
English | 中文
DSH 侧边栏里与「工作区」同级、可折叠的「最近任务」区块:没有工作区的对话归到这里,而不是和归档目录、已删除项目一起堆在未分组。这个区块不是插件画出来的仿制品,而是一条真正的工作区记录——所以从它开出来的对话,真的属于它。
DSH 按会话 cwd 归属的工作区给对话分组。不带工作区开出来的对话压根没有归属,于是落进「未分组」这个兜底桶。本插件为一个专用目录注册一条真正的工作区记录:于是「没有工作区」变成一个有正常行为的普通分组——改名、拖拽排序、置顶、归档、搜索全都可用——并且这个分组在侧边栏里被提升成一个独立可折叠的区块,与「工作区」同层、两者互不影响地展开收缩。
| 部分 | 说明 |
|---|---|
| 分组 | 通过 ctx.workspaceRegistry 为 <recentTasksDir> 注册的真 workspace 记录,创建时排在最后;此后插件不再改写持久顺序,你拖到哪里它就待在哪里 |
| 入口 | 侧边栏底部、设置旁的「+ 最近任务对话」,以及 Mod+Alt+N / Mod+Shift+N 快捷键,两者都会在该工作区里开会话 |
| 设置 | 设置 → 通用 里的「最近任务」几行——目录、分组名、自动重建——由本插件的官方实时配置驱动 |
| Host API | POST /dsh-recent-tasks/api/state 与 /ensure,只读且幂等;唯一的写入路径是官方设置表单 |
效果图
区块的行为与其他分组完全一致:GPT-6 特点介绍 是正在运行的那条对话,其余按使用时间排列,侧边栏底部的「+ 最近任务对话」在同一个目录里开新对话。(此图摄于提升为区块之前;提升后的读法见下面「侧边栏层级」一节。)
为什么要注册真工作区
另一条路——自己画一个分组——走不通:侧边栏的分组就是 workspace 存储域,没有任何插槽能让第三方插件插进一个平行分组。注册记录取代影子侧边栏的好处:
- 那里开的会话就是普通会话,
cwd是最近任务目录,分组的sessionIds走官方路径自己长出来(Workspace.sessionIds按realpath(cwd) === path过滤,也因此不可能把某条对话拖进别的分组); - 文件访问边界不变:会话的沙箱根目录就是这个目录,需要越界时在会话内提权,与工作区对话完全一致(设计 §8.7);
- 不打补丁改官方代码,不动
workspace命名空间的任何文案,「未分组」照旧;插件自己的分组名靠改写它自己的那条记录跟随界面语言,绝不碰官方命名空间,也绝不碰别的分组。
侧边栏层级:最近任务是一个独立区块
「最近任务」在侧边栏读作与「工作区」同层的区块,两块各自折叠:点「工作区」前面的箭头会收起其它工作区分组(最近任务不受影响),点「最近任务」前的箭头收起它自己那组对话;它下面的对话行相对标题缩进一级。
- 这是表现层适配,不是新功能:仍然是一条真工作区记录,插件不写任何工作区数据——既不重排列表,也不碰自己以外的任何分组。
- 它只认官方稳定属性(
data-row-key、role、内联的--dsh-workspace-indent),不认官方 class——那些是构建哈希,版本一更新就变。DSH 升级若把抓手挪走,这个提升会自动停用:侧边栏回到官方原样,不留残留、不报错,只在控制台留一行说明。 - 自动停用的场景:切成「单列表」、搜索中、把侧边栏收成 56px 细栏,以及树模式下「最近任务」被嵌套在别的目录之下(那时它不是同层)。这些状态下再切回来会立刻恢复。
- 已知取舍:这个区块随工作区列表一起滚动;「工作区」折叠期间,位于被隐藏分组里的当前会话高亮看不到,展开即恢复;官方重渲染的同一帧里(React 换掉容器、我们的重放还没跑到)被折叠的分组可能闪现一帧。
- 折叠状态只存在你自己的浏览器里(
dsh.recent-tasks.workspaces-collapsed.v1),不上报、不同步。
环境要求
- DSH 的
web或desktopprofile,0.2.0-rc.1及以上(开发与验证基于0.2.0-rc.2,cordis~4.0.4) - Node.js
>= 22.19.0——只在从源码构建时需要 - 装进 profile 时推荐用 pnpm
- 无网络访问、无原生模块、无安装脚本、无运行时依赖
安装
bundle 条目(
id: recent-tasks)由本包自带的cordis.patch.yml自行声明,不需要手写补丁文件。
从 npm 安装(推荐)
dsh plugin --profile desktop add dsh-recent-tasks已发布到 npm:dsh-recent-tasks。包里声明了 repository,插件市场因此能把它对应回本仓库并展示版本与下载量;安装走预构建包,完全跳过构建授权那一步。这是插件市场优先采用的安装方式。
从 GitHub 安装
按仓库身份安装,拿到 main 的最新提交——想试未发布改动时有用;在没有走 npm 的情况下,插件市场也是靠它把这个包对应到 winditer/dsh-recent-tasks:
dsh plugin --profile desktop add github:winditer/dsh-recent-tasksWeb profile 用 --profile web。重启 DSH(完全退出再打开),侧边栏底部就会出现「最近任务」分组。
构建产物 dist/client.js 是特意提交进仓库的:github: 安装不会执行本包的构建(pnpm 默认阻止生命周期脚本),提交进去的 bundle 才能让这条路开箱可用,使用端不需要编译任何东西。
从应用内安装
设置 → 插件 → 安装 bundle,指向本目录(或一个 file: 规格)。scripts/install-into-profile.sh 是等价的手工路径:pnpm add file:<repo> 外加一条 dsh.profile.bundles。
dependencies 那条不能省——只写进 dsh.profile.bundles 的名字会被桌面端崩溃恢复流程抹掉(它按 dependencies 重新推导 bundle 列表)。
从预构建 tarball 安装
每个 Release 都会附带打包好的安装包,既不用下载整个仓库,使用端也不需要编译:
dsh plugin --profile desktop add https://github.com/winditer/dsh-recent-tasks/releases/download/v0.3.0/dsh-recent-tasks-0.3.0.tgz这个 URL 特意固定到 tag,而不是写成 releases/latest/download/…:latest 每次请求现算,但文件名是字面量——带版本号的文件名会在下一个 Release 改名的那一刻让那条 URL 失效。
从源码安装(开发)
git clone https://github.com/winditer/dsh-recent-tasks.git && cd dsh-recent-tasks
npm install
npm run build # 重新生成 dist/client.js
dsh plugin --profile desktop add .dist/client.js 每次启动前由 node scripts/build.mjs 重新生成;万一缺失,Host 半边照常加载,应用日志里会出现 client bundle not found; run \pnpm run build` before launch`。
设置
三项实时配置就是普通的官方设置:设置 → 通用(本插件自己的那一行)和设置 → 插件(由插件 Config 自动推导)都能看到,且写入同一份文档。
| 键 | 默认 | 含义 |
|---|---|---|
| recentTasksDir | ~/Documents/DSH 最近任务 | 分组占用的目录。首次使用时创建;必须是绝对路径。改它会立刻为新目录注册/复用记录。 |
| title | 最近任务 | 创建记录时使用的分组名。当记录里的标题仍是本插件自己的默认名(最近任务 / Recent tasks)时,它会跟随界面语言;其他任何标题都算你手动定的,永不被改写。 |
| autoRepair | true | 分组被删除后自动重建。关掉它,删除就变成可见的错误,而不是静默重建。 |
| enabled | true | 总开关(仅 patch,无表单)。 |
| apiPrefix | /dsh-recent-tasks/api | Host API 路径(仅 patch)。 |
改动由 dsh-config-editor 落盘到当前 profile 的 cordis.patch.yml(原子写、校验、失败回滚),并对运行中的 Host 实时生效:loader 把新值提交进插件的 Volatile 引用并发出 loader/volatile-update,Host 半边据此立刻重新 ensure。本插件自己从不写任何配置文件。
优先级:profile patch 层优先于本包内置默认值,所以用户改动能跨升级保留。
行为细节
- 绝不静默失败。 每次拒绝都是一个错误码(
bad-config、dir-not-writable、dir-not-directory、missing-record、disabled、unavailable…),由客户端从本插件自己的 locale 命名空间渲染;Host 从不下发展示文案。 - 接口很小、只在本地。 两个幂等 POST(
state、ensure),且必须带content-type: application/json;这条限制同时也是 CSRF 防线——跨站「简单请求」设不了这个类型,而本路由不响应预检。接口不暴露任何机密,也从不接受浏览器传来的路径或命令。 state绝不写盘。 不 mkdir、不创建、不排序;记录被删就以workspaceId: null+missing-record如实上报,只有ensure会修复。- 能复用就不新建。 配置目录已经是工作区时复用那条记录,且不动它的标题(DSH 的
create按realpath幂等,复用时会忽略标题)。 - 分组名跟随界面语言。 侧边栏那一行和右上角工作区切换菜单都是原样渲染记录里持久化的
title——DSH 只对自己那个default-workspace哨兵值做本地化,插件也无法往内置workspace命名空间里加词条。所以只要记录里的标题还是本插件自己的默认名(最近任务/Recent tasks),客户端半边就会用官方的workspaces.rename(侧边栏自己「重命名」用的同一个调用)把它改成当前语言:两处立刻刷新,且持久化。你自己在侧边栏改过名,或把title配成别的,插件从此不再碰它。磁盘目录名永不改动:路径是会话归属的依据,改名会让已有对话掉回「未分组」。 - 嵌套目录会警告。 最近任务目录落在别的工作区内部时会显示为它的子级,设置行会明说,而不是假装没事。
- 删除是诚实的。
autoRepair关闭时分组被删,入口和快捷键都会报missing-record——绝不把对话悄悄开到别处。 - 快捷键有退路。
session.new已占用Mod+N(桌面)与Mod+Alt+N(Web),而shortcuts.register拒绝重叠默认值。桌面用Mod+Alt+N;Web/macOS 与 Web/Windows 用Mod+Shift+N;Web/Linux 不绑定。万一仍冲突会被捕获,侧边栏入口始终可用。
排查
| 现象 | 原因 / 处理 |
|---|---|
| 侧边栏没有「最近任务」分组 | 看 设置 → 通用 → 最近任务 的错误码:missing-record(且自动重建关闭)或 dir-not-writable。 |
| 分组嵌套在别的工作区下面 | recentTasksDir 落在某个已注册工作区内部——换一个不在任何工作区里的目录。 |
| 入口提示 unavailable | Host 半边没挂上(行被禁用,或行在 workspaceRegistry 之前挂载)。安装后重载一次应用。 |
| 设置行显示只读提示 | 当前组合里没有 configForms(即没有官方设置界面);直接改 profile cordis.patch.yml 里的 recent-tasks 行。 |
| 对话还是进了「未分组」 | 那些对话的 cwd 不是本工作区的目录。历史对话永不迁移(设计 §6.3/N3):本分组只承接通过入口/快捷键新开的对话。 |
开发
node scripts/build.mjs # 构建 dist/client.js
node --test test/*.test.js # 75 项测试:协议、Host、客户端逻辑、侧边栏区块适配器、bundle 契约
node scripts/build.mjs && node --test test/*.test.js # 即 npm run verify
npm pack --dry-run # 查看将要发布的文件清单node_modules/@deepseek-ai/* 是指向 .asar/ 下解包出来的 0.2.0-rc.1 依赖树的软链接,只为让 node --test 能解析 @deepseek-ai/schemastery;生产环境走 DSH 自己的 profile 解析拦截,所以包里把它们声明为可选 peer,不带任何运行时依赖。
目录
| 路径 | 作用 |
|---|---|
| src/host.js → lib/index.js | Host 半边:Config、注册逻辑、JSON API、实时配置监听 |
| src/client.js → dist/client.js | 浏览器半边:侧边栏入口、设置行、轮询、快捷键 |
| src/client-core.js | 无 React 的纯逻辑:状态机、planCreate、快照 store、快捷键判定、折叠标志、区块提升判据 |
| src/client-adapter.js | 侧边栏区块适配器:运行时定位官方节点、打 data-dsh-rt-* 标签、注入折叠开关、自愈重放(表现层,不写数据) |
| src/protocol.js | 共享词表:错误码、方法、路径处理、响应解析 |
| src/locales.js | recentTasks 中英文词典(扁平键,键集 1:1) |
| assets/ | 本 README 使用的效果图(插件市场也会抓取) |
| screenshots.json | 插件市场读取的效果图清单(约定:放在 package.json 旁,路径相对于本仓库) |
| test/ | protocol · host · client · adapter(假 DOM)· bundle |
| docs/superpowers/ | 本仓库自己的 SPEC 与实现计划(specs/、plans/) |
| .agents/notes/ | 实现笔记,含已验证的官方事实与与草稿 SPEC 的偏离说明 |
许可
MIT © 2026 dsh-recent-tasks contributors
