Skip to content

feat: add clickable file paths to agent tool outputs and inline code#13465

Open
DeJeune wants to merge 3 commits intomainfrom
DeJeune/clickable-file-paths
Open

feat: add clickable file paths to agent tool outputs and inline code#13465
DeJeune wants to merge 3 commits intomainfrom
DeJeune/clickable-file-paths

Conversation

@DeJeune
Copy link
Collaborator

@DeJeune DeJeune commented Mar 14, 2026

What this PR does

Before this PR:
File paths displayed in agent tool outputs (Edit, Write, Read, Glob, Grep, MultiEdit, NotebookEdit) and inline code blocks were plain, non-interactive text.

After this PR:
File paths are clickable — clicking opens the file directly. An ellipsis dropdown menu provides additional actions: "Reveal in Finder" and "Open in Editor" (VS Code, Cursor, Zed).

Why we need it and why it was done in this way

Clickable file paths improve developer workflow by allowing quick navigation to files referenced in agent tool outputs without manual copy-paste.

The following tradeoffs were made:

  • A shared ClickableFilePath component is used across all agent tools for consistency.
  • Inline code file path detection uses a conservative regex (/^\/[\w.-]+(?:\/[\w.-]+)+$/) to avoid false positives on non-path inline code. This means some edge-case paths (e.g., with spaces or @) won't be detected in inline code, but tool-output paths are always clickable since they come from structured input.

The following alternatives were considered:

  • Detecting file paths via a more permissive regex — rejected due to high false-positive risk on inline code snippets.

Breaking changes

None.

Special notes for your reviewer

  • The ClickableFilePath component reuses existing useExternalApps hook and window.api.file IPC APIs.
  • i18n keys added for open_file, open_file_error, file_not_found, and reveal_in_finder across all locale files.
  • Review fix: hardcoded 'Finder' string on macOS was replaced with t() for proper i18n compliance.
  • Review fix: test mocks for openPath/showInFolder now return Promises to avoid TypeError on .catch().

Checklist

Release note

Add clickable file paths in agent tool outputs — click to open files, with dropdown to reveal in Finder or open in external editors (VS Code, Cursor, Zed).

DeJeune and others added 2 commits March 14, 2026 20:01
Make file paths in agent tool outputs (Edit, Write, Read, Glob, Grep,
MultiEdit, NotebookEdit) and inline code blocks interactive. Clicking
opens the file; a dropdown menu offers "Reveal in Finder" and "Open in
Editor" (VS Code, Cursor, Zed) integration.

- Add ClickableFilePath component with editor dropdown
- Detect absolute file paths in inline code blocks
- Add i18n keys for file operations across all locales
- Add unit tests for ClickableFilePath and CodeBlock detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
@EurFelux EurFelux self-requested a review March 14, 2026 12:11
Copy link
Collaborator

@EurFelux EurFelux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice feature! The clickable file paths across all agent tool outputs are a solid developer workflow improvement. The implementation is clean and well-tested. A few observations:

Key items:

  1. Code duplicationgetEditorIcon and the openInEditor URL construction pattern are duplicated from OpenExternalAppButton.tsx. Consider extracting into a shared utility.
  2. GrepTool regex/^(\/[^:]+)(:.*)?$/ is quite permissive and could match non-path lines. A tighter pattern would reduce false positives.
  3. NotebookEditTool fallback — Minor inconsistency in the ternary fallback vs other tools in this PR.
  4. Accessibility — The clickable <span> could benefit from keyboard navigation support (tabIndex, onKeyDown).

Overall, this looks good! The conservative inline code path regex is a sensible tradeoff, and the test coverage is appreciated.

Comment on lines +15 to +23
const getEditorIcon = (app: ExternalAppInfo) => {
switch (app.id) {
case 'vscode':
return <VSCodeIcon className="size-4" />
case 'cursor':
return <CursorIcon className="size-4" />
case 'zed':
return <ZedIcon className="size-4" />
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code duplication: getEditorIcon is duplicated here and in OpenExternalAppButton.tsx (line 12-21). Consider extracting it into a shared utility, e.g., under @renderer/components/Icons/ or a shared hooks module.

This also applies to the openInEditor logic pattern (lines 35-41) which shares the same URL construction approach as OpenExternalAppButton.tsx (lines 40-54), but without the error handling (logger.error + window.toast.error) that the existing implementation has for unsupported editors.

<div>{truncatedOutput}</div>
<div>
{truncatedOutput?.split('\n').map((line, i) => {
const match = line.match(/^(\/[^:]+)(:.*)?$/)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex over-matching: The pattern /^(\/[^:]+)(:.*)?$/ will match any line starting with /, which could include error messages like /usr/bin/bash: No such file or directory.

Also, for content output mode, grep lines look like /path/to/file:42:matched content — the (:.*)? capture will grab :42:matched content as a single string, losing the structure. This is probably fine for display purposes, but worth noting.

A slightly tighter pattern like /^(\/[\w./@-]+[^:])(:.*)?$/ could reduce false positives while still matching most real paths.

<ToolHeader toolName={AgentToolsType.NotebookEdit} variant="collapse-label" showStatus={false} />
<Tag color="blue">{input?.notebook_path}</Tag>
<Tag color="blue">
{input?.notebook_path ? <ClickableFilePath path={input.notebook_path} /> : input?.notebook_path}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant fallback: The ternary fallback input?.notebook_path (when falsy) will render undefined, which is harmless but semantically odd. Other tools in this PR (e.g., EditTool, WriteTool) use undefined explicitly as the fallback:

// Consistent with other tools:
{input?.notebook_path ? <ClickableFilePath path={input.notebook_path} /> : undefined}

nit: Minor inconsistency, but would be good to align with the pattern used in sibling components.

Comment on lines +88 to +93
<span
onClick={handleOpen}
className="cursor-pointer hover:underline"
style={{ color: 'var(--color-link)', wordBreak: 'break-all' }}>
{displayName ?? path}
</span>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessibility: This clickable <span> lacks keyboard support. Users who navigate with keyboard (Tab + Enter) won't be able to open files. Consider adding:

<span
  role="link"
  tabIndex={0}
  onClick={handleOpen}
  onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleOpen(e as any) }}
  className="cursor-pointer hover:underline"
  style={{ color: 'var(--color-link)', wordBreak: 'break-all' }}>

Or alternatively, use a <button> styled as a link. This is a nit for now — just worth keeping in mind for a11y.

@EurFelux
Copy link
Collaborator

EurFelux commented Mar 14, 2026

Note

This issue/comment/review was translated by Claude.

image

This is too inconspicuous.


Original Content image

这个也太不显眼了

- Extract getEditorIcon and buildEditorUrl into shared editorUtils
- Reuse shared utility in both ClickableFilePath and OpenExternalAppButton
- Tighten GrepTool regex to reduce false positives on non-path lines
- Fix NotebookEditTool redundant fallback to use undefined explicitly
- Add keyboard accessibility (role="link", tabIndex, Enter/Space)
- Make dropdown trigger more visible (larger font, hover background)
- Add keyboard accessibility tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants