From 280d7e6f9100aa01d21bfddad15c7fa358e8bbca Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:43:49 -0500 Subject: [PATCH 01/16] core: add handoff agent for session context extraction --- packages/opencode/src/agent/agent.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index e338559be7..1eb095b1c1 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -184,6 +184,18 @@ export namespace Agent { ), prompt: PROMPT_TITLE, }, + handoff: { + name: "handoff", + mode: "primary", + options: {}, + native: true, + hidden: true, + temperature: 0.5, + permission: PermissionNext.fromConfig({ + "*": "allow", + }), + prompt: "none", + }, summary: { name: "summary", mode: "primary", From 84171018f2b2d180b54d268c296f1ea112372472 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:43:51 -0500 Subject: [PATCH 02/16] core: add handoff prompt template for extracting session context --- .../opencode/src/session/prompt/handoff.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/opencode/src/session/prompt/handoff.txt diff --git a/packages/opencode/src/session/prompt/handoff.txt b/packages/opencode/src/session/prompt/handoff.txt new file mode 100644 index 0000000000..d4e7ad745d --- /dev/null +++ b/packages/opencode/src/session/prompt/handoff.txt @@ -0,0 +1,17 @@ +Extract relevant context from the conversation above for continuing this work. Write from my perspective (first person: "I did...", "I told you..."). + +Consider what would be useful to know based on my request below. Questions that might be relevant: + +- What did I just do or implement? +- What instructions did I already give you which are still relevant (e.g. follow patterns in the codebase)? +- What files did I already tell you that's important or that I am working on (and should continue working on)? +- Did I provide a plan or spec that should be included? +- What did I already tell you that's important (certain libraries, patterns, constraints, preferences)? +- What important technical details did I discover (APIs, methods, patterns)? +- What caveats, limitations, or open questions did I find? + +Extract what matters for the specific request below. Don't answer questions that aren't relevant. Pick an appropriate length based on the complexity of the request. + +Focus on capabilities and behavior, not file-by-file changes. Avoid excessive implementation details (variable names, storage keys, constants) unless critical. + +Format: Plain text with bullets. No markdown headers, no bold/italic, no code fences. Use workspace-relative paths for files. From aab2a6df3b53d1850d1486692aac8906ad8cce7e Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:43:53 -0500 Subject: [PATCH 03/16] core: add tool-only output mode to LLM for handoff extraction --- packages/opencode/src/session/llm.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 4be6e2538f..ee4a1df3d2 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -39,6 +39,7 @@ export namespace LLM { small?: boolean tools: Record retries?: number + output?: "tool" } export type StreamOutput = StreamTextResult @@ -215,6 +216,7 @@ export namespace LLM { tools, maxOutputTokens, abortSignal: input.abort, + toolChoice: input.output === "tool" ? "required" : undefined, headers: { ...(input.model.providerID.startsWith("opencode") ? { From 3ec6bff038f767328b4864b63a4c202bada72ae1 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:43:55 -0500 Subject: [PATCH 04/16] core: add handoff session extraction logic with status tracking --- packages/opencode/src/session/handoff.ts | 105 +++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 packages/opencode/src/session/handoff.ts diff --git a/packages/opencode/src/session/handoff.ts b/packages/opencode/src/session/handoff.ts new file mode 100644 index 0000000000..0ad9aa11c6 --- /dev/null +++ b/packages/opencode/src/session/handoff.ts @@ -0,0 +1,105 @@ +import { fn } from "@/util/fn" +import z from "zod" +import { MessageV2 } from "./message-v2" +import { LLM } from "./llm" +import { Agent } from "@/agent/agent" +import { Provider } from "@/provider/provider" +import { iife } from "@/util/iife" +import { Identifier } from "@/id/id" +import PROMPT_HANDOFF from "./prompt/handoff.txt" +import { type Tool } from "ai" +import { SessionStatus } from "./status" +import { defer } from "@/util/defer" + +export namespace SessionHandoff { + const HandoffTool: Tool = { + description: + "A tool to extract relevant information from the thread and select relevant files for another agent to continue the conversation. Use this tool to identify the most important context and files needed.", + inputSchema: z.object({ + text: z.string().describe(PROMPT_HANDOFF), + files: z + .string() + .array() + .describe( + [ + "An array of file or directory paths (workspace-relative) that are relevant to accomplishing the goal.", + "", + 'IMPORTANT: Return as a JSON array of strings, e.g., ["packages/core/src/session/message-v2.ts", "packages/core/src/session/prompt/handoff.txt"]', + "", + "Rules:", + "- Maximum 10 files. Only include the most critical files needed for the task.", + "- You can include directories if multiple files from that directory are needed", + "- Prioritize by importance and relevance. PUT THE MOST IMPORTANT FILES FIRST.", + '- Return workspace-relative paths (e.g., "packages/core/src/session/message-v2.ts")', + "- Do not use absolute paths or invent files", + ].join("\n"), + ), + }), + async execute(_args, _ctx) { + return {} + }, + } + + export const handoff = fn( + z.object({ + sessionID: z.string(), + model: z.object({ providerID: z.string(), modelID: z.string() }), + goal: z.string().optional(), + }), + async (input) => { + SessionStatus.set(input.sessionID, { type: "busy" }) + using _ = defer(() => SessionStatus.set(input.sessionID, { type: "idle" })) + const messages = await MessageV2.filterCompacted(MessageV2.stream(input.sessionID)) + const agent = await Agent.get("handoff") + const model = await iife(async () => { + if (agent.model) return Provider.getModel(agent.model.providerID, agent.model.modelID) + const small = await Provider.getSmallModel(input.model.providerID) + if (small) return small + return Provider.getModel(input.model.providerID, input.model.modelID) + }) + const user = { + info: { + model: { + providerID: model.providerID, + modelID: model.id, + }, + agent: agent.name, + sessionID: input.sessionID, + id: Identifier.ascending("user"), + role: "user", + time: { + created: Date.now(), + }, + } satisfies MessageV2.User, + parts: [ + { + type: "text", + text: PROMPT_HANDOFF + "\n\nMy request:\n" + (input.goal ?? "general summarization"), + id: Identifier.ascending("part"), + sessionID: input.sessionID, + messageID: Identifier.ascending("message"), + }, + ] satisfies MessageV2.TextPart[], + } satisfies MessageV2.WithParts + const abort = new AbortController() + const stream = await LLM.stream({ + agent, + messages: MessageV2.toModelMessages([...messages, user], model), + sessionID: input.sessionID, + abort: abort.signal, + model, + system: [], + small: true, + user: user.info, + output: "tool", + tools: { + handoff: HandoffTool, + }, + }) + + const [result] = await stream.toolCalls + if (!result) throw new Error("Handoff tool did not return a result") + return result.input + }, + ) +} From 3b2550106b45f2f78024fd342377769aaaecea2a Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:01 -0500 Subject: [PATCH 05/16] sdk: regenerate client types for handoff endpoint --- packages/sdk/js/src/v2/gen/sdk.gen.ts | 44 ++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 45 +++++++++++ packages/sdk/openapi.json | 102 ++++++++++++++++++++++++ 3 files changed, 191 insertions(+) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index b757b75350..77ae899be8 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -110,6 +110,8 @@ import type { SessionForkResponses, SessionGetErrors, SessionGetResponses, + SessionHandoffErrors, + SessionHandoffResponses, SessionInitErrors, SessionInitResponses, SessionListResponses, @@ -1766,6 +1768,48 @@ export class Session extends HeyApiClient { ...params, }) } + + /** + * Handoff session + * + * Extract context and relevant files for another agent to continue the conversation. + */ + public handoff( + parameters: { + sessionID: string + directory?: string + model?: { + providerID: string + modelID: string + } + goal?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "body", key: "model" }, + { in: "body", key: "goal" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/handoff", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } } export class Part extends HeyApiClient { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index d72c37a28b..6580121275 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3803,6 +3803,51 @@ export type PermissionRespondResponses = { export type PermissionRespondResponse = PermissionRespondResponses[keyof PermissionRespondResponses] +export type SessionHandoffData = { + body?: { + model: { + providerID: string + modelID: string + } + goal?: string + } + path: { + /** + * Session ID + */ + sessionID: string + } + query?: { + directory?: string + } + url: "/session/{sessionID}/handoff" +} + +export type SessionHandoffErrors = { + /** + * Bad request + */ + 400: BadRequestError + /** + * Not found + */ + 404: NotFoundError +} + +export type SessionHandoffError = SessionHandoffErrors[keyof SessionHandoffErrors] + +export type SessionHandoffResponses = { + /** + * Handoff data extracted + */ + 200: { + text: string + files: Array + } +} + +export type SessionHandoffResponse = SessionHandoffResponses[keyof SessionHandoffResponses] + export type PermissionReplyData = { body?: { reply: "once" | "always" | "reject" diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index f50cc06c10..dc4da08701 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -3297,6 +3297,108 @@ ] } }, + "/session/{sessionID}/handoff": { + "post": { + "operationId": "session.handoff", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "sessionID", + "schema": { + "type": "string" + }, + "required": true, + "description": "Session ID" + } + ], + "summary": "Handoff session", + "description": "Extract context and relevant files for another agent to continue the conversation.", + "responses": { + "200": { + "description": "Handoff data extracted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["text", "files"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "model": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "modelID": { + "type": "string" + } + }, + "required": ["providerID", "modelID"] + }, + "goal": { + "type": "string" + } + }, + "required": ["model"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.session.handoff({\n ...\n})" + } + ] + } + }, "/permission/{requestID}/reply": { "post": { "operationId": "permission.reply", From e563cff034985253486770be387fef2983939db0 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:04 -0500 Subject: [PATCH 06/16] core: add handoff API endpoint for session extraction --- .../opencode/src/server/routes/session.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 82e6f3121b..3dc293e9f1 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -7,6 +7,7 @@ import { MessageV2 } from "../../session/message-v2" import { SessionPrompt } from "../../session/prompt" import { SessionCompaction } from "../../session/compaction" import { SessionRevert } from "../../session/revert" +import { SessionHandoff } from "../../session/handoff" import { SessionStatus } from "@/session/status" import { SessionSummary } from "@/session/summary" import { Todo } from "../../session/todo" @@ -935,5 +936,41 @@ export const SessionRoutes = lazy(() => }) return c.json(true) }, + ) + .post( + "/:sessionID/handoff", + describeRoute({ + summary: "Handoff session", + description: "Extract context and relevant files for another agent to continue the conversation.", + operationId: "session.handoff", + responses: { + 200: { + description: "Handoff data extracted", + content: { + "application/json": { + schema: resolver(z.object({ text: z.string(), files: z.string().array() })), + }, + }, + }, + ...errors(400, 404), + }, + }), + validator( + "param", + z.object({ + sessionID: z.string().meta({ description: "Session ID" }), + }), + ), + validator("json", SessionHandoff.handoff.schema.omit({ sessionID: true })), + async (c) => { + const params = c.req.valid("param") + const body = c.req.valid("json") + const result = await SessionHandoff.handoff({ + sessionID: params.sessionID, + model: body.model, + goal: body.goal, + }) + return c.json(result) + }, ), ) From 0eaa6b5fc8267866b18298457e0f381d1a3ff61c Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:07 -0500 Subject: [PATCH 07/16] tui: add handoff autocomplete suggestion with text command --- .../cli/cmd/tui/component/prompt/autocomplete.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 42cf82b421..05fd6d143d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -355,6 +355,18 @@ export function Autocomplete(props: { const commands = createMemo((): AutocompleteOption[] => { const results: AutocompleteOption[] = [...command.slashes()] + results.push({ + display: "/handoff", + description: "Handoff to another context with a goal", + onSelect: () => { + const newText = "/handoff " + const cursor = props.input().logicalCursor + props.input().deleteRange(0, 0, cursor.row, cursor.col) + props.input().insertText(newText) + props.input().cursorOffset = Bun.stringWidth(newText) + }, + }) + for (const serverCommand of sync.data.command) { if (serverCommand.source === "skill") continue const label = serverCommand.source === "mcp" ? ":mcp" : "" From 4cc91049420b6c84b16ab9ada08a69cd5f03f0b0 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:09 -0500 Subject: [PATCH 08/16] tui: add handoff text command handling in prompt --- .../cli/cmd/tui/component/prompt/index.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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 8576dd5763..44dfa9bf29 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -569,6 +569,27 @@ 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(() => { From 2fef02f48759c0d7545331acb4efacd193b3e982 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:12 -0500 Subject: [PATCH 09/16] tui: fix disabled commands from appearing in slash autocomplete --- packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx index 38dc402758..aa899c19c0 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx @@ -83,6 +83,7 @@ function init() { }, slashes() { return visibleOptions().flatMap((option) => { + if (option.disabled) return [] const slash = option.slash if (!slash) return [] return { From 601e631624745ce5cf49a5500b6b7f9a7a2f839d Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:18 -0500 Subject: [PATCH 10/16] tui: fix route navigation state reconciliation --- packages/opencode/src/cli/cmd/tui/context/route.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index 358461921b..123a606dde 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -1,4 +1,4 @@ -import { createStore } from "solid-js/store" +import { createStore, reconcile } from "solid-js/store" import { createSimpleContext } from "./helper" import type { PromptInfo } from "../component/prompt/history" @@ -32,7 +32,7 @@ export const { use: useRoute, provider: RouteProvider } = createSimpleContext({ }, navigate(route: Route) { console.log("navigate", route) - setStore(route) + setStore(reconcile(route)) }, } }, From 0d365fa613d0162beeff987f0ebf4f6ed4e65e03 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:21 -0500 Subject: [PATCH 11/16] tui: add handoff mode to prompt history types --- packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx index e90503e9f5..2a1ed8b382 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx @@ -9,7 +9,7 @@ import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk/v2" export type PromptInfo = { input: string - mode?: "normal" | "shell" + mode?: "normal" | "shell" | "handoff" parts: ( | Omit | Omit From 9b2fd57e6e0f1fbc7ab803836b3f8ff3cf825888 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:23 -0500 Subject: [PATCH 12/16] tui: remove hardcoded handoff from autocomplete --- .../cli/cmd/tui/component/prompt/autocomplete.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 05fd6d143d..42cf82b421 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -355,18 +355,6 @@ export function Autocomplete(props: { const commands = createMemo((): AutocompleteOption[] => { const results: AutocompleteOption[] = [...command.slashes()] - results.push({ - display: "/handoff", - description: "Handoff to another context with a goal", - onSelect: () => { - const newText = "/handoff " - const cursor = props.input().logicalCursor - props.input().deleteRange(0, 0, cursor.row, cursor.col) - props.input().insertText(newText) - props.input().cursorOffset = Bun.stringWidth(newText) - }, - }) - for (const serverCommand of sync.data.command) { if (serverCommand.source === "skill") continue const label = serverCommand.source === "mcp" ? ":mcp" : "" From f689fc7f755d13cae8d2c0a4237822b142a002a9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Feb 2026 18:44:35 -0500 Subject: [PATCH 13/16] 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} >