Repository navigation
Releases: whybebabo/dsh-better-workspace
Release list
v0.1.7 — 全量审查后的重命名、拖拽与删除残留修复,附新建分组自动定位
v0.1.7 — 全量审查后的重命名、拖拽与删除残留修复,附新建分组自动定位
本版是第三轮全量代码审查后的修复版。重点是两条用户可见的功能错误(会话改名、第二次拖拽落点),以及一条会留下永久残留的删除边界;同时带上了一项体验改进——新建分组后自动定位到该分组。sidecar 与偏好格式未变,升级无需迁移。
主要修复
- 会话重命名:0.2.1 的
UiWorkspace没有renameSession,插件此前每次保存都会失败并提示「当前宿主版本不支持重命名会话」。现在与宿主自己的改名对话框一致,走sessions.using(id, { source: 'workspaceOperation' }, ref => ref.binding.session.rename(title))并检查结果。 - 拖拽顺序:工作区与会话的手动顺序是「对展示顺序的置换」,所以落点必须按当前屏幕上的顺序翻译。第二次拖拽此前会跳到别处(
A B C → C A B → B A C,期望C B A)。现在树与拖拽基线共用同一派生(workspaceSessionIds/ungroupedSessionIds:记录顺序 + 按cwd补漏的会话按时间倒序、排除子智能体)。 - 永久删除的残留:父会话已消失的孤儿子会话现在可以单独删除。此前它一边被「去删主会话」挡住、另一边主会话已不存在,日志会永久留在磁盘上且界面上完全不可见;
partial结果的重试也会带上未删掉的子会话。 - sidecar 版本:由更新版本插件写入的文件一律只读、不写回、也不拿
.bak覆盖,绝不把新数据降级;主文件损坏但备份可读时保持可写(下一次保存重建主文件),并在横幅里说明原因——只读现在只表示「完全读不出来」。 - 禁用开关:
config.enabled: false时宿主仍答复/state{enabled:false},面板据此交还原生 UI,不再盖着一条 404 横幅。 - 面板细节:折叠侧栏的点击代理只处理该 frame 内的按钮(不再劫持其他面板的「搜索」);对话框补上 Tab 焦点陷阱;
/state读取带序号,旧响应不会覆盖新快照(消除幻影冲突);409 按code分类,存储错误本地化。
新增:新建分组后自动选中
- 新建会话界面的分组选择栏:在输入框上方的分组 chip 里点「新建分组」,创建成功后当前会话直接归入新分组,chip 立刻显示新分组名;跨工作区创建时不会写入越权归属,chip 保持原样。
- 左侧工作区
⋯菜单 → 新建分组:创建后自动展开所属工作区(连收起的归档桶一起)与目标分组,把新分组行滚进视野,并播放 1.6 秒后淡出的高亮;搜索与「只看此分组」的筛选刻意保持不变,避免把用户从当前视图里带走。 - 高亮按
data-dts-workspace/data-dts-topic精确定位,避免tp_default这类跨工作区同 id 的分组被误点亮;prefers-reduced-motion下改为静态底色,不做动画。
测试
- 260 项测试全部通过,
npm run check与发布预检通过。 - 新增用例覆盖:改名走宿主
sessions.using、连续两次拖拽(工作区与会话)、孤儿会话独立删除、更高版本只读不降级、回退备份仍可写、禁用时只答复/state、无table时自愈拒绝猜测、composer 建组后会话被归入并切换 chip、工作区菜单建组后展开 / 滚动 / 高亮。
安装
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.7
# 或直接从仓库 tag 安装
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.7已有 0.1.x 用户升级后无需迁移。
English
v0.1.7 — rename, drag ordering and deletion-residue fixes from the full review, plus new-topic auto-select
This is the fix release after the third full code review. It covers two user-visible errors (session rename, the landing point of a second drag) and a deletion edge that left artifacts behind forever, plus one usability improvement: the topic you just created is selected for you. Sidecar and preference formats are unchanged, so no migration is required.
- Session rename: 0.2.1's
UiWorkspacehas norenameSession, so every save failed with "this host build cannot rename sessions". The plugin now does what the host's own dialog does:sessions.using(id, { source: 'workspaceOperation' }, ref => ref.binding.session.rename(title)), checking the result. - Drag ordering: the manual order is a permutation of the displayed list, so a drop must be translated against what is on screen. A second drag used to land elsewhere (
A B C → C A B → B A Cinstead ofC B A). The tree and the drop baseline now share one derivation (workspaceSessionIds/ungroupedSessionIds: record order, then cwd-matched stragglers by recency, subagents excluded). - Deletion residue: an orphaned subagent whose parent artifact is gone can now be deleted on its own. It used to be blocked with "delete its parent" while the parent no longer existed, leaving the log on disk and invisible in the UI; a
partialresult's retry now carries the children that survived. - Sidecar version: a document written by a newer plugin is read-only, never written back and never "recovered" from
.bak, so newer data is not downgraded. A primary recovered from the backup stays writable (the next save rebuilds it) and the banner says so — read-only now only means "nothing readable at all". - Kill switch: with
config.enabled: falsethe host still answers/state{enabled:false}, and the panel hands the region back instead of covering it with a 404 banner. - Panel details: the collapsed-rail proxy only handles buttons inside that frame (no more hijacking another panel's "Search"); dialogs trap Tab;
/statereads are sequenced so a stale response cannot overwrite a newer revision (no phantom conflicts); 409 is classified bycodeand store errors are localized.
New: the topic you just created is selected for you
- Topic selector above the composer: creating a topic from the chip files the open session into it, and the chip switches to the new name immediately. A cross-workspace create writes no out-of-scope assignment, so the chip stays where it was.
- Workspace
⋯menu → New topic: the workspace (including a collapsed archive bucket) and the target topic expand, the new row scrolls into view, and a highlight fades out after 1.6 seconds. Search and "only this topic" filters are deliberately left untouched, so the view you were in is not taken away from you. - The highlight targets
data-dts-workspace/data-dts-topic, so atp_defaultin another workspace is never lit by mistake; withprefers-reduced-motionit becomes a static tint instead of an animation.
Validation: all 260 tests pass, along with npm run check and the npm publish preflight. New cases cover the rename going through the host's sessions.using, two consecutive drags (workspaces and sessions), deleting an orphaned subagent, a newer version staying read-only, a recovered backup staying writable, enabled:false answering only /state, repair refusing to guess without a raw table, the composer create filing the open session in and switching the chip, and the workspace-menu create expanding, scrolling and highlighting.
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.7
# or install directly from the repository tag
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.7Existing 0.1.x users can upgrade without migration.
v0.1.6 — 侧栏样式与工作区流程修复
v0.1.6 — 侧栏样式与工作区流程修复
本版集中修复侧栏原生样式、跨工作区状态隔离和添加工作区流程;数据结构与偏好格式未变,升级无需迁移。
主要改动
- 对齐原生侧栏的字体、行高与行尺寸,减少插件与宿主界面之间的视觉差异。
- 新会话待归组状态与拖拽插入线按工作区隔离,不再让多个工作区的默认分组同时响应;移除 pending 蓝框,但保留复用空白会话时的正确归组。
- 添加工作区统一委托宿主原版流程:Web 与 Desktop 均使用当前部署组合的目录选择器,远程隧道继续使用宿主的应用内目录浏览器,不使用插件自定义面板。
测试
- 241 项测试全部通过,
npm run check与 npm 发布预检通过。 - 隔离浏览器实例确认添加工作区会启动宿主原生目录面板,且不会出现插件自定义对话框。
安装
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.6
# 或直接从仓库 tag 安装
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.6已有 0.1.5 用户升级后无需迁移。
English
v0.1.6 — stock sidebar styling and workspace-flow fixes
This release aligns the sidebar with the host UI, isolates workspace-specific state, and restores the stock add-workspace flow. Data structures and preference formats are unchanged, so no migration is required.
- Match stock sidebar typography, row heights, and row metrics.
- Scope pending-session assignment and drag insertion indicators by workspace, preventing default groups in other workspaces from reacting. The pending blue ring is removed while correct assignment of reused blank sessions is preserved.
- Delegate add-workspace to the host on both Web and Desktop. Remote tunnels keep using the host's in-app directory browser; no plugin-owned picker is used.
Validation: all 241 tests pass, along with npm run check and the npm publish preflight. An isolated browser smoke test confirmed that the action launches the host's native directory panel without a custom plugin dialog.
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.6
# or install directly from the repository tag
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.6Existing 0.1.5 users can upgrade without migration.
v0.1.5 — 全量审查后的删除、锁与面板修复
0.1.5 — 全量审查后的删除、锁与面板修复
本版是一次全量代码审查后的修复版。重点在永久删除的身份绑定与竞态收口、跨进程锁回收,以及面板交互与筛选的一批可见问题;数据结构与偏好格式未变,升级无需迁移。
修复:永久删除不再可能删错对象
- 目录移入
.trash-*隔离区后,重新读取隔离对象的首行 header 并核对id;不匹配就还原并放弃。此前“检查路径”与“删除路径”之间换目录,可能把另一个会话当成目标删掉。 - 子会话级联使用同一套身份校验;级联删除失败时准确报
partial,不会把残留子会话计入已删除。 - 运行状态在停止后、真正删除前再次探测;空闲会话在 detach 前复检运行状态与 15 秒写租约(租约检查放在 detach 之前,避免 flush 刷新 mtime 造成误判)。
- 压缩会话 header 改为流式解压读取,超过旧 64KB 窗口的
session.v4.jsonl.zstd也能正常判定和删除。 - 隔离区创建失败不再被当作“目标已删除”静默成功。
修复:锁回收与大批量清理
- 过期锁回收改为“硬链接 claim + 令牌/inode 校验”;后继者刚接手的新锁不会被误删,也不会出现两个写者同时认为自己持锁。
- 默认过期窗口 2s、等待上限 6s,崩溃遗留的锁不会让每个请求都在可回收前先超时。
- 删除后的归属清理按 200 条分批,超大子会话树不会再让整批清理失败。
修复:工作区归属自愈
- adopt 登记工作区路径时同时使用原始拼写与
realpath;通过 symlink 拼写注册的工作区也能正确匹配cwd,macOS 的/var↔/private/var不再漏归。
修复:面板交互与筛选
- 菜单触发器改为保存元素并支持再次点击关闭;标题栏、工作区行、分组行、会话行行为一致。
- 原生弹窗渲染在覆盖区域内时自动解除
inert/隐藏并交还区域;关闭后恢复,macOS 下同时恢复透明覆盖标记。 - 「未分组」内部可以正常拖拽排序,顺序写入
sessionOrder.__ungrouped__。 - 正在「只看此分组」时,只有确实属于该工作区的新空白才会被归组;别的窗口创建的空白不会再被误写。
- 删除分组失败、复制 Session ID 失败都有可见提示;新建分组默认选择未使用的 emoji;工作区/分组筛选失效时能独立清理。
- 会话树按
cwd建立一次索引,搜索/刷新不再对每个工作区全量扫描会话;折叠全部也会一并折叠归档桶。
测试
- 234 项测试全部通过(含 macOS 默认 TMPDIR)。
- 新增覆盖:preview/execute 之间换目录、隔离后身份不匹配、子会话换目录、分析后重新运行/写入、隔离区创建失败、大于旧窗口的 zstd header、symlink 工作区、后继者锁竞态、菜单开关、原生弹窗交还、未分组排序、跨工作区新会话归组。
- 另在隔离的本地 DSH
0.2.1-rc.2实例上完成真实冒烟:面板挂载、菜单开关、原生弹窗交还、/state、revision 冲突、分组增删、真实删除流水线与 zstd header 识别均通过。
安装
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.5
# 或直接从仓库的 tag 安装(不经过 npm)
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.5已有 0.1.4 的用户升级后无需迁移:分组归属、偏好与本地面板数据格式均未变。
English
0.1.5 — safer deletion, stale-lock reclaim, and panel fixes after a full review
This is a fix release following a full code review. Data structures and preference formats are unchanged, so existing users need no migration.
Permanent deletion can no longer remove the wrong object
- After a directory is moved into the
.trash-*quarantine, its header is read again from the quarantined object and itsidis verified. A mismatch restores the directory and aborts; a directory swapped between the check and the removal used to be deleted under another session's id. - Subagent cascades use the same identity check, and a surviving child is reported as
partialrather than counted as removed. - Running state is re-probed after a stop and immediately before removal; idle sessions also re-check the 15-second write lease before detaching (the lease check runs before
detach, because a flush refreshes the mtime). - Compressed headers are read through a streaming decompressor, so
session.v4.jsonl.zstdartifacts larger than the old 64 KB window verify and delete correctly. - A quarantine that cannot be created now fails loudly instead of being mistaken for "already deleted".
Stale-lock reclaim and large cleanup batches
- Reclaiming a stale lock now uses a hard-link claim plus token/inode verification, so it cannot unlink a successor's fresh lock or leave two writers believing they own the store.
- The default stale window is 2 s and the wait deadline 6 s, so a crash-left lock cannot make every request time out just before it becomes reclaimable.
- Assignment cleanup after a delete is sent in batches of 200 ids, so a very large subagent tree no longer fails the whole cleanup.
Workspace membership repair
- Adoption registers both the original spelling and the
realpathof each workspace, so workspaces opened through a symlink match their sessions'cwd; macOS/var↔/private/varno longer strands sessions.
Panel interactions and filters
- Menus keep their trigger element, so clicking the same trigger again closes the popover — consistently for the header, workspace, topic, and session rows.
- When a native dialog renders inside the covered region, the region is released from
inert/hiding and handed back; it is re-covered when the dialog closes (including the transparent-cover marker on macOS). - Sessions inside Ungrouped can be reordered by drag; the order is persisted as
sessionOrder.__ungrouped__. - With a topic filter active, only a new blank that really belongs to that workspace is filed; blanks created by another window are left alone.
- Failed topic deletion and failed session-ID copying now show visible errors; new topics default to an unused emoji; stale workspace/topic filters clear independently.
- The session tree builds one
cwdindex per pass instead of scanning every session once per workspace; collapse-all also collapses archived buckets.
Tests
- 234/234 tests pass on the default macOS TMPDIR.
- New coverage: directory swaps between preview and execute, identity mismatch after quarantine, swapped subagent directories, a session that starts running/writing after analysis, failed quarantine creation, zstd headers larger than the old window, symlinked workspaces, a successor lock during stale reclaim, menu toggling, native-dialog hand-off, Ungrouped ordering, and cross-workspace new-session filing.
- Smoke-tested on an isolated local DSH
0.2.1-rc.2: panel mount, menu toggling, native-dialog hand-off,/state, revision conflicts, topic create/delete, a real deletion run, and zstd header detection.
Install
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.5
# or from the repository tag
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.5Existing 0.1.4 users need no migration: topic assignments, preferences, and the local panel data format are unchanged.
v0.1.4 — 指针移向弹层不再把它关掉
0.1.4 — 输入框主题弹层不再被指针移动误关
只含一条修复的补丁版:输入框上的主题 chip 打开的弹层,此前在鼠标从 chip 移向弹层的路上就会被关掉,里面的主题项根本点不到。
修复:hero 行的 hover 逻辑抢走了弹层
- 弹层本身是
role="dialog"(Popover组件自带),但 chip 上写的是aria-haspopup="menu"。宿主的 hero 行有一套 hover 驱动的菜单处理器,按 menu 触发器对待它,指针一移动就判定为「离开」,把刚打开的弹层关掉。 - chip 改为
aria-haspopup="dialog",与实际结构一致:hero 行不再接管它,弹层在指针移动过程中保持打开。 - 顺带修掉无障碍语义:屏幕阅读器现在按对话框播报这个触发器,而不是菜单。
测试
- 新增一条面板测试:断言 chip 的
aria-haspopup为dialog,且点击后弹层的role为dialog。本版 222 个测试全通过。
安装
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.4
# 或直接从仓库的 tag 安装(不经过 npm)
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.4已有 0.1.3 的用户升级后无需迁移:分组归属、偏好与本地面板数据格式均未变。
English
0.1.4 — the composer topic popover no longer closes when the pointer moves
A one-fix patch. The topic chip above the composer opens a popover, but moving the pointer from the chip toward the popover used to close it before any topic item could be clicked.
- The popover is a
role="dialog"(built intoPopover), while the chip declaredaria-haspopup="menu". The host hero row has a hover-driven menu handler; it treated the chip as a menu trigger, saw the pointer leave, and closed the just-opened popover. - The chip now declares
aria-haspopup="dialog", matching its actual structure: the hero row leaves it alone and the popover stays open while the pointer moves. - This also fixes the accessibility semantics: screen readers now announce a dialog trigger, not a menu.
Tests: a new panel test asserts the chip's aria-haspopup is dialog and that the opened popover has role="dialog". 222/222 tests pass.
Install
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.4
# or from the repository tag
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.4Existing 0.1.3 users need no migration: topic assignments, preferences, and the local panel data format are unchanged.
v0.1.3 — 分组行直接新建会话、侧栏收起后输入框主题可读、路由与输入加固
0.1.3 — 分组行直接新建会话、侧栏收起后输入框主题可读、路由与输入加固
本版三件事:分组行尾的 + 可以直接在该分组下新建会话;侧栏收成 rail 后,输入框上的主题 chip 不再消失、也不再指错工作区;以及一批路由/输入/缓存上的加固,每一条都对应一个能复现的错。
新增:分组行 + 新建会话
- 分组行尾新增 +,点击即在该分组下新建会话(工作区行尾的 + 仍是在该工作区新建)。只有一个入口、一条归属规则,不再需要先建会话再手动移动。
- 分组新建后默认排在该工作区分组列表的最上面,不用再滚到底部去找。
- 标题栏 + 的「新会话」、工作区行 +、rail 上的「新建会话」:会话不再写入任何分组归属,留在系统默认分组,并按原生顺序排在前面——与官方列表一致。早先版本会把隐式新建的会话塞进「最近创建的分组」,那个分组通常排在最后,于是新会话直接掉到工作区最底部;该行为已移除。
- 新建是异步的:创建被拒绝时立刻释放等待状态并提示,不会留下一个 90 秒的「待归组」标记把下一次新建的会话误塞进用户从未成功打开过的分组。
修复:侧栏收起后输入框主题 chip
- 输入框里的主题 chip 被 portal 到宿主输入框(在面板覆盖区域之外),因此面板收成 rail 时它仍然在屏。此前它跟着面板一起卸载,收起侧栏后主题 chip 直接消失。
- chip 的 store 订阅改为跟随「座位」而不是面板,收起状态下也会跟随宿主会话列表更新已打开的会话;展示的内容不再停在收起前那一帧。
- chip 写哪个工作区,以会话自身为准:先看会话记录归属,再看
cwd路径;只有宿主还没记账的空会话才回退到看板上可见的标题。 - 标题不是身份:两个同名目录会得到同一个标题。此前
resolveComposerWorkspace匹配到第一个就返回,同名工作区会把会话写进错误的 sidecar;现在歧义匹配返回null,宁可隐藏 chip 也不猜。 - 面板从收起状态起步时,也会读一次文档来确定当前会话属于哪个分组(此前会因「不在屏上」而跳过这次 GET)。
修复:搜索、删除与缓存
- 搜索不再留下空的工作区标题:无命中行的分组/工作区整块隐藏。
- 部分删除的归属清理:会话文件已删除、只残留部分子智能体的(
partial)此前不会被清掉主题归属,留下指向不存在会话的悬挂记录;现在与干净删除一视同仁地清掉,连带子智能体一起。返回结果会区分「全部成功」与「部分完成:N 个会话已删除,但子智能体未能全部清理」,并把子智能体数量与归属清理的失败原因一并报出来。 - 渲染缓存修剪:分组、区块、会话行的缓存按当前存活 key 剪枝,不再随会话增删无限增长。
- 只读快照不再粘住:模块级的「存储损坏」标记此前会一直挂在进程上,即使文件已被修复(自己修的或手工修的)仍持续报只读;现在只读与否取决于这一次读取,修好即可回到可写。
修复:行内事件不再劫持嵌套控件
分组行/会话行上的按键与双击处理会挂到行容器上。此前在行里的按钮、输入框、菜单等嵌套控件上操作,事件会冒泡到行上被当成「打开会话」或「进入改名」,导致点 + 却打开了会话、在菜单里双击却触发重命名。现在只处理来自行本身的事件。
加固:路由与写入
- 拒绝来源不可判定的写请求:
Origin: null(沙箱 iframe、file://页面)此前被当成同源放行,现在直接拒绝——它永远不可能是本应用自己的来源。 - 写请求体必须是
application/json(否则 415)。只有 origin 检查不够:text/plain请求体属于 CORS 简单请求,不发预检;强制 JSON 后,跨站写必须走预检,而浏览器会挡下它。Desktop 载体不带请求头且已认证,予以豁免。 - 对应的路由、存储、模型、面板测试补齐;本版测试 221 个全通过。
安装
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.3
# 或直接从仓库的 tag 安装(不经过 npm)
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.3已有 0.1.2 的用户升级后无需迁移:分组归属、偏好与本地面板数据格式未变。
English
0.1.3 — create a session from a topic row, keep the composer topic chip readable after collapse, and harden routes/input
Three changes: a topic row's + creates a session directly in that topic; after the sidebar collapses to the rail, the composer topic chip no longer disappears or points at the wrong workspace; and a set of route/input/cache fixes, each tied to a reproducible bug.
New: create a session from a topic row
- A + at the end of each topic row creates a session in that topic (a workspace row's + still creates one in the workspace). One entry point, one assignment rule — no more create-then-move.
- New topics start at the top of the workspace's topic list instead of the bottom.
- The header + "new session", a workspace row's +, and the rail's "new session" write no assignment, so the session stays in the system default group and sorts first there, matching the official list. Earlier versions filed implicit new sessions into the newest topic, which usually sat last and dropped the row at the bottom of the workspace; that behavior is gone.
- Session creation is asynchronous: a rejected create immediately clears the pending state and shows an error, instead of leaving a 90-second "awaiting topic" flag that would file the next new session into a topic the user never opened.
Fix: composer topic chip after collapse
- The chip is portaled into the host composer (outside the covered region), so it remains on screen when the panel collapses to the rail. It used to unmount with the panel and disappear.
- Its store subscription now follows the seat rather than the panel, so it keeps tracking the open session while collapsed instead of freezing on the pre-collapse snapshot.
- The chip resolves the workspace from the session itself — first its recorded workspace, then
cwd; only a blank the host has not accounted for falls back to the visible title. - Titles are not identity: two workspaces with the same folder name used to collide, and the first match could write the session into the wrong sidecar. Ambiguous matches now return
null, hiding the chip rather than guessing. - Starting from the collapsed state, the panel also reads the document once to determine the open session's topic (it used to skip that GET because it was not on screen).
Fix: search, deletion, and caches
- Search no longer leaves empty workspace headers: groups/workspaces with no matching rows are hidden entirely.
- Partial deletes clean up topic assignments too: a session whose log was removed but whose subagents only partly survived (
partial) used to leave dangling assignments. It is now cleaned up like a clean delete, children included. The result distinguishes "all succeeded" from "partially completed: N sessions removed, but some subagents could not be cleaned", and reports the subagent counts and cleanup failures. - Render caches for groups, sections, and rows are pruned to live keys instead of growing with every session add/remove.
- A read-only snapshot no longer sticks: the module-level "storage corrupt" flag used to stay on the process even after the file was repaired (by the plugin or by hand), so the store kept reporting read-only. Read-only now depends on this read, and the store becomes writable as soon as the file is fixed.
Fix: row events no longer hijack nested controls
Key and double-click handlers on group/session rows are attached to the row container. Interacting with a button, input, or menu inside the row used to bubble up and be treated as "open session" or "start rename", so clicking + opened the session and double-clicking inside a menu triggered rename. Only events from the row itself are handled now.
Hardening: routes and writes
- Reject writes with an undeterminable origin:
Origin: null(sandboxed iframe,file://page) used to be treated as same-origin; it is now refused, since it can never be this app's own origin. - Write bodies must be
application/json(otherwise 415). Origin checks alone are not enough: atext/plainbody is a CORS simple request with no preflight; requiring JSON forces a preflight that the browser will block for cross-site writes. The authenticated Desktop carrier, which sends no request headers, is exempt. - Route, store, model, and panel tests were extended; 221/221 tests pass.
Install
# npm
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.3
# or from the repository tag
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.3Existing 0.1.2 users need no migration: topic assignments, preferences, and the local panel data format are unchanged.
v0.1.2 — 孤儿会话归位、全选收敛到可见行、删除安全加固
0.1.2 — 孤儿会话归位、全选收敛到可见行、删除安全加固
本版主要修三类问题:会话"看起来丢了"(实际落在未分组)、批量永久删除会作用到从未上屏的行,以及删除流程里两处会把"没删掉"报成"删掉了"的路径。
新增:工作区归属自愈
宿主只在自己的 session.create 路径上把会话记进 workspace.sessionIds。以下情形会留下"cwd 对得上、但没人记账"的会话,原生列表把它们显示在末尾的「未分组」:
- 工作区被删除后又重建;
- header 写完、
attachSession之前宿主崩溃或窗口被关; - 会话经由其它路径恢复。
新增 POST /dsh-better-workspace/api/sessions/adopt,在面板首次上屏时调用一次(手动「刷新」也会再调)。它只用宿主自己的 workspace.attachSession(id) 归位——该命令会重新校验 cwd、拒绝不匹配、并把 id 前插(所以归位后正好落在工作区最前面)。子智能体、已归档、已有归属、cwd 无法解析的会话一律不碰;被宿主拒绝的逐条记入 failed,不强行写。
workspaceRegistry / sessionPersistence 缺失时路由返回 available: false,面板静默跳过,不影响其它功能。
修复:三个「全选」给出三个数字
分组头复选框、分组 ⋯ 菜单、工作区 ⋯ 菜单此前各自读 section.rows,而正文只渲染前 60 条。同一分组(70 条)实测得到 60 / 69 / 70 三个计数,删除确认框写的是「永久删除 70 个会话?」——其中 10 条用户从未见过。
现在只有一个派生 model.selectableIds,四个入口共用:
| 情形 | 可选范围 |
|---|---|
| 分组 ≤ 60 条 | 全部 |
| 分组 > 60 条且未展开 | 已渲染的 60 条,并提示「已选中 N 条;另有 M 条未显示,展开后再选」 |
| 用户点「显示全部 M 条」后 | 全部(展开状态移入 prefs,菜单也看得见) |
| 分组被折叠 | 全部(折叠是用户主动操作,标题上仍有条数) |
前端的「新会话」占位行在任何入口都不会进入选择。
修复:新建会话落在哪
隐式新建(标题栏 +、工作区行 +、rail)此前会被塞进「最近创建的分组」;分组按创建时间升序渲染,那个分组通常排在最后,于是新会话直接掉到工作区最底部,还不在用户正在看的分组里。现在隐式新建不写任何归属,留在默认分组——即该桶的第一个,与官方列表一致。只有在「只看此分组」时,新会话才归入当前筛选的那个分组(且仅限该筛选所在的工作区)。
同时修正 orderBy:拖动产生的顺序只对它点名过的 id 生效,这些 id 在各自槽位上互相置换;未出现在手动顺序里的会话(例如拖拽之后才新建的)保持宿主位置,不再被降级到末尾。
修复:删除流程的两处"假成功"
隔离区创建失败被当成"目标已不存在"。 removeVerifiedDir 里 mkdir(quarantine) 的错误被 .catch(() => {}) 吞掉,随后的 rename 因父目录缺失报 ENOENT,而 ENOENT 被解释成"已经删掉了"直接返回成功。后果不是"删不掉",是清错了记录:子会话日志仍在盘上,却被计入 subagentsRemoved,它的分组归属/归档/置顶随即被清掉,条目报 deleted。
现在 mkdir 失败直接抛 delete-incomplete;ENOENT 只有在 lstat 确认原目录真的不在时才算成功;子会话补上和主目录一样的"删后自证",失败的子会话让该条目变成 partial 而不是成功。
其它
samePath:工作区记录与会话 header 的路径按拼写比较(Windows 折叠大小写与分隔符,POSIX 不折叠)。此前严格===会让大小写不同的会话掉进未分组。- 批量目标只含仍存在的行:另一窗口删掉的会话不会再让整批停在「失败:no such session」。
- 工具栏派生一次:
selectedRows()原本每次渲染算 5–7 遍且是 O(n·m),现在共享一份useMemo。 - 会话标题的悬停滚动改为逐帧驱动,爬到头就停(不再来回摆),常量与原生一致(0.03 px/ms、8 px 死区)。
npm run check改为真正加载lib/*.js的模块图并核对产物注册 id。node --check只做语法检查、不解析 import——lib/adopt.js曾经未被跟踪时,旧检查照样通过,而宿主入口ERR_MODULE_NOT_FOUND。- 发布卫生:
package-lock.json此前停在未加 scope 的0.1.0,任何npm install都会产生无关 diff;README 预览图改用绝对地址(docs/不进 npm 包,相对路径在 npmjs 上必然 404)。
测试
196 → 206 项。新增的守护都用变异法复核过:
| 测试 | 对应的实现 | 删掉实现后的表现 |
|---|---|---|
test/store-crossprocess.test.mjs |
跨进程文件锁 | fork 两个真实 node 进程各写 10 次:旧 store.test.mjs 21/21 仍全绿,本测试报「丢写」 |
test/delete-swap.test.mjs |
执行期二次校验 | 在 analyze 与删除之间 renameSync 换目录:报 deleted 且换入的目录被删 |
| 同上 | 隔离区创建失败必须 delete-incomplete |
恢复旧的 mkdir().catch() 后本测试变红 |
test/select-scope.test.mjs |
「可选范围」的唯一定义 | 含一条源码级断言,防止新增入口又直接读 section.rows |
升级
dsh plugin --profile web remove @whybebabo/dsh-better-workspace
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.2或直接从 tag 安装:
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.2分组数据保存在 <DSH_HOME>/dsh-better-workspace/topics.v1.json,升级不会动它。第一次打开面板时归位修复会自动跑一次;什么都不需要做。
English
Adopts sessions that fell into Ungrouped because the host never accounted them (a workspace deleted and re-created, a crash between the header write and attachSession), through the host's own workspace.attachSession — which re-validates cwd, refuses a mismatch, and prepends. Subagents, archived and already-accounted sessions are never touched.
Select all now has exactly one definition (model.selectableIds): the group checkbox, the topic menu and the workspace menu used to derive their own sets and gave 60 / 69 / 70 for the same 70-row bucket, so a batch permanent delete could target rows that were never rendered. Held-back rows are reported instead, and the reveal state moved into prefs so the menus can see it.
Delete no longer reports a removal that did not happen: a failed quarantine mkdir used to be swallowed, its rename failed with ENOENT, and that ENOENT was read as "already gone" — a success that dropped the surviving child's topic/archive records. It now fails with delete-incomplete, ENOENT only counts when the source is really absent, and children are proven gone before their records are cleaned.
Also: implicit new sessions stay in the default group instead of being filed into the newest topic (which put them at the bottom of the workspace); orderBy is a permutation of the ids it names, so a session created after a drag keeps the host's position; Windows path comparison folds case and separators; batch targets are pruned to rows that still exist; npm run check loads the real module graph.
206 tests, up from 196. The new guards were mutation-checked — the cross-process lock and the pre-removal re-check can each be deleted while the old suites stayed green.
v0.1.1 — 修复 Web 启动失败
修复 Web 启动失败 / Fix: Web boot failure
0.1.0 会把整个 Web UI 卡在 Failed to load plugins。本版修复该问题。
根因
DSH 的 client-modules 加载器按 package.json 的 name 定位插件的浏览器端 bundle:先按包名建图,再在 bundle 执行后检查 __ModuleLoader__.load({ id }) 注册的 id 是否与包名一致。
改名到 @whybebabo/dsh-better-workspace 时,client/build.mjs 里硬编码的 id 没跟着改:
| 值 | |
|---|---|
| 加载器期望(包名) | @whybebabo/dsh-better-workspace |
| bundle 实际注册 | dsh-better-workspace |
旧的无 scope 包名下两者恰好相等,所以从未暴露;改名后不可能再相等。于是加载器报 loaded without registering,整条 entry 启动失败,Web UI 卡在 Failed to load plugins。Desktop 与 Web 共用同一份客户端产物,同样受影响。
修复
client/build.mjs改为从package.json读取注册 id,不再硬编码——换包名不可能再漂移;- 同一 id 通过 esbuild
define注入客户端源码,<style>标签与加载器的 key 保持一致; - 新增
test/client-registration.test.mjs锁住该约定(对 0.1.0 的坏产物会失败):注册 id 必须等于包名、只注册一次、样式 id 一致。
插件 row id(cordis.patch.yml 里的 dsh-better-workspace)故意保持不变:它命名的是 sidecar 目录 <DSH_HOME>/dsh-better-workspace/,改动会让既有分组数据失联。只有客户端模块 id 需要跟包名走。
升级
dsh plugin --profile web remove @whybebabo/dsh-better-workspace
dsh plugin --profile web add @whybebabo/dsh-better-workspace@0.1.1或直接从 tag 安装:
dsh plugin --profile web add github:whybebabo/dsh-better-workspace#v0.1.1分组数据保存在 <DSH_HOME>/dsh-better-workspace/topics.v1.json,升级不会动它。
说明
- 0.1.0 已从 npm 与 GitHub 撤回(其 tarball 会因注册 id 不匹配而无法启动),版本号因此前进到 0.1.1;
npm test159 项全绿。
0.1.0 broke the whole Web UI with Failed to load plugins. This release fixes it.
The client-modules loader addresses a plugin's browser bundle by the package.json name of its row, but the bundle still registered the pre-rename short id, so the loader reported loaded without registering and aborted boot. The id is now read from package.json instead of hardcoded, and test/client-registration.test.mjs pins the contract. The host row id (which names the sidecar directory) is unchanged on purpose, so existing topic data is unaffected.