From 94ae0006adfb1b29fe4c8c56e86162e53a13ba1e Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Tue, 14 Apr 2026 19:56:04 -0700 Subject: [PATCH] fix --- apps/sim/app/api/mcp/copilot/route.ts | 46 ++++++++++++--- .../home/components/message-content/utils.ts | 2 + .../app/workspace/[workspaceId]/home/types.ts | 3 + .../lib/copilot/generated/tool-catalog-v1.ts | 56 ++++++++++++++++++- .../lib/copilot/generated/tool-schemas-v1.ts | 39 ++++++++++++- apps/sim/lib/copilot/request/subagent.ts | 46 ++++++++++++++- apps/sim/lib/copilot/tools/mcp/definitions.ts | 30 +++------- 7 files changed, 187 insertions(+), 35 deletions(-) diff --git a/apps/sim/app/api/mcp/copilot/route.ts b/apps/sim/app/api/mcp/copilot/route.ts index ebff9bb80b..f2bc6a2754 100644 --- a/apps/sim/app/api/mcp/copilot/route.ts +++ b/apps/sim/app/api/mcp/copilot/route.ts @@ -17,6 +17,7 @@ import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { validateOAuthAccessToken } from '@/lib/auth/oauth-token' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' +import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context' import { ORCHESTRATION_TIMEOUT_MS, SIM_AGENT_API_URL } from '@/lib/copilot/constants' import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless' import { orchestrateSubagentStream } from '@/lib/copilot/request/subagent' @@ -136,14 +137,14 @@ When the user refers to a workflow by name or description ("the email one", "my ### Organization - \`rename_workflow\` — rename a workflow -- \`move_workflow\` — move a workflow into a folder (or root with null) -- \`move_folder\` — nest a folder inside another (or root with null) +- \`move_workflow\` — move a workflow into a folder (or back to root by clearing the folder id) +- \`move_folder\` — nest a folder inside another (or move it back to root by clearing the parent id) - \`create_folder(name, parentId)\` — create nested folder hierarchies ### Key Rules - You can test workflows immediately after building — deployment is only needed for external access (API, chat, MCP). -- All workflow-scoped copilot tools require \`workflowId\`. +- Tools that operate on a specific workflow such as \`sim_workflow\`, \`sim_test\`, \`sim_deploy\`, and workflow-scoped \`sim_info\` requests require \`workflowId\`. - If the user reports errors, route through \`sim_workflow\` and ask it to reproduce, inspect logs, and fix the issue end to end. - Variable syntax: \`\` for block outputs, \`{{ENV_VAR}}\` for env vars. ` @@ -667,10 +668,10 @@ async function handleDirectToolCall( } /** - * Build mode uses the main chat orchestrator with the 'fast' command instead of - * the subagent endpoint. In Go, 'workflow' is not a registered subagent — it's a mode - * (ModeFast) on the main chat processor that bypasses subagent orchestration and - * executes all tools directly. + * Build mode uses the main /api/mcp orchestrator instead of /api/subagent/workflow. + * The main agent still delegates workflow work to the workflow subagent inside Go; + * this helper simply uses the full headless lifecycle so build requests behave like + * the primary MCP chat flow. */ async function handleBuildToolCall( args: Record, @@ -680,6 +681,8 @@ async function handleBuildToolCall( try { const requestText = (args.request as string) || JSON.stringify(args) const workflowId = args.workflowId as string | undefined + let resolvedWorkflowName: string | undefined + let resolvedWorkspaceId: string | undefined const resolved = workflowId ? await (async () => { @@ -688,8 +691,10 @@ async function handleBuildToolCall( userId, action: 'read', }) + resolvedWorkflowName = authorization.workflow?.name || undefined + resolvedWorkspaceId = authorization.workflow?.workspaceId || undefined return authorization.allowed - ? { status: 'resolved' as const, workflowId } + ? { status: 'resolved' as const, workflowId, workflowName: resolvedWorkflowName } : { status: 'not_found' as const, message: 'workflowId is required for build. Call create_workflow first.', @@ -697,6 +702,10 @@ async function handleBuildToolCall( })() : await resolveWorkflowIdForUser(userId) + if (resolved.status === 'resolved') { + resolvedWorkflowName ||= resolved.workflowName + } + if (!resolved || resolved.status !== 'resolved') { return { content: [ @@ -719,10 +728,29 @@ async function handleBuildToolCall( } const chatId = generateId() + const executionContext = await prepareExecutionContext(userId, resolved.workflowId, chatId, { + workspaceId: resolvedWorkspaceId, + }) + resolvedWorkspaceId = executionContext.workspaceId + let workspaceContext: string | undefined + if (resolvedWorkspaceId) { + try { + workspaceContext = await generateWorkspaceContext(resolvedWorkspaceId, userId) + } catch (error) { + logger.warn('Failed to generate workspace context for build tool call', { + workflowId: resolved.workflowId, + workspaceId: resolvedWorkspaceId, + error: error instanceof Error ? error.message : String(error), + }) + } + } const requestPayload = { message: requestText, workflowId: resolved.workflowId, + ...(resolvedWorkflowName ? { workflowName: resolvedWorkflowName } : {}), + ...(resolvedWorkspaceId ? { workspaceId: resolvedWorkspaceId } : {}), + ...(workspaceContext ? { workspaceContext } : {}), userId, model: DEFAULT_COPILOT_MODEL, mode: 'agent', @@ -734,8 +762,10 @@ async function handleBuildToolCall( const result = await runHeadlessCopilotLifecycle(requestPayload, { userId, workflowId: resolved.workflowId, + workspaceId: resolvedWorkspaceId, chatId, goRoute: '/api/mcp', + executionContext, autoExecuteTools: true, timeout: ORCHESTRATION_TIMEOUT_MS, interactive: false, diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts index 12a491b7d2..dcfa27a3b2 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts @@ -2,6 +2,7 @@ import type { ComponentType, SVGProps } from 'react' import { Asterisk, Blimp, + Bug, Calendar, Database, Eye, @@ -44,6 +45,7 @@ const TOOL_ICONS: Record = { create_workflow: Layout, edit_workflow: Pencil, workflow: Hammer, + debug: Bug, run: PlayOutline, deploy: Rocket, auth: Integration, diff --git a/apps/sim/app/workspace/[workspaceId]/home/types.ts b/apps/sim/app/workspace/[workspaceId]/home/types.ts index b3ed394fb7..ce1ef99b20 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/types.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/types.ts @@ -2,6 +2,7 @@ import { Agent, Auth, CreateWorkflow, + Debug, Deploy, EditWorkflow, FunctionExecute, @@ -161,6 +162,7 @@ export interface ChatMessage { export const SUBAGENT_LABELS: Record = { workflow: 'Workflow Agent', + debug: 'Debug Agent', deploy: 'Deploy Agent', auth: 'Auth Agent', research: 'Research Agent', @@ -200,6 +202,7 @@ export const TOOL_UI_METADATA: Record = { [CreateWorkflow.id]: { title: 'Creating workflow' }, [EditWorkflow.id]: { title: 'Editing workflow' }, [Workflow.id]: { title: 'Workflow Agent' }, + [Debug.id]: { title: 'Debug Agent' }, [RUN_SUBAGENT_ID]: { title: 'Run Agent' }, [Deploy.id]: { title: 'Deploy Agent' }, [Auth.id]: { title: 'Auth Agent' }, diff --git a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts index 3f9136e3aa..2af7b1d660 100644 --- a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts @@ -17,6 +17,7 @@ export interface ToolCatalogEntry { | 'create_job' | 'create_workflow' | 'create_workspace_mcp_server' + | 'debug' | 'delete_file' | 'delete_folder' | 'delete_workflow' @@ -70,6 +71,7 @@ export interface ToolCatalogEntry { | 'respond' | 'restore_resource' | 'revert_to_version' + | 'run' | 'run_block' | 'run_from_block' | 'run_workflow' @@ -105,6 +107,7 @@ export interface ToolCatalogEntry { | 'create_job' | 'create_workflow' | 'create_workspace_mcp_server' + | 'debug' | 'delete_file' | 'delete_folder' | 'delete_workflow' @@ -158,6 +161,7 @@ export interface ToolCatalogEntry { | 'respond' | 'restore_resource' | 'revert_to_version' + | 'run' | 'run_block' | 'run_from_block' | 'run_workflow' @@ -187,11 +191,13 @@ export interface ToolCatalogEntry { subagentId?: | 'agent' | 'auth' + | 'debug' | 'deploy' | 'file' | 'job' | 'knowledge' | 'research' + | 'run' | 'superagent' | 'table' | 'workflow' @@ -444,6 +450,31 @@ export const CreateWorkspaceMcpServer: ToolCatalogEntry = { requiredPermission: 'admin', } +export const Debug: ToolCatalogEntry = { + id: 'debug', + name: 'debug', + route: 'subagent', + mode: 'async', + parameters: { + properties: { + context: { + description: + 'Pre-gathered context: workflow state JSON, block schemas, error logs. The debug agent will skip re-reading anything included here.', + type: 'string', + }, + request: { + description: + 'What to debug. Include error messages, block IDs, and any context about the failure.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'debug', + internal: true, +} + export const DeleteFile: ToolCatalogEntry = { id: 'delete_file', name: 'delete_file', @@ -2039,7 +2070,8 @@ export const Read: ToolCatalogEntry = { }, path: { type: 'string', - description: "Path to the file to read (e.g. 'workflows/My Workflow/state.json').", + description: + "Path to the file to read (e.g. 'workflows/My Workflow/state.json' or 'workflows/Projects/Q1/My Workflow/state.json').", }, }, required: ['path'], @@ -2231,6 +2263,26 @@ export const RevertToVersion: ToolCatalogEntry = { requiredPermission: 'admin', } +export const Run: ToolCatalogEntry = { + id: 'run', + name: 'run', + route: 'subagent', + mode: 'async', + parameters: { + properties: { + context: { + description: 'Pre-gathered context: workflow state, block IDs, input requirements.', + type: 'string', + }, + request: { description: 'What to run or what logs to check.', type: 'string' }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'run', + internal: true, +} + export const RunBlock: ToolCatalogEntry = { id: 'run_block', name: 'run_block', @@ -3264,6 +3316,7 @@ export const TOOL_CATALOG: Record = { [CreateJob.id]: CreateJob, [CreateWorkflow.id]: CreateWorkflow, [CreateWorkspaceMcpServer.id]: CreateWorkspaceMcpServer, + [Debug.id]: Debug, [DeleteFile.id]: DeleteFile, [DeleteFolder.id]: DeleteFolder, [DeleteWorkflow.id]: DeleteWorkflow, @@ -3317,6 +3370,7 @@ export const TOOL_CATALOG: Record = { [Respond.id]: Respond, [RestoreResource.id]: RestoreResource, [RevertToVersion.id]: RevertToVersion, + [Run.id]: Run, [RunBlock.id]: RunBlock, [RunFromBlock.id]: RunFromBlock, [RunWorkflow.id]: RunWorkflow, diff --git a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts index ac4b51df2c..78e624c847 100644 --- a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts @@ -266,6 +266,25 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { }, resultSchema: undefined, }, + debug: { + parameters: { + properties: { + context: { + description: + 'Pre-gathered context: workflow state JSON, block schemas, error logs. The debug agent will skip re-reading anything included here.', + type: 'string', + }, + request: { + description: + 'What to debug. Include error messages, block IDs, and any context about the failure.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + resultSchema: undefined, + }, delete_file: { parameters: { type: 'object', @@ -1872,7 +1891,8 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { }, path: { type: 'string', - description: "Path to the file to read (e.g. 'workflows/My Workflow/state.json').", + description: + "Path to the file to read (e.g. 'workflows/My Workflow/state.json' or 'workflows/Projects/Q1/My Workflow/state.json').", }, }, required: ['path'], @@ -2070,6 +2090,23 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { }, resultSchema: undefined, }, + run: { + parameters: { + properties: { + context: { + description: 'Pre-gathered context: workflow state, block IDs, input requirements.', + type: 'string', + }, + request: { + description: 'What to run or what logs to check.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + resultSchema: undefined, + }, run_block: { parameters: { type: 'object', diff --git a/apps/sim/lib/copilot/request/subagent.ts b/apps/sim/lib/copilot/request/subagent.ts index 43f56abe12..d940309469 100644 --- a/apps/sim/lib/copilot/request/subagent.ts +++ b/apps/sim/lib/copilot/request/subagent.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context' import { SIM_AGENT_API_URL } from '@/lib/copilot/constants' import { MothershipStreamV1EventType, @@ -16,8 +17,10 @@ import type { } from '@/lib/copilot/request/types' import { prepareExecutionContext } from '@/lib/copilot/tools/handlers/context' import { env } from '@/lib/core/config/env' +import { isHosted } from '@/lib/core/config/feature-flags' import { generateId } from '@/lib/core/utils/uuid' import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' +import { getWorkflowById } from '@/lib/workflows/utils' const logger = createLogger('CopilotSubagentOrchestrator') @@ -49,10 +52,40 @@ export async function orchestrateSubagentStream( options: SubagentOrchestratorOptions ): Promise { const { userId, workflowId, workspaceId, userPermission } = options - const execContext = await buildExecutionContext(userId, workflowId, workspaceId) + const chatId = + (typeof requestPayload.chatId === 'string' && requestPayload.chatId) || generateId() + const execContext = await buildExecutionContext(userId, workflowId, workspaceId, chatId) + let resolvedWorkflowName = + typeof requestPayload.workflowName === 'string' ? requestPayload.workflowName : undefined + let resolvedWorkspaceId = + execContext.workspaceId || + (typeof requestPayload.workspaceId === 'string' ? requestPayload.workspaceId : workspaceId) + + if (workflowId && (!resolvedWorkflowName || !resolvedWorkspaceId)) { + const workflow = await getWorkflowById(workflowId) + resolvedWorkflowName ||= workflow?.name || undefined + resolvedWorkspaceId ||= workflow?.workspaceId || undefined + } + + let resolvedWorkspaceContext = + typeof requestPayload.workspaceContext === 'string' + ? requestPayload.workspaceContext + : undefined + if (!resolvedWorkspaceContext && resolvedWorkspaceId) { + try { + resolvedWorkspaceContext = await generateWorkspaceContext(resolvedWorkspaceId, userId) + } catch (error) { + logger.warn('Failed to generate workspace context for subagent request', { + agentId, + workspaceId: resolvedWorkspaceId, + error: error instanceof Error ? error.message : String(error), + }) + } + } const msgId = requestPayload?.messageId const context = createStreamingContext({ + chatId, messageId: typeof msgId === 'string' ? msgId : generateId(), }) @@ -69,8 +102,13 @@ export async function orchestrateSubagentStream( }, body: JSON.stringify({ ...requestPayload, + chatId, userId, stream: true, + ...(resolvedWorkflowName ? { workflowName: resolvedWorkflowName } : {}), + ...(resolvedWorkspaceId ? { workspaceId: resolvedWorkspaceId } : {}), + ...(resolvedWorkspaceContext ? { workspaceContext: resolvedWorkspaceContext } : {}), + isHosted, ...(userPermission ? { userPermission } : {}), }), }, @@ -135,16 +173,18 @@ function normalizeStructuredResult(data: unknown): SubagentOrchestratorResult['s async function buildExecutionContext( userId: string, workflowId?: string, - workspaceId?: string + workspaceId?: string, + chatId?: string ): Promise { if (workflowId) { - return prepareExecutionContext(userId, workflowId) + return prepareExecutionContext(userId, workflowId, chatId, { workspaceId }) } const decryptedEnvVars = await getEffectiveDecryptedEnv(userId, workspaceId) return { userId, workflowId: workflowId || '', workspaceId, + chatId, decryptedEnvVars, } } diff --git a/apps/sim/lib/copilot/tools/mcp/definitions.ts b/apps/sim/lib/copilot/tools/mcp/definitions.ts index 52ba61daed..65e02d1e94 100644 --- a/apps/sim/lib/copilot/tools/mcp/definitions.ts +++ b/apps/sim/lib/copilot/tools/mcp/definitions.ts @@ -284,16 +284,18 @@ CAN DO: - Configure block settings and connections - Set environment variables and workflow variables - Move, rename, delete workflows and folders +- Run or inspect workflows through the nested run/debug specialists when validation is needed +- Delegate deployment or auth setup to the nested specialists when needed CANNOT DO: -- Run or test workflows (use sim_test separately) -- Deploy workflows (use sim_deploy separately) +- Replace dedicated testing flows like sim_test when you want a standalone execution-only pass +- Replace dedicated deploy flows like sim_deploy when you want deployment as a separate step WORKFLOW: 1. Call create_workflow to get a workflowId (for new workflows) 2. Call sim_workflow with the request and workflowId -3. Workflow agent gathers info and builds in one pass -4. Call sim_test to verify it works +3. Workflow agent gathers info, builds, and can delegate run/debug/auth/deploy help in one pass +4. Call sim_test when you want a dedicated execution-only verification pass 5. Optionally call sim_deploy to make it externally accessible`, inputSchema: { type: 'object', @@ -375,7 +377,7 @@ ALSO CAN: }, { name: 'sim_test', - agentId: 'test', + agentId: 'run', description: `Run a workflow and verify its outputs. Works on both deployed and undeployed (draft) workflows. Use after building to verify correctness. Supports full and partial execution: @@ -476,7 +478,7 @@ Supports full and partial execution: name: 'sim_info', agentId: 'info', description: - "Inspect a workflow's blocks, connections, outputs, variables, and metadata. Use for questions about the Sim platform itself — how blocks work, what integrations are available, platform concepts, etc. Always provide workflowId to scope results to a specific workflow.", + "Inspect a workflow's blocks, connections, outputs, variables, and metadata. Use for questions about the Sim platform itself — how blocks work, what integrations are available, platform concepts, etc. Provide workflowId when you want results scoped to a specific workflow.", inputSchema: { type: 'object', properties: { @@ -488,22 +490,6 @@ Supports full and partial execution: }, annotations: { readOnlyHint: true }, }, - { - name: 'sim_workflow', - agentId: 'workflow', - description: - 'Manage workflow-level configuration: environment variables, settings, scheduling, and deployment status. Use for any data about a specific workflow — its settings, credentials, variables, or deployment state.', - inputSchema: { - type: 'object', - properties: { - request: { type: 'string' }, - workflowId: { type: 'string' }, - context: { type: 'object' }, - }, - required: ['request'], - }, - annotations: { destructiveHint: false }, - }, { name: 'sim_research', agentId: 'research',