summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-10 15:28:42 -0600
committerAdam <[email protected]>2026-02-10 15:28:46 -0600
commit50f3e74d0589c8b6120d329ca35dfe74ef94e5e0 (patch)
tree0087762c1fe5e62d87bcfa17cf2a103c6a7eb84f
parent21475a1dfde8286ca918ec6bd638ea1296b34252 (diff)
downloadopencode-50f3e74d0589c8b6120d329ca35dfe74ef94e5e0.tar.gz
opencode-50f3e74d0589c8b6120d329ca35dfe74ef94e5e0.zip
fix(app): task tool rendering
-rw-r--r--packages/app/src/pages/directory-layout.tsx9
-rw-r--r--packages/ui/src/components/message-part.tsx102
-rw-r--r--packages/ui/src/context/data.tsx8
3 files changed, 87 insertions, 32 deletions
diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx
index b2a17b96b..f36bb7ab4 100644
--- a/packages/app/src/pages/directory-layout.tsx
+++ b/packages/app/src/pages/directory-layout.tsx
@@ -54,6 +54,13 @@ export default function Layout(props: ParentProps) {
navigate(`/${params.dir}/session/${sessionID}`)
}
+ const sessionHref = (sessionID: string) => {
+ if (params.dir) return `/${params.dir}/session/${sessionID}`
+ return `/session/${sessionID}`
+ }
+
+ const syncSession = (sessionID: string) => sync.session.sync(sessionID)
+
return (
<DataProvider
data={sync.data}
@@ -62,6 +69,8 @@ export default function Layout(props: ParentProps) {
onQuestionReply={replyToQuestion}
onQuestionReject={rejectQuestion}
onNavigateToSession={navigateToSession}
+ onSessionHref={sessionHref}
+ onSyncSession={syncSession}
>
<LocalProvider>{props.children}</LocalProvider>
</DataProvider>
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 83847a533..3f61b3186 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -877,6 +877,74 @@ ToolRegistry.register({
const data = useData()
const i18n = useI18n()
const childSessionId = () => props.metadata.sessionId as string | undefined
+
+ const href = createMemo(() => {
+ const sessionId = childSessionId()
+ if (!sessionId) return
+
+ const direct = data.sessionHref?.(sessionId)
+ if (direct) return direct
+
+ if (typeof window === "undefined") return
+ const path = window.location.pathname
+ const idx = path.indexOf("/session")
+ if (idx === -1) return
+ return `${path.slice(0, idx)}/session/${sessionId}`
+ })
+
+ createEffect(() => {
+ const sessionId = childSessionId()
+ if (!sessionId) return
+ const sync = data.syncSession
+ if (!sync) return
+ Promise.resolve(sync(sessionId)).catch(() => undefined)
+ })
+
+ const handleLinkClick = (e: MouseEvent) => {
+ const sessionId = childSessionId()
+ const url = href()
+ if (!sessionId || !url) return
+
+ e.stopPropagation()
+
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
+
+ const nav = data.navigateToSession
+ if (!nav || typeof window === "undefined") return
+
+ e.preventDefault()
+ const before = window.location.pathname + window.location.search + window.location.hash
+ nav(sessionId)
+ setTimeout(() => {
+ const after = window.location.pathname + window.location.search + window.location.hash
+ if (after === before) window.location.assign(url)
+ }, 50)
+ }
+
+ const trigger = () => (
+ <div data-slot="basic-tool-tool-info-structured">
+ <div data-slot="basic-tool-tool-info-main">
+ <span data-slot="basic-tool-tool-title" class="capitalize">
+ {i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool })}
+ </span>
+ <Show when={props.input.description}>
+ <Switch>
+ <Match when={href()}>
+ {(url) => (
+ <a data-slot="basic-tool-tool-subtitle" class="clickable" href={url()} onClick={handleLinkClick}>
+ {props.input.description}
+ </a>
+ )}
+ </Match>
+ <Match when={true}>
+ <span data-slot="basic-tool-tool-subtitle">{props.input.description}</span>
+ </Match>
+ </Switch>
+ </Show>
+ </div>
+ </div>
+ )
+
const childToolParts = createMemo(() => {
const sessionId = childSessionId()
if (!sessionId) return []
@@ -924,13 +992,6 @@ ToolRegistry.register({
})
}
- const handleSubtitleClick = () => {
- const sessionId = childSessionId()
- if (sessionId && data.navigateToSession) {
- data.navigateToSession(sessionId)
- }
- }
-
const renderChildToolPart = () => {
const toolData = childToolPart()
if (!toolData) return null
@@ -958,21 +1019,7 @@ ToolRegistry.register({
<Switch>
<Match when={childPermission()}>
<>
- <Show
- when={childToolPart()}
- fallback={
- <BasicTool
- icon="task"
- defaultOpen={true}
- trigger={{
- title: i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }),
- titleClass: "capitalize",
- subtitle: props.input.description,
- }}
- onSubtitleClick={handleSubtitleClick}
- />
- }
- >
+ <Show when={childToolPart()} fallback={<BasicTool icon="task" defaultOpen={true} trigger={trigger()} />}>
{renderChildToolPart()}
</Show>
<div data-component="permission-prompt">
@@ -991,16 +1038,7 @@ ToolRegistry.register({
</>
</Match>
<Match when={true}>
- <BasicTool
- icon="task"
- defaultOpen={true}
- trigger={{
- title: i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }),
- titleClass: "capitalize",
- subtitle: props.input.description,
- }}
- onSubtitleClick={handleSubtitleClick}
- >
+ <BasicTool icon="task" defaultOpen={true} trigger={trigger()}>
<div
ref={autoScroll.scrollRef}
onScroll={autoScroll.handleScroll}
diff --git a/packages/ui/src/context/data.tsx b/packages/ui/src/context/data.tsx
index dcb9adb39..51bffa050 100644
--- a/packages/ui/src/context/data.tsx
+++ b/packages/ui/src/context/data.tsx
@@ -48,6 +48,10 @@ export type QuestionRejectFn = (input: { requestID: string }) => void
export type NavigateToSessionFn = (sessionID: string) => void
+export type SessionHrefFn = (sessionID: string) => string
+
+export type SyncSessionFn = (sessionID: string) => void | Promise<void>
+
export const { use: useData, provider: DataProvider } = createSimpleContext({
name: "Data",
init: (props: {
@@ -57,6 +61,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
onQuestionReply?: QuestionReplyFn
onQuestionReject?: QuestionRejectFn
onNavigateToSession?: NavigateToSessionFn
+ onSessionHref?: SessionHrefFn
+ onSyncSession?: SyncSessionFn
}) => {
return {
get store() {
@@ -69,6 +75,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
replyToQuestion: props.onQuestionReply,
rejectQuestion: props.onQuestionReject,
navigateToSession: props.onNavigateToSession,
+ sessionHref: props.onSessionHref,
+ syncSession: props.onSyncSession,
}
},
})