Skip to content

Commit ff2e369

Browse files
Sg312waleedlatif1TheodoreSpeaksicecrasher321claude
authored
fix(mothership): fix workflow vfs reads (#4156)
* v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li <theo@sim.ai> * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha * fix build error * improvement(mothership): new agent loop (#3920) * feat(transport): replace shared chat transport with mothership-stream module * improvement(contracts): regenerate contracts from go * feat(tools): add tool catalog codegen from go tool contracts * feat(tools): add tool-executor dispatch framework for sim side tool routing * feat(orchestrator): rewrite tool dispatch with catalog-driven executor and simplified resume loop * feat(orchestrator): checkpoint resume flow * refactor(copilot): consolidate orchestrator into request/ layer * refactor(mothership): reorganize lib/copilot into structured subdirectories * refactor(mothership): canonical transcript layer, dead code cleanup, type consolidation * refactor(mothership): rebase onto latest staging * refactor(mothership): rename request continue to lifecycle * feat(trace): add initial version of request traces * improvement(stream): batch stream from redis * fix(resume): fix the resume checkpoint * fix(resume): fix resume client tool * fix(subagents): subagent resume should join on existing subagent text block * improvement(reconnect): harden reconnect logic * fix(superagent): fix superagent integration tools * improvement(stream): improve stream perf * Rebase with origin dev * fix(tests): fix failing test * fix(build): fix type errors * fix(build): fix build errors * fix(build): fix type errors * feat(mothership): add cli execution * fix(mothership): fix function execute tests * Force redeploy * feat(motheship): add docx support * feat(mothership): append * Add deps * improvement(mothership): docs * File types * Add client retry logic * Fix stream reconnect * Eager tool streaming * Fix client side tools * Security * Fix shell var injection * Remove auto injected tasks * Fix 10mb tool response limit * Fix trailing leak * Remove dead tools * file/folder tools * Folder tools * Hide function code inline * Dont show internal tool result reads * Fix spacing * Auth vfs * Empty folders should show in vfs * Fix run workflow * change to node runtime * revert back to bun runtime * Fix * Appends * Remove debug logs * Patch * Fix patch tool * Temp * Checkpoint * File writes * Fix * Remove tool truncation limits * Bad hook * replace react markdown with streamdown * Checkpoitn * fix code block * fix stream persistence * temp * Fix file tools * tool joining * cleanup subagent + streaming issues * streamed text change * Tool display intetns * Fix dev * Fix tests * Fix dev * Speed up dev ci * Add req id * Fix persistence * Tool call names * fix payload accesses * Fix name * fix snapshot crash bug * fix * Fix * remove worker code * Clickable resources * Options ordering * Folder vfs * Restore and mass delete tools * Fix * lint * Update request tracing and skills and handlers * Fix editable * fix type error * Html code * fix(chat): make inline code inherit parent font size in markdown headers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * improved autolayout * durable stream for files * one more fix * POSSIBLE BREAKAGE: SCROLLING * Fixes * Fixes * Lint fix * fix(resource): fix resource view disappearing on ats (#4103) Co-authored-by: Theodore Li <theo@sim.ai> * Fixes * feat(mothership): add execution logs as a resource type Adds `log` as a first-class mothership resource type so copilot can open and display workflow execution logs as tabs alongside workflows, tables, files, and knowledge bases. - Add `log` to MothershipResourceType, all Zod enums, and VALID_RESOURCE_TYPES - Register log in RESOURCE_REGISTRY (Library icon) and RESOURCE_INVALIDATORS - Add EmbeddedLog and EmbeddedLogActions components in resource-content - Export WorkflowOutputSection from log-details for reuse in EmbeddedLog - Add log resolution branch in open_resource handler via new getLogById service - Include log id in get_workflow_logs response and extract resources from output - Exclude log from manual add-resource dropdown (enters via copilot tools only) - Regenerate copilot contracts after adding log to open_resource Go enum * Fix perf and message queueing * Fix abort * fix(ui): dont delete resource on clearing from context, set resource closed on new task (#4113) Co-authored-by: Theodore Li <theo@sim.ai> * improvement(mothership): structure sim side typing * address comments * reactive text editor tweaks * Fix file read and tool call name persistence bug * Fix code stream + create file opening resource * fix use chat race + headless trace issues * Fix type issue * Fix mothership block req lifecycle * Fix build * Move copy reqid * Fix * fix(ui): fix resource tag transition from home to task (#4132) Co-authored-by: Theodore Li <theo@sim.ai> * Fix persistence * Clean code, fix bugs * Fix * Fixes --------- Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Theodore Li <theodoreqili@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Theodore Li <theo@sim.ai>
1 parent 64cdab2 commit ff2e369

File tree

16 files changed

+293
-57
lines changed

16 files changed

+293
-57
lines changed

apps/sim/app/api/mcp/copilot/route.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -688,19 +688,26 @@ async function handleBuildToolCall(
688688
userId,
689689
action: 'read',
690690
})
691-
return authorization.allowed ? { workflowId } : null
691+
return authorization.allowed
692+
? { status: 'resolved' as const, workflowId }
693+
: {
694+
status: 'not_found' as const,
695+
message: 'workflowId is required for build. Call create_workflow first.',
696+
}
692697
})()
693698
: await resolveWorkflowIdForUser(userId)
694699

