dsh-linked-workspace 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.2 — 2026-11-11
4
+
5
+ Three-day review round: security, data-integrity and large-directory fixes.
6
+
7
+ - **Document preview XSS (high)**: the preview sanitizer now normalizes control characters, whitespace and zero-width characters before checking a URL scheme, so `jav\tascript:` links (and `vbscript:`, `file:`, `blob:`, `data:text/html`) can no longer run in the GUI. Only `http(s)`, `mailto`, `tel`, relative URLs and bitmap `data:image/*` sources are kept; SVG data URLs stay blocked.
8
+ - **Mirror containment on Windows**: `realpathSync.native` is used for the containment check, so a directory junction can no longer make `.remote` (or a mirrored file) point outside the workspace. Linked `.local`/`.remote` entries that are themselves links are removed as links, never followed.
9
+ - **No silent truncation when copying**: copying a remote file larger than 64 MB, or reading one incompletely, now fails with an explicit error instead of writing a truncated copy. The `linked_workspace` read tool no longer mirrors a truncated payload into `.remote`.
10
+ - **Large local files**: `local.read` reads only the requested head of the file instead of loading the whole thing into memory, so peeking at multi-gigabyte files no longer allocates them (or fails with `ERR_FS_FILE_TOO_LARGE`).
11
+ - **Non-empty directory delete from the tool**: `linked_workspace` `delete` now removes directories recursively, matching the UI, instead of failing on SFTP `rmdir`.
12
+ - **Paged directory listings**: `fs.list` and `local.list` return `limit`/`offset` with `total` and `hasMore` (default 500, max 5000); both trees render a “Load more (N left)” row. A 20,000-entry folder no longer freezes the UI (it used to render all 20,012 rows).
13
+ - **Deep sync trees**: the mirror sync marks a job truncated when it stops at the depth limit, instead of reporting success with missing files.
14
+ - **Accessibility**: visible `:focus-visible` rings for tree rows, inputs, buttons and tabs; `role="treeitem"`/`aria-level`/`aria-selected`/`aria-expanded` on tree rows with ←/→ collapse/expand; the add-workspace dialog focuses its first field, traps Tab, and closes on Escape.
15
+ - **Excel preview honesty**: `read_xlsx` reports `totalRow`/`totalCol`/`truncated`, and the grid notes when it shows only the first 200 rows × 40 columns or the first 20 worksheets.
16
+ - **Build and host fixes**: the client build fails loudly when a `src/parts/*` file is missing; the dead branch in `linked.update` is gone; `HostPool` reuses one in-flight connection per host instead of leaking a duplicate on concurrent first connects.
17
+
18
+ Earlier unreleased work included in this version:
19
+
20
+ - Fixed linked-directory context-menu creation on both the remote and local trees: New folder and New file are direct menu actions, so the scrollable menu no longer clips the creation submenu.
21
+ - CSV, HTML, SVG and other preview formats now create ordinary empty files; only Markdown, PDF, DOCX, XLSX and PPTX use document templates.
22
+ - Creation keeps the name dialog open on failure, displays the error and prevents repeated submissions while pending. New files and folders reject existing names with HTTP 409 instead of replacing or silently reusing them, on both remote and local trees.
23
+
24
+ ## 0.8.1 — 2026-11-09
25
+
26
+ - **Image zoom**: the image preview gains zoom-out/zoom-in buttons, a percentage readout, “Fit to window” and “1:1” controls, plus `Ctrl/Cmd + wheel` zoom.
27
+ - **HTML preview**: `.html` and `.htm` files now render in a sandboxed iframe in the content panel. Scripts are disabled; the status bar notes this.
28
+
3
29
  ## 0.8.0 — 2026-11-09
4
30
 
5
31
  - **Image previews**: PNG/JPG/GIF/WebP/BMP/ICO/AVIF/SVG now render in the content panel for both remote and local files (`media.read` / `local.media`, 24 MB cap).
