dsh-copy-file-path
中文说明 / Chinese README
Adds Copy relative path and Copy absolute path to the delivered-file card menu of the DeepSeek Harness (dsh) web UI.
At the end of a turn, dsh shows the files that turn delivered as cards. Each card has an Open button and a ⋯ menu holding Open in default app and Show in File Explorer / Open containing folder. This plugin appends two more rows to that menu:
| Row | Copies |
|---|
| 复制相对路径 / Copy relative path | the path relative to the session workspace |
| 复制绝对路径 / Copy absolute path | the absolute host path |
A click copies the value and shows a short confirmation pill with what was copied.
⋯ ┌──────────────────────────┐
│ ↗ Open in default app │ ← shipped by dsh-client-ui-deliverables
│ ▤ Show in File Explorer │
│ ⧉ Copy relative path │ ← this plugin
│ ⧉ Copy absolute path │ ← this plugin
└──────────────────────────┘
Install
dsh plugin --profile web add github:Meowrium/dsh-copy-file-path
Then restart dsh web and hard-refresh the page (Ctrl+Shift+R) — the host mounts the bundle at startup and the browser shell rebuilds its client modules.
Why this needs a plugin
Those menu rows are a hard-coded items array inside @deepseek-ai/dsh-client-ui-deliverables, rendered by the @deepseek-ai/dsh-client-ui-primitives Menu (a role="menu" list portalled to document.body). There is no slot, registry or hook for extra rows, and a shadowing plugin would have to reimplement the whole card.
So this plugin post-processes the DOM instead — the same technique as dsh-mermaid: no core patch, no monkey-patching of React internals. It watches for that portal, and while a delivery card's chevron is expanded (aria-expanded="true") it clones the last row, relabels it, gives it a copy icon and owns the click. The injected rows are plain DOM: the click handler stops propagation so the React root never sees the event, and the popup is dismissed through the same outside-pointerdown path a real click elsewhere would use.
Where the two paths come from
Nothing is guessed and nothing is rewritten — every value is taken from what dsh itself already has:
| Value | Source |
|---|
| Absolute path | the card's preview-button title, i.e. exactly what dsh resolves and opens (resolveWorkspacePath(cwd, file.path)). Copied verbatim, separators untouched. |
| Relative path | the path the turn declared to the present tool, recovered from the card's own aria-labels ({path} 的更多文件操作 / More file actions for {path}). If the declaration was itself absolute, it is recomputed against the workspace root. |
| Workspace root | derived from that (absolute, declared) pair — exact by construction. The client session store (sessions.list) is only the fallback, for cards whose labels carry no declared path. |
If a value cannot be established, that row is simply not offered — an unusable path is never copied.
Compatibility
dsh web UI with delivered-file cards. Tested against 0.1.6-alpha.1 on Windows; not verified against older lines (the dsh.client / bundle shape is shared with the 0.1.5 line, but that is untested here).
- No runtime dependencies. The browser half uses the DOM plus the optional
locale and sessions client services: inject is empty, so the plugin still loads — English labels and card-derived workspace root — when either service is absent.
Tests
Both harnesses run without a dsh instance:
node test/paths.test.mjs # 35 assertions on the path helpers
test/fixture.html reproduces the delivery card plus the primitives menu portal (structure read from the shipped 0.1.6-alpha.1 code), loads lib/client.js through a stub module loader and runs 23 checks in a real browser: injection and label language, both copied values, menu dismissal, no event leakage into the React container, a negative control (an unrelated popup gets nothing), idempotence, and full teardown. Open the file in a browser — results render on the page and in document.title.
Limitations
- Only the delivered-file card menu is covered. The "Files changed" chips and the sidebar file tree have no menu of their own, so they are untouched.
- The menu belongs to a shipped component: a future dsh release that changes the card markup may need this plugin updated. The detector degrades by injecting nothing rather than by mislabelling an unrelated menu.
- Copies text only: no file is read, opened or transmitted, and there is no host access at all (the host half is deliberately inert).
Uninstall
dsh plugin --profile web remove dsh-copy-file-path
License
MIT