Skip to content

fix(media): accept workspace-relative paths in /sidebar/file and the HTML preview (#618) - #622

Open
yanzhaohui1999 wants to merge 3 commits into
omdsh-dev:mainfrom
yanzhaohui1999:fix/618-media-relative-path
Open

fix(media): accept workspace-relative paths in /sidebar/file and the HTML preview (#618)#622
yanzhaohui1999 wants to merge 3 commits into
omdsh-dev:mainfrom
yanzhaohui1999:fix/618-media-relative-path

Conversation

@yanzhaohui1999

@yanzhaohui1999 yanzhaohui1999 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

问题(真机抓包,已复现)

v0.19.0 迁到 DSH 原生右侧栏后,同一个文件从两种入口打开,结果不同

入口 请求里的 path=(Chromium Network,过滤 sidebar/file 结果
聊天正文里的蓝色文件链接 .sidebar-route-demo/demo.svg(相对,无前导 / 破图(路由 400 "... is not an absolute path"
同一回合「本轮文件改动」里的 chip /Users/y/workspace/dsh-better-sidebar/.sidebar-route-demo/demo.svg(绝对) 正常渲染

两条请求的 sessionId / cwd 完全相同,唯一差异就是路径拼法。

受影响的是全部靠 mediaUrl 取字节的渲染:图片、PDF、二进制下载、.html 渲染模式(走 /sidebar/html);markdown 预览里的本地图片走同一条 mediaUrl 通道(同因,未单独抓包)。文本 / 代码 / markdown 正文不受影响。

已跑通的最小复现

  1. 让模型 write 一个 .svg(文本可写 → 进 produced → 收尾消息里出现蓝色行内代码链接;.svg 又落在 image viewer 的扩展名里 → 必走 mediaUrl);
  2. 点那条蓝链接 → 破图,Network 里 sidebar/filepath=相对拼法;
  3. 点同一回合「本轮文件改动」里同一文件的 chip → 正常渲染path=绝对拼法。

更正 #618 的复现步骤:原文「从文件树(或聊天里的文件链接)点开该图片」中,文件树那半不成立 —— 文件树 / 产物 chip 播的是绝对拼法(上面第 3 步即反例)。issue 里的 curl 仍然成立:

GET /sidebar/file?…&path=chart.png           → 400 "chart.png" is not an absolute path
GET /sidebar/file?…&path=/home/me/chart.png  → 200 image/png
GET /sidebar/html/<sid>/chart.html           → 400 cannot resolve target "/chart.html"

根因:播地址的一侧与收地址的一侧,对同一份地址语法的假定不一致

# 位置 事实
1 DSH ui-chatopenFile 注入 fileAddressFor(sessionId, cwd, path) session cwd → 工作区内的文件写成相对拼法(util/workspace-path:路径在 cwd 之下 → sessionFileAddress(sessionId, 相对路径))。聊天里一切文件打开(工具行 / 产物行 / 正文提及 / 行内代码路径)都经这里 —— 抓包第 1 行就是它的产物
2 src/client/service.ts:708 插件自己开文件用 scope?.cwd 建地址,而 openSidebarFile()src/client/intercept.tsx不传 scopecwd === undefinedfileAddressFor 保留绝对拼法 —— 抓包第 2 行就是它的产物,也是「从文件树点开不复现」的原因
3 src/client/native/index.ts fileParamsOf()address.path 原样交给 tab → tab.path 就是地址里的拼法(0.18 及以前恒为绝对路径)
4 src/client/EditorHost.tsx + src/fs-tree.ts:129 mediaUrl(scope, path) 原样交给 /sidebar/file,而它是全插件唯一硬性要求绝对路径的文件路由(requireAbsolute);fs.readresolveGitPath() 会把相对路径 join 到 cwd → 坏掉的只有媒体通道
5 src/html-route.ts /sidebar/html 是同因异面,且宿主无法补救:编码器丢掉前导 /、解码器再统一补成绝对路径,相对拼法在该语法里不可表达(chart.html 被当成 /chart.html

归属:触发点在宿主(聊天漏斗按相对拼法播地址)。插件侧容错仍然必须 —— 本仓硬约束不改 DSH 源码;相对拼法本就合法(dsh-resource://file/session/<sid>/<path> 对工作区内文件就是这么拼的);第三方 viewer 与「客户端 cwd 尚未 hydrate」的窗口同样会送相对拼法。可选的上游改法(DSH 无 issue 追踪,只能走 Discussions):聊天漏斗改送绝对拼法,或在地址契约里写明两种拼法消费方都必须接受。

修复

# 决策
1 宿主新增 resolveWorkspaceTarget(cwd, target)src/path-security.ts):绝对目标原样透传,相对目标 join 到 session 权威 cwd;/sidebar/fileensureWorkspacePath 之前套用它 → 与 fs.read 的历史语义对齐,并覆盖「客户端 cwd 尚未 hydrate」与第三方 mediaUrl viewer 这两类兜底场景
2 客户端 mediaUrl / downloadUrl / htmlUrl 统一先经 resolveSidebarPath(cwd, path) 再编码(无 cwd 时原样返回 → 由宿主 join)。图片这条症状靠这里(抓包第 1 行:客户端把相对拼法原样发了出去)
3 /sidebar/html 的地址语法不改:编码器改为可表达相对路径会破坏已发布 URL 的兼容性与相对资源解析语义,因此这一侧只能由调用方解析
4 围栏语义不变:join 之后仍走 ensureWorkspacePath 的 realpath + 包含检查(../ 逃逸、工作区外绝对路径、指向工作区外的软链接照旧 403)

本 PR 覆盖的内容(避免误解)

现象 为什么不在本 PR
正文里点 presented(被 present 交付过)的文件 → 在宿主桌面打开(远程访问时表现为"在 Mac 上弹出来") 那是 DSH chatFileMentions 的交付语义:命中 deliveries 就走 POST /api/present.open,与路径拼法无关。插件要接管需包装 chatFileMentions 服务值 + 设置开关(待办 P2)
回合结束时「交付行」里 presented 文件的按钮消失 插件的 conversation.chat.turnTail 接管目前只认 produced,会把 DSH 原生 presented 那半边顶掉(待办 P1,另见 #390
正文里 bash / 脚本生成的文件路径不可点 它们不进 produced(DSH 只从 write/edit 的工具结果记路径)也不进 presented,除非被 present

验证

  • 真机抓包(DSH 0.1.5-rc.1 + 插件 0.19.0,Chromium):上面那两条请求 —— 相对拼法破图、绝对拼法正常,唯一变量是拼法。
  • 新增 tests/media-relative-path.spec.ts(10 例,假 ctx 挂真实路由 + 临时工作区):相对路径且无 cwd 参数 200、嵌套相对 200、绝对路径不变、?download=1../ 逃逸 403、工作区外绝对 403、缺失文件 400、软链接逃逸 403、客户端 builder 产物直接喂给真实路由 200htmlUrl 产物喂给 /sidebar/html 200。
  • 新增 tests/media-url-relative.spec.ts(7 例):相对路径 join、Windows 反斜杠 cwd、绝对 / 工作区外绝对不变、无 cwd 透传、html 相对与绝对拼法产出同一 URL。
  • 反向验证:把 src/ 三处改动 stash 掉后,这 17 例中 11 例失败(宿主 7 + 客户端 4;其余 6 例断言的是「绝对路径不变 / 无 cwd 透传」这类既有行为)。
  • 门禁:pnpm typecheck ✅、pnpm build ✅(客户端纯度门通过)、改动文件 eslint ✅。
  • 全量 pnpm test2 failed | 124 passed(1325 例中 29 例 posix_spawnp failed,与本机基线(stash 后单跑同两个 pty 文件)失败数完全一致,属沙箱禁 pty 的环境性失败。

文档 / 兼容性

  • docs/plans/2026-09-10-media-relative-path-618.md:根因、两条入口的真机抓包、最小复现、上游可选改法与「不做」清单。
  • docs/external-plugin-guide.md:补充 path 的两种拼法,以及 /sidebar/html 必须由调用方先解析成绝对路径。
  • 对消费插件是放宽(此前相对拼法 400,现在可用),绝对路径行为完全不变。
  • 客户端包由宿主以 cache-control: public, max-age=31536000, immutable 供给 → 修复需硬刷新或重启宿主才在浏览器生效;宿主侧改动重启即生效。

冲突提示

与开放中的 #554fix/absolute-markdown-session-preview)同改 src/client/api.tssrc/index.ts,hunk 不重叠,仅 src/index.ts 顶部 import 区块会小冲突,后合的一方 rebase 一次即可。


Fixes #618。curl 复现与地址语法分析来自 @longisland-icetea 的 issue;其中「从文件树点开」那一步经真机对照不成立,已在正文更正。

…ev#618)

Since the native right-Sidebar migration a file tab is seeded by a
dsh-resource://file/session/<sid>/<path> address, which spells an
in-workspace file RELATIVE to the session root. /sidebar/file was the only
file route that demanded an absolute path (requireAbsolute -> 400), so every
image / PDF / download viewer answered 400 and stayed blank, while the text
channel kept working because fs.read has always joined a relative target
onto the session cwd. /sidebar/html is the same root cause and cannot be
fixed host-side (its encoder drops a leading /, its decoder rebuilds an
absolute path), so the client must resolve before encoding.

Host: resolveWorkspaceTarget() joins a relative target onto the session's
authoritative cwd before ensureWorkspacePath, keeping the realpath fence —
../ escapes, outside absolute paths and outward symlinks still 403.
Client: mediaUrl / downloadUrl / htmlUrl resolve through
resolveSidebarPath(cwd, path) first; an absolute path is unchanged and a
relative one without a cwd is left to the host.

Two new specs (17 cases) drive the real routes through a fake context over a
temporary workspace; stashing the source change fails 15 of them.

Fixes omdsh-dev#618. Root cause, reproduction requests and the client-side HTML
constraint were reported by @longisland-icetea; see
docs/plans/2026-09-10-media-relative-path-618.md.
@yanzhaohui1999
yanzhaohui1999 force-pushed the fix/618-media-relative-path branch from 799ca7a to 064e8b8 Compare September 10, 2026 15:14
@yanzhaohui1999

Copy link
Copy Markdown
Contributor Author

本 PR 正文的三次修订(方便 review,不必重读全文)

版本 时间 正文基于什么 问题
v1 14:46 #618 原文转述(含「从文件树点开」) 复现入口有误 —— 按它复现不出来
v2 15:14 我们自己的代码追踪:只有聊天入口会播相对拼法 当时只有地址层推断,没有真机记录;「从文件树不成立」是推出来的
v3(当前) 16:01 真机抓包:同一文件两条请求,path= 相对 → 破图 / 绝对 → 正常渲染;外加已跑通的最小复现

代码自始至终没有变过git diff 4a37cd0 064e8b8 -- src tests 为空 —— 三次修订只动了 PR 正文与 docs/plans/2026-09-10-media-relative-path-618.md

v3 相对 v2 的实质新增(约 850 字):真机抓包表、已跑通的最小复现、「本 PR 不覆盖的内容」(presented → 宿主桌面 / 交付行被 presented 顶掉 / bash 产物在正文里不可点)。其余约 71% 与 v2 逐字相同。

顺带说明本 PR 的边界:只修「路径拼法」这条线(相对拼法被媒体 / HTML 路由拒绝),不涉及「点开之后去哪」—— presented 文件走宿主桌面是 DSH 的交付语义,另开待办。

longisland-icetea pushed a commit to longisland-icetea/DSH-better-sidebar that referenced this pull request Sep 11, 2026
…a paths

Same fix as the local patch this replaces, reviewed upstream and carrying its
own 17 regression cases (tests/media-relative-path.spec.ts,
tests/media-url-relative.spec.ts) plus the guide and plan updates, so the local
fork stops maintaining a parallel implementation of issue omdsh-dev#618.

Applied verbatim from the pull request (PR omdsh-dev#622, branch
fix/618-media-relative-path). Verified here: typecheck clean, 17/17 tests pass,
and on the live server a relative image path answers 200 while an escaping one
still answers 403.
longisland-icetea pushed a commit to longisland-icetea/DSH-better-sidebar that referenced this pull request Sep 11, 2026
…a paths

Same fix as the local patch this replaces, reviewed upstream and carrying its
own 17 regression cases (tests/media-relative-path.spec.ts,
tests/media-url-relative.spec.ts) plus the guide and plan updates, so the local
fork stops maintaining a parallel implementation of issue omdsh-dev#618.

Applied verbatim from the pull request (PR omdsh-dev#622, branch
fix/618-media-relative-path). Verified here: typecheck clean, 17/17 tests pass,
and on the live server a relative image path answers 200 while an escaping one
still answers 403.
longisland-icetea pushed a commit to longisland-icetea/DSH-better-sidebar that referenced this pull request Sep 11, 2026
…a paths

Same fix as the local patch this replaces, reviewed upstream and carrying its
own 17 regression cases (tests/media-relative-path.spec.ts,
tests/media-url-relative.spec.ts) plus the guide and plan updates, so the local
fork stops maintaining a parallel implementation of issue omdsh-dev#618.

Applied verbatim from the pull request (PR omdsh-dev#622, branch
fix/618-media-relative-path). Verified here: typecheck clean, 17/17 tests pass,
and on the live server a relative image path answers 200 while an escaping one
still answers 403.
@54jayus

54jayus commented Sep 14, 2026

Copy link
Copy Markdown

神医,解决了,谢谢大佬。希望作者速速merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants