mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-10 14:45:16 -05:00
Fix run from block in copilot
This commit is contained in:
@@ -250,10 +250,10 @@ export const subAgentSSEHandlers: Record<string, SSEHandler> = {
|
||||
sendAutoAcceptConfirmation(id)
|
||||
}
|
||||
|
||||
// Client-executable run tools: execute on the client for real-time feedback.
|
||||
// The server defers execution in interactive mode; we execute here and
|
||||
// report back via mark-complete.
|
||||
if (CLIENT_EXECUTABLE_RUN_TOOLS.has(name)) {
|
||||
// Client-executable run tools: if auto-allowed, execute immediately for
|
||||
// real-time feedback. For non-auto-allowed, the user must click "Allow"
|
||||
// first — handleRun in tool-call.tsx triggers executeRunToolOnClient.
|
||||
if (CLIENT_EXECUTABLE_RUN_TOOLS.has(name) && isAutoAllowed) {
|
||||
executeRunToolOnClient(id, name, args || {})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -490,6 +490,23 @@ export const subAgentHandlers: Record<string, SSEHandler> = {
|
||||
options.timeout || STREAM_TIMEOUT_MS,
|
||||
options.abortSignal
|
||||
)
|
||||
if (completion?.status === 'rejected') {
|
||||
toolCall.status = 'rejected'
|
||||
toolCall.endTime = Date.now()
|
||||
markToolComplete(
|
||||
toolCall.id,
|
||||
toolCall.name,
|
||||
400,
|
||||
completion.message || 'Tool execution rejected'
|
||||
).catch((err) => {
|
||||
logger.error('markToolComplete fire-and-forget failed (subagent run tool rejected)', {
|
||||
toolCallId: toolCall.id,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
})
|
||||
markToolResultSeen(toolCallId)
|
||||
return
|
||||
}
|
||||
const success = completion?.status === 'success'
|
||||
toolCall.status = success ? 'success' : 'error'
|
||||
toolCall.endTime = Date.now()
|
||||
|
||||
@@ -146,10 +146,12 @@ export async function waitForToolDecision(
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a tool completion signal (success/error) from the client.
|
||||
* Unlike waitForToolDecision which returns on any status, this ignores
|
||||
* intermediate statuses like 'accepted'/'rejected'/'background' and only
|
||||
* returns when the client reports final completion via success/error.
|
||||
* Wait for a tool completion signal (success/error/rejected) from the client.
|
||||
* Unlike waitForToolDecision which returns on any status, this ignores the
|
||||
* initial 'accepted' status and only returns on terminal statuses:
|
||||
* - success: client finished executing successfully
|
||||
* - error: client execution failed
|
||||
* - rejected: user clicked Skip (subagent run tools where user hasn't auto-allowed)
|
||||
*
|
||||
* Used for client-executable run tools: the client executes the workflow
|
||||
* and posts success/error to /api/copilot/confirm when done. The server
|
||||
@@ -166,8 +168,12 @@ export async function waitForToolCompletion(
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (abortSignal?.aborted) return null
|
||||
const decision = await getToolConfirmation(toolCallId)
|
||||
// Only return on completion statuses, not accept/reject decisions
|
||||
if (decision?.status === 'success' || decision?.status === 'error') {
|
||||
// Return on completion/terminal statuses, not intermediate 'accepted'
|
||||
if (
|
||||
decision?.status === 'success' ||
|
||||
decision?.status === 'error' ||
|
||||
decision?.status === 'rejected'
|
||||
) {
|
||||
return decision
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, interval))
|
||||
|
||||
@@ -50,6 +50,18 @@ import {
|
||||
import { getLatestBlock } from '@/blocks/registry'
|
||||
import { getCustomTool } from '@/hooks/queries/custom-tools'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
/** Resolve a block ID to its human-readable name from the workflow store. */
|
||||
function resolveBlockName(blockId: string | undefined): string | undefined {
|
||||
if (!blockId) return undefined
|
||||
try {
|
||||
const blocks = useWorkflowStore.getState().blocks
|
||||
return blocks[blockId]?.name || undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export enum ClientToolCallState {
|
||||
generating = 'generating',
|
||||
@@ -1742,12 +1754,12 @@ const META_generate_api_key: ToolMetadata = {
|
||||
const META_run_block: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: { text: 'Preparing to run block', icon: Loader2 },
|
||||
[ClientToolCallState.pending]: { text: 'Run this block?', icon: Play },
|
||||
[ClientToolCallState.pending]: { text: 'Run block?', icon: Play },
|
||||
[ClientToolCallState.executing]: { text: 'Running block', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Executed block', icon: Play },
|
||||
[ClientToolCallState.success]: { text: 'Ran block', icon: Play },
|
||||
[ClientToolCallState.error]: { text: 'Failed to run block', icon: XCircle },
|
||||
[ClientToolCallState.rejected]: { text: 'Skipped block execution', icon: MinusCircle },
|
||||
[ClientToolCallState.aborted]: { text: 'Aborted block execution', icon: MinusCircle },
|
||||
[ClientToolCallState.rejected]: { text: 'Skipped running block', icon: MinusCircle },
|
||||
[ClientToolCallState.aborted]: { text: 'Aborted running block', icon: MinusCircle },
|
||||
[ClientToolCallState.background]: { text: 'Running block in background', icon: Play },
|
||||
},
|
||||
interrupt: {
|
||||
@@ -1775,23 +1787,24 @@ const META_run_block: ToolMetadata = {
|
||||
getDynamicText: (params, state) => {
|
||||
const blockId = params?.blockId || params?.block_id
|
||||
if (blockId && typeof blockId === 'string') {
|
||||
const name = resolveBlockName(blockId) || blockId
|
||||
switch (state) {
|
||||
case ClientToolCallState.success:
|
||||
return `Executed block ${blockId}`
|
||||
return `Ran ${name}`
|
||||
case ClientToolCallState.executing:
|
||||
return `Running block ${blockId}`
|
||||
return `Running ${name}`
|
||||
case ClientToolCallState.generating:
|
||||
return `Preparing to run block ${blockId}`
|
||||
return `Preparing to run ${name}`
|
||||
case ClientToolCallState.pending:
|
||||
return `Run block ${blockId}?`
|
||||
return `Run ${name}?`
|
||||
case ClientToolCallState.error:
|
||||
return `Failed to run block ${blockId}`
|
||||
return `Failed to run ${name}`
|
||||
case ClientToolCallState.rejected:
|
||||
return `Skipped running block ${blockId}`
|
||||
return `Skipped running ${name}`
|
||||
case ClientToolCallState.aborted:
|
||||
return `Aborted running block ${blockId}`
|
||||
return `Aborted running ${name}`
|
||||
case ClientToolCallState.background:
|
||||
return `Running block ${blockId} in background`
|
||||
return `Running ${name} in background`
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
@@ -1801,12 +1814,12 @@ const META_run_block: ToolMetadata = {
|
||||
const META_run_from_block: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: { text: 'Preparing to run from block', icon: Loader2 },
|
||||
[ClientToolCallState.pending]: { text: 'Run from this block?', icon: Play },
|
||||
[ClientToolCallState.pending]: { text: 'Run from block?', icon: Play },
|
||||
[ClientToolCallState.executing]: { text: 'Running from block', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Executed from block', icon: Play },
|
||||
[ClientToolCallState.success]: { text: 'Ran from block', icon: Play },
|
||||
[ClientToolCallState.error]: { text: 'Failed to run from block', icon: XCircle },
|
||||
[ClientToolCallState.rejected]: { text: 'Skipped run from block', icon: MinusCircle },
|
||||
[ClientToolCallState.aborted]: { text: 'Aborted run from block', icon: MinusCircle },
|
||||
[ClientToolCallState.rejected]: { text: 'Skipped running from block', icon: MinusCircle },
|
||||
[ClientToolCallState.aborted]: { text: 'Aborted running from block', icon: MinusCircle },
|
||||
[ClientToolCallState.background]: { text: 'Running from block in background', icon: Play },
|
||||
},
|
||||
interrupt: {
|
||||
@@ -1834,23 +1847,24 @@ const META_run_from_block: ToolMetadata = {
|
||||
getDynamicText: (params, state) => {
|
||||
const blockId = params?.startBlockId || params?.start_block_id
|
||||
if (blockId && typeof blockId === 'string') {
|
||||
const name = resolveBlockName(blockId) || blockId
|
||||
switch (state) {
|
||||
case ClientToolCallState.success:
|
||||
return `Executed from block ${blockId}`
|
||||
return `Ran from ${name}`
|
||||
case ClientToolCallState.executing:
|
||||
return `Running from block ${blockId}`
|
||||
return `Running from ${name}`
|
||||
case ClientToolCallState.generating:
|
||||
return `Preparing to run from block ${blockId}`
|
||||
return `Preparing to run from ${name}`
|
||||
case ClientToolCallState.pending:
|
||||
return `Run from block ${blockId}?`
|
||||
return `Run from ${name}?`
|
||||
case ClientToolCallState.error:
|
||||
return `Failed to run from block ${blockId}`
|
||||
return `Failed to run from ${name}`
|
||||
case ClientToolCallState.rejected:
|
||||
return `Skipped running from block ${blockId}`
|
||||
return `Skipped running from ${name}`
|
||||
case ClientToolCallState.aborted:
|
||||
return `Aborted running from block ${blockId}`
|
||||
return `Aborted running from ${name}`
|
||||
case ClientToolCallState.background:
|
||||
return `Running from block ${blockId} in background`
|
||||
return `Running from ${name} in background`
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
@@ -1860,12 +1874,12 @@ const META_run_from_block: ToolMetadata = {
|
||||
const META_run_workflow_until_block: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: { text: 'Preparing to run until block', icon: Loader2 },
|
||||
[ClientToolCallState.pending]: { text: 'Run until this block?', icon: Play },
|
||||
[ClientToolCallState.pending]: { text: 'Run until block?', icon: Play },
|
||||
[ClientToolCallState.executing]: { text: 'Running until block', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Executed until block', icon: Play },
|
||||
[ClientToolCallState.success]: { text: 'Ran until block', icon: Play },
|
||||
[ClientToolCallState.error]: { text: 'Failed to run until block', icon: XCircle },
|
||||
[ClientToolCallState.rejected]: { text: 'Skipped run until block', icon: MinusCircle },
|
||||
[ClientToolCallState.aborted]: { text: 'Aborted run until block', icon: MinusCircle },
|
||||
[ClientToolCallState.rejected]: { text: 'Skipped running until block', icon: MinusCircle },
|
||||
[ClientToolCallState.aborted]: { text: 'Aborted running until block', icon: MinusCircle },
|
||||
[ClientToolCallState.background]: { text: 'Running until block in background', icon: Play },
|
||||
},
|
||||
interrupt: {
|
||||
@@ -1893,23 +1907,24 @@ const META_run_workflow_until_block: ToolMetadata = {
|
||||
getDynamicText: (params, state) => {
|
||||
const blockId = params?.stopAfterBlockId || params?.stop_after_block_id
|
||||
if (blockId && typeof blockId === 'string') {
|
||||
const name = resolveBlockName(blockId) || blockId
|
||||
switch (state) {
|
||||
case ClientToolCallState.success:
|
||||
return `Executed until block ${blockId}`
|
||||
return `Ran until ${name}`
|
||||
case ClientToolCallState.executing:
|
||||
return `Running until block ${blockId}`
|
||||
return `Running until ${name}`
|
||||
case ClientToolCallState.generating:
|
||||
return `Preparing to run until block ${blockId}`
|
||||
return `Preparing to run until ${name}`
|
||||
case ClientToolCallState.pending:
|
||||
return `Run until block ${blockId}?`
|
||||
return `Run until ${name}?`
|
||||
case ClientToolCallState.error:
|
||||
return `Failed to run until block ${blockId}`
|
||||
return `Failed to run until ${name}`
|
||||
case ClientToolCallState.rejected:
|
||||
return `Skipped running until block ${blockId}`
|
||||
return `Skipped running until ${name}`
|
||||
case ClientToolCallState.aborted:
|
||||
return `Aborted running until block ${blockId}`
|
||||
return `Aborted running until ${name}`
|
||||
case ClientToolCallState.background:
|
||||
return `Running until block ${blockId} in background`
|
||||
return `Running until ${name} in background`
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
|
||||
Reference in New Issue
Block a user