package/README.en.md CHANGED
@@ -61,7 +61,7 @@ The content panel now edits **Markdown / PDF / DOCX / XLSX / PPTX** files on bot
61
61
  - **Refresh**: reload roots, git status and search.
62
62
  - File-type icons for JS/TS/Python/JSON/Markdown/Shell/YAML/images/archives/config files, etc.
63
63
  - Editor: line numbers, syntax highlighting (layered rendering), theme colors, `Ctrl/Cmd+S` save, conflict prompt (reload/force save), read-only protection for binary and >2 MB files, UTF-8/LF/CRLF/line-count status bar. The embedded content panel hides the path/save header; unsaved changes show a gray dot in the file tab. Read-only tabs render the file content but cannot be edited or saved.
64
- - **Image and CSV preview**: PNG/JPG/GIF/WebP/BMP/ICO/AVIF/SVG render directly in the content panel (up to 24 MB). CSV/TSV opens as a table by default with a Table/Source toggle; the source view remains editable and saveable. Tables are capped at 1000 rows and 80 columns.
64
+ - **Image, CSV and HTML preview**: PNG/JPG/GIF/WebP/BMP/ICO/AVIF/SVG render directly in the content panel (up to 24 MB) with toolbar zoom, `Ctrl/Cmd + wheel` zoom, fit-to-window and 1:1 actual-size controls. CSV/TSV opens as a table by default with a Table/Source toggle; the source view remains editable and saveable. Tables are capped at 1000 rows and 80 columns. HTML/HTM files render in a sandboxed iframe with scripts disabled.
65
65
  - **Shortcuts**: `Ctrl/Cmd+S` saves the current file; `Ctrl/Cmd+W` closes the current tab (with a save/discard/cancel prompt when dirty); `Ctrl+Tab` / `Shift+Ctrl+Tab` cycles tabs; `Ctrl/Cmd+B` toggles the directory panel; `Ctrl/Cmd+Shift+F` opens file search.
66
66
  - **Auto save**: enable it under Settings → Connections → Editor behavior. It saves about one second after you stop typing. When off, use `Ctrl+S`. Closing a dirty file always asks first instead of discarding silently.
67
67
  - Multiple files can be open at once (up to 12 slots). A single click opens a preview tab and replaces the current unpinned preview; double-click pins the tab; clicking the same file focuses its existing tab. Local files use the same internal editor and can be edited and saved.
@@ -119,6 +119,21 @@ Old “Remote directories” tabs are restored after refresh through hidden lega
119
119
 
120
120
  See [CHANGELOG.md](CHANGELOG.md) for the full version history.
121
121
 
122
+ ### 0.8.2
123
+
124
+ Security, data-integrity and large-directory fixes from the three-day review round:
125
+
126
+ - The document-preview sanitizer normalizes control characters, whitespace and zero-width characters before checking a URL scheme, so `jav\tascript:`, `vbscript:`, `file:` and `data:text/html` links no longer run in the GUI; only `http(s)`, `mailto`, `tel`, relative URLs and bitmap `data:image` sources are kept.
127
+ - Windows junctions can no longer escape the `.remote` containment check (`realpathSync.native`); linked `.local`/`.remote` entries are removed as links, never followed.
128
+ - Copying a remote file over 64 MB, or reading one incompletely, now fails loudly instead of writing a truncated copy; the read tool no longer mirrors truncated payloads.
129
+ - `local.read` reads only the requested head instead of loading the whole file into memory.
130
+ - The `linked_workspace` `delete` action removes non-empty directories recursively.
131
+ - Directory listings are paged (500 by default, 5000 max, with `total`/`hasMore`) and both trees render a “Load more” row, so a 20,000-entry folder no longer freezes the UI.
132
+ - Mirror sync marks a job truncated at the depth limit instead of reporting success with files missing.
133
+ - Accessibility: `:focus-visible` rings, `treeitem`/`aria-level`/`aria-expanded` semantics, `←`/`→` collapse and expand, and an add-workspace dialog that focuses its first field, traps Tab and closes on Escape.
134
+ - Excel previews report `totalRow`/`totalCol`/`truncated` and note when the grid shows only the first 200 rows × 40 columns or the first 20 worksheets.
135
+ - The client build fails when a `src/parts/*` file is missing; the dead `linked.update` branch is gone; `HostPool` reuses one in-flight connection per host.
136
+
122
137
  ### 0.7.0
