Skip to content

feat: open local file links from chat#119

Merged
agegr merged 3 commits into
agegr:mainfrom
zhudatou630:codex/linked-file-viewing
Jul 6, 2026
Merged

feat: open local file links from chat#119
agegr merged 3 commits into
agegr:mainfrom
zhudatou630:codex/linked-file-viewing

Conversation

@zhudatou630

Copy link
Copy Markdown
Contributor

Summary

  • Turn local markdown file links in chat into open-file actions inside the existing file viewer.
  • Pass the source session id to file requests so files referenced by a session can be opened even when they are outside the current project root.
  • Keep file access scoped to known roots or files referenced in the session log.

Why

Agents often mention local file paths in their responses. Opening those links directly in the app avoids manual path copying while keeping the file endpoint constrained.

Validation

  • node --test lib/file-links.test.mjs lib/session-file-references.test.mjs
  • node_modules/.bin/tsc --noEmit
  • npm run lint

@zhudatou630 zhudatou630 marked this pull request as ready for review July 5, 2026 15:14
Copilot AI review requested due to automatic review settings July 5, 2026 15:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds “open local file link” behavior to chat markdown so paths referenced by the agent can be opened directly in the app’s existing file viewer, including (when authorized) files outside the current project root by scoping access to known roots or files explicitly referenced in the session log.

Changes:

  • Add client-side parsing/normalization of local file hrefs and wire “open file” callbacks through chat rendering.
  • Propagate a sourceSessionId into file-viewer requests so /api/files can authorize reads/downloads based on session references.
  • Add session-log reference detection utilities + tests to support server-side authorization checks.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
lib/session-reader.ts Exposes getSessionEntries() for reading a session’s entry list from disk.
lib/session-file-references.ts Adds server helper to authorize a path by checking whether it appears in a session log.
lib/session-file-references-core.ts Implements “exact path reference” detection across session entries.
lib/session-file-references.test.mjs Adds unit tests for exact session-file reference detection.
lib/file-links.ts Adds local-file href parsing/normalization for markdown links (absolute + cwd-relative).
lib/file-links.test.mjs Adds unit tests for resolveLocalFileHref().
components/TabBar.tsx Extends file-tab metadata to include optional sourceSessionId.
components/MessageView.tsx Threads cwd and onOpenFile through message rendering into MarkdownBody.
components/MarkdownBody.tsx Intercepts qualifying local file links and triggers onOpenFile instead of navigation.
components/FileViewer.tsx Adds sessionId query param plumbing to file API requests (read/meta/preview/watch/download).
components/ChatWindow.tsx Supplies message cwd and onOpenFile handler to MessageView.
components/AppShell.tsx Implements “open linked file” behavior and stores sourceSessionId on file tabs.
app/api/files/[...path]/route.ts Allows file reads outside allowed roots when the file is referenced by the provided session id.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +292 to +296
const allowedBySessionReference =
!allowedByRoot &&
type !== "list" &&
await isFilePathReferencedBySession(filePath, sessionId);
if (!allowedByRoot && !allowedBySessionReference) {
Comment on lines +81 to +85
const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {
if (event.defaultPrevented || event.button !== 0) return;
event.preventDefault();
openFile(filePath);
};
Comment on lines +1 to +9
import { getSessionEntries, resolveSessionPath } from "./session-reader";
export { isFilePathReferencedByEntries } from "./session-file-references-core";
import { isFilePathReferencedByEntries } from "./session-file-references-core";

export async function isFilePathReferencedBySession(filePath: string, sessionId: string | null): Promise<boolean> {
if (!sessionId) return false;
try {
const sessionPath = await resolveSessionPath(sessionId);
if (!sessionPath) return false;
Comment thread lib/file-links.ts
Comment on lines +60 to +68
function fileUrlToPath(href: string): string | null {
try {
const url = new URL(href);
if (url.protocol !== "file:") return null;
return safeDecode(url.pathname);
} catch {
return null;
}
}
Comment thread lib/file-links.ts
Comment on lines +82 to +86
if (lowerHref.startsWith("/api/") || lowerHref.startsWith("/_next/")) return null;
if (/^(https?|mailto|tel|data|blob|about):/i.test(normalizedHref)) return null;

if (lowerHref.startsWith("file:")) {
candidate = fileUrlToPath(normalizedHref);
@agegr agegr merged commit deff4cb into agegr:main Jul 6, 2026
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.

3 participants