Skip to content

Commit 6751745

Browse files
committed
Fixes
1 parent fee6ac5 commit 6751745

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)
@@ -1421,6 +1444,7 @@ export function useChat(
14211444
let activeSubagent: string | undefined
14221445
let activeSubagentParentToolCallId: string | undefined
14231446
let activeCompactionId: string | undefined
1447+
const subagentByParentToolCallId = new Map<string, string>()
14241448

14251449
if (preserveState) {
14261450
for (let i = blocks.length - 1; i >= 0; i--) {
@@ -1443,20 +1467,32 @@ export function useChat(
14431467
streamingBlocksRef.current = []
14441468
}
14451469

1446-
const ensureTextBlock = (): ContentBlock => {
1470+
const ensureTextBlock = (subagentName?: string): ContentBlock => {
14471471
const last = blocks[blocks.length - 1]
1448-
if (last?.type === 'text' && last.subagent === activeSubagent) return last
1472+
if (last?.type === 'text' && last.subagent === subagentName) return last
14491473
const b: ContentBlock = { type: 'text', content: '' }
1474+
if (subagentName) b.subagent = subagentName
14501475
blocks.push(b)
14511476
return b
14521477
}
14531478

1454-
const appendInlineErrorTag = (tag: string) => {
1479+
const resolveScopedSubagent = (
1480+
agentId: string | undefined,
1481+
parentToolCallId: string | undefined
1482+
): string | undefined => {
1483+
if (agentId) return agentId
1484+
if (parentToolCallId) {
1485+
const scoped = subagentByParentToolCallId.get(parentToolCallId)
1486+
if (scoped) return scoped
1487+
}
1488+
return activeSubagent
1489+
}
1490+
1491+
const appendInlineErrorTag = (tag: string, subagentName?: string) => {
14551492
if (runningText.includes(tag)) return
1456-
const tb = ensureTextBlock()
1493+
const tb = ensureTextBlock(subagentName)
14571494
const prefix = runningText.length > 0 && !runningText.endsWith('\n') ? '\n' : ''
14581495
tb.content = `${tb.content ?? ''}${prefix}${tag}`
1459-
if (activeSubagent) tb.subagent = activeSubagent
14601496
runningText += `${prefix}${tag}`
14611497
streamingContentRef.current = runningText
14621498
flush()
@@ -1570,6 +1606,13 @@ export function useChat(
15701606
}
15711607

15721608
logger.debug('SSE event received', parsed)
1609+
const scopedParentToolCallId =
1610+
typeof parsed.scope?.parentToolCallId === 'string'
1611+
? parsed.scope.parentToolCallId
1612+
: undefined
1613+
const scopedAgentId =
1614+
typeof parsed.scope?.agentId === 'string' ? parsed.scope.agentId : undefined
1615+
const scopedSubagent = resolveScopedSubagent(scopedAgentId, scopedParentToolCallId)
15731616
switch (parsed.type) {
15741617
case MothershipStreamV1EventType.session: {
15751618
const payload = parsed.payload
@@ -1623,16 +1666,15 @@ export function useChat(
16231666
case MothershipStreamV1EventType.text: {
16241667
const chunk = parsed.payload.text
16251668
if (chunk) {
1626-
const contentSource: 'main' | 'subagent' = activeSubagent ? 'subagent' : 'main'
1669+
const contentSource: 'main' | 'subagent' = scopedSubagent ? 'subagent' : 'main'
16271670
const needsBoundaryNewline =
16281671
lastContentSource !== null &&
16291672
lastContentSource !== contentSource &&
16301673
runningText.length > 0 &&
16311674
!runningText.endsWith('\n')
1632-
const tb = ensureTextBlock()
1675+
const tb = ensureTextBlock(scopedSubagent)
16331676
const normalizedChunk = needsBoundaryNewline ? `\n${chunk}` : chunk
16341677
tb.content = (tb.content ?? '') + normalizedChunk
1635-
if (activeSubagent) tb.subagent = activeSubagent
16361678
runningText += normalizedChunk
16371679
lastContentSource = contentSource
16381680
streamingContentRef.current = runningText
@@ -1823,22 +1865,24 @@ export function useChat(
18231865
}
18241866
const tc = blocks[idx].toolCall!
18251867
const outputObj = asPayloadRecord(payload.output)
1826-
const success =
1827-
payload.success ?? payload.status === MothershipStreamV1ToolOutcome.success
18281868
const isCancelled =
18291869
outputObj?.reason === 'user_cancelled' ||
18301870
outputObj?.cancelledByUser === true ||
18311871
payload.status === MothershipStreamV1ToolOutcome.cancelled
1872+
const status = isCancelled
1873+
? ToolCallStatus.cancelled
1874+
: resolveLiveToolStatus(payload)
1875+
const isSuccess = status === ToolCallStatus.success
18321876

1833-
if (isCancelled) {
1834-
tc.status = 'cancelled'
1877+
if (status === ToolCallStatus.cancelled) {
1878+
tc.status = ToolCallStatus.cancelled
18351879
tc.displayTitle = 'Stopped by user'
18361880
} else {
1837-
tc.status = success ? 'success' : 'error'
1881+
tc.status = status
18381882
}
18391883
tc.streamingArgs = undefined
18401884
tc.result = {
1841-
success: !!success,
1885+
success: isSuccess,
18421886
output: payload.output,
18431887
error: typeof payload.error === 'string' ? payload.error : undefined,
18441888
}
@@ -1925,7 +1969,7 @@ export function useChat(
19251969
})
19261970
setActiveResourceId(fileResource.id)
19271971
invalidateResourceQueries(queryClient, workspaceId, 'file', fileResource.id)
1928-
} else if (!activeSubagent || activeSubagent !== FILE_SUBAGENT_ID) {
1972+
} else if (tc.calledBy !== FILE_SUBAGENT_ID) {
19291973
setResources((rs) => rs.filter((r) => r.id !== 'streaming-file'))
19301974
}
19311975
}
@@ -1971,7 +2015,7 @@ export function useChat(
19712015
status: 'executing',
19722016
displayTitle,
19732017
params: args,
1974-
calledBy: activeSubagent,
2018+
calledBy: scopedSubagent,
19752019
},
19762020
})
19772021
if (name === ReadTool.id || isResourceToolName(name)) {
@@ -2087,23 +2131,18 @@ export function useChat(
20872131
}
20882132
const spanData = asPayloadRecord(payload.data)
20892133
const parentToolCallId =
2090-
typeof parsed.scope?.parentToolCallId === 'string'
2091-
? parsed.scope.parentToolCallId
2092-
: typeof spanData?.tool_call_id === 'string'
2093-
? spanData.tool_call_id
2094-
: undefined
2134+
scopedParentToolCallId ??
2135+
(typeof spanData?.tool_call_id === 'string' ? spanData.tool_call_id : undefined)
20952136
const isPendingPause = spanData?.pending === true
2096-
const name =
2097-
typeof payload.agent === 'string'
2098-
? payload.agent
2099-
: typeof parsed.scope?.agentId === 'string'
2100-
? parsed.scope.agentId
2101-
: undefined
2137+
const name = typeof payload.agent === 'string' ? payload.agent : scopedAgentId
21022138
if (payload.event === MothershipStreamV1SpanLifecycleEvent.start && name) {
21032139
const isSameActiveSubagent =
21042140
activeSubagent === name &&
21052141
activeSubagentParentToolCallId &&
21062142
parentToolCallId === activeSubagentParentToolCallId
2143+
if (parentToolCallId) {
2144+
subagentByParentToolCallId.set(parentToolCallId, name)
2145+
}
21072146
activeSubagent = name
21082147
activeSubagentParentToolCallId = parentToolCallId
21092148
if (!isSameActiveSubagent) {
@@ -2127,6 +2166,9 @@ export function useChat(
21272166
if (isPendingPause) {
21282167
break
21292168
}
2169+
if (parentToolCallId) {
2170+
subagentByParentToolCallId.delete(parentToolCallId)
2171+
}
21302172
if (previewSessionRef.current && !activePreviewSessionIdRef.current) {
21312173
const lastFileResource = resourcesRef.current.find(
21322174
(r) => r.type === 'file' && r.id !== 'streaming-file'
@@ -2136,8 +2178,14 @@ export function useChat(
21362178
setActiveResourceId(lastFileResource.id)
21372179
}
21382180
}
2139-
activeSubagent = undefined
2140-
activeSubagentParentToolCallId = undefined
2181+
if (
2182+
!parentToolCallId ||
2183+
parentToolCallId === activeSubagentParentToolCallId ||
2184+
name === activeSubagent
2185+
) {
2186+
activeSubagent = undefined
2187+
activeSubagentParentToolCallId = undefined
2188+
}
21412189
blocks.push({ type: 'subagent_end' })
21422190
flush()
21432191
}
@@ -2146,7 +2194,7 @@ export function useChat(
21462194
case MothershipStreamV1EventType.error: {
21472195
sawStreamError = true
21482196
setError(parsed.payload.message || parsed.payload.error || 'An error occurred')
2149-
appendInlineErrorTag(buildInlineErrorTag(parsed.payload))
2197+
appendInlineErrorTag(buildInlineErrorTag(parsed.payload), scopedSubagent)
21502198
break
21512199
}
21522200
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)