diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx index aa9f8abc63..e4e086108a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx @@ -130,6 +130,7 @@ interface FileViewerProps { onSaveStatusChange?: (status: 'idle' | 'saving' | 'saved' | 'error') => void saveRef?: React.MutableRefObject<(() => Promise) | null> streamingContent?: string + streamingMode?: 'append' | 'replace' } export function FileViewer({ @@ -143,6 +144,7 @@ export function FileViewer({ onSaveStatusChange, saveRef, streamingContent, + streamingMode, }: FileViewerProps) { const category = resolveFileCategory(file.type, file.name) @@ -158,6 +160,7 @@ export function FileViewer({ onSaveStatusChange={onSaveStatusChange} saveRef={saveRef} streamingContent={streamingContent} + streamingMode={streamingMode} /> ) } @@ -195,6 +198,7 @@ interface TextEditorProps { onSaveStatusChange?: (status: 'idle' | 'saving' | 'saved' | 'error') => void saveRef?: React.MutableRefObject<(() => Promise) | null> streamingContent?: string + streamingMode?: 'append' | 'replace' } function TextEditor({ @@ -207,6 +211,7 @@ function TextEditor({ onSaveStatusChange, saveRef, streamingContent, + streamingMode = 'append', }: TextEditorProps) { const initializedRef = useRef(false) const contentRef = useRef('') @@ -237,15 +242,13 @@ function TextEditor({ const [content, setContent] = useState('') const [savedContent, setSavedContent] = useState('') const savedContentRef = useRef('') + const wasStreamingRef = useRef(false) useEffect(() => { if (streamingContent !== undefined) { - const isSplicedFull = - fetchedContent !== undefined && - streamingContent.length > fetchedContent.length * 0.5 && - streamingContent.startsWith(fetchedContent.slice(0, Math.min(100, fetchedContent.length))) + wasStreamingRef.current = true const nextContent = - fetchedContent === undefined || isSplicedFull + streamingMode === 'replace' || fetchedContent === undefined ? streamingContent : fetchedContent.endsWith(streamingContent) || fetchedContent.endsWith(`\n${streamingContent}`) @@ -257,6 +260,17 @@ function TextEditor({ return } + if (wasStreamingRef.current) { + wasStreamingRef.current = false + if (fetchedContent !== undefined) { + setContent(fetchedContent) + setSavedContent(fetchedContent) + savedContentRef.current = fetchedContent + contentRef.current = fetchedContent + return + } + } + if (fetchedContent === undefined) return if (!initializedRef.current) { diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx index 1610ae319c..b9d403ef1f 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx @@ -1,7 +1,7 @@ 'use client' import { - FileWrite, + File as FileTool, Read as ReadTool, ToolSearchToolRegex, WorkspaceFile, @@ -43,12 +43,12 @@ const SUBAGENT_KEYS = new Set(Object.keys(SUBAGENT_LABELS)) /** * Maps subagent names to the Mothership tool that dispatches them when the - * tool name differs from the subagent name (e.g. `workspace_file` → `file_write`). + * tool name differs from the subagent name (e.g. `workspace_file` → `file`). * When a `subagent` block arrives, any trailing dispatch tool in the previous * group is absorbed so it doesn't render as a separate Mothership entry. */ const SUBAGENT_DISPATCH_TOOLS: Record = { - [FileWrite.id]: WorkspaceFile.id, + [FileTool.id]: WorkspaceFile.id, } function isToolResultRead(params?: Record): boolean { 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 cd5ffb8cd8..4c66041de2 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 @@ -57,7 +57,7 @@ const TOOL_ICONS: Record = { debug: Bug, context_compaction: Asterisk, open_resource: Eye, - file_write: File, + file: File, } export function getAgentIcon(name: string): IconComponent { diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index a147143727..ffec433cf9 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -85,11 +85,16 @@ export const ResourceContent = memo(function ResourceContent({ }: ResourceContentProps) { const streamFileName = streamingFile?.fileName || 'file.md' - const isPatchStream = useMemo(() => { - if (!streamingFile) return false - return /"operation"\s*:\s*"patch"/.test(streamingFile.content) + const streamOperation = useMemo(() => { + if (!streamingFile) return undefined + const m = streamingFile.content.match(/"operation"\s*:\s*"(\w+)"/) + return m?.[1] }, [streamingFile]) + const isWriteStream = streamOperation === 'write' + const isPatchStream = streamOperation === 'patch' + const isUpdateStream = streamOperation === 'update' + const { data: allFiles = [] } = useWorkspaceFiles(workspaceId) const activeFileRecord = useMemo(() => { if (!isPatchStream || resource.type !== 'file') return undefined @@ -112,13 +117,25 @@ export const ResourceContent = memo(function ResourceContent({ if (!streamingFile) return undefined const raw = streamingFile.content - if (isPatchStream && fetchedFileContent) { + // Do not guess. Until the operation key has streamed in, we don't know + // whether the payload should append, replace, or splice into the file. + // Rendering early here can show content at the end of the file and then + // "snap" to the right place once the operation/mode becomes known. + if (!streamOperation) return undefined + + if (isPatchStream) { + if (!fetchedFileContent) return undefined return extractPatchPreview(raw, fetchedFileContent) } const extracted = extractFileContent(raw) - return extracted.length > 0 ? extracted : undefined - }, [streamingFile, isPatchStream, fetchedFileContent]) + if (extracted.length === 0) return undefined + + if (isUpdateStream) return extracted + if (isWriteStream) return extracted + + return undefined + }, [streamingFile, streamOperation, isWriteStream, isPatchStream, isUpdateStream, fetchedFileContent]) const syntheticFile = useMemo(() => { const ext = getFileExtension(streamFileName) const SOURCE_MIME_MAP: Record = { @@ -140,6 +157,9 @@ export const ResourceContent = memo(function ResourceContent({ } }, [workspaceId, streamFileName]) + const streamingFileMode: 'append' | 'replace' = + isWriteStream ? 'append' : 'replace' + if (streamingFile && resource.id === 'streaming-file') { return (
@@ -150,6 +170,7 @@ export const ResourceContent = memo(function ResourceContent({ canEdit={false} previewMode={previewMode ?? 'preview'} streamingContent={streamingExtractedContent} + streamingMode={streamingFileMode} /> ) : (
@@ -172,6 +193,7 @@ export const ResourceContent = memo(function ResourceContent({ fileId={resource.id} previewMode={previewMode} streamingContent={streamingExtractedContent} + streamingMode={streamingFileMode} /> ) @@ -460,9 +482,10 @@ interface EmbeddedFileProps { fileId: string previewMode?: PreviewMode streamingContent?: string + streamingMode?: 'append' | 'replace' } -function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: EmbeddedFileProps) { +function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent, streamingMode }: EmbeddedFileProps) { const { canEdit } = useUserPermissionsContext() const { data: files = [], isLoading, isFetching } = useWorkspaceFiles(workspaceId) const file = useMemo(() => files.find((f) => f.id === fileId), [files, fileId]) @@ -490,6 +513,7 @@ function EmbeddedFile({ workspaceId, fileId, previewMode, streamingContent }: Em file={file} workspaceId={workspaceId} canEdit={canEdit} + streamingMode={streamingMode} previewMode={previewMode} streamingContent={streamingContent} /> diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx index 6efcadee9c..8a60efbca4 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx @@ -30,7 +30,7 @@ function fileTitlesEquivalent(streamFileName: string, resourceTitle: string): bo } /** - * Whether the active resource should show the in-progress file_write stream. + * Whether the active resource should show the in-progress file stream. * The synthetic `streaming-file` tab always shows it; a real file tab shows it when * the streamed `fileName` matches that resource (so users who stay on the open file see live text). */ diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index 7a037c97ea..789703b11b 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -27,7 +27,7 @@ import { DeployApi, DeployChat, DeployMcp, - FileWrite, + File as FileTool, MoveFolder, MoveWorkflow, Read as ReadTool, @@ -914,9 +914,16 @@ export function useChat( ) if (existingFileMatch) { - setActiveResourceId(matchedResourceId) - setResources((rs) => rs.filter((resource) => resource.id !== 'streaming-file')) - } else if (fileName || fileIdMatch || activeSubagent === 'file_write') { + const hadStreamingResource = resourcesRef.current.some( + (resource) => resource.id === 'streaming-file' + ) + if (hadStreamingResource) { + setResources((rs) => rs.filter((resource) => resource.id !== 'streaming-file')) + setActiveResourceId(matchedResourceId) + } else if (activeResourceIdRef.current === null) { + setActiveResourceId(matchedResourceId) + } + } else if (fileName || fileIdMatch || activeSubagent === FileTool.id) { const hasStreamingResource = resourcesRef.current.some( (resource) => resource.id === 'streaming-file' ) @@ -927,8 +934,6 @@ export function useChat( title: fileName || 'Writing file...', }) setActiveResourceId('streaming-file') - } else if (activeResourceIdRef.current !== 'streaming-file') { - setActiveResourceId('streaming-file') } } const next = { fileName, fileId: matchedResourceId, content: raw } @@ -1294,7 +1299,7 @@ export function useChat( if (!isSameActiveSubagent) { blocks.push({ type: 'subagent', content: name }) } - if (name === FileWrite.id) { + if (name === FileTool.id) { const emptyFile = { fileName: '', content: '' } streamingFileRef.current = emptyFile setStreamingFile(emptyFile) diff --git a/apps/sim/app/workspace/[workspaceId]/home/types.ts b/apps/sim/app/workspace/[workspaceId]/home/types.ts index ab994b54d7..53eb80cda6 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/types.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/types.ts @@ -190,7 +190,7 @@ export const SUBAGENT_LABELS: Record = { run: 'Run agent', agent: 'Agent manager', job: 'Job agent', - file_write: 'File Write', + file: 'File', } as const export interface ToolUIMetadata { diff --git a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts index e74f798e5e..b40064b502 100644 --- a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts @@ -3,860 +3,682 @@ // export interface ToolCatalogEntry { - clientExecutable?: boolean - executor: 'client' | 'go' | 'sim' | 'subagent' - hidden?: boolean - id: - | 'agent' - | 'auth' - | 'check_deployment_status' - | 'complete_job' - | 'context_write' - | 'crawl_website' - | 'create_file' - | 'create_folder' - | 'create_job' - | 'create_workflow' - | 'create_workspace_mcp_server' - | 'debug' - | 'delete_folder' - | 'delete_workflow' - | 'delete_workspace_mcp_server' - | 'deploy' - | 'deploy_api' - | 'deploy_chat' - | 'deploy_mcp' - | 'download_to_workspace_file' - | 'edit_workflow' - | 'file_write' - | 'function_execute' - | 'generate_api_key' - | 'generate_image' - | 'generate_visualization' - | 'get_block_outputs' - | 'get_block_upstream_references' - | 'get_deployed_workflow_state' - | 'get_deployment_version' - | 'get_execution_summary' - | 'get_job_logs' - | 'get_page_contents' - | 'get_platform_actions' - | 'get_workflow_data' - | 'get_workflow_logs' - | 'glob' - | 'grep' - | 'job' - | 'knowledge' - | 'knowledge_base' - | 'list_folders' - | 'list_user_workspaces' - | 'list_workspace_mcp_servers' - | 'manage_credential' - | 'manage_custom_tool' - | 'manage_job' - | 'manage_mcp_tool' - | 'manage_skill' - | 'materialize_file' - | 'move_folder' - | 'move_workflow' - | 'oauth_get_auth_link' - | 'oauth_request_access' - | 'open_resource' - | 'read' - | 'redeploy' - | 'rename_workflow' - | 'research' - | 'respond' - | 'revert_to_version' - | 'run' - | 'run_block' - | 'run_from_block' - | 'run_workflow' - | 'run_workflow_until_block' - | 'scrape_page' - | 'search_documentation' - | 'search_library_docs' - | 'search_online' - | 'search_patterns' - | 'set_environment_variables' - | 'set_file_context' - | 'set_global_workflow_variables' - | 'superagent' - | 'table' - | 'tool_search_tool_regex' - | 'update_job_history' - | 'update_workspace_mcp_server' - | 'user_memory' - | 'user_table' - | 'workflow' - | 'workspace_file' - internal?: boolean - mode: 'async' | 'sync' - name: - | 'agent' - | 'auth' - | 'check_deployment_status' - | 'complete_job' - | 'context_write' - | 'crawl_website' - | 'create_file' - | 'create_folder' - | 'create_job' - | 'create_workflow' - | 'create_workspace_mcp_server' - | 'debug' - | 'delete_folder' - | 'delete_workflow' - | 'delete_workspace_mcp_server' - | 'deploy' - | 'deploy_api' - | 'deploy_chat' - | 'deploy_mcp' - | 'download_to_workspace_file' - | 'edit_workflow' - | 'file_write' - | 'function_execute' - | 'generate_api_key' - | 'generate_image' - | 'generate_visualization' - | 'get_block_outputs' - | 'get_block_upstream_references' - | 'get_deployed_workflow_state' - | 'get_deployment_version' - | 'get_execution_summary' - | 'get_job_logs' - | 'get_page_contents' - | 'get_platform_actions' - | 'get_workflow_data' - | 'get_workflow_logs' - | 'glob' - | 'grep' - | 'job' - | 'knowledge' - | 'knowledge_base' - | 'list_folders' - | 'list_user_workspaces' - | 'list_workspace_mcp_servers' - | 'manage_credential' - | 'manage_custom_tool' - | 'manage_job' - | 'manage_mcp_tool' - | 'manage_skill' - | 'materialize_file' - | 'move_folder' - | 'move_workflow' - | 'oauth_get_auth_link' - | 'oauth_request_access' - | 'open_resource' - | 'read' - | 'redeploy' - | 'rename_workflow' - | 'research' - | 'respond' - | 'revert_to_version' - | 'run' - | 'run_block' - | 'run_from_block' - | 'run_workflow' - | 'run_workflow_until_block' - | 'scrape_page' - | 'search_documentation' - | 'search_library_docs' - | 'search_online' - | 'search_patterns' - | 'set_environment_variables' - | 'set_file_context' - | 'set_global_workflow_variables' - | 'superagent' - | 'table' - | 'tool_search_tool_regex' - | 'update_job_history' - | 'update_workspace_mcp_server' - | 'user_memory' - | 'user_table' - | 'workflow' - | 'workspace_file' - requiredPermission?: 'admin' | 'write' - requiresConfirmation?: boolean - subagentId?: - | 'agent' - | 'auth' - | 'debug' - | 'deploy' - | 'file_write' - | 'job' - | 'knowledge' - | 'research' - | 'run' - | 'superagent' - | 'table' - | 'workflow' + clientExecutable?: boolean; + executor: "client" | "go" | "sim" | "subagent"; + hidden?: boolean; + id: "agent" | "auth" | "check_deployment_status" | "complete_job" | "context_write" | "crawl_website" | "create_file" | "create_folder" | "create_job" | "create_workflow" | "create_workspace_mcp_server" | "debug" | "delete_folder" | "delete_workflow" | "delete_workspace_mcp_server" | "deploy" | "deploy_api" | "deploy_chat" | "deploy_mcp" | "download_to_workspace_file" | "edit_workflow" | "file" | "function_execute" | "generate_api_key" | "generate_image" | "generate_visualization" | "get_block_outputs" | "get_block_upstream_references" | "get_deployed_workflow_state" | "get_deployment_version" | "get_execution_summary" | "get_job_logs" | "get_page_contents" | "get_platform_actions" | "get_workflow_data" | "get_workflow_logs" | "glob" | "grep" | "job" | "knowledge" | "knowledge_base" | "list_folders" | "list_user_workspaces" | "list_workspace_mcp_servers" | "manage_credential" | "manage_custom_tool" | "manage_job" | "manage_mcp_tool" | "manage_skill" | "materialize_file" | "move_folder" | "move_workflow" | "oauth_get_auth_link" | "oauth_request_access" | "open_resource" | "read" | "redeploy" | "rename_workflow" | "research" | "respond" | "revert_to_version" | "run" | "run_block" | "run_from_block" | "run_workflow" | "run_workflow_until_block" | "scrape_page" | "search_documentation" | "search_library_docs" | "search_online" | "search_patterns" | "set_environment_variables" | "set_file_context" | "set_global_workflow_variables" | "superagent" | "table" | "tool_search_tool_regex" | "update_job_history" | "update_workspace_mcp_server" | "user_memory" | "user_table" | "workflow" | "workspace_file"; + internal?: boolean; + mode: "async" | "sync"; + name: "agent" | "auth" | "check_deployment_status" | "complete_job" | "context_write" | "crawl_website" | "create_file" | "create_folder" | "create_job" | "create_workflow" | "create_workspace_mcp_server" | "debug" | "delete_folder" | "delete_workflow" | "delete_workspace_mcp_server" | "deploy" | "deploy_api" | "deploy_chat" | "deploy_mcp" | "download_to_workspace_file" | "edit_workflow" | "file" | "function_execute" | "generate_api_key" | "generate_image" | "generate_visualization" | "get_block_outputs" | "get_block_upstream_references" | "get_deployed_workflow_state" | "get_deployment_version" | "get_execution_summary" | "get_job_logs" | "get_page_contents" | "get_platform_actions" | "get_workflow_data" | "get_workflow_logs" | "glob" | "grep" | "job" | "knowledge" | "knowledge_base" | "list_folders" | "list_user_workspaces" | "list_workspace_mcp_servers" | "manage_credential" | "manage_custom_tool" | "manage_job" | "manage_mcp_tool" | "manage_skill" | "materialize_file" | "move_folder" | "move_workflow" | "oauth_get_auth_link" | "oauth_request_access" | "open_resource" | "read" | "redeploy" | "rename_workflow" | "research" | "respond" | "revert_to_version" | "run" | "run_block" | "run_from_block" | "run_workflow" | "run_workflow_until_block" | "scrape_page" | "search_documentation" | "search_library_docs" | "search_online" | "search_patterns" | "set_environment_variables" | "set_file_context" | "set_global_workflow_variables" | "superagent" | "table" | "tool_search_tool_regex" | "update_job_history" | "update_workspace_mcp_server" | "user_memory" | "user_table" | "workflow" | "workspace_file"; + requiredPermission?: "admin" | "write"; + requiresConfirmation?: boolean; + subagentId?: "agent" | "auth" | "debug" | "deploy" | "file" | "job" | "knowledge" | "research" | "run" | "superagent" | "table" | "workflow"; } export const Agent: ToolCatalogEntry = { - id: 'agent', - name: 'agent', - executor: 'subagent', - mode: 'async', - subagentId: 'agent', + id: "agent", + name: "agent", + executor: "subagent", + mode: "async", + subagentId: "agent", internal: true, - requiredPermission: 'write', -} + requiredPermission: "write", +}; export const Auth: ToolCatalogEntry = { - id: 'auth', - name: 'auth', - executor: 'subagent', - mode: 'async', - subagentId: 'auth', + id: "auth", + name: "auth", + executor: "subagent", + mode: "async", + subagentId: "auth", internal: true, -} +}; export const CheckDeploymentStatus: ToolCatalogEntry = { - id: 'check_deployment_status', - name: 'check_deployment_status', - executor: 'sim', - mode: 'async', -} + id: "check_deployment_status", + name: "check_deployment_status", + executor: "sim", + mode: "async", +}; export const CompleteJob: ToolCatalogEntry = { - id: 'complete_job', - name: 'complete_job', - executor: 'sim', - mode: 'async', -} + id: "complete_job", + name: "complete_job", + executor: "sim", + mode: "async", +}; export const ContextWrite: ToolCatalogEntry = { - id: 'context_write', - name: 'context_write', - executor: 'go', - mode: 'sync', -} + id: "context_write", + name: "context_write", + executor: "go", + mode: "sync", +}; export const CrawlWebsite: ToolCatalogEntry = { - id: 'crawl_website', - name: 'crawl_website', - executor: 'go', - mode: 'sync', -} + id: "crawl_website", + name: "crawl_website", + executor: "go", + mode: "sync", +}; export const CreateFile: ToolCatalogEntry = { - id: 'create_file', - name: 'create_file', - executor: 'sim', - mode: 'async', -} + id: "create_file", + name: "create_file", + executor: "sim", + mode: "async", +}; export const CreateFolder: ToolCatalogEntry = { - id: 'create_folder', - name: 'create_folder', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "create_folder", + name: "create_folder", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const CreateJob: ToolCatalogEntry = { - id: 'create_job', - name: 'create_job', - executor: 'sim', - mode: 'async', -} + id: "create_job", + name: "create_job", + executor: "sim", + mode: "async", +}; export const CreateWorkflow: ToolCatalogEntry = { - id: 'create_workflow', - name: 'create_workflow', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "create_workflow", + name: "create_workflow", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const CreateWorkspaceMcpServer: ToolCatalogEntry = { - id: 'create_workspace_mcp_server', - name: 'create_workspace_mcp_server', - executor: 'sim', - mode: 'async', + id: "create_workspace_mcp_server", + name: "create_workspace_mcp_server", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const Debug: ToolCatalogEntry = { - id: 'debug', - name: 'debug', - executor: 'subagent', - mode: 'async', - subagentId: 'debug', + id: "debug", + name: "debug", + executor: "subagent", + mode: "async", + subagentId: "debug", internal: true, -} +}; export const DeleteFolder: ToolCatalogEntry = { - id: 'delete_folder', - name: 'delete_folder', - executor: 'sim', - mode: 'async', + id: "delete_folder", + name: "delete_folder", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'write', -} + requiredPermission: "write", +}; export const DeleteWorkflow: ToolCatalogEntry = { - id: 'delete_workflow', - name: 'delete_workflow', - executor: 'sim', - mode: 'async', + id: "delete_workflow", + name: "delete_workflow", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'write', -} + requiredPermission: "write", +}; export const DeleteWorkspaceMcpServer: ToolCatalogEntry = { - id: 'delete_workspace_mcp_server', - name: 'delete_workspace_mcp_server', - executor: 'sim', - mode: 'async', + id: "delete_workspace_mcp_server", + name: "delete_workspace_mcp_server", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const Deploy: ToolCatalogEntry = { - id: 'deploy', - name: 'deploy', - executor: 'subagent', - mode: 'async', - subagentId: 'deploy', + id: "deploy", + name: "deploy", + executor: "subagent", + mode: "async", + subagentId: "deploy", internal: true, -} +}; export const DeployApi: ToolCatalogEntry = { - id: 'deploy_api', - name: 'deploy_api', - executor: 'sim', - mode: 'async', + id: "deploy_api", + name: "deploy_api", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const DeployChat: ToolCatalogEntry = { - id: 'deploy_chat', - name: 'deploy_chat', - executor: 'sim', - mode: 'async', + id: "deploy_chat", + name: "deploy_chat", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const DeployMcp: ToolCatalogEntry = { - id: 'deploy_mcp', - name: 'deploy_mcp', - executor: 'sim', - mode: 'async', + id: "deploy_mcp", + name: "deploy_mcp", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const DownloadToWorkspaceFile: ToolCatalogEntry = { - id: 'download_to_workspace_file', - name: 'download_to_workspace_file', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "download_to_workspace_file", + name: "download_to_workspace_file", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const EditWorkflow: ToolCatalogEntry = { - id: 'edit_workflow', - name: 'edit_workflow', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "edit_workflow", + name: "edit_workflow", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; -export const FileWrite: ToolCatalogEntry = { - id: 'file_write', - name: 'file_write', - executor: 'subagent', - mode: 'async', - subagentId: 'file_write', +export const File: ToolCatalogEntry = { + id: "file", + name: "file", + executor: "subagent", + mode: "async", + subagentId: "file", internal: true, -} +}; export const FunctionExecute: ToolCatalogEntry = { - id: 'function_execute', - name: 'function_execute', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "function_execute", + name: "function_execute", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const GenerateApiKey: ToolCatalogEntry = { - id: 'generate_api_key', - name: 'generate_api_key', - executor: 'sim', - mode: 'async', + id: "generate_api_key", + name: "generate_api_key", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const GenerateImage: ToolCatalogEntry = { - id: 'generate_image', - name: 'generate_image', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "generate_image", + name: "generate_image", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const GenerateVisualization: ToolCatalogEntry = { - id: 'generate_visualization', - name: 'generate_visualization', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "generate_visualization", + name: "generate_visualization", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const GetBlockOutputs: ToolCatalogEntry = { - id: 'get_block_outputs', - name: 'get_block_outputs', - executor: 'sim', - mode: 'async', -} + id: "get_block_outputs", + name: "get_block_outputs", + executor: "sim", + mode: "async", +}; export const GetBlockUpstreamReferences: ToolCatalogEntry = { - id: 'get_block_upstream_references', - name: 'get_block_upstream_references', - executor: 'sim', - mode: 'async', -} + id: "get_block_upstream_references", + name: "get_block_upstream_references", + executor: "sim", + mode: "async", +}; export const GetDeployedWorkflowState: ToolCatalogEntry = { - id: 'get_deployed_workflow_state', - name: 'get_deployed_workflow_state', - executor: 'sim', - mode: 'async', -} + id: "get_deployed_workflow_state", + name: "get_deployed_workflow_state", + executor: "sim", + mode: "async", +}; export const GetDeploymentVersion: ToolCatalogEntry = { - id: 'get_deployment_version', - name: 'get_deployment_version', - executor: 'sim', - mode: 'async', -} + id: "get_deployment_version", + name: "get_deployment_version", + executor: "sim", + mode: "async", +}; export const GetExecutionSummary: ToolCatalogEntry = { - id: 'get_execution_summary', - name: 'get_execution_summary', - executor: 'sim', - mode: 'async', -} + id: "get_execution_summary", + name: "get_execution_summary", + executor: "sim", + mode: "async", +}; export const GetJobLogs: ToolCatalogEntry = { - id: 'get_job_logs', - name: 'get_job_logs', - executor: 'sim', - mode: 'async', -} + id: "get_job_logs", + name: "get_job_logs", + executor: "sim", + mode: "async", +}; export const GetPageContents: ToolCatalogEntry = { - id: 'get_page_contents', - name: 'get_page_contents', - executor: 'go', - mode: 'sync', -} + id: "get_page_contents", + name: "get_page_contents", + executor: "go", + mode: "sync", +}; export const GetPlatformActions: ToolCatalogEntry = { - id: 'get_platform_actions', - name: 'get_platform_actions', - executor: 'sim', - mode: 'async', -} + id: "get_platform_actions", + name: "get_platform_actions", + executor: "sim", + mode: "async", +}; export const GetWorkflowData: ToolCatalogEntry = { - id: 'get_workflow_data', - name: 'get_workflow_data', - executor: 'sim', - mode: 'async', -} + id: "get_workflow_data", + name: "get_workflow_data", + executor: "sim", + mode: "async", +}; export const GetWorkflowLogs: ToolCatalogEntry = { - id: 'get_workflow_logs', - name: 'get_workflow_logs', - executor: 'sim', - mode: 'async', -} + id: "get_workflow_logs", + name: "get_workflow_logs", + executor: "sim", + mode: "async", +}; export const Glob: ToolCatalogEntry = { - id: 'glob', - name: 'glob', - executor: 'sim', - mode: 'async', -} + id: "glob", + name: "glob", + executor: "sim", + mode: "async", +}; export const Grep: ToolCatalogEntry = { - id: 'grep', - name: 'grep', - executor: 'sim', - mode: 'async', -} + id: "grep", + name: "grep", + executor: "sim", + mode: "async", +}; export const Job: ToolCatalogEntry = { - id: 'job', - name: 'job', - executor: 'subagent', - mode: 'async', - subagentId: 'job', + id: "job", + name: "job", + executor: "subagent", + mode: "async", + subagentId: "job", internal: true, -} +}; export const Knowledge: ToolCatalogEntry = { - id: 'knowledge', - name: 'knowledge', - executor: 'subagent', - mode: 'async', - subagentId: 'knowledge', + id: "knowledge", + name: "knowledge", + executor: "subagent", + mode: "async", + subagentId: "knowledge", internal: true, -} +}; export const KnowledgeBase: ToolCatalogEntry = { - id: 'knowledge_base', - name: 'knowledge_base', - executor: 'sim', - mode: 'async', + id: "knowledge_base", + name: "knowledge_base", + executor: "sim", + mode: "async", requiresConfirmation: true, -} +}; export const ListFolders: ToolCatalogEntry = { - id: 'list_folders', - name: 'list_folders', - executor: 'sim', - mode: 'async', -} + id: "list_folders", + name: "list_folders", + executor: "sim", + mode: "async", +}; export const ListUserWorkspaces: ToolCatalogEntry = { - id: 'list_user_workspaces', - name: 'list_user_workspaces', - executor: 'sim', - mode: 'async', -} + id: "list_user_workspaces", + name: "list_user_workspaces", + executor: "sim", + mode: "async", +}; export const ListWorkspaceMcpServers: ToolCatalogEntry = { - id: 'list_workspace_mcp_servers', - name: 'list_workspace_mcp_servers', - executor: 'sim', - mode: 'async', -} + id: "list_workspace_mcp_servers", + name: "list_workspace_mcp_servers", + executor: "sim", + mode: "async", +}; export const ManageCredential: ToolCatalogEntry = { - id: 'manage_credential', - name: 'manage_credential', - executor: 'sim', - mode: 'async', + id: "manage_credential", + name: "manage_credential", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const ManageCustomTool: ToolCatalogEntry = { - id: 'manage_custom_tool', - name: 'manage_custom_tool', - executor: 'sim', - mode: 'async', + id: "manage_custom_tool", + name: "manage_custom_tool", + executor: "sim", + mode: "async", requiresConfirmation: true, -} +}; export const ManageJob: ToolCatalogEntry = { - id: 'manage_job', - name: 'manage_job', - executor: 'sim', - mode: 'async', -} + id: "manage_job", + name: "manage_job", + executor: "sim", + mode: "async", +}; export const ManageMcpTool: ToolCatalogEntry = { - id: 'manage_mcp_tool', - name: 'manage_mcp_tool', - executor: 'sim', - mode: 'async', + id: "manage_mcp_tool", + name: "manage_mcp_tool", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'write', -} + requiredPermission: "write", +}; export const ManageSkill: ToolCatalogEntry = { - id: 'manage_skill', - name: 'manage_skill', - executor: 'sim', - mode: 'async', + id: "manage_skill", + name: "manage_skill", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'write', -} + requiredPermission: "write", +}; export const MaterializeFile: ToolCatalogEntry = { - id: 'materialize_file', - name: 'materialize_file', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "materialize_file", + name: "materialize_file", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const MoveFolder: ToolCatalogEntry = { - id: 'move_folder', - name: 'move_folder', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "move_folder", + name: "move_folder", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const MoveWorkflow: ToolCatalogEntry = { - id: 'move_workflow', - name: 'move_workflow', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "move_workflow", + name: "move_workflow", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const OauthGetAuthLink: ToolCatalogEntry = { - id: 'oauth_get_auth_link', - name: 'oauth_get_auth_link', - executor: 'sim', - mode: 'async', -} + id: "oauth_get_auth_link", + name: "oauth_get_auth_link", + executor: "sim", + mode: "async", +}; export const OauthRequestAccess: ToolCatalogEntry = { - id: 'oauth_request_access', - name: 'oauth_request_access', - executor: 'sim', - mode: 'async', + id: "oauth_request_access", + name: "oauth_request_access", + executor: "sim", + mode: "async", requiresConfirmation: true, -} +}; export const OpenResource: ToolCatalogEntry = { - id: 'open_resource', - name: 'open_resource', - executor: 'sim', - mode: 'async', -} + id: "open_resource", + name: "open_resource", + executor: "sim", + mode: "async", +}; export const Read: ToolCatalogEntry = { - id: 'read', - name: 'read', - executor: 'sim', - mode: 'async', -} + id: "read", + name: "read", + executor: "sim", + mode: "async", +}; export const Redeploy: ToolCatalogEntry = { - id: 'redeploy', - name: 'redeploy', - executor: 'sim', - mode: 'async', + id: "redeploy", + name: "redeploy", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const RenameWorkflow: ToolCatalogEntry = { - id: 'rename_workflow', - name: 'rename_workflow', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "rename_workflow", + name: "rename_workflow", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const Research: ToolCatalogEntry = { - id: 'research', - name: 'research', - executor: 'subagent', - mode: 'async', - subagentId: 'research', + id: "research", + name: "research", + executor: "subagent", + mode: "async", + subagentId: "research", internal: true, -} +}; export const Respond: ToolCatalogEntry = { - id: 'respond', - name: 'respond', - executor: 'sim', - mode: 'async', + id: "respond", + name: "respond", + executor: "sim", + mode: "async", internal: true, hidden: true, -} +}; export const RevertToVersion: ToolCatalogEntry = { - id: 'revert_to_version', - name: 'revert_to_version', - executor: 'sim', - mode: 'async', + id: "revert_to_version", + name: "revert_to_version", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const Run: ToolCatalogEntry = { - id: 'run', - name: 'run', - executor: 'subagent', - mode: 'async', - subagentId: 'run', + id: "run", + name: "run", + executor: "subagent", + mode: "async", + subagentId: "run", internal: true, -} +}; export const RunBlock: ToolCatalogEntry = { - id: 'run_block', - name: 'run_block', - executor: 'client', - mode: 'async', + id: "run_block", + name: "run_block", + executor: "client", + mode: "async", clientExecutable: true, requiresConfirmation: true, -} +}; export const RunFromBlock: ToolCatalogEntry = { - id: 'run_from_block', - name: 'run_from_block', - executor: 'client', - mode: 'async', + id: "run_from_block", + name: "run_from_block", + executor: "client", + mode: "async", clientExecutable: true, requiresConfirmation: true, -} +}; export const RunWorkflow: ToolCatalogEntry = { - id: 'run_workflow', - name: 'run_workflow', - executor: 'client', - mode: 'async', + id: "run_workflow", + name: "run_workflow", + executor: "client", + mode: "async", clientExecutable: true, requiresConfirmation: true, -} +}; export const RunWorkflowUntilBlock: ToolCatalogEntry = { - id: 'run_workflow_until_block', - name: 'run_workflow_until_block', - executor: 'client', - mode: 'async', + id: "run_workflow_until_block", + name: "run_workflow_until_block", + executor: "client", + mode: "async", clientExecutable: true, requiresConfirmation: true, -} +}; export const ScrapePage: ToolCatalogEntry = { - id: 'scrape_page', - name: 'scrape_page', - executor: 'go', - mode: 'sync', -} + id: "scrape_page", + name: "scrape_page", + executor: "go", + mode: "sync", +}; export const SearchDocumentation: ToolCatalogEntry = { - id: 'search_documentation', - name: 'search_documentation', - executor: 'sim', - mode: 'async', -} + id: "search_documentation", + name: "search_documentation", + executor: "sim", + mode: "async", +}; export const SearchLibraryDocs: ToolCatalogEntry = { - id: 'search_library_docs', - name: 'search_library_docs', - executor: 'go', - mode: 'sync', -} + id: "search_library_docs", + name: "search_library_docs", + executor: "go", + mode: "sync", +}; export const SearchOnline: ToolCatalogEntry = { - id: 'search_online', - name: 'search_online', - executor: 'go', - mode: 'sync', -} + id: "search_online", + name: "search_online", + executor: "go", + mode: "sync", +}; export const SearchPatterns: ToolCatalogEntry = { - id: 'search_patterns', - name: 'search_patterns', - executor: 'go', - mode: 'sync', -} + id: "search_patterns", + name: "search_patterns", + executor: "go", + mode: "sync", +}; export const SetEnvironmentVariables: ToolCatalogEntry = { - id: 'set_environment_variables', - name: 'set_environment_variables', - executor: 'sim', - mode: 'async', + id: "set_environment_variables", + name: "set_environment_variables", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'write', -} + requiredPermission: "write", +}; export const SetFileContext: ToolCatalogEntry = { - id: 'set_file_context', - name: 'set_file_context', - executor: 'sim', - mode: 'async', + id: "set_file_context", + name: "set_file_context", + executor: "sim", + mode: "async", hidden: true, -} +}; export const SetGlobalWorkflowVariables: ToolCatalogEntry = { - id: 'set_global_workflow_variables', - name: 'set_global_workflow_variables', - executor: 'sim', - mode: 'async', + id: "set_global_workflow_variables", + name: "set_global_workflow_variables", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'write', -} + requiredPermission: "write", +}; export const Superagent: ToolCatalogEntry = { - id: 'superagent', - name: 'superagent', - executor: 'subagent', - mode: 'async', - subagentId: 'superagent', + id: "superagent", + name: "superagent", + executor: "subagent", + mode: "async", + subagentId: "superagent", internal: true, -} +}; export const Table: ToolCatalogEntry = { - id: 'table', - name: 'table', - executor: 'subagent', - mode: 'async', - subagentId: 'table', + id: "table", + name: "table", + executor: "subagent", + mode: "async", + subagentId: "table", internal: true, -} +}; export const ToolSearchToolRegex: ToolCatalogEntry = { - id: 'tool_search_tool_regex', - name: 'tool_search_tool_regex', - executor: 'sim', - mode: 'async', -} + id: "tool_search_tool_regex", + name: "tool_search_tool_regex", + executor: "sim", + mode: "async", +}; export const UpdateJobHistory: ToolCatalogEntry = { - id: 'update_job_history', - name: 'update_job_history', - executor: 'sim', - mode: 'async', -} + id: "update_job_history", + name: "update_job_history", + executor: "sim", + mode: "async", +}; export const UpdateWorkspaceMcpServer: ToolCatalogEntry = { - id: 'update_workspace_mcp_server', - name: 'update_workspace_mcp_server', - executor: 'sim', - mode: 'async', + id: "update_workspace_mcp_server", + name: "update_workspace_mcp_server", + executor: "sim", + mode: "async", requiresConfirmation: true, - requiredPermission: 'admin', -} + requiredPermission: "admin", +}; export const UserMemory: ToolCatalogEntry = { - id: 'user_memory', - name: 'user_memory', - executor: 'go', - mode: 'sync', -} + id: "user_memory", + name: "user_memory", + executor: "go", + mode: "sync", +}; export const UserTable: ToolCatalogEntry = { - id: 'user_table', - name: 'user_table', - executor: 'sim', - mode: 'async', + id: "user_table", + name: "user_table", + executor: "sim", + mode: "async", requiresConfirmation: true, -} +}; export const Workflow: ToolCatalogEntry = { - id: 'workflow', - name: 'workflow', - executor: 'subagent', - mode: 'async', - subagentId: 'workflow', + id: "workflow", + name: "workflow", + executor: "subagent", + mode: "async", + subagentId: "workflow", internal: true, -} +}; export const WorkspaceFile: ToolCatalogEntry = { - id: 'workspace_file', - name: 'workspace_file', - executor: 'sim', - mode: 'async', - requiredPermission: 'write', -} + id: "workspace_file", + name: "workspace_file", + executor: "sim", + mode: "async", + requiredPermission: "write", +}; export const TOOL_CATALOG: Record = { [Agent.id]: Agent, @@ -880,7 +702,7 @@ export const TOOL_CATALOG: Record = { [DeployMcp.id]: DeployMcp, [DownloadToWorkspaceFile.id]: DownloadToWorkspaceFile, [EditWorkflow.id]: EditWorkflow, - [FileWrite.id]: FileWrite, + [File.id]: File, [FunctionExecute.id]: FunctionExecute, [GenerateApiKey.id]: GenerateApiKey, [GenerateImage.id]: GenerateImage, @@ -942,4 +764,4 @@ export const TOOL_CATALOG: Record = { [UserTable.id]: UserTable, [Workflow.id]: Workflow, [WorkspaceFile.id]: WorkspaceFile, -} +}; diff --git a/apps/sim/lib/copilot/request/handlers/tool.ts b/apps/sim/lib/copilot/request/handlers/tool.ts index 46e0d29827..e5d50743c3 100644 --- a/apps/sim/lib/copilot/request/handlers/tool.ts +++ b/apps/sim/lib/copilot/request/handlers/tool.ts @@ -21,7 +21,7 @@ import type { StreamingContext, ToolCallState, } from '@/lib/copilot/request/types' -import { isSimExecuted } from '@/lib/copilot/tool-executor' +import { isSimExecuted, getToolEntry } from '@/lib/copilot/tool-executor' import { isWorkflowToolName } from '@/lib/copilot/tools/workflow-tools' import type { ToolScope } from './types' import { @@ -191,8 +191,10 @@ async function handleCallPhase( if (isGoHandledInternalRead) return const { clientExecutable, simExecutable, internal } = getEventUI(event) + const catalogEntry = getToolEntry(toolName) + const isInternal = internal || catalogEntry?.internal === true const staticSimExecuted = isSimExecuted(toolName) - const willDispatch = !internal && (staticSimExecuted || simExecutable || clientExecutable) + const willDispatch = !isInternal && (staticSimExecuted || simExecutable || clientExecutable) logger.info('Tool call routing decision', { toolCallId, toolName, @@ -203,12 +205,12 @@ async function handleCallPhase( clientExecutable, simExecutable, staticSimExecuted, - internal, + internal: isInternal, hasPendingPromise: context.pendingToolPromises.has(toolCallId), existingStatus: existing?.status, willDispatch, }) - if (internal) return + if (isInternal) return if (!willDispatch) return await dispatchToolExecution( diff --git a/apps/sim/lib/copilot/tool-executor/index.ts b/apps/sim/lib/copilot/tool-executor/index.ts index a623fe7350..8bec66e812 100644 --- a/apps/sim/lib/copilot/tool-executor/index.ts +++ b/apps/sim/lib/copilot/tool-executor/index.ts @@ -8,6 +8,7 @@ export { } from './executor' export { ensureHandlersRegistered } from './register-handlers' export { + getToolEntry, isGoExecuted, isKnownTool, isSimExecuted,