123
138
 
124
139
  Single-click tree navigation with preview tabs and double-click pinning, remote/local terminal buttons with `cd`, VSCode-like shortcuts, unsaved-close prompts, and an auto-save switch under Settings → Connections.
package/README.md CHANGED
@@ -62,7 +62,7 @@
62
62
  - **刷新**:重新拉取根目录、git 状态与搜索。
63
63
  - 不同文件类型使用不同彩色图标(JS/TS/Python/JSON/Markdown/Shell/YAML/图片/压缩包/配置文件等)。
64
64
  - 编辑器:行号、语法高亮(叠层渲染)、主题配色、`Ctrl/Cmd+S` 保存、冲突提示(重新加载/强制保存)、二进制与 >2 MB 文件只读保护、UTF-8/LF/CRLF/行数状态栏;内嵌内容面板不显示文件路径/保存工具条,未保存修改在文件标签前显示灰点。
65
- - **图片与 CSV 预览**:PNG/JPG/GIF/WebP/BMP/ICO/AVIF/SVG 直接在内容面板按图片显示(最大 24 MB);CSV/TSV 默认按表格显示,可在「表格 / 源码」间切换,源码视图仍可编辑保存。超过 1000 行或 80 列时只展示截断后的表格。
65
+ - **图片、CSV 与网页预览**:PNG/JPG/GIF/WebP/BMP/ICO/AVIF/SVG 直接在内容面板按图片显示(最大 24 MB),支持按钮缩放、`Ctrl/Cmd + 滚轮` 缩放、适应窗口和 1:1 原始大小;CSV/TSV 默认按表格显示,可在「表格 / 源码」间切换,源码视图仍可编辑保存。超过 1000 行或 80 列时只展示截断后的表格。HTML/HTM 文件以沙箱 iframe 渲染网页预览(脚本禁用)。
66
66
  - **快捷键**:`Ctrl/Cmd+S` 保存当前文件;`Ctrl/Cmd+W` 关闭当前标签(未保存时提示保存/放弃/取消);`Ctrl+Tab` / `Shift+Ctrl+Tab` 切换标签;`Ctrl/Cmd+B` 显示/隐藏目录面板;`Ctrl/Cmd+Shift+F` 打开文件搜索。
67
67
  - **自动保存**:设置 → 连接 →「编辑器行为」中开启;停止输入约 1 秒后自动保存。关闭时使用 `Ctrl+S` 手动保存;未保存文件关闭前会弹窗,不会静默丢弃。
68
68
  - **多个文件同时打开**:文件内容面板内最多同时打开 12 个标签;单击文件打开预览标签并替换未固定的预览,双击固定标签,重复点击同一文件聚焦原标签;本地文件与远端文件共用内部编辑器,本地文件可编辑保存。
@@ -114,6 +114,21 @@ dsh plugin --profile "$DSH_PROFILE" add "file:$PWD/dsh-linked-workspace"
114
114
 
115
115
  已打开的旧右侧栏「远程目录」标签页会在刷新后自动恢复(插件保留了两个隐藏的旧 tab 类型别名),标签标题也会自动改成「联动工作区目录」,新开的标签使用新的 `dsh-linked-workspace` 类型。
116
116
 
117
+ ### 0.8.2 变化
118
+
119
+ 安全、数据完整性与大目录体验修复(三日评审轮):
120
+
121
+ - 文档预览净化器先归一控制字符/空白/零宽字符再判 scheme,`jav\tascript:`、`vbscript:`、`file:`、`data:text/html` 不再可执行;只保留 http(s)/mailto/tel、相对链接与位图 `data:image`。
122
+ - Windows junction 不再能绕过 `.remote` 包含性校验(改用 `realpathSync.native`);链接目录只删链接本身。
123
+ - 远端复制 >64 MB 或读取不完整时直接报错,不再静默写入截断副本;工具读文件只在未截断时同步到 `.remote`。
124
+ - `local.read` 只读取所需头部,不再把超大文件整体读入内存。
125
+ - 工具 `delete` 可递归删除非空远端目录。
126
+ - 目录列表分页(默认 500、最大 5000,返回 `total`/`hasMore`),树底「加载更多」;2 万项目录不再卡死。
127
+ - 同步深度超限会标记为截断,不再把漏文件当成功。
128
+ - 可访问性:`:focus-visible` 焦点环、树行 `treeitem`/`aria-level`/`aria-expanded`、`←`/`→` 折叠展开、添加工作区弹窗聚焦首字段 + Tab 循环 + Esc 关闭。
129
+ - Excel 预览返回 `totalRow`/`totalCol`/`truncated`,并提示「仅显示前 200 行 × 40 列」「仅显示前 20 个工作表」。
130
+ - 客户端构建缺 `src/parts/*` 文件时直接失败;移除 `linked.update` 死分支;`HostPool` 并发首连复用同一连接。
131
+
117
132
  ### 0.5.11 变化
118
133
 
119
134
  远程目录的只读文件现在能看到代码内容;设置里「打开工作区」会跳转并关闭设置,远程目录「打开」会打开对应远程标签。
package/docs/API.md CHANGED
@@ -58,12 +58,12 @@ relative to the scope root, or absolute inside it.
58
58
  | `workspaces.remove` | `{ id }` | `{}` |
59
59
  | `workspaces.rename` | `{ id, name }` | `{ workspace }` |
60
60
  | `workspaces.test` | `{ id }` | `{ ok, latencyMs?, resolvedPath?, error? }` |
61
- | `fs.list` | `{ workspaceId?/linkedId?, path }` | `{ path, entries: RemoteEntry[] }` |
61
+ | `fs.list` | `{ workspaceId?/linkedId?, path, limit?, offset? }` | `{ path, entries: RemoteEntry[], total, offset, limit, hasMore }` (default 500, max 5000) |
62
62
  | `fs.read` | `{ workspaceId?/linkedId?, path, maxBytes? }` | `FileRead` |
63
63
  | `media.read` | `{ workspaceId?/linkedId?, path }` | `{ path, size, mtime, mime, base64 }` (PNG/JPG/GIF/WebP/BMP/ICO/AVIF/SVG, max 24 MB) |
64
- | `fs.write` | `{ workspaceId?/linkedId?, path, content, expectedMtime? }` | `{ path, size, mtime }` |
64
+ | `fs.write` | `{ workspaceId?/linkedId?, path, content, expectedMtime?, createOnly? }` | `{ path, size, mtime }` (`createOnly: true`: existing target → 409) |
65
65
  | `fs.stat` | `{ workspaceId?/linkedId?, path }` | `{ path, relative, type, size, mtime, mode }` |
66
- | `fs.mkdir` | `{ workspaceId?/linkedId?, path }` | `{}` |
66
+ | `fs.mkdir` | `{ workspaceId?/linkedId?, path, createOnly? }` | `{}` (`createOnly: true`: existing target → 409) |
67
67
  | `fs.rename` | `{ workspaceId?/linkedId?, from, to }` | `{}` |
68
68
  | `fs.move` | `{ workspaceId?/linkedId?, path, targetDir? }` | `{ path }` (same remote tree; collision → 409) |
69
69
  | `fs.delete` | `{ workspaceId?/linkedId?, path }` | `{}` |
