From c47346cc6237044ecb60ff22c4011d89744af581 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Fri, 22 May 2026 20:54:19 +0900 Subject: feat: message queue/interrupt system, CORS fix, mobile fixes, chat splitting - Add message queue allowing users to send messages while agent is running - Queue messages are injected into tool results as [USER INTERRUPT] - Retrieve tool interrupted via Promise.race when user message arrives - Queued messages show with 'queued' badge and cancel button - Consumed messages repositioned and chat splits at interrupt point - New assistant message block created after interrupt for clean flow - Add POST /chat/cancel endpoint for cancelling queued messages - Fix CORS to allow any origin (Tailscale/LAN access) - Fix crypto.randomUUID fallback for non-secure contexts (HTTP) - Fix frontend API URL derivation from page hostname - Auto-create DB tab if missing on processMessage (foreign key fix) - Add error logging to processMessage catch block - Fix working directory input sync on agent switch - Fix agent mode button to re-apply agent settings --- packages/core/src/tools/retrieve.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'packages/core/src/tools/retrieve.ts') diff --git a/packages/core/src/tools/retrieve.ts b/packages/core/src/tools/retrieve.ts index 93f4c89..8e14a96 100644 --- a/packages/core/src/tools/retrieve.ts +++ b/packages/core/src/tools/retrieve.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import type { ToolDefinition } from "../types/index.js"; +import type { ToolDefinition, ToolExecuteContext } from "../types/index.js"; export interface RetrieveCallbacks { getResult( @@ -24,11 +24,33 @@ export function createRetrieveTool(callbacks: RetrieveCallbacks): ToolDefinition parameters: z.object({ agent_id: z.string().describe("The agent_id returned by a previous summon call."), }), - execute: async (args: Record): Promise => { + execute: async (args: Record, context?: ToolExecuteContext): Promise => { const agentId = args.agent_id as string; + const queueCallbacks = context?.queueCallbacks; try { - const outcome = await callbacks.getResult(agentId); + let outcome: { status: "done"; result: string } | { status: "error"; error: string }; + + if (queueCallbacks) { + const childPromise = callbacks.getResult(agentId); + const { promise: queuePromise, cancel: cancelQueueWait } = queueCallbacks.waitForQueuedMessage(); + const queueSignal = queuePromise.then(() => "QUEUE_INTERRUPT" as const); + + const raceResult = await Promise.race([childPromise, queueSignal]); + + if (raceResult === "QUEUE_INTERRUPT") { + const queuedMsgs = queueCallbacks.dequeueMessages(); + const userMessages = queuedMsgs.map((m) => m.message).join("\n---\n"); + return `The subagent (agent_id: ${agentId}) has not completed its task yet. You will need to call retrieve with this agent_id again later to get the result.\n\n[USER INTERRUPT]\nThe user has sent you message(s) while you were working. You MUST address these before continuing with your current task:\n\n${userMessages}`; + } + + // Child finished first — clean up the queue listener + cancelQueueWait(); + outcome = raceResult; + } else { + outcome = await callbacks.getResult(agentId); + } + if (outcome.status === "done") { return ["", outcome.result, ""].join("\n"); } -- cgit v1.2.3