summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/agent/agent.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-22 20:54:19 +0900
committerAdam Malczewski <[email protected]>2026-05-22 20:54:19 +0900
commitc47346cc6237044ecb60ff22c4011d89744af581 (patch)
tree2359a25e687e1290ba5180fd60eae83b03b53a23 /packages/core/src/agent/agent.ts
parent288b21cec98421fda57028a0c8c9d835cfbb14b0 (diff)
downloaddispatch-c47346cc6237044ecb60ff22c4011d89744af581.tar.gz
dispatch-c47346cc6237044ecb60ff22c4011d89744af581.zip
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
Diffstat (limited to 'packages/core/src/agent/agent.ts')
-rw-r--r--packages/core/src/agent/agent.ts76
1 files changed, 55 insertions, 21 deletions
diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts
index c2c5880..b4c0eec 100644
--- a/packages/core/src/agent/agent.ts
+++ b/packages/core/src/agent/agent.ts
@@ -11,6 +11,7 @@ import type {
AgentEvent,
AgentStatus,
ChatMessage,
+ QueueCallbacks,
ToolCall,
ToolResult,
} from "../types/index.js";
@@ -72,9 +73,11 @@ export class Agent {
messages: ChatMessage[] = [];
private config: AgentConfig;
+ private queueCallbacks?: QueueCallbacks;
- constructor(config: AgentConfig) {
+ constructor(config: AgentConfig, queueCallbacks?: QueueCallbacks) {
this.config = config;
+ this.queueCallbacks = queueCallbacks;
}
private async executeToolWithStreaming(
@@ -185,11 +188,12 @@ export class Agent {
}
try {
- const execPromise = tool.execute(tc.arguments, {
- onOutput: (data: string, stream: "stdout" | "stderr") => {
- shellOutputQueue.push({ data, stream });
- },
- });
+ const execPromise = tool.execute(tc.arguments, {
+ onOutput: (data: string, stream: "stdout" | "stderr") => {
+ shellOutputQueue.push({ data, stream });
+ },
+ queueCallbacks: this.queueCallbacks,
+ });
const rawResult = await execPromise;
return {
@@ -401,15 +405,30 @@ export class Agent {
}
}
- // Drain any remaining shell output emitted before we read the result
- while (shellOutputQueue.length > 0) {
- const item = shellOutputQueue.shift()!;
- yield { type: "shell-output", data: item.data, stream: item.stream };
+ // Drain any remaining shell output emitted before we read the result
+ while (shellOutputQueue.length > 0) {
+ const item = shellOutputQueue.shift()!;
+ yield { type: "shell-output", data: item.data, stream: item.stream };
+ }
+
+ // Check for queued user messages and append them to the tool result
+ let finalToolResult = toolResult;
+ if (this.queueCallbacks) {
+ const queuedMsgs = this.queueCallbacks.dequeueMessages();
+ if (queuedMsgs.length > 0) {
+ const userMessages = queuedMsgs
+ .map((m) => m.message)
+ .join("\n---\n");
+ finalToolResult = {
+ ...toolResult,
+ result: `${toolResult.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}`,
+ };
}
+ }
- stepToolResults.push(toolResult);
- allToolResults.push(toolResult);
- yield { type: "tool-result", toolResult };
+ stepToolResults.push(finalToolResult);
+ allToolResults.push(finalToolResult);
+ yield { type: "tool-result", toolResult: finalToolResult };
}
// Add tool results back to step messages so LLM can see them
@@ -431,14 +450,29 @@ export class Agent {
}
}
- const assistantMessage: ChatMessage = {
- role: "assistant",
- content: finalText,
- toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
- toolResults: allToolResults.length > 0 ? allToolResults : undefined,
- };
- this.messages.push(assistantMessage);
- yield { type: "done", message: assistantMessage };
+ const assistantMessage: ChatMessage = {
+ role: "assistant",
+ content: finalText,
+ toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
+ toolResults: allToolResults.length > 0 ? allToolResults : undefined,
+ };
+ this.messages.push(assistantMessage);
+
+ // Drain any remaining queued messages that arrived after the last tool call
+ if (this.queueCallbacks) {
+ const remaining = this.queueCallbacks.dequeueMessages();
+ if (remaining.length > 0) {
+ // These messages arrived too late to be injected into a tool result.
+ // Append them as a user message to the conversation so they're not lost.
+ const userMessages = remaining.map(m => m.message).join("\n---\n");
+ this.messages.push({
+ role: "user",
+ content: userMessages,
+ });
+ }
+ }
+
+ yield { type: "done", message: assistantMessage };
} catch (err) {
const errorMsg = formatError(err, this.config);
yield { type: "error", error: errorMsg };