diff --git a/apps/sim/lib/copilot/client-sse/subagent-handlers.ts b/apps/sim/lib/copilot/client-sse/subagent-handlers.ts index d78360cad..98abb45e4 100644 --- a/apps/sim/lib/copilot/client-sse/subagent-handlers.ts +++ b/apps/sim/lib/copilot/client-sse/subagent-handlers.ts @@ -227,7 +227,18 @@ export const subAgentSSEHandlers: Record = { const resultData = asRecord(data?.data) const toolCallId: string | undefined = data?.toolCallId || (resultData.id as string | undefined) - const success: boolean | undefined = data?.success !== false + // Determine success: explicit `success` field takes priority; otherwise + // infer from presence of result data vs error (same logic as server-side + // inferToolSuccess). The Go backend uses `*bool` with omitempty so + // `success` is present when explicitly set, and absent for non-tool events. + const hasExplicitSuccess = + data?.success !== undefined || resultData.success !== undefined + const explicitSuccess = data?.success ?? resultData.success + const hasResultData = data?.result !== undefined || resultData.result !== undefined + const hasError = !!data?.error || !!resultData.error + const success: boolean = hasExplicitSuccess + ? !!explicitSuccess + : hasResultData && !hasError if (!toolCallId) return if (!context.subAgentToolCalls[parentToolCallId]) return diff --git a/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts b/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts index 9a061029e..8b5025897 100644 --- a/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts +++ b/apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts @@ -7,7 +7,10 @@ import { markToolResultSeen, wasToolResultSeen, } from '@/lib/copilot/orchestrator/sse-utils' -import { markToolComplete } from '@/lib/copilot/orchestrator/tool-executor' +import { + isToolAvailableOnSimSide, + markToolComplete, +} from '@/lib/copilot/orchestrator/tool-executor' import type { ContentBlock, ExecutionContext, @@ -360,6 +363,15 @@ export const subAgentHandlers: Record = { return } + // Tools that only exist on the Go backend (e.g. search_patterns, + // search_errors, remember_debug) should NOT be re-executed on the Sim side. + // The Go backend already executed them and will send its own tool_result + // SSE event with the real outcome. Trying to execute them here would fail + // with "Tool not found" and incorrectly mark the tool as failed. + if (!isToolAvailableOnSimSide(toolName)) { + return + } + if (options.autoExecuteTools !== false) { await executeToolAndReport(toolCallId, context, execContext, options) } diff --git a/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts b/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts index 12dbbf598..b56abd436 100644 --- a/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts +++ b/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts @@ -136,6 +136,21 @@ const SIM_WORKFLOW_TOOL_HANDLERS: Record< executeCreateWorkspaceMcpServer(p as CreateWorkspaceMcpServerParams, c), } +/** + * Check whether a tool can be executed on the Sim (TypeScript) side. + * + * Tools that are only available on the Go backend (e.g. search_patterns, + * search_errors, remember_debug) will return false. The subagent tool_call + * handler uses this to decide whether to execute a tool locally or let the + * Go backend's own tool_result SSE event handle it. + */ +export function isToolAvailableOnSimSide(toolName: string): boolean { + if (SERVER_TOOLS.has(toolName)) return true + if (toolName in SIM_WORKFLOW_TOOL_HANDLERS) return true + const resolvedToolName = resolveToolId(toolName) + return !!getTool(resolvedToolName) +} + /** * Execute a tool server-side without calling internal routes. */