Skip to content

Commit cddaff7

Browse files
committed
Preserve execution events for deployed Copilot workflow runs
1 parent 070d3e3 commit cddaff7

2 files changed

Lines changed: 71 additions & 63 deletions

File tree

‎apps/sim/app/api/workflows/[id]/execute/route.async.test.ts‎

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -682,74 +682,81 @@ describe('workflow execute async route', () => {
682682
expect(mockEnqueue).not.toHaveBeenCalled()
683683
})
684684

685-
it('binds a Copilot workflow tool only to its server log and waits before terminal SSE', async () => {
686-
let releasePostExecution: (() => void) | undefined
687-
loggingSessionMockFns.mockWaitForPostExecution.mockImplementationOnce(
688-
() =>
689-
new Promise<void>((resolve) => {
690-
releasePostExecution = resolve
691-
})
692-
)
685+
it.each([true, false])(
686+
'binds a Copilot tool and waits for logs before terminal SSE (draft=%s)',
687+
async (useDraftState) => {
688+
let releasePostExecution: (() => void) | undefined
689+
loggingSessionMockFns.mockWaitForPostExecution.mockImplementationOnce(
690+
() =>
691+
new Promise<void>((resolve) => {
692+
releasePostExecution = resolve
693+
})
694+
)
693695

694-
const response = await POST(createBoundCopilotExecutionRequest(), {
695-
params: Promise.resolve({ id: 'workflow-1' }),
696-
})
697-
const bodyPromise = response.text()
696+
const response = await POST(createBoundCopilotExecutionRequest({ useDraftState }), {
697+
params: Promise.resolve({ id: 'workflow-1' }),
698+
})
699+
const bodyPromise = response.text()
698700

699-
await vi.waitFor(() => {
700-
expect(loggingSessionMockFns.mockWaitForPostExecution).toHaveBeenCalledTimes(1)
701-
})
702-
let streamCompleted = false
703-
void bodyPromise.then(() => {
704-
streamCompleted = true
705-
})
706-
await Promise.resolve()
701+
await vi.waitFor(() => {
702+
expect(loggingSessionMockFns.mockWaitForPostExecution).toHaveBeenCalledTimes(1)
703+
})
704+
let streamCompleted = false
705+
void bodyPromise.then(() => {
706+
streamCompleted = true
707+
})
708+
await Promise.resolve()
707709

708-
expect(response.status).toBe(200)
709-
expect(streamCompleted).toBe(false)
710-
expect(mockSettleClientWorkflowToolExecution).not.toHaveBeenCalled()
711-
expect(mockClaimWorkflowToolExecution).toHaveBeenCalledWith(
712-
'copilot-tool-1',
713-
'execution-123',
714-
'client'
715-
)
716-
expect(mockReleaseWorkflowToolExecutionClaim).not.toHaveBeenCalled()
717-
expect(loggingSessionMockFns.mockSetTrustedExecutionCorrelation).toHaveBeenCalledWith({
718-
executionId: 'execution-123',
719-
requestId: 'req-12345678',
720-
source: 'workflow',
721-
workflowId: 'workflow-1',
722-
triggerType: 'copilot',
723-
copilotToolCallId: 'copilot-tool-1',
724-
})
725-
const executionArgs = mockExecuteWorkflowCore.mock.calls[0][0]
726-
expect(executionArgs).not.toHaveProperty('copilotToolCallId')
727-
expect(executionArgs.snapshot.metadata).not.toHaveProperty('copilotToolCallId')
728-
729-
releasePostExecution?.()
730-
const body = await bodyPromise
731-
expect(body).toContain('execution:completed')
732-
await vi.waitFor(() =>
733-
expect(mockSettleClientWorkflowToolExecution).toHaveBeenCalledExactlyOnceWith(
710+
expect(response.status).toBe(200)
711+
expect(streamCompleted).toBe(false)
712+
expect(mockSettleClientWorkflowToolExecution).not.toHaveBeenCalled()
713+
expect(mockClaimWorkflowToolExecution).toHaveBeenCalledWith(
734714
'copilot-tool-1',
735-
expect.any(String)
715+
'execution-123',
716+
'client'
736717
)
737-
)
738-
})
718+
expect(mockReleaseWorkflowToolExecutionClaim).not.toHaveBeenCalled()
719+
expect(loggingSessionMockFns.mockSetTrustedExecutionCorrelation).toHaveBeenCalledWith({
720+
executionId: 'execution-123',
721+
requestId: 'req-12345678',
722+
source: 'workflow',
723+
workflowId: 'workflow-1',
724+
triggerType: 'copilot',
725+
copilotToolCallId: 'copilot-tool-1',
726+
})
727+
const executionArgs = mockExecuteWorkflowCore.mock.calls[0][0]
728+
expect(executionArgs.snapshot.metadata.useDraftState).toBe(useDraftState)
729+
expect(executionArgs).not.toHaveProperty('copilotToolCallId')
730+
expect(executionArgs.snapshot.metadata).not.toHaveProperty('copilotToolCallId')
731+
732+
releasePostExecution?.()
733+
const body = await bodyPromise
734+
expect(body).toContain('execution:completed')
735+
await vi.waitFor(() =>
736+
expect(mockSettleClientWorkflowToolExecution).toHaveBeenCalledExactlyOnceWith(
737+
'copilot-tool-1',
738+
expect.any(String)
739+
)
740+
)
741+
}
742+
)
739743

740-
it('settles failed Copilot execution after the route finishes error handling', async () => {
741-
mockExecuteWorkflowCore.mockRejectedValueOnce(new Error('Execution rejected'))
742-
const response = await POST(createBoundCopilotExecutionRequest(), {
743-
params: Promise.resolve({ id: 'workflow-1' }),
744-
})
745-
expect(await response.text()).toContain('execution:error')
746-
await vi.waitFor(() =>
747-
expect(mockSettleClientWorkflowToolExecution).toHaveBeenCalledExactlyOnceWith(
748-
'copilot-tool-1',
749-
expect.any(String)
744+
it.each([true, false])(
745+
'settles failed Copilot execution after error handling (draft=%s)',
746+
async (useDraftState) => {
747+
mockExecuteWorkflowCore.mockRejectedValueOnce(new Error('Execution rejected'))
748+
const response = await POST(createBoundCopilotExecutionRequest({ useDraftState }), {
749+
params: Promise.resolve({ id: 'workflow-1' }),
750+
})
751+
expect(await response.text()).toContain('execution:error')
752+
await vi.waitFor(() =>
753+
expect(mockSettleClientWorkflowToolExecution).toHaveBeenCalledExactlyOnceWith(
754+
'copilot-tool-1',
755+
expect.any(String)
756+
)
750757
)
751-
)
752-
})
758+
}
759+
)
753760

754761
it('settles a detached Copilot stream only after its cancelled execution and cleanup end', async () => {
755762
let releaseCleanup: (() => void) | undefined

‎apps/sim/app/api/workflows/[id]/execute/route.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,8 +1669,9 @@ async function handleExecutePost(
16691669
}
16701670
}
16711671

1672-
if (shouldUseDraftState) {
1673-
reqLogger.info('Using SSE console log streaming (manual execution)')
1672+
/** Bound Copilot clients consume execution events for both draft and deployed state. */
1673+
if (shouldUseDraftState || copilotToolCallId) {
1674+
reqLogger.info('Using SSE console log streaming')
16741675
} else {
16751676
reqLogger.info('Using streaming API response')
16761677

0 commit comments

Comments
 (0)