fix(files): open file paths agents print, from the terminal and the chat

A path an agent prints was already underlined in the terminal, but clicking
one opened the preview overlay on "File not found": file-content/file-raw
resolve against the session workingDir and refuse anything outside it, and the
paths agents print most (a /tmp capture, Claude's own scratchpad, another
checkout) are outside it by definition. In the response viewer those paths were
not links at all.

- openFilePreview() detects an out-of-workspace path and registers it through
  POST /api/sessions/:id/attachments first, rendering by attachment id. That is
  the surface built for live external files, so the server-side guard is
  unchanged: secret trees blocked, symlinks resolved, extension allowlist. The
  workspace routes keep refusing escapes exactly as before.
- New optional `notify` field on that route. `notify: false` suppresses only the
  attachment:detected broadcast, so a click does not also pop a card announcing
  the file already filling the screen. Default stays true for the CLI and
  publish callers.
- _linkifyFilePaths() links paths in rendered response-viewer markdown. It walks
  text nodes and builds anchors with DOM APIs (the source is model output; never
  a string rebuild of sanitized markup), skips subtrees already inside an <a>,
  and keeps the message text byte-identical so copy-code is unaffected.
- One path pattern in constants.js now feeds both the xterm link provider and
  the chat linkifier, a fresh instance per call since lastIndex is per-object
  state. It picks up /Users and /mnt roots (nothing was clickable on macOS or
  WSL), plus docx/pptx and video/audio extensions.
- .file-preview-overlay moves to z-index 5100, above the response viewer at
  5000. At its old 2000 a path clicked in the chat opened the overlay behind the
  panel it was launched from.

Verified end to end on an isolated instance, desktop and phone viewport: real
clicks in the terminal and the chat both render the image, external md and pdf
render, /etc/hosts is still refused, workspace previews unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-16 17:03:42 +02:00
parent 869a507482
commit 4e2c1b9989
11 changed files with 456 additions and 24 deletions
+16 -1
View File
@@ -9855,13 +9855,18 @@ kbd {
/* ========== File Preview Overlay ========== */
/* Above the response viewer (5000) and its backdrop (4999): a file path in the
chat opens this overlay, and at the old 2000 it rendered BEHIND the panel it
was launched from — the click looked dead. Same relationship the path picker
and its preview already have (10020 / 10030). Still below the toast and
picker band (10000+), so a "Saved" toast keeps landing on top. */
.file-preview-overlay {
position: fixed;
inset: 0;
background: var(--modal-backdrop);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
z-index: 2000;
z-index: 5100;
display: none;
align-items: center;
justify-content: center;
@@ -12413,6 +12418,16 @@ kbd {
border-bottom-color: var(--accent);
}
/* File paths linkified out of the message text. Monospace so a path still reads
as a path in prose, and break-all because these are long and the viewer is
narrow on a phone. Colour/underline come from the .rv-text a rule above. */
.rv-text a.rv-path {
font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', Menlo, Monaco, monospace;
font-size: 0.92em;
word-break: break-all;
cursor: pointer;
}
/* Tables — scroll wrapper keeps table proper while allowing horizontal overflow */
.rv-table-wrap {
margin: 1em 0;