Skip to content

Commit a300077

Browse files
committed
fix(terminal): terminal console update for child spans"
1 parent e14a3a5 commit a300077

11 files changed

Lines changed: 624 additions & 74 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4575,7 +4575,7 @@ export function useChat(
45754575
}).catch(() => {})
45764576
}
45774577

4578-
consoleStore.cancelRunningEntries(workflowId)
4578+
consoleStore.cancelRunningEntries(workflowId, executionId ?? undefined)
45794579
const now = new Date()
45804580
consoleStore.addConsole({
45814581
input: {},

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@ export function useWorkflowExecution() {
11371137
executionIdRef.current,
11381138
data.finalBlockLogs
11391139
)
1140-
cancelRunningEntries(activeWorkflowId)
1140+
cancelRunningEntries(activeWorkflowId, executionIdRef.current)
11411141
}
11421142

11431143
executionResult = {
@@ -1716,7 +1716,7 @@ export function useWorkflowExecution() {
17161716
executionIdRef.current,
17171717
data.finalBlockLogs
17181718
)
1719-
cancelRunningEntries(workflowId)
1719+
cancelRunningEntries(workflowId, executionIdRef.current)
17201720

17211721
if (data.success) {
17221722
executedBlockIds.add(blockId)
@@ -1901,7 +1901,9 @@ export function useWorkflowExecution() {
19011901
sorted.filter((e) => e.executionId !== executionId).map((e) => e.executionId!)
19021902
)
19031903
if (otherExecutionIds.size > 0) {
1904-
cancelRunningEntries(reconnectWorkflowId)
1904+
for (const staleExecutionId of otherExecutionIds) {
1905+
cancelRunningEntries(reconnectWorkflowId, staleExecutionId)
1906+
}
19051907
consolePersistence.persist()
19061908
}
19071909
}
@@ -1943,7 +1945,9 @@ export function useWorkflowExecution() {
19431945
activated = true
19441946
setCurrentExecutionId(reconnectWorkflowId, capturedExecutionId)
19451947
setIsExecuting(reconnectWorkflowId, true)
1946-
clearExecutionEntries(capturedExecutionId)
1948+
if (fromEventId === 0) {
1949+
clearExecutionEntries(capturedExecutionId)
1950+
}
19471951
}
19481952

19491953
const wrapHandler =
@@ -1963,7 +1967,7 @@ export function useWorkflowExecution() {
19631967
.some((entry) => entry.isRunning && entry.executionId === capturedExecutionId)
19641968

19651969
if (activated || hasRunningEntry) {
1966-
cancelRunningEntries(reconnectWorkflowId)
1970+
cancelRunningEntries(reconnectWorkflowId, capturedExecutionId)
19671971
}
19681972

19691973
if (currentId === capturedExecutionId) {
@@ -2016,7 +2020,7 @@ export function useWorkflowExecution() {
20162020
capturedExecutionId,
20172021
data?.finalBlockLogs
20182022
)
2019-
cancelRunningEntries(reconnectWorkflowId)
2023+
cancelRunningEntries(reconnectWorkflowId, capturedExecutionId)
20202024
},
20212025
onExecutionError: (data) => {
20222026
reconnectionComplete = true
@@ -2099,7 +2103,7 @@ export function useWorkflowExecution() {
20992103
.getState()
21002104
.getCurrentExecutionId(reconnectWorkflowId)
21012105
if (currentId === capturedExecutionId) {
2102-
cancelRunningEntries(reconnectWorkflowId)
2106+
cancelRunningEntries(reconnectWorkflowId, capturedExecutionId)
21032107
setCurrentExecutionId(reconnectWorkflowId, null)
21042108
setIsExecuting(reconnectWorkflowId, false)
21052109
setActiveBlocks(reconnectWorkflowId, new Set())

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.test.ts

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,144 @@ import { resetTerminalConsoleMock, terminalConsoleMockFns } from '@sim/testing'
55
import { beforeEach, describe, expect, it, vi } from 'vitest'
66
import {
77
addExecutionErrorConsoleEntry,
8+
createBlockEventHandlers,
89
handleExecutionErrorConsole,
910
reconcileFinalBlockLogs,
1011
} from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils'
1112
import type { BlockLog } from '@/executor/types'
13+
import { useExecutionStore } from '@/stores/execution'
1214

1315
describe('workflow-execution-utils', () => {
1416
beforeEach(() => {
1517
resetTerminalConsoleMock()
18+
vi.mocked(useExecutionStore.getState).mockReturnValue({
19+
getCurrentExecutionId: vi.fn(() => 'exec-1'),
20+
} as any)
21+
})
22+
23+
describe('createBlockEventHandlers', () => {
24+
it('skips duplicate block start rows during reconnect replay', () => {
25+
terminalConsoleMockFns.mockAddConsole({
26+
workflowId: 'wf-1',
27+
blockId: 'fn-1',
28+
blockName: 'Function 1',
29+
blockType: 'function',
30+
executionId: 'exec-1',
31+
executionOrder: 7,
32+
isRunning: false,
33+
success: true,
34+
iterationCurrent: 0,
35+
iterationTotal: 2,
36+
iterationType: 'loop',
37+
iterationContainerId: 'loop-1',
38+
childWorkflowBlockId: 'child-inst-1',
39+
childWorkflowName: 'Child Workflow',
40+
parentIterations: [
41+
{
42+
iterationCurrent: 1,
43+
iterationTotal: 3,
44+
iterationType: 'parallel',
45+
iterationContainerId: 'parallel-1',
46+
},
47+
],
48+
})
49+
50+
const addConsole = vi.fn()
51+
const handlers = createBlockEventHandlers(
52+
{
53+
workflowId: 'wf-1',
54+
executionIdRef: { current: 'exec-1' },
55+
workflowEdges: [],
56+
activeBlocksSet: new Set<string>(),
57+
activeBlockRefCounts: new Map<string, number>(),
58+
accumulatedBlockLogs: [],
59+
accumulatedBlockStates: new Map(),
60+
executedBlockIds: new Set<string>(),
61+
includeStartConsoleEntry: true,
62+
},
63+
{
64+
addConsole,
65+
updateConsole: vi.fn(),
66+
setActiveBlocks: vi.fn(),
67+
setBlockRunStatus: vi.fn(),
68+
setEdgeRunStatus: vi.fn(),
69+
}
70+
)
71+
72+
handlers.onBlockStarted({
73+
blockId: 'fn-1',
74+
blockName: 'Function 1',
75+
blockType: 'function',
76+
executionOrder: 7,
77+
iterationCurrent: 0,
78+
iterationTotal: 2,
79+
iterationType: 'loop',
80+
iterationContainerId: 'loop-1',
81+
childWorkflowBlockId: 'child-inst-1',
82+
childWorkflowName: 'Child Workflow',
83+
parentIterations: [
84+
{
85+
iterationCurrent: 1,
86+
iterationTotal: 3,
87+
iterationType: 'parallel',
88+
iterationContainerId: 'parallel-1',
89+
},
90+
],
91+
})
92+
93+
expect(addConsole).not.toHaveBeenCalled()
94+
})
95+
96+
it('keeps distinct start rows when replay identity differs', () => {
97+
terminalConsoleMockFns.mockAddConsole({
98+
workflowId: 'wf-1',
99+
blockId: 'fn-1',
100+
blockName: 'Function 1',
101+
blockType: 'function',
102+
executionId: 'exec-1',
103+
executionOrder: 7,
104+
isRunning: true,
105+
iterationCurrent: 0,
106+
iterationTotal: 2,
107+
iterationType: 'loop',
108+
iterationContainerId: 'loop-1',
109+
})
110+
111+
const addConsole = vi.fn()
112+
const handlers = createBlockEventHandlers(
113+
{
114+
workflowId: 'wf-1',
115+
executionIdRef: { current: 'exec-1' },
116+
workflowEdges: [],
117+
activeBlocksSet: new Set<string>(),
118+
activeBlockRefCounts: new Map<string, number>(),
119+
accumulatedBlockLogs: [],
120+
accumulatedBlockStates: new Map(),
121+
executedBlockIds: new Set<string>(),
122+
includeStartConsoleEntry: true,
123+
},
124+
{
125+
addConsole,
126+
updateConsole: vi.fn(),
127+
setActiveBlocks: vi.fn(),
128+
setBlockRunStatus: vi.fn(),
129+
setEdgeRunStatus: vi.fn(),
130+
}
131+
)
132+
133+
handlers.onBlockStarted({
134+
blockId: 'fn-1',
135+
blockName: 'Function 1',
136+
blockType: 'function',
137+
executionOrder: 7,
138+
iterationCurrent: 1,
139+
iterationTotal: 2,
140+
iterationType: 'loop',
141+
iterationContainerId: 'loop-1',
142+
})
143+
144+
expect(addConsole).toHaveBeenCalledTimes(1)
145+
})
16146
})
17147

18148
describe('addExecutionErrorConsoleEntry', () => {
@@ -225,6 +355,103 @@ describe('workflow-execution-utils', () => {
225355
expect(updateConsole).not.toHaveBeenCalled()
226356
})
227357

358+
it('reconciles child workflow spans before running entries are swept to canceled', () => {
359+
terminalConsoleMockFns.mockAddConsole({
360+
workflowId: 'wf-1',
361+
blockId: 'workflow-1',
362+
blockName: 'Workflow 1',
363+
blockType: 'workflow',
364+
executionId: 'exec-1',
365+
executionOrder: 2,
366+
isRunning: false,
367+
success: true,
368+
childWorkflowInstanceId: 'child-inst-1',
369+
})
370+
terminalConsoleMockFns.mockAddConsole({
371+
workflowId: 'wf-1',
372+
blockId: 'starter',
373+
blockName: 'Start',
374+
blockType: 'starter',
375+
executionId: 'exec-1',
376+
executionOrder: 3,
377+
isRunning: true,
378+
childWorkflowBlockId: 'child-inst-1',
379+
childWorkflowName: 'Workflow 1',
380+
})
381+
terminalConsoleMockFns.mockAddConsole({
382+
workflowId: 'wf-1',
383+
blockId: 'api-1',
384+
blockName: 'API 1',
385+
blockType: 'api',
386+
executionId: 'exec-1',
387+
executionOrder: 4,
388+
isRunning: true,
389+
childWorkflowBlockId: 'child-inst-1',
390+
childWorkflowName: 'Workflow 1',
391+
})
392+
393+
const startedAt = new Date().toISOString()
394+
const endedAt = new Date(Date.now() + 20).toISOString()
395+
const updateConsole = vi.fn()
396+
reconcileFinalBlockLogs(updateConsole, 'wf-1', 'exec-1', [
397+
makeLog({
398+
blockId: 'workflow-1',
399+
blockName: 'Workflow 1',
400+
blockType: 'workflow',
401+
executionOrder: 2,
402+
success: true,
403+
childTraceSpans: [
404+
{
405+
id: 'starter-span',
406+
name: 'Start',
407+
type: 'starter',
408+
blockId: 'starter',
409+
status: 'success',
410+
duration: 5,
411+
startTime: startedAt,
412+
endTime: endedAt,
413+
output: {},
414+
},
415+
{
416+
id: 'api-span',
417+
name: 'API 1',
418+
type: 'api',
419+
blockId: 'api-1',
420+
status: 'error',
421+
errorHandled: true,
422+
duration: 20,
423+
startTime: startedAt,
424+
endTime: endedAt,
425+
output: { error: 'Request failed' },
426+
},
427+
],
428+
}),
429+
])
430+
431+
expect(updateConsole).toHaveBeenCalledTimes(2)
432+
expect(updateConsole.mock.calls[0]).toEqual([
433+
'starter',
434+
expect.objectContaining({
435+
success: true,
436+
isRunning: false,
437+
isCanceled: false,
438+
childWorkflowBlockId: 'child-inst-1',
439+
}),
440+
'exec-1',
441+
])
442+
expect(updateConsole.mock.calls[1]).toEqual([
443+
'api-1',
444+
expect.objectContaining({
445+
success: false,
446+
error: 'Request failed',
447+
isRunning: false,
448+
isCanceled: false,
449+
childWorkflowBlockId: 'child-inst-1',
450+
}),
451+
'exec-1',
452+
])
453+
})
454+
228455
it('is a no-op when finalBlockLogs is empty or executionId is missing', () => {
229456
const updateConsole = vi.fn()
230457
reconcileFinalBlockLogs(updateConsole, 'wf-1', 'exec-1', [])
@@ -256,6 +483,7 @@ describe('workflow-execution-utils', () => {
256483

257484
expect(calls[0]).toBe('cancel')
258485
expect(calls).toContain('add')
486+
expect(cancelRunningEntries).toHaveBeenCalledWith('wf-1', 'exec-1')
259487
})
260488

261489
it('reconciles finalBlockLogs before sweeping running entries (Fix C)', () => {

0 commit comments

Comments
 (0)