From 8a2eacf1795751f27cff7ba2cc43b026f8fe1fa2 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Mon, 9 Feb 2026 17:42:53 -0800 Subject: [PATCH] Fix skip and mtb --- .../orchestrator/sse-handlers/handlers.ts | 116 ++++++++++-------- .../sse-handlers/tool-execution.ts | 3 +- apps/sim/stores/panel/copilot/store.ts | 1 + 3 files changed, 71 insertions(+), 49 deletions(-) diff --git a/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts b/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts index 809eb6595..85151381a 100644 --- a/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts +++ b/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts @@ -211,6 +211,24 @@ export const sseHandlers: Record = { options.timeout || STREAM_TIMEOUT_MS, options.abortSignal ) + if (completion?.status === 'background') { + toolCall.status = 'skipped' + toolCall.endTime = Date.now() + markToolComplete( + toolCall.id, + toolCall.name, + 202, + completion.message || 'Tool execution moved to background', + { background: true } + ).catch((err) => { + logger.error('markToolComplete fire-and-forget failed (run tool background)', { + toolCallId: toolCall.id, + error: err instanceof Error ? err.message : String(err), + }) + }) + markToolResultSeen(toolCallId) + return + } const success = completion?.status === 'success' toolCall.status = success ? 'success' : 'error' toolCall.endTime = Date.now() @@ -235,48 +253,40 @@ export const sseHandlers: Record = { if (decision?.status === 'rejected' || decision?.status === 'error') { toolCall.status = 'rejected' toolCall.endTime = Date.now() - await markToolComplete( + // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport + markToolComplete( toolCall.id, toolCall.name, 400, decision.message || 'Tool execution rejected', { skipped: true, reason: 'user_rejected' } - ) - markToolResultSeen(toolCall.id) - await options.onEvent?.({ - type: 'tool_result', - toolCallId: toolCall.id, - data: { - id: toolCall.id, - name: toolCall.name, - success: false, - result: { skipped: true, reason: 'user_rejected' }, - }, + ).catch((err) => { + logger.error('markToolComplete fire-and-forget failed (rejected)', { + toolCallId: toolCall.id, + error: err instanceof Error ? err.message : String(err), + }) }) + markToolResultSeen(toolCall.id) return } if (decision?.status === 'background') { toolCall.status = 'skipped' toolCall.endTime = Date.now() - await markToolComplete( + // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport + markToolComplete( toolCall.id, toolCall.name, 202, decision.message || 'Tool execution moved to background', { background: true } - ) - markToolResultSeen(toolCall.id) - await options.onEvent?.({ - type: 'tool_result', - toolCallId: toolCall.id, - data: { - id: toolCall.id, - name: toolCall.name, - success: true, - result: { background: true }, - }, + ).catch((err) => { + logger.error('markToolComplete fire-and-forget failed (background)', { + toolCallId: toolCall.id, + error: err instanceof Error ? err.message : String(err), + }) }) + markToolResultSeen(toolCall.id) return } } @@ -436,47 +446,39 @@ export const subAgentHandlers: Record = { if (decision?.status === 'rejected' || decision?.status === 'error') { toolCall.status = 'rejected' toolCall.endTime = Date.now() - await markToolComplete( + // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport + markToolComplete( toolCall.id, toolCall.name, 400, decision.message || 'Tool execution rejected', { skipped: true, reason: 'user_rejected' } - ) - markToolResultSeen(toolCall.id) - await options?.onEvent?.({ - type: 'tool_result', - toolCallId: toolCall.id, - data: { - id: toolCall.id, - name: toolCall.name, - success: false, - result: { skipped: true, reason: 'user_rejected' }, - }, + ).catch((err) => { + logger.error('markToolComplete fire-and-forget failed (subagent rejected)', { + toolCallId: toolCall.id, + error: err instanceof Error ? err.message : String(err), + }) }) + markToolResultSeen(toolCall.id) return } if (decision?.status === 'background') { toolCall.status = 'skipped' toolCall.endTime = Date.now() - await markToolComplete( + // Fire-and-forget: must NOT await — see deadlock note in executeToolAndReport + markToolComplete( toolCall.id, toolCall.name, 202, decision.message || 'Tool execution moved to background', { background: true } - ) - markToolResultSeen(toolCall.id) - await options?.onEvent?.({ - type: 'tool_result', - toolCallId: toolCall.id, - data: { - id: toolCall.id, - name: toolCall.name, - success: true, - result: { background: true }, - }, + ).catch((err) => { + logger.error('markToolComplete fire-and-forget failed (subagent background)', { + toolCallId: toolCall.id, + error: err instanceof Error ? err.message : String(err), + }) }) + markToolResultSeen(toolCall.id) return } } @@ -507,6 +509,24 @@ export const subAgentHandlers: Record = { markToolResultSeen(toolCallId) return } + if (completion?.status === 'background') { + toolCall.status = 'skipped' + toolCall.endTime = Date.now() + markToolComplete( + toolCall.id, + toolCall.name, + 202, + completion.message || 'Tool execution moved to background', + { background: true } + ).catch((err) => { + logger.error('markToolComplete fire-and-forget failed (subagent run tool background)', { + toolCallId: toolCall.id, + error: err instanceof Error ? err.message : String(err), + }) + }) + markToolResultSeen(toolCallId) + return + } const success = completion?.status === 'success' toolCall.status = success ? 'success' : 'error' toolCall.endTime = Date.now() diff --git a/apps/sim/lib/copilot/orchestrator/sse-handlers/tool-execution.ts b/apps/sim/lib/copilot/orchestrator/sse-handlers/tool-execution.ts index 739b11c46..26865176c 100644 --- a/apps/sim/lib/copilot/orchestrator/sse-handlers/tool-execution.ts +++ b/apps/sim/lib/copilot/orchestrator/sse-handlers/tool-execution.ts @@ -172,7 +172,8 @@ export async function waitForToolCompletion( if ( decision?.status === 'success' || decision?.status === 'error' || - decision?.status === 'rejected' + decision?.status === 'rejected' || + decision?.status === 'background' ) { return decision } diff --git a/apps/sim/stores/panel/copilot/store.ts b/apps/sim/stores/panel/copilot/store.ts index e0283aa01..1dd8540ee 100644 --- a/apps/sim/stores/panel/copilot/store.ts +++ b/apps/sim/stores/panel/copilot/store.ts @@ -1500,6 +1500,7 @@ export const useCopilotStore = create()( else if (newState === 'success' || newState === 'accepted') norm = ClientToolCallState.success else if (newState === 'aborted') norm = ClientToolCallState.aborted + else if (newState === 'background') norm = ClientToolCallState.background else if (typeof newState === 'number') norm = newState as unknown as ClientToolCallState map[id] = { ...current,