Compare commits

...

2 Commits

Author SHA1 Message Date
Nicholas Tindle
e17749b72c Merge branch 'dev' into claude/debug-upload-blocked-RFGA7 2026-02-19 10:14:38 -06:00
Claude
0697f07f51 fix(frontend): resolve workspace:// URLs in regular markdown links
resolveWorkspaceUrls() only handled image syntax ![alt](workspace://id)
but not regular link syntax [text](workspace://id). When the AI returned
file download links using workspace:// protocol, Streamdown's
rehype-harden sanitizer blocked them because "workspace://" is not in
the allowed URL-scheme whitelist, causing "[blocked]" to appear next to
the file name in the chat UI.

Add a second replace pass that catches regular markdown links with
workspace:// and rewrites them to the /api/proxy download path, matching
the existing image-link handling.

https://claude.ai/code/session_0184TVJJcEoB8wbX9htCnv4b
2026-02-19 15:35:44 +00:00

View File

@@ -33,12 +33,15 @@ import { ViewAgentOutputTool } from "../../tools/ViewAgentOutput/ViewAgentOutput
/**
* Resolve workspace:// URLs in markdown text to proxy download URLs.
* Detects MIME type from the hash fragment (e.g. workspace://id#video/mp4)
* and prefixes the alt text with "video:" so the custom img component can
* render a <video> element instead.
*
* Handles both image syntax `![alt](workspace://id#mime)` and regular link
* syntax `[text](workspace://id)`. For images the MIME type hash fragment is
* inspected so that videos can be rendered with a `<video>` element via the
* custom img component.
*/
function resolveWorkspaceUrls(text: string): string {
return text.replace(
// Handle image links: ![alt](workspace://id#mime)
let resolved = text.replace(
/!\[([^\]]*)\]\(workspace:\/\/([^)#\s]+)(?:#([^)\s]*))?\)/g,
(_match, alt: string, fileId: string, mimeHint?: string) => {
const apiPath = getGetWorkspaceDownloadFileByIdUrl(fileId);
@@ -49,6 +52,21 @@ function resolveWorkspaceUrls(text: string): string {
return `![${alt || "Image"}](${url})`;
},
);
// Handle regular links: [text](workspace://id) — without the leading "!"
// These are blocked by Streamdown's rehype-harden sanitizer because
// "workspace://" is not in the allowed URL-scheme whitelist, which causes
// "[blocked]" to appear next to the link text.
resolved = resolved.replace(
/(?<!!)\[([^\]]*)\]\(workspace:\/\/([^)#\s]+)(?:#([^)\s]*))?\)/g,
(_match, linkText: string, fileId: string) => {
const apiPath = getGetWorkspaceDownloadFileByIdUrl(fileId);
const url = `/api/proxy${apiPath}`;
return `[${linkText || "Download file"}](${url})`;
},
);
return resolved;
}
/**