From d92be79840793a4af1110a66449ddab678bbb928 Mon Sep 17 00:00:00 2001 From: waleed Date: Sun, 3 May 2026 23:47:50 -0700 Subject: [PATCH 1/3] fix(mothership): stop persisting log resources from get_workflow_logs and self-heal stale log panel entries --- .../resource-content/resource-content.tsx | 29 ++++++++++++++++--- .../mothership-view/mothership-view.tsx | 1 + .../[workspaceId]/home/hooks/use-chat.ts | 12 ++++++++ apps/sim/lib/copilot/resources/extraction.ts | 15 ---------- 4 files changed, 38 insertions(+), 19 deletions(-) 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 46f78e1f89e..1e19f5628e7 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 @@ -1,6 +1,6 @@ 'use client' -import { lazy, memo, Suspense, useEffect, useMemo } from 'react' +import { lazy, memo, Suspense, useEffect, useMemo, useRef } from 'react' import { createLogger } from '@sim/logger' import { Square } from 'lucide-react' import { useRouter } from 'next/navigation' @@ -13,6 +13,7 @@ import { SquareArrowUpRight, WorkflowX, } from '@/components/emcn/icons' +import { isApiClientError } from '@/lib/api/client/errors' import type { FilePreviewSession } from '@/lib/copilot/request/session' import { cancelRunToolExecution, @@ -70,6 +71,7 @@ interface ResourceContentProps { previewSession?: FilePreviewSession | null genericResourceData?: GenericResourceData previewContextKey?: string + onNotFound?: (resourceId: string) => void } /** @@ -86,6 +88,7 @@ export const ResourceContent = memo(function ResourceContent({ previewSession, genericResourceData, previewContextKey, + onNotFound, }: ResourceContentProps) { const streamFileName = previewSession?.fileName || 'file.md' const syntheticFile = useMemo(() => { @@ -179,7 +182,13 @@ export const ResourceContent = memo(function ResourceContent({ return case 'log': - return + return ( + onNotFound(resource.id) : undefined} + /> + ) case 'generic': return ( @@ -618,10 +627,22 @@ function EmbeddedFolder({ workspaceId, folderId }: EmbeddedFolderProps) { interface EmbeddedLogProps { logId: string + onNotFound?: () => void } -function EmbeddedLog({ logId }: EmbeddedLogProps) { - const { data: log, isLoading } = useLogDetail(logId) +function EmbeddedLog({ logId, onNotFound }: EmbeddedLogProps) { + const { data: log, isLoading, error } = useLogDetail(logId) + + const onNotFoundRef = useRef(onNotFound) + useEffect(() => { + onNotFoundRef.current = onNotFound + }, [onNotFound]) + + useEffect(() => { + if (isApiClientError(error) && error.status === 404) { + onNotFoundRef.current?.() + } + }, [error]) if (isLoading) return LOADING_SKELETON 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 fcfb08ff948..4eb7227c850 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 @@ -128,6 +128,7 @@ export const MothershipView = memo( previewSession={previewForActive} genericResourceData={active.type === 'generic' ? genericResourceData : undefined} previewContextKey={chatId} + onNotFound={(resourceId) => onRemoveResource('log', resourceId)} /> ) : (
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 b4dccd4d4df..0799193d148 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -1420,6 +1420,18 @@ export function useChat( const removeResource = useCallback((resourceType: MothershipResourceType, resourceId: string) => { setResources((prev) => prev.filter((r) => !(r.type === resourceType && r.id === resourceId))) setActiveResourceId((prev) => (prev === resourceId ? null : prev)) + + const persistChatId = chatIdRef.current ?? selectedChatIdRef.current + if (persistChatId) { + // boundary-raw-fetch: fire-and-forget side-effect; intentionally avoids requestJson's response parsing/throw semantics so a transient failure cannot interrupt the caller + fetch('/api/mothership/chat/resources', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ chatId: persistChatId, resourceType, resourceId }), + }).catch((err) => { + logger.warn('Failed to persist resource removal', err) + }) + } }, []) const reorderResources = useCallback((newOrder: MothershipResource[]) => { diff --git a/apps/sim/lib/copilot/resources/extraction.ts b/apps/sim/lib/copilot/resources/extraction.ts index 29ca644a21a..6cba5a3bee0 100644 --- a/apps/sim/lib/copilot/resources/extraction.ts +++ b/apps/sim/lib/copilot/resources/extraction.ts @@ -7,7 +7,6 @@ import { FunctionExecute, GenerateImage, GenerateVisualization, - GetWorkflowLogs, Knowledge, KnowledgeBase, UserTable, @@ -30,7 +29,6 @@ const RESOURCE_TOOL_NAMES: Set = new Set([ Knowledge.id, GenerateVisualization.id, GenerateImage.id, - GetWorkflowLogs.id, ]) export function isResourceToolName(toolName: string): boolean { @@ -214,19 +212,6 @@ export function extractResourcesFromToolResult( return resources } - case GetWorkflowLogs.id: { - const entries = Array.isArray(output) ? output : Array.isArray(result.data) ? result.data : [] - const resources: ChatResource[] = [] - for (const entry of entries) { - const rec = asRecord(entry) - const logId = rec.id as string | undefined - if (logId) { - resources.push({ type: 'log', id: logId, title: 'Log' }) - } - } - return resources - } - default: return [] } From fd3152607188aa50f0c220d44661ead28b38405c Mon Sep 17 00:00:00 2001 From: waleed Date: Sun, 3 May 2026 23:56:12 -0700 Subject: [PATCH 2/3] fix(mothership): skip retries on 404 in useLogDetail for instant self-heal --- apps/sim/hooks/queries/logs.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/sim/hooks/queries/logs.ts b/apps/sim/hooks/queries/logs.ts index bd5b0e5e695..d2f4bbfa4e9 100644 --- a/apps/sim/hooks/queries/logs.ts +++ b/apps/sim/hooks/queries/logs.ts @@ -7,6 +7,7 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' +import { isApiClientError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import { cancelWorkflowExecutionContract, @@ -194,6 +195,8 @@ export function useLogDetail(logId: string | undefined, options?: UseLogDetailOp enabled: Boolean(logId) && (options?.enabled ?? true), refetchInterval: options?.refetchInterval ?? false, staleTime: 30 * 1000, + retry: (failureCount, err) => + !(isApiClientError(err) && err.status === 404) && failureCount < 3, }) } From 6ce9f5992a23140e0db94c7645e9170f255a5019 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 4 May 2026 00:05:48 -0700 Subject: [PATCH 3/3] fix(mothership): simplify onNotFoundRef sync to inline assignment --- .../components/resource-content/resource-content.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 1e19f5628e7..33d4ade4e56 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 @@ -634,9 +634,7 @@ function EmbeddedLog({ logId, onNotFound }: EmbeddedLogProps) { const { data: log, isLoading, error } = useLogDetail(logId) const onNotFoundRef = useRef(onNotFound) - useEffect(() => { - onNotFoundRef.current = onNotFound - }, [onNotFound]) + onNotFoundRef.current = onNotFound useEffect(() => { if (isApiClientError(error) && error.status === 404) {