Skip to content

Commit df8e9c2

Browse files
committed
Preserve execution events for deployed Copilot workflow runs
1 parent d77aae7 commit df8e9c2

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
@@ -681,74 +681,81 @@ describe('workflow execute async route', () => {
681681
expect(mockEnqueue).not.toHaveBeenCalled()
682682
})
683683

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

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

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

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

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

753760
it('settles a detached Copilot stream only after its cancelled execution and cleanup end', async () => {
754761
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
@@ -1670,8 +1670,9 @@ async function handleExecutePost(
16701670
}
16711671
}
16721672

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

0 commit comments

Comments
 (0)