From f689fc7f755d13cae8d2c0a4237822b142a002a9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:35 -0500 Subject: [PATCH] tui: convert handoff from text command to dedicated mode with slash command --- .../cli/cmd/tui/component/prompt/index.tsx | 95 +++++++++++++------ 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 44dfa9bf29..495a20b5e7 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -119,7 +119,7 @@ export function Prompt(props: PromptProps) { const [store, setStore] = createStore<{ prompt: PromptInfo - mode: "normal" | "shell" + mode: "normal" | "shell" | "handoff" extmarkToPartIndex: Map interrupt: number placeholder: number @@ -338,6 +338,20 @@ export function Prompt(props: PromptProps) { )) }, }, + { + title: "Handoff", + value: "prompt.handoff", + disabled: props.sessionID === undefined, + category: "Prompt", + slash: { + name: "handoff", + }, + onSelect: () => { + input.clear() + setStore("mode", "handoff") + setStore("prompt", { input: "", parts: [] }) + }, + }, ] }) @@ -515,17 +529,45 @@ export function Prompt(props: PromptProps) { async function submit() { if (props.disabled) return if (autocomplete?.visible) return + const selectedModel = local.model.current() + if (!selectedModel) { + promptModelWarning() + return + } + + if (store.mode === "handoff") { + const result = await sdk.client.session.handoff({ + sessionID: props.sessionID!, + goal: store.prompt.input, + model: { + providerID: selectedModel.providerID, + modelID: selectedModel.modelID, + }, + }) + if (result.data) { + route.navigate({ + type: "home", + initialPrompt: { + input: result.data.text, + parts: + result.data.files.map((file) => ({ + type: "file", + url: file, + filename: file, + mime: "text/plain", + })) ?? [], + }, + }) + } + return + } + if (!store.prompt.input) return const trimmed = store.prompt.input.trim() if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") { exit() return } - const selectedModel = local.model.current() - if (!selectedModel) { - promptModelWarning() - return - } const sessionID = props.sessionID ? props.sessionID : await (async () => { @@ -569,27 +611,6 @@ export function Prompt(props: PromptProps) { command: inputText, }) setStore("mode", "normal") - } else if (inputText.startsWith("/handoff ")) { - // Handle handoff command specially - call endpoint and replace prompt - const goal = inputText.slice(9).trim() // Remove "/handoff " prefix - if (goal) { - const result = await sdk.client.session.handoff({ - sessionID, - goal, - model: { - providerID: selectedModel.providerID, - modelID: selectedModel.modelID, - }, - }) - if (result.data) { - // Replace prompt with the handoff text - const handoffText = result.data.text - input.setText(handoffText) - setStore("prompt", { input: handoffText, parts: [] }) - // Don't submit yet - let user review and submit manually - return - } - } } else if ( inputText.startsWith("/") && iife(() => { @@ -747,6 +768,7 @@ export function Prompt(props: PromptProps) { const highlight = createMemo(() => { if (keybind.leader) return theme.border if (store.mode === "shell") return theme.primary + if (store.mode === "handoff") return theme.warning return local.agent.color(local.agent.current().name) }) @@ -818,7 +840,11 @@ export function Prompt(props: PromptProps) { flexGrow={1} >