Skip to content

Commit 1beb35c

Browse files
committed
Fix run from block in copilot
1 parent 7670cdf commit 1beb35c

File tree

4 files changed

+84
-46
lines changed

4 files changed

+84
-46
lines changed

apps/sim/lib/copilot/client-sse/subagent-handlers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,10 @@ export const subAgentSSEHandlers: Record<string, SSEHandler> = {
250250
sendAutoAcceptConfirmation(id)
251251
}
252252

253-
// Client-executable run tools: execute on the client for real-time feedback.
254-
// The server defers execution in interactive mode; we execute here and
255-
// report back via mark-complete.
256-
if (CLIENT_EXECUTABLE_RUN_TOOLS.has(name)) {
253+
// Client-executable run tools: if auto-allowed, execute immediately for
254+
// real-time feedback. For non-auto-allowed, the user must click "Allow"
255+
// first — handleRun in tool-call.tsx triggers executeRunToolOnClient.
256+
if (CLIENT_EXECUTABLE_RUN_TOOLS.has(name) && isAutoAllowed) {
257257
executeRunToolOnClient(id, name, args || {})
258258
}
259259
},

apps/sim/lib/copilot/orchestrator/sse-handlers/handlers.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,23 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
490490
options.timeout || STREAM_TIMEOUT_MS,
491491
options.abortSignal
492492
)
493+
if (completion?.status === 'rejected') {
494+
toolCall.status = 'rejected'
495+
toolCall.endTime = Date.now()
496+
markToolComplete(
497+
toolCall.id,
498+
toolCall.name,
499+
400,
500+
completion.message || 'Tool execution rejected'
501+
).catch((err) => {
502+
logger.error('markToolComplete fire-and-forget failed (subagent run tool rejected)', {
503+
toolCallId: toolCall.id,
504+
error: err instanceof Error ? err.message : String(err),
505+
})
506+
})
507+
markToolResultSeen(toolCallId)
508+
return
509+
}
493510
const success = completion?.status === 'success'
494511
toolCall.status = success ? 'success' : 'error'
495512
toolCall.endTime = Date.now()

apps/sim/lib/copilot/orchestrator/sse-handlers/tool-execution.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,12 @@ export async function waitForToolDecision(
146146
}
147147

148148
/**
149-
* Wait for a tool completion signal (success/error) from the client.
150-
* Unlike waitForToolDecision which returns on any status, this ignores
151-
* intermediate statuses like 'accepted'/'rejected'/'background' and only
152-
* returns when the client reports final completion via success/error.
149+
* Wait for a tool completion signal (success/error/rejected) from the client.
150+
* Unlike waitForToolDecision which returns on any status, this ignores the
151+
* initial 'accepted' status and only returns on terminal statuses:
152+
* - success: client finished executing successfully
153+
* - error: client execution failed
154+
* - rejected: user clicked Skip (subagent run tools where user hasn't auto-allowed)
153155
*
154156
* Used for client-executable run tools: the client executes the workflow
155157
* and posts success/error to /api/copilot/confirm when done. The server
@@ -166,8 +168,12 @@ export async function waitForToolCompletion(
166168
while (Date.now() - start < timeoutMs) {
167169
if (abortSignal?.aborted) return null
168170
const decision = await getToolConfirmation(toolCallId)
169-
// Only return on completion statuses, not accept/reject decisions
170-
if (decision?.status === 'success' || decision?.status === 'error') {
171+
// Return on completion/terminal statuses, not intermediate 'accepted'
172+
if (
173+
decision?.status === 'success' ||
174+
decision?.status === 'error' ||
175+
decision?.status === 'rejected'
176+
) {
171177
return decision
172178
}
173179
await new Promise((resolve) => setTimeout(resolve, interval))

apps/sim/lib/copilot/tools/client/tool-display-registry.ts

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ import {
5050
import { getLatestBlock } from '@/blocks/registry'
5151
import { getCustomTool } from '@/hooks/queries/custom-tools'
5252
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
53+
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
54+
55+
/** Resolve a block ID to its human-readable name from the workflow store. */
56+
function resolveBlockName(blockId: string | undefined): string | undefined {
57+
if (!blockId) return undefined
58+
try {
59+
const blocks = useWorkflowStore.getState().blocks
60+
return blocks[blockId]?.name || undefined
61+
} catch {
62+
return undefined
63+
}
64+
}
5365

5466
export enum ClientToolCallState {
5567
generating = 'generating',
@@ -1742,12 +1754,12 @@ const META_generate_api_key: ToolMetadata = {
17421754
const META_run_block: ToolMetadata = {
17431755
displayNames: {
17441756
[ClientToolCallState.generating]: { text: 'Preparing to run block', icon: Loader2 },
1745-
[ClientToolCallState.pending]: { text: 'Run this block?', icon: Play },
1757+
[ClientToolCallState.pending]: { text: 'Run block?', icon: Play },
17461758
[ClientToolCallState.executing]: { text: 'Running block', icon: Loader2 },
1747-
[ClientToolCallState.success]: { text: 'Executed block', icon: Play },
1759+
[ClientToolCallState.success]: { text: 'Ran block', icon: Play },
17481760
[ClientToolCallState.error]: { text: 'Failed to run block', icon: XCircle },
1749-
[ClientToolCallState.rejected]: { text: 'Skipped block execution', icon: MinusCircle },
1750-
[ClientToolCallState.aborted]: { text: 'Aborted block execution', icon: MinusCircle },
1761+
[ClientToolCallState.rejected]: { text: 'Skipped running block', icon: MinusCircle },
1762+
[ClientToolCallState.aborted]: { text: 'Aborted running block', icon: MinusCircle },
17511763
[ClientToolCallState.background]: { text: 'Running block in background', icon: Play },
17521764
},
17531765
interrupt: {
@@ -1775,23 +1787,24 @@ const META_run_block: ToolMetadata = {
17751787
getDynamicText: (params, state) => {
17761788
const blockId = params?.blockId || params?.block_id
17771789
if (blockId && typeof blockId === 'string') {
1790+
const name = resolveBlockName(blockId) || blockId
17781791
switch (state) {
17791792
case ClientToolCallState.success:
1780-
return `Executed block ${blockId}`
1793+
return `Ran ${name}`
17811794
case ClientToolCallState.executing:
1782-
return `Running block ${blockId}`
1795+
return `Running ${name}`
17831796
case ClientToolCallState.generating:
1784-
return `Preparing to run block ${blockId}`
1797+
return `Preparing to run ${name}`
17851798
case ClientToolCallState.pending:
1786-
return `Run block ${blockId}?`
1799+
return `Run ${name}?`
17871800
case ClientToolCallState.error:
1788-
return `Failed to run block ${blockId}`
1801+
return `Failed to run ${name}`
17891802
case ClientToolCallState.rejected:
1790-
return `Skipped running block ${blockId}`
1803+
return `Skipped running ${name}`
17911804
case ClientToolCallState.aborted:
1792-
return `Aborted running block ${blockId}`
1805+
return `Aborted running ${name}`
17931806
case ClientToolCallState.background:
1794-
return `Running block ${blockId} in background`
1807+
return `Running ${name} in background`
17951808
}
17961809
}
17971810
return undefined
@@ -1801,12 +1814,12 @@ const META_run_block: ToolMetadata = {
18011814
const META_run_from_block: ToolMetadata = {
18021815
displayNames: {
18031816
[ClientToolCallState.generating]: { text: 'Preparing to run from block', icon: Loader2 },
1804-
[ClientToolCallState.pending]: { text: 'Run from this block?', icon: Play },
1817+
[ClientToolCallState.pending]: { text: 'Run from block?', icon: Play },
18051818
[ClientToolCallState.executing]: { text: 'Running from block', icon: Loader2 },
1806-
[ClientToolCallState.success]: { text: 'Executed from block', icon: Play },
1819+
[ClientToolCallState.success]: { text: 'Ran from block', icon: Play },
18071820
[ClientToolCallState.error]: { text: 'Failed to run from block', icon: XCircle },
1808-
[ClientToolCallState.rejected]: { text: 'Skipped run from block', icon: MinusCircle },
1809-
[ClientToolCallState.aborted]: { text: 'Aborted run from block', icon: MinusCircle },
1821+
[ClientToolCallState.rejected]: { text: 'Skipped running from block', icon: MinusCircle },
1822+
[ClientToolCallState.aborted]: { text: 'Aborted running from block', icon: MinusCircle },
18101823
[ClientToolCallState.background]: { text: 'Running from block in background', icon: Play },
18111824
},
18121825
interrupt: {
@@ -1834,23 +1847,24 @@ const META_run_from_block: ToolMetadata = {
18341847
getDynamicText: (params, state) => {
18351848
const blockId = params?.startBlockId || params?.start_block_id
18361849
if (blockId && typeof blockId === 'string') {
1850+
const name = resolveBlockName(blockId) || blockId
18371851
switch (state) {
18381852
case ClientToolCallState.success:
1839-
return `Executed from block ${blockId}`
1853+
return `Ran from ${name}`
18401854
case ClientToolCallState.executing:
1841-
return `Running from block ${blockId}`
1855+
return `Running from ${name}`
18421856
case ClientToolCallState.generating:
1843-
return `Preparing to run from block ${blockId}`
1857+
return `Preparing to run from ${name}`
18441858
case ClientToolCallState.pending:
1845-
return `Run from block ${blockId}?`
1859+
return `Run from ${name}?`
18461860
case ClientToolCallState.error:
1847-
return `Failed to run from block ${blockId}`
1861+
return `Failed to run from ${name}`
18481862
case ClientToolCallState.rejected:
1849-
return `Skipped running from block ${blockId}`
1863+
return `Skipped running from ${name}`
18501864
case ClientToolCallState.aborted:
1851-
return `Aborted running from block ${blockId}`
1865+
return `Aborted running from ${name}`
18521866
case ClientToolCallState.background:
1853-
return `Running from block ${blockId} in background`
1867+
return `Running from ${name} in background`
18541868
}
18551869
}
18561870
return undefined
@@ -1860,12 +1874,12 @@ const META_run_from_block: ToolMetadata = {
18601874
const META_run_workflow_until_block: ToolMetadata = {
18611875
displayNames: {
18621876
[ClientToolCallState.generating]: { text: 'Preparing to run until block', icon: Loader2 },
1863-
[ClientToolCallState.pending]: { text: 'Run until this block?', icon: Play },
1877+
[ClientToolCallState.pending]: { text: 'Run until block?', icon: Play },
18641878
[ClientToolCallState.executing]: { text: 'Running until block', icon: Loader2 },
1865-
[ClientToolCallState.success]: { text: 'Executed until block', icon: Play },
1879+
[ClientToolCallState.success]: { text: 'Ran until block', icon: Play },
18661880
[ClientToolCallState.error]: { text: 'Failed to run until block', icon: XCircle },
1867-
[ClientToolCallState.rejected]: { text: 'Skipped run until block', icon: MinusCircle },
1868-
[ClientToolCallState.aborted]: { text: 'Aborted run until block', icon: MinusCircle },
1881+
[ClientToolCallState.rejected]: { text: 'Skipped running until block', icon: MinusCircle },
1882+
[ClientToolCallState.aborted]: { text: 'Aborted running until block', icon: MinusCircle },
18691883
[ClientToolCallState.background]: { text: 'Running until block in background', icon: Play },
18701884
},
18711885
interrupt: {
@@ -1893,23 +1907,24 @@ const META_run_workflow_until_block: ToolMetadata = {
18931907
getDynamicText: (params, state) => {
18941908
const blockId = params?.stopAfterBlockId || params?.stop_after_block_id
18951909
if (blockId && typeof blockId === 'string') {
1910+
const name = resolveBlockName(blockId) || blockId
18961911
switch (state) {
18971912
case ClientToolCallState.success:
1898-
return `Executed until block ${blockId}`
1913+
return `Ran until ${name}`
18991914
case ClientToolCallState.executing:
1900-
return `Running until block ${blockId}`
1915+
return `Running until ${name}`
19011916
case ClientToolCallState.generating:
1902-
return `Preparing to run until block ${blockId}`
1917+
return `Preparing to run until ${name}`
19031918
case ClientToolCallState.pending:
1904-
return `Run until block ${blockId}?`
1919+
return `Run until ${name}?`
19051920
case ClientToolCallState.error:
1906-
return `Failed to run until block ${blockId}`
1921+
return `Failed to run until ${name}`
19071922
case ClientToolCallState.rejected:
1908-
return `Skipped running until block ${blockId}`
1923+
return `Skipped running until ${name}`
19091924
case ClientToolCallState.aborted:
1910-
return `Aborted running until block ${blockId}`
1925+
return `Aborted running until ${name}`
19111926
case ClientToolCallState.background:
1912-
return `Running until block ${blockId} in background`
1927+
return `Running until ${name} in background`
19131928
}
19141929
}
19151930
return undefined

0 commit comments

Comments
 (0)