fix(desktop): open external links in system browser (#39820)

This commit is contained in:
Luke Parker 2026-07-31 18:02:11 +10:00 committed by GitHub
commit 2039c90c06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 242 additions and 258 deletions

View file

@ -62,7 +62,6 @@ import { AnimatedCountList } from "./tool-count-summary"
import { ToolStatusTitle } from "./tool-status-title"
import { patchFiles } from "./apply-patch-file"
import { animate } from "motion"
import { useLocation } from "@solidjs/router"
import { attached, inline, kind, typeLabel } from "./message-file"
import { readPartText } from "./message-part-text"
import { SessionProgressIndicatorV2 } from "../v2/components/session-progress-indicator-v2"
@ -583,29 +582,18 @@ function urls(text: string | undefined) {
})
}
function sessionLink(id: string | undefined, path: string, href?: (id: string) => string | undefined) {
if (!id) return
const direct = href?.(id)
if (direct) return direct
const idx = path.indexOf("/session")
if (idx === -1) return
return `${path.slice(0, idx)}/session/${id}`
}
function currentSession(path: string) {
return path.match(/\/session\/([^/?#]+)/)?.[1]
function sessionLink(id: string | undefined, href?: (id: string) => string | undefined) {
if (!id) return undefined
return href?.(id)
}
function taskSession(
input: Record<string, any>,
path: string,
parentID: string | undefined,
sessions: Session[] | undefined,
agents?: readonly { name: string; color?: string }[],
) {
const parentID = currentSession(path)
if (!parentID) return
if (!parentID) return undefined
const description = typeof input.description === "string" ? input.description : ""
const agent = taskAgent(input.subagent_type, agents).name
return (sessions ?? [])
@ -1575,7 +1563,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
})
const taskHref = createMemo(() => {
if (part().tool !== "task") return
return sessionLink(taskId(), useLocation().pathname, data.sessionHref)
return sessionLink(taskId(), data.sessionHref)
})
const taskSubtitle = createMemo(() => {
if (part().tool !== "task") return undefined
@ -1614,6 +1602,14 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
onOpenChange={props.onToolOpenChange ? handleToolOpenChange : undefined}
subtitle={taskSubtitle()}
href={taskHref()}
onSubtitleClick={(event) => {
if (!data.navigateToSession) return
if (event.button !== 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return
const id = taskId()
if (!id) return
event.preventDefault()
data.navigateToSession(id)
}}
/>
)
}}
@ -1994,11 +1990,10 @@ ToolRegistry.register({
render(props) {
const data = useData()
const i18n = useI18n()
const location = useLocation()
const childSessionId = createMemo(() => {
const value = props.metadata.sessionId
if (typeof value === "string" && value) return value
return taskSession(props.input, location.pathname, data.store.session, data.store.agent)
return taskSession(props.input, data.sessionID, data.store.session, data.store.agent)
})
const agent = createMemo(() => taskAgent(props.input.subagent_type, data.store.agent))
const title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
@ -2015,18 +2010,13 @@ ToolRegistry.register({
})
const running = createMemo(() => props.status === "pending" || props.status === "running")
const href = createMemo(() => sessionLink(childSessionId(), location.pathname, data.sessionHref))
const href = createMemo(() => sessionLink(childSessionId(), data.sessionHref))
const clickable = createMemo(() => !!(childSessionId() && (data.navigateToSession || href())))
const open = () => {
const id = childSessionId()
if (!id) return
if (data.navigateToSession) {
data.navigateToSession(id)
return
}
const value = href()
if (value) window.location.assign(value)
data.navigateToSession?.(id)
}
const navigate = (event: MouseEvent) => {

View file

@ -16,6 +16,7 @@ export interface ToolErrorCardProps extends Omit<ComponentProps<typeof Card>, "c
onOpenChange?: (open: boolean) => void
subtitle?: string
href?: string
onSubtitleClick?: (event: MouseEvent) => void
}
export function ToolErrorCard(props: ToolErrorCardProps) {
@ -35,6 +36,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
"onOpenChange",
"subtitle",
"href",
"onSubtitleClick",
])
const setOpen = (value: boolean) => {
if (props.open === undefined) setState("open", value)
@ -113,7 +115,10 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
data-slot="basic-tool-tool-subtitle"
class="clickable subagent-link"
href={split.href!}
onClick={(e) => e.stopPropagation()}
onClick={(event) => {
event.stopPropagation()
split.onSubtitleClick?.(event)
}}
>
{subtitle()}
</a>

View file

@ -47,6 +47,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
init: (props: {
data: Data
directory: string
sessionID?: string
onNavigateToSession?: NavigateToSessionFn
onSessionHref?: SessionHrefFn
}) => {
@ -57,6 +58,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
get directory() {
return props.directory
},
get sessionID() {
return props.sessionID
},
navigateToSession: props.onNavigateToSession,
sessionHref: props.onSessionHref,
}