@@ -71,7 +71,7 @@ relative to the scope root, or absolute inside it.
71
71
  | `settings.get` | `{}` | `{ editorTheme, editorThemeFamily, editorThemeMode, fileOpenMode, autoSave }` |
72
72
  | `settings.update` | `{ editorTheme?, editorThemeFamily?, editorThemeMode?, fileOpenMode?, autoSave? }` | `{ editorTheme, editorThemeFamily, editorThemeMode, fileOpenMode, autoSave }` |
73
73
  | `local.browse` | `{ path? }` | `{ path, parent, home, entries: [{ name, path }] }` |
74
- | `local.mkdir` | `{ path, name }` | `{ path }` |
74
+ | `local.mkdir` | `{ path, name }` | `{ path }` (existing target → 409) |
75
75
  | `linked.list` | `{}` | `{ links: LinkInfo[] }` (each link carries `running`) |
76
76
  | `linked.create` | `{ hostId, remotePath, localPath, name?, mirror? }` | `{ link, workspaceId: string|null }` |
77
77
  | `linked.update` | `{ id, name?, mirror? }` | `{ link }` |
@@ -79,7 +79,7 @@ relative to the scope root, or absolute inside it.
79
79
  | `linked.sync` | `{ id, maxFiles?, maxBytes? }` | `{ job: SyncJob }` (starts a background job) |
80
80
  | `linked.syncStatus` | `{ id }` | `{ job: SyncJob | null }` |
81
81
  | `linked.syncCancel` | `{ id }` | `{ cancelled: boolean }` |
82
- | `local.list` | `{ path }` | `{ path, entries: [{ name, path, type, size, mtime }] }` |
82
+ | `local.list` | `{ path, limit?, offset? }` | `{ path, entries: [{ name, path, type, size, mtime }], total, offset, limit, hasMore }` |
83
83
  | `local.openExternal` | `{ path }` | `{ opened, path }` |
84
84
  | `terminal.open` | `{ workspaceId?/linkedId?, side: 'remote'\|'local' }` | `{ opened, side, path, host? }` |
85
85
  | `fs.openExternal` | `{ workspaceId?/linkedId?, path }` | `{ opened, localPath, size }` |
@@ -102,7 +102,7 @@ relative to the scope root, or absolute inside it.
102
102
  | `local.move` | `{ path, targetDir?, root? }` | `{ path, type }` (same local tree; collision → 409) |
103
103
  | `local.delete` | `{ path, root? }` | `{}` (recursive) |
104
104
  | `local.copy` | `{ path, targetDir? }` | `{ path, type }` |
105
- | `local.newFile` | `{ path }` | `{ path }` |
105
+ | `local.newFile` | `{ path }` | `{ path }` (existing target → 409) |
106
106
  | `local.chmod` | `{ path, mode }` | `{ path, mode }` |
107
107
  | `local.edit` | `{ path, app? }` | `{ path }` (opens Notepad) |
108
108
 
package/docs/VERIFY.md CHANGED
@@ -89,6 +89,8 @@ Host code (lib/) reloads live with `plugin_manager set_plugin include:linked-wor
89
89
  19. 编辑器:内部内容面板不显示路径/保存头部(Ctrl+S 保存);修改未保存时文件标签名前出现灰色圆点;行号、语法高亮(字符串/注释/关键字有颜色)、主题配色、
90
90
  远端并发修改 → 冲突提示(重新加载/强制保存)、二进制/大文件只读;底部状态栏完整可见(不再被视口裁切)。
91
91
 
92
+ 20. **右键新建回归**:远端目录和本地目录的文件夹均右击后,菜单中直接出现「新建文件夹」「新建文件」(不依赖向右展开的子菜单);分别在根目录和嵌套目录创建文件夹、`.txt`、`.csv`、`.html`、`.svg`、`.png`,应创建空普通文件。`.md`、`.pdf`、`.docx`、`.xlsx`、`.pptx` 仍创建对应模板并打开;重复名称应留在命名对话框并显示错误,不得覆盖已有文件。
93
+
92
94
  ## 联动会话(模型工具)
93
95
 
94
96
  1. 在联动工作区里新建会话,问模型「这个联动工作区的远端根目录是什么」→ 它应调用 `linked_workspace` 的 `list`/`info`。
@@ -123,3 +125,23 @@ Host code (lib/) reloads live with `plugin_manager set_plugin include:linked-wor
123
125
  5. 移动后目录树与内容面板一致;已打开的旧标签关闭,已修改未保存的文件阻止移动并提示先保存。
124
126
  6. 目标目录已有同名文件时提示冲突(409),不覆盖;文件夹不能移动到自身或其子目录。
125
127
  7. 移动选择器不能浏览到当前工作区/本地目录之外,根目录本身不可作为被移动项。
128
+
129
+ ## 图片缩放与网页预览(0.8.1)
130
+
131
+ 1. 打开 PNG/SVG:图片默认适应窗口;点「适应窗口」重新适配,「1:1」恢复原始像素。
132
+ 2. 点 `−` / `+` 时缩放比例按 20% 步进,状态栏百分比同步;`Ctrl/Cmd + 滚轮` 可连续缩放,最小 5%、最大 800%。
133
+ 3. 放大后图片区域出现滚动条,可查看四周;缩小后图片保持居中,不出现拉伸变形。
134
+ 4. 本地与远端各打开一个 `.html`/`.htm`:内容面板以 iframe 渲染页面,状态栏显示 HTML、大小和「网页脚本已禁用」。
135
+ 5. HTML 内联样式与静态标签正常显示;页面内脚本不执行,链接不会跳出 DSH 主界面。
136
+ 6. 读取失败的 HTML(权限/过大/二进制)显示错误提示和重试按钮。
137
+
138
+ ## 安全、分页与可访问性(0.8.2)
139
+
140
+ 1. **预览链接净化**:在本地或远端放一个 Markdown 文件,内容含 `<a href="jav&#9;ascript:document.title='X'">click</a>` 与 `[example](https://example.com)`;预览后点第一个链接:页面不应跳转、标题不变、DSH 界面不被替换;第二个链接仍指向 `https://example.com`。`vbscript:`、`file:`、`data:text/html`、`data:image/svg+xml` 链接同样应被移除。
141
+ 2. **大目录分页**:造一个 20 000 项的目录(`1..20000 | % { New-Item -ItemType File "f$_.txt" }`),在目录树展开它:应只出现 500 行和「加载更多(剩余 19500 项)」,页面不卡顿、可滚动、可点其他文件;点「加载更多」追加 500 行并更新剩余计数,滚到底部后可继续加载。
142
+ 3. **键盘操作**:Tab 到目录树行应看到明显焦点环;聚焦树行时 `←` 收起、`→` 展开目录;读屏应能听到 treeitem 层级与展开状态。
143
+ 4. **添加工作区弹窗**:打开后焦点直接在第一个输入框;连按 Tab 焦点在弹窗内循环,不会跑到背景页面;`Esc` 关闭弹窗。
144
+ 5. **大文件窥视**:打开一个 >2 GB 的本地文件(只读预览):应显示「文件过大,仅显示前 512 KB」而不是报错或长时间卡住;内存不随文件大小增长。
145
+ 6. **复制上限**:在远端复制一个 >64 MB 的文本/二进制文件:应提示「超过 64 MB,无法复制」,目标目录不得出现被截断的副本。
146
+ 7. **非空目录删除(工具)**:让 agent 用 `linked_workspace` 的 `delete` 删除一个含文件的远端目录:应递归删除成功,不再报 SFTP `Failure`。
147
+ 8. **Excel 截断提示**:打开一个 >200 行或 >40 列的 xlsx:表格上方应出现「仅显示前 200 行 × 40 列」;工作表超过 20 个时出现「仅显示前 20 个工作表」。