diff options
| author | Adam <[email protected]> | 2026-02-10 15:28:42 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-02-10 15:28:46 -0600 |
| commit | 50f3e74d0589c8b6120d329ca35dfe74ef94e5e0 (patch) | |
| tree | 0087762c1fe5e62d87bcfa17cf2a103c6a7eb84f | |
| parent | 21475a1dfde8286ca918ec6bd638ea1296b34252 (diff) | |
| download | opencode-50f3e74d0589c8b6120d329ca35dfe74ef94e5e0.tar.gz opencode-50f3e74d0589c8b6120d329ca35dfe74ef94e5e0.zip | |
fix(app): task tool rendering
| -rw-r--r-- | packages/app/src/pages/directory-layout.tsx | 9 | ||||
| -rw-r--r-- | packages/ui/src/components/message-part.tsx | 102 | ||||
| -rw-r--r-- | packages/ui/src/context/data.tsx | 8 |
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, } }, }) |