695-
if (!resolved?.workflowId) {
700+
if (!resolved || resolved.status !== 'resolved') {
696701
return {
697702
content: [
698703
{
699704
type: 'text',
700705
text: JSON.stringify(
701706
{
702707
success: false,
703-
error: 'workflowId is required for build. Call create_workflow first.',
708+
error:
709+
resolved?.message ??
710+
'workflowId is required for build. Call create_workflow first.',
704711
},
705712
null,
706713
2

apps/sim/app/api/v1/copilot/chat/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ const RequestSchema = z.object({
2929
*
3030
* workflowId is optional - if not provided:
3131
* - If workflowName is provided, finds that workflow
32-
* - Otherwise uses the user's first workflow as context
33-
* - The copilot can still operate on any workflow using list_user_workflows
32+
* - If exactly one workflow is available, uses that workflow as context
33+
* - Otherwise requires workflowId or workflowName to disambiguate
3434
*/
3535
export async function POST(req: NextRequest) {
3636
let messageId: string | undefined
@@ -54,11 +54,11 @@ export async function POST(req: NextRequest) {
5454
parsed.workflowName,
5555
auth.keyType === 'workspace' ? auth.workspaceId : undefined
5656
)
57-
if (!resolved) {
57+
if (resolved.status !== 'resolved') {
5858
return NextResponse.json(
5959
{
6060
success: false,
61-
error: 'No workflows found. Create a workflow first or provide a valid workflowId.',
61+
error: resolved.message,
6262
},
6363
{ status: 400 }
6464
)

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ interface AgentGroupProps {
2121
}
2222

2323
function isToolDone(status: ToolCallData['status']): boolean {
24-
return status === 'success' || status === 'error' || status === 'cancelled'
24+
return (
25+
status === 'success' ||
26+
status === 'error' ||
27+
status === 'cancelled' ||
28+
status === 'skipped' ||
29+
status === 'rejected'
30+
)
2531
}
2632

2733
export function AgentGroup({

apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ function resolveAgentLabel(key: string): string {
7070
}
7171

7272
function isToolDone(status: ToolCallData['status']): boolean {
73-
return status === 'success' || status === 'error' || status === 'cancelled'
73+
return (
74+
status === 'success' ||
75+
status === 'error' ||
76+
status === 'cancelled' ||
77+
status === 'skipped' ||
78+
status === 'rejected'
79+
)
7480
}
7581

7682
function isDelegatingTool(tc: NonNullable<ContentBlock['toolCall']>): boolean {
@@ -87,6 +93,10 @@ function mapToolStatusToClientState(
8793
return ClientToolCallState.error
8894
case 'cancelled':
8995
return ClientToolCallState.cancelled
96+
case 'skipped':
97+
return ClientToolCallState.aborted
98+
case 'rejected':
99+
return ClientToolCallState.rejected
90100
default:
91101
return ClientToolCallState.executing
92102
}

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/generic-resource-content.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ export function GenericResourceContent({ data }: GenericResourceContentProps) {
4141
{entry.status === 'error' && (
4242
<span className='ml-auto text-[12px] text-[var(--text-error)]'>Error</span>
4343
)}
44+
{entry.status === 'skipped' && (
45+
<span className='ml-auto text-[12px] text-[var(--text-muted)]'>Skipped</span>
46+
)}
47+
{entry.status === 'rejected' && (
48+
<span className='ml-auto text-[12px] text-[var(--text-muted)]'>Rejected</span>
49+
)}
4450
</div>
4551
{entry.streamingArgs && (
4652
<pre className='overflow-x-auto whitespace-pre-wrap break-words font-mono text-[12px] text-[var(--text-body)]'>

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ import type {
119119
MothershipResourceType,
120120
QueuedMessage,
121121
} from '../types'
122+
import { ToolCallStatus } from '../types'
122123

123124
const FILE_SUBAGENT_ID = 'file'
124125

@@ -610,6 +611,28 @@ function getToolUI(ui?: MothershipStreamV1ToolUI): StreamToolUI | undefined {
610611
}
611612
}
612613

614+
function resolveLiveToolStatus(
615+
payload: Partial<{
616+
status: string
617+
success: boolean
618+
}>
619+
): ToolCallStatus {
620+
switch (payload.status) {
621+
case MothershipStreamV1ToolOutcome.success:
622+
return ToolCallStatus.success
623+
case MothershipStreamV1ToolOutcome.error:
624+
return ToolCallStatus.error
625+
case MothershipStreamV1ToolOutcome.cancelled:
626+
return ToolCallStatus.cancelled
627+
case MothershipStreamV1ToolOutcome.skipped:
628+
return ToolCallStatus.skipped
629+
case MothershipStreamV1ToolOutcome.rejected:
630+
return ToolCallStatus.rejected
631+
default:
632+
return payload.success === true ? ToolCallStatus.success : ToolCallStatus.error
633+
}
634+
}
635+
613636
/** Adds a workflow to the React Query cache with a top-insertion sort order if it doesn't already exist. */
614637
function ensureWorkflowInRegistry(resourceId: string, title: string, workspaceId: string): boolean {
615638
const workflows = getWorkflows(workspaceId)
@@ -1396,6 +1419,7 @@ export function useChat(
13961419
let activeSubagent: string | undefined
13971420
let activeSubagentParentToolCallId: string | undefined
13981421
let activeCompactionId: string | undefined
1422+
const subagentByParentToolCallId = new Map<string, string>()
13991423

14001424
if (preserveState) {
14011425
for (let i = blocks.length - 1; i >= 0; i--) {
@@ -1418,20 +1442,32 @@ export function useChat(
14181442
streamingBlocksRef.current = []
14191443
}
14201444

1421-
const ensureTextBlock = (): ContentBlock => {
1445+
const ensureTextBlock = (subagentName?: string): ContentBlock => {
14221446
const last = blocks[blocks.length - 1]
1423-
if (last?.type === 'text' && last.subagent === activeSubagent) return last
1447+
if (last?.type === 'text' && last.subagent === subagentName) return last
14241448
const b: ContentBlock = { type: 'text', content: '' }
1449+
if (subagentName) b.subagent = subagentName
14251450
blocks.push(b)
14261451
return b
14271452
}
14281453

1429-
const appendInlineErrorTag = (tag: string) => {
1454+
const resolveScopedSubagent = (
1455+
agentId: string | undefined,
1456+
parentToolCallId: string | undefined
1457+
): string | undefined => {
1458+
if (agentId) return agentId
1459+
if (parentToolCallId) {
1460+
const scoped = subagentByParentToolCallId.get(parentToolCallId)
1461+
if (scoped) return scoped
1462+
}
1463+
return activeSubagent
1464+
}
1465+
1466+
const appendInlineErrorTag = (tag: string, subagentName?: string) => {
14301467
if (runningText.includes(tag)) return
1431-
const tb = ensureTextBlock()
1468+
const tb = ensureTextBlock(subagentName)
14321469
const prefix = runningText.length > 0 && !runningText.endsWith('\n') ? '\n' : ''
14331470
tb.content = `${tb.content ?? ''}${prefix}${tag}`
1434-
if (activeSubagent) tb.subagent = activeSubagent
14351471
runningText += `${prefix}${tag}`
14361472
streamingContentRef.current = runningText
14371473
flush()
@@ -1545,6 +1581,13 @@ export function useChat(
15451581
}
15461582

15471583
logger.debug('SSE event received', parsed)
1584+
const scopedParentToolCallId =
1585+
typeof parsed.scope?.parentToolCallId === 'string'
1586+
? parsed.scope.parentToolCallId
1587+
: undefined
1588+
const scopedAgentId =
1589+
typeof parsed.scope?.agentId === 'string' ? parsed.scope.agentId : undefined
1590+
const scopedSubagent = resolveScopedSubagent(scopedAgentId, scopedParentToolCallId)
15481591
switch (parsed.type) {
15491592
case MothershipStreamV1EventType.session: {
15501593
const payload = parsed.payload
@@ -1600,16 +1643,15 @@ export function useChat(
16001643
case MothershipStreamV1EventType.text: {
16011644
const chunk = parsed.payload.text
16021645
if (chunk) {
1603-
const contentSource: 'main' | 'subagent' = activeSubagent ? 'subagent' : 'main'
1646+
const contentSource: 'main' | 'subagent' = scopedSubagent ? 'subagent' : 'main'
16041647
const needsBoundaryNewline =
16051648
lastContentSource !== null &&
16061649
lastContentSource !== contentSource &&
16071650
runningText.length > 0 &&
16081651
!runningText.endsWith('\n')
1609-
const tb = ensureTextBlock()
1652+
const tb = ensureTextBlock(scopedSubagent)
16101653
const normalizedChunk = needsBoundaryNewline ? `\n${chunk}` : chunk
16111654
tb.content = (tb.content ?? '') + normalizedChunk
1612-
if (activeSubagent) tb.subagent = activeSubagent
16131655
runningText += normalizedChunk
16141656
lastContentSource = contentSource
16151657
streamingContentRef.current = runningText
@@ -1800,22 +1842,24 @@ export function useChat(
18001842
}
18011843
const tc = blocks[idx].toolCall!
18021844
const outputObj = asPayloadRecord(payload.output)
1803-
const success =
1804-
payload.success ?? payload.status === MothershipStreamV1ToolOutcome.success
18051845
const isCancelled =
18061846
outputObj?.reason === 'user_cancelled' ||
18071847
outputObj?.cancelledByUser === true ||
18081848
payload.status === MothershipStreamV1ToolOutcome.cancelled
1849+
const status = isCancelled
1850+
? ToolCallStatus.cancelled
1851+
: resolveLiveToolStatus(payload)
1852+
const isSuccess = status === ToolCallStatus.success
18091853

1810-
if (isCancelled) {
1811-
tc.status = 'cancelled'
1854+
if (status === ToolCallStatus.cancelled) {
1855+
tc.status = ToolCallStatus.cancelled
18121856
tc.displayTitle = 'Stopped by user'
18131857
} else {
1814-
tc.status = success ? 'success' : 'error'
1858+
tc.status = status
18151859
}
18161860
tc.streamingArgs = undefined
18171861
tc.result = {
1818-
success: !!success,
1862+
success: isSuccess,
18191863
output: payload.output,
18201864
error: typeof payload.error === 'string' ? payload.error : undefined,
18211865
}
@@ -1902,7 +1946,7 @@ export function useChat(
19021946
})
19031947
setActiveResourceId(fileResource.id)
19041948
invalidateResourceQueries(queryClient, workspaceId, 'file', fileResource.id)
1905-
} else if (!activeSubagent || activeSubagent !== FILE_SUBAGENT_ID) {
1949+
} else if (tc.calledBy !== FILE_SUBAGENT_ID) {
19061950
setResources((rs) => rs.filter((r) => r.id !== 'streaming-file'))
19071951
}
19081952
}
@@ -1948,7 +1992,7 @@ export function useChat(
19481992
status: 'executing',
19491993
displayTitle,
19501994
params: args,
1951-
calledBy: activeSubagent,
1995+
calledBy: scopedSubagent,
19521996
},
19531997
})
19541998
if (name === ReadTool.id || isResourceToolName(name)) {
@@ -2064,23 +2108,18 @@ export function useChat(
20642108
}
20652109
const spanData = asPayloadRecord(payload.data)
20662110
const parentToolCallId =
2067-
typeof parsed.scope?.parentToolCallId === 'string'
2068-
? parsed.scope.parentToolCallId
2069-
: typeof spanData?.tool_call_id === 'string'
2070-
? spanData.tool_call_id
2071-
: undefined
2111+
scopedParentToolCallId ??
2112+
(typeof spanData?.tool_call_id === 'string' ? spanData.tool_call_id : undefined)
20722113
const isPendingPause = spanData?.pending === true
2073-
const name =
2074-
typeof payload.agent === 'string'
2075-
? payload.agent
2076-
: typeof parsed.scope?.agentId === 'string'
2077-
? parsed.scope.agentId
2078-
: undefined
2114+
const name = typeof payload.agent === 'string' ? payload.agent : scopedAgentId
20792115
if (payload.event === MothershipStreamV1SpanLifecycleEvent.start && name) {
20802116
const isSameActiveSubagent =
20812117
activeSubagent === name &&
20822118
activeSubagentParentToolCallId &&
20832119
parentToolCallId === activeSubagentParentToolCallId
2120+
if (parentToolCallId) {
2121+
subagentByParentToolCallId.set(parentToolCallId, name)
2122+
}
20842123
activeSubagent = name
20852124
activeSubagentParentToolCallId = parentToolCallId
20862125
if (!isSameActiveSubagent) {
@@ -2104,6 +2143,9 @@ export function useChat(
21042143
if (isPendingPause) {
21052144
break
21062145
}
2146+
if (parentToolCallId) {
2147+
subagentByParentToolCallId.delete(parentToolCallId)
2148+
}
21072149
if (previewSessionRef.current && !activePreviewSessionIdRef.current) {
21082150
const lastFileResource = resourcesRef.current.find(
21092151
(r) => r.type === 'file' && r.id !== 'streaming-file'
@@ -2113,8 +2155,14 @@ export function useChat(
21132155
setActiveResourceId(lastFileResource.id)
21142156
}
21152157
}
2116-
activeSubagent = undefined
2117-
activeSubagentParentToolCallId = undefined
2158+
if (
2159+
!parentToolCallId ||
2160+
parentToolCallId === activeSubagentParentToolCallId ||
2161+
name === activeSubagent
2162+
) {
2163+
activeSubagent = undefined
2164+
activeSubagentParentToolCallId = undefined
2165+
}
21182166
blocks.push({ type: 'subagent_end' })
21192167
flush()
21202168
}
@@ -2123,7 +2171,7 @@ export function useChat(
21232171
case MothershipStreamV1EventType.error: {
21242172
sawStreamError = true
21252173
setError(parsed.payload.message || parsed.payload.error || 'An error occurred')
2126-
appendInlineErrorTag(buildInlineErrorTag(parsed.payload))
2174+
appendInlineErrorTag(buildInlineErrorTag(parsed.payload), scopedSubagent)
21272175
break
21282176
}
21292177
case MothershipStreamV1EventType.complete: {

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ export const ToolCallStatus = {
5959
success: 'success',
6060
error: 'error',
6161
cancelled: 'cancelled',
62+
skipped: 'skipped',
63+
rejected: 'rejected',
6264
} as const
6365
export type ToolCallStatus = (typeof ToolCallStatus)[keyof typeof ToolCallStatus]
6466

0 commit comments

Comments
 (0)