diff --git a/extensions/browser/browser-profiles.ts b/extensions/browser/browser-profiles.ts index a7a908dc93f..57098127923 100644 --- a/extensions/browser/browser-profiles.ts +++ b/extensions/browser/browser-profiles.ts @@ -1,5 +1,6 @@ export { DEFAULT_AI_SNAPSHOT_MAX_CHARS, + DEFAULT_BROWSER_ACTION_TIMEOUT_MS, DEFAULT_BROWSER_DEFAULT_PROFILE_NAME, DEFAULT_BROWSER_EVALUATE_ENABLED, DEFAULT_OPENCLAW_BROWSER_COLOR, diff --git a/extensions/browser/src/browser/config.ts b/extensions/browser/src/browser/config.ts index f1099117bb5..2ce42664745 100644 --- a/extensions/browser/src/browser/config.ts +++ b/extensions/browser/src/browser/config.ts @@ -35,6 +35,7 @@ import { DEFAULT_UPLOAD_DIR } from "./paths.js"; export { DEFAULT_AI_SNAPSHOT_MAX_CHARS, + DEFAULT_BROWSER_ACTION_TIMEOUT_MS, DEFAULT_BROWSER_DEFAULT_PROFILE_NAME, DEFAULT_BROWSER_EVALUATE_ENABLED, DEFAULT_OPENCLAW_BROWSER_COLOR, diff --git a/src/gateway/server.roles-allowlist-update.test.ts b/src/gateway/server.roles-allowlist-update.test.ts index 837f9428aca..a0c6d6bcf11 100644 --- a/src/gateway/server.roles-allowlist-update.test.ts +++ b/src/gateway/server.roles-allowlist-update.test.ts @@ -263,7 +263,9 @@ describe("gateway update.run", () => { ); const res = await onceMessage(ws, (o) => o.type === "res" && o.id === id); expect(res.ok).toBe(true); - expect(updateMock).toHaveBeenCalledOnce(); + await vi.waitFor(() => { + expect(updateMock).toHaveBeenCalledOnce(); + }, FAST_WAIT_OPTS); } finally { process.off("SIGUSR1", sigusr1); } diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index 2c2a63a8fb0..7dfbcdb9e51 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -128,6 +128,7 @@ const BROWSER_HELPER_EXPORT_PARITY_CONTRACTS: readonly BrowserHelperExportParity extensionPath: "extensions/browser/browser-profiles.ts", expectedExports: [ "DEFAULT_AI_SNAPSHOT_MAX_CHARS", + "DEFAULT_BROWSER_ACTION_TIMEOUT_MS", "DEFAULT_BROWSER_DEFAULT_PROFILE_NAME", "DEFAULT_BROWSER_EVALUATE_ENABLED", "DEFAULT_OPENCLAW_BROWSER_COLOR", diff --git a/ui/src/ui/chat/tool-cards.ts b/ui/src/ui/chat/tool-cards.ts index d142368cc7f..f1844a454dd 100644 --- a/ui/src/ui/chat/tool-cards.ts +++ b/ui/src/ui/chat/tool-cards.ts @@ -303,10 +303,14 @@ export function renderToolPreview( `; } -export function buildSidebarContent(value: string): SidebarContent { +export function buildSidebarContent( + value: string, + options?: { rawText?: string | null }, +): SidebarContent { return { kind: "markdown", content: value, + ...(options?.rawText ? { rawText: options.rawText } : {}), }; } diff --git a/ui/src/ui/sidebar-content.ts b/ui/src/ui/sidebar-content.ts index ed90de26ae0..adad3a23f0b 100644 --- a/ui/src/ui/sidebar-content.ts +++ b/ui/src/ui/sidebar-content.ts @@ -1,6 +1,7 @@ export type MarkdownSidebarContent = { kind: "markdown"; content: string; + rawText?: string | null; }; export type CanvasSidebarContent = { diff --git a/ui/src/ui/views/agents-panels-status-files.ts b/ui/src/ui/views/agents-panels-status-files.ts index 7a8156ad3ee..db7fd5caaee 100644 --- a/ui/src/ui/views/agents-panels-status-files.ts +++ b/ui/src/ui/views/agents-panels-status-files.ts @@ -47,6 +47,28 @@ function getExtensionLabel(fileName: string) { return ext ? `${ext.toUpperCase()} Preview` : "Preview"; } +function formatWorkspaceRelativePath(filePath: string, workspace: string | null | undefined) { + const normalizedPath = filePath.trim(); + const normalizedWorkspace = workspace?.trim(); + if (!normalizedPath) { + return ""; + } + if (normalizedWorkspace && normalizedPath === normalizedWorkspace) { + return "."; + } + if (normalizedWorkspace && normalizedPath.startsWith(`${normalizedWorkspace}/`)) { + return normalizedPath.slice(normalizedWorkspace.length + 1) || "."; + } + const pathParts = normalizedPath.split(/[\\/]+/); + for (let index = pathParts.length - 1; index >= 0; index -= 1) { + const pathPart = pathParts[index]; + if (pathPart) { + return pathPart; + } + } + return normalizedPath; +} + function toDomId(value: string) { const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-"); return normalized.replace(/^-+|-+$/g, "") || "preview"; @@ -413,6 +435,9 @@ export function renderAgentFiles(params: { const draftByteSize = formatBytes(new TextEncoder().encode(draft).length); const draftWordCount = countWords(draft); const draftLineCount = countLines(draft); + const activePathLabel = activeEntry + ? formatWorkspaceRelativePath(activeEntry.path, list?.workspace) + : ""; const previewTitleId = activeEntry ? `agent-file-preview-title-${toDomId(activeEntry.name)}` : ""; const previewStatusLabel = activeEntry?.missing ? "Will Create on Save" @@ -574,7 +599,7 @@ export function renderAgentFiles(params: { ${activeEntry.name}
- ${activeEntry.path} + ${activePathLabel}
diff --git a/ui/src/ui/views/agents.test.ts b/ui/src/ui/views/agents.test.ts index 2032cd39003..1024f19937d 100644 --- a/ui/src/ui/views/agents.test.ts +++ b/ui/src/ui/views/agents.test.ts @@ -220,7 +220,7 @@ describe("renderAgentFiles", () => { expect(container.querySelector(".md-preview-dialog__reader.sidebar-markdown")).not.toBeNull(); expect(container.querySelector(".md-preview-dialog__path")?.textContent?.trim()).toBe( - "/tmp/workspace/USER.md", + "USER.md", ); expect(container.querySelector(".md-preview-dialog__chip strong")?.textContent).toBe( "Saved Preview", diff --git a/ui/src/ui/views/chat.test.ts b/ui/src/ui/views/chat.test.ts index a1acbaf183d..e072a68cb79 100644 --- a/ui/src/ui/views/chat.test.ts +++ b/ui/src/ui/views/chat.test.ts @@ -111,6 +111,42 @@ describe("renderChat", () => { cleanupChatModuleState(); }); + it("keeps markdown raw text toggles idempotent", () => { + const container = document.createElement("div"); + const onOpenSidebar = vi.fn(); + const rawMarkdown = "```ts\nconst value = 1;\n```"; + + render( + renderChat( + createProps({ + sidebarOpen: true, + sidebarContent: { + kind: "markdown", + content: `\`\`\`\n${rawMarkdown}\n\`\`\``, + rawText: rawMarkdown, + }, + stream: null, + streamStartedAt: null, + onCloseSidebar: () => undefined, + onOpenSidebar, + }), + ), + container, + ); + + const rawButton = Array.from(container.querySelectorAll("button")).find( + (button) => button.textContent?.includes("View Raw Text"), + ); + rawButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + + expect(rawButton).not.toBeNull(); + expect(onOpenSidebar).toHaveBeenCalledWith({ + kind: "markdown", + content: `\`\`\`\n${rawMarkdown}\n\`\`\``, + rawText: rawMarkdown, + }); + }); + it("renders configured assistant text avatars in transcript groups", () => { const container = document.createElement("div"); diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index cd21b098086..d248c8fa65a 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -141,6 +141,11 @@ function getPinnedMessages(sessionKey: string): PinnedMessages { ); } +function toPlainTextCodeFence(value: string, language = ""): string { + const fenceHeader = language ? `\`\`\`${language}` : "```"; + return `${fenceHeader}\n${value}\n\`\`\``; +} + function getDeletedMessages(sessionKey: string): DeletedMessages { return getOrCreateSessionCacheValue( deletedMessagesMap, @@ -1129,14 +1134,17 @@ export function renderChat(props: ChatProps) { return; } if (props.sidebarContent.kind === "markdown") { + const rawText = props.sidebarContent.rawText ?? props.sidebarContent.content; props.onOpenSidebar( - buildSidebarContent(`\`\`\`\n${props.sidebarContent.content}\n\`\`\``), + buildSidebarContent(toPlainTextCodeFence(rawText), { rawText }), ); return; } if (props.sidebarContent.rawText?.trim()) { props.onOpenSidebar( - buildSidebarContent(`\`\`\`json\n${props.sidebarContent.rawText}\n\`\`\``), + buildSidebarContent( + toPlainTextCodeFence(props.sidebarContent.rawText, "json"), + ), ); } },