mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-04 03:35:04 -05:00
improvement(tag-dropdown): removed custom styling on tag dropdown popover, fixed execution ordering in terminal and loops entries (#3126)
* improvement(tag-dropdown): removeed custom styling on tag dropdown popover, fixed execution ordering in terminal and loops entries * ack pr comments * handle old records
This commit is contained in:
@@ -567,6 +567,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
blockType: string,
|
||||
executionOrder: number,
|
||||
iterationContext?: IterationContext
|
||||
) => {
|
||||
logger.info(`[${requestId}] 🔷 onBlockStart called:`, { blockId, blockName, blockType })
|
||||
@@ -579,6 +580,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
blockId,
|
||||
blockName,
|
||||
blockType,
|
||||
executionOrder,
|
||||
...(iterationContext && {
|
||||
iterationCurrent: iterationContext.iterationCurrent,
|
||||
iterationTotal: iterationContext.iterationTotal,
|
||||
@@ -617,6 +619,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
error: callbackData.output.error,
|
||||
durationMs: callbackData.executionTime || 0,
|
||||
startedAt: callbackData.startedAt,
|
||||
executionOrder: callbackData.executionOrder,
|
||||
endedAt: callbackData.endedAt,
|
||||
...(iterationContext && {
|
||||
iterationCurrent: iterationContext.iterationCurrent,
|
||||
@@ -644,6 +647,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
output: callbackData.output,
|
||||
durationMs: callbackData.executionTime || 0,
|
||||
startedAt: callbackData.startedAt,
|
||||
executionOrder: callbackData.executionOrder,
|
||||
endedAt: callbackData.endedAt,
|
||||
...(iterationContext && {
|
||||
iterationCurrent: iterationContext.iterationCurrent,
|
||||
|
||||
@@ -908,8 +908,10 @@ const PopoverContextCapture: React.FC<{
|
||||
* When in nested folders, goes back one level at a time.
|
||||
* At the root folder level, closes the folder.
|
||||
*/
|
||||
const TagDropdownBackButton: React.FC = () => {
|
||||
const { isInFolder, closeFolder, colorScheme, size } = usePopoverContext()
|
||||
const TagDropdownBackButton: React.FC<{ setSelectedIndex: (index: number) => void }> = ({
|
||||
setSelectedIndex,
|
||||
}) => {
|
||||
const { isInFolder, closeFolder, size, isKeyboardNav, setKeyboardNav } = usePopoverContext()
|
||||
const nestedNav = useNestedNavigation()
|
||||
|
||||
if (!isInFolder) return null
|
||||
@@ -922,28 +924,31 @@ const TagDropdownBackButton: React.FC = () => {
|
||||
closeFolder()
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (isKeyboardNav) return
|
||||
setKeyboardNav(false)
|
||||
setSelectedIndex(-1)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex min-w-0 cursor-pointer items-center gap-[8px] rounded-[6px] px-[6px] font-base',
|
||||
size === 'sm' ? 'h-[22px] text-[11px]' : 'h-[26px] text-[13px]',
|
||||
colorScheme === 'inverted'
|
||||
? 'text-white hover:bg-[#363636] hover:text-white dark:text-[var(--text-primary)] dark:hover:bg-[var(--surface-5)]'
|
||||
: 'text-[var(--text-primary)] hover:bg-[var(--border-1)]'
|
||||
)}
|
||||
role='button'
|
||||
onClick={handleBackClick}
|
||||
<PopoverItem
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleBackClick(e)
|
||||
}}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
>
|
||||
<svg
|
||||
className={size === 'sm' ? 'h-3 w-3' : 'h-3.5 w-3.5'}
|
||||
className={cn('shrink-0', size === 'sm' ? 'h-3 w-3' : 'h-3.5 w-3.5')}
|
||||
fill='none'
|
||||
viewBox='0 0 24 24'
|
||||
stroke='currentColor'
|
||||
>
|
||||
<path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M15 19l-7-7 7-7' />
|
||||
</svg>
|
||||
<span>Back</span>
|
||||
</div>
|
||||
<span className='shrink-0'>Back</span>
|
||||
</PopoverItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1961,8 +1966,8 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
<TagDropdownBackButton />
|
||||
<PopoverScrollArea ref={scrollAreaRef}>
|
||||
<TagDropdownBackButton setSelectedIndex={setSelectedIndex} />
|
||||
{flatTagList.length === 0 ? (
|
||||
<div className='px-[6px] py-[8px] text-[12px] text-[var(--white)]/60'>
|
||||
No matching tags found
|
||||
|
||||
@@ -105,11 +105,9 @@ export function useTerminalFilters() {
|
||||
})
|
||||
}
|
||||
|
||||
// Apply sorting by timestamp
|
||||
// Sort by executionOrder (monotonically increasing integer from server)
|
||||
result = [...result].sort((a, b) => {
|
||||
const timeA = new Date(a.timestamp).getTime()
|
||||
const timeB = new Date(b.timestamp).getTime()
|
||||
const comparison = timeA - timeB
|
||||
const comparison = a.executionOrder - b.executionOrder
|
||||
return sortConfig.direction === 'asc' ? comparison : -comparison
|
||||
})
|
||||
|
||||
|
||||
@@ -184,13 +184,9 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
|
||||
group.blocks.push(entry)
|
||||
}
|
||||
|
||||
// Sort blocks within each iteration by start time ascending (oldest first, top-down)
|
||||
// Sort blocks within each iteration by executionOrder ascending (oldest first, top-down)
|
||||
for (const group of iterationGroupsMap.values()) {
|
||||
group.blocks.sort((a, b) => {
|
||||
const aStart = new Date(a.startedAt || a.timestamp).getTime()
|
||||
const bStart = new Date(b.startedAt || b.timestamp).getTime()
|
||||
return aStart - bStart
|
||||
})
|
||||
group.blocks.sort((a, b) => a.executionOrder - b.executionOrder)
|
||||
}
|
||||
|
||||
// Group iterations by iterationType to create subflow parents
|
||||
@@ -225,6 +221,8 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
|
||||
const totalDuration = allBlocks.reduce((sum, b) => sum + (b.durationMs || 0), 0)
|
||||
|
||||
// Create synthetic subflow parent entry
|
||||
// Use the minimum executionOrder from all child blocks for proper ordering
|
||||
const subflowExecutionOrder = Math.min(...allBlocks.map((b) => b.executionOrder))
|
||||
const syntheticSubflow: ConsoleEntry = {
|
||||
id: `subflow-${iterationType}-${firstIteration.blocks[0]?.executionId || 'unknown'}`,
|
||||
timestamp: new Date(subflowStartMs).toISOString(),
|
||||
@@ -234,6 +232,7 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
|
||||
blockType: iterationType,
|
||||
executionId: firstIteration.blocks[0]?.executionId,
|
||||
startedAt: new Date(subflowStartMs).toISOString(),
|
||||
executionOrder: subflowExecutionOrder,
|
||||
endedAt: new Date(subflowEndMs).toISOString(),
|
||||
durationMs: totalDuration,
|
||||
success: !allBlocks.some((b) => b.error),
|
||||
@@ -251,6 +250,8 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
|
||||
)
|
||||
const iterDuration = iterBlocks.reduce((sum, b) => sum + (b.durationMs || 0), 0)
|
||||
|
||||
// Use the minimum executionOrder from blocks in this iteration
|
||||
const iterExecutionOrder = Math.min(...iterBlocks.map((b) => b.executionOrder))
|
||||
const syntheticIteration: ConsoleEntry = {
|
||||
id: `iteration-${iterationType}-${iterGroup.iterationCurrent}-${iterBlocks[0]?.executionId || 'unknown'}`,
|
||||
timestamp: new Date(iterStartMs).toISOString(),
|
||||
@@ -260,6 +261,7 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
|
||||
blockType: iterationType,
|
||||
executionId: iterBlocks[0]?.executionId,
|
||||
startedAt: new Date(iterStartMs).toISOString(),
|
||||
executionOrder: iterExecutionOrder,
|
||||
endedAt: new Date(iterEndMs).toISOString(),
|
||||
durationMs: iterDuration,
|
||||
success: !iterBlocks.some((b) => b.error),
|
||||
@@ -300,14 +302,9 @@ function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
|
||||
nodeType: 'block' as const,
|
||||
}))
|
||||
|
||||
// Combine all nodes and sort by start time ascending (oldest first, top-down)
|
||||
// Combine all nodes and sort by executionOrder ascending (oldest first, top-down)
|
||||
const allNodes = [...subflowNodes, ...regularNodes]
|
||||
allNodes.sort((a, b) => {
|
||||
const aStart = new Date(a.entry.startedAt || a.entry.timestamp).getTime()
|
||||
const bStart = new Date(b.entry.startedAt || b.entry.timestamp).getTime()
|
||||
return aStart - bStart
|
||||
})
|
||||
|
||||
allNodes.sort((a, b) => a.entry.executionOrder - b.entry.executionOrder)
|
||||
return allNodes
|
||||
}
|
||||
|
||||
|
||||
@@ -926,6 +926,7 @@ export function useWorkflowExecution() {
|
||||
})
|
||||
|
||||
// Add entry to terminal immediately with isRunning=true
|
||||
// Use server-provided executionOrder to ensure correct sort order
|
||||
const startedAt = new Date().toISOString()
|
||||
addConsole({
|
||||
input: {},
|
||||
@@ -933,6 +934,7 @@ export function useWorkflowExecution() {
|
||||
success: undefined,
|
||||
durationMs: undefined,
|
||||
startedAt,
|
||||
executionOrder: data.executionOrder,
|
||||
endedAt: undefined,
|
||||
workflowId: activeWorkflowId,
|
||||
blockId: data.blockId,
|
||||
@@ -948,8 +950,6 @@ export function useWorkflowExecution() {
|
||||
},
|
||||
|
||||
onBlockCompleted: (data) => {
|
||||
logger.info('onBlockCompleted received:', { data })
|
||||
|
||||
activeBlocksSet.delete(data.blockId)
|
||||
setActiveBlocks(new Set(activeBlocksSet))
|
||||
setBlockRunStatus(data.blockId, 'success')
|
||||
@@ -976,6 +976,7 @@ export function useWorkflowExecution() {
|
||||
success: true,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
executionOrder: data.executionOrder,
|
||||
endedAt,
|
||||
})
|
||||
|
||||
@@ -987,6 +988,7 @@ export function useWorkflowExecution() {
|
||||
replaceOutput: data.output,
|
||||
success: true,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
endedAt,
|
||||
isRunning: false,
|
||||
// Pass through iteration context for subflow grouping
|
||||
@@ -1027,6 +1029,7 @@ export function useWorkflowExecution() {
|
||||
error: data.error,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
executionOrder: data.executionOrder,
|
||||
endedAt,
|
||||
})
|
||||
|
||||
@@ -1039,6 +1042,7 @@ export function useWorkflowExecution() {
|
||||
success: false,
|
||||
error: data.error,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
endedAt,
|
||||
isRunning: false,
|
||||
// Pass through iteration context for subflow grouping
|
||||
@@ -1163,6 +1167,7 @@ export function useWorkflowExecution() {
|
||||
|
||||
if (existingLogs.length === 0) {
|
||||
// No blocks executed yet - this is a pre-execution error
|
||||
// Use 0 for executionOrder so validation errors appear first
|
||||
addConsole({
|
||||
input: {},
|
||||
output: {},
|
||||
@@ -1170,6 +1175,7 @@ export function useWorkflowExecution() {
|
||||
error: data.error,
|
||||
durationMs: data.duration || 0,
|
||||
startedAt: new Date(Date.now() - (data.duration || 0)).toISOString(),
|
||||
executionOrder: 0,
|
||||
endedAt: new Date().toISOString(),
|
||||
workflowId: activeWorkflowId,
|
||||
blockId: 'validation',
|
||||
@@ -1237,6 +1243,7 @@ export function useWorkflowExecution() {
|
||||
blockType = error.blockType || blockType
|
||||
}
|
||||
|
||||
// Use MAX_SAFE_INTEGER so execution errors appear at the end of the log
|
||||
useTerminalConsoleStore.getState().addConsole({
|
||||
input: {},
|
||||
output: {},
|
||||
@@ -1244,6 +1251,7 @@ export function useWorkflowExecution() {
|
||||
error: normalizedMessage,
|
||||
durationMs: 0,
|
||||
startedAt: new Date().toISOString(),
|
||||
executionOrder: Number.MAX_SAFE_INTEGER,
|
||||
endedAt: new Date().toISOString(),
|
||||
workflowId: activeWorkflowId || '',
|
||||
blockId,
|
||||
@@ -1615,6 +1623,7 @@ export function useWorkflowExecution() {
|
||||
success: true,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
executionOrder: data.executionOrder,
|
||||
endedAt,
|
||||
})
|
||||
|
||||
@@ -1624,6 +1633,7 @@ export function useWorkflowExecution() {
|
||||
success: true,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
executionOrder: data.executionOrder,
|
||||
endedAt,
|
||||
workflowId,
|
||||
blockId: data.blockId,
|
||||
@@ -1653,6 +1663,7 @@ export function useWorkflowExecution() {
|
||||
output: {},
|
||||
success: false,
|
||||
error: data.error,
|
||||
executionOrder: data.executionOrder,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
endedAt,
|
||||
@@ -1665,6 +1676,7 @@ export function useWorkflowExecution() {
|
||||
error: data.error,
|
||||
durationMs: data.durationMs,
|
||||
startedAt,
|
||||
executionOrder: data.executionOrder,
|
||||
endedAt,
|
||||
workflowId,
|
||||
blockId: data.blockId,
|
||||
|
||||
@@ -111,6 +111,7 @@ export async function executeWorkflowWithFullLogging(
|
||||
success: true,
|
||||
durationMs: event.data.durationMs,
|
||||
startedAt: new Date(Date.now() - event.data.durationMs).toISOString(),
|
||||
executionOrder: event.data.executionOrder,
|
||||
endedAt: new Date().toISOString(),
|
||||
workflowId: activeWorkflowId,
|
||||
blockId: event.data.blockId,
|
||||
@@ -140,6 +141,7 @@ export async function executeWorkflowWithFullLogging(
|
||||
error: event.data.error,
|
||||
durationMs: event.data.durationMs,
|
||||
startedAt: new Date(Date.now() - event.data.durationMs).toISOString(),
|
||||
executionOrder: event.data.executionOrder,
|
||||
endedAt: new Date().toISOString(),
|
||||
workflowId: activeWorkflowId,
|
||||
blockId: event.data.blockId,
|
||||
|
||||
@@ -21,12 +21,13 @@ import {
|
||||
generatePauseContextId,
|
||||
mapNodeMetadataToPauseScopes,
|
||||
} from '@/executor/human-in-the-loop/utils.ts'
|
||||
import type {
|
||||
BlockHandler,
|
||||
BlockLog,
|
||||
BlockState,
|
||||
ExecutionContext,
|
||||
NormalizedBlockOutput,
|
||||
import {
|
||||
type BlockHandler,
|
||||
type BlockLog,
|
||||
type BlockState,
|
||||
type ExecutionContext,
|
||||
getNextExecutionOrder,
|
||||
type NormalizedBlockOutput,
|
||||
} from '@/executor/types'
|
||||
import { streamingResponseFormatProcessor } from '@/executor/utils'
|
||||
import { buildBlockExecutionError, normalizeError } from '@/executor/utils/errors'
|
||||
@@ -68,7 +69,7 @@ export class BlockExecutor {
|
||||
if (!isSentinel) {
|
||||
blockLog = this.createBlockLog(ctx, node.id, block, node)
|
||||
ctx.blockLogs.push(blockLog)
|
||||
this.callOnBlockStart(ctx, node, block)
|
||||
this.callOnBlockStart(ctx, node, block, blockLog.executionOrder)
|
||||
}
|
||||
|
||||
const startTime = performance.now()
|
||||
@@ -159,7 +160,7 @@ export class BlockExecutor {
|
||||
|
||||
this.state.setBlockOutput(node.id, normalizedOutput, duration)
|
||||
|
||||
if (!isSentinel) {
|
||||
if (!isSentinel && blockLog) {
|
||||
const displayOutput = filterOutputForLog(block.metadata?.id || '', normalizedOutput, {
|
||||
block,
|
||||
})
|
||||
@@ -170,8 +171,9 @@ export class BlockExecutor {
|
||||
this.sanitizeInputsForLog(resolvedInputs),
|
||||
displayOutput,
|
||||
duration,
|
||||
blockLog!.startedAt,
|
||||
blockLog!.endedAt
|
||||
blockLog.startedAt,
|
||||
blockLog.executionOrder,
|
||||
blockLog.endedAt
|
||||
)
|
||||
}
|
||||
|
||||
@@ -268,7 +270,7 @@ export class BlockExecutor {
|
||||
}
|
||||
)
|
||||
|
||||
if (!isSentinel) {
|
||||
if (!isSentinel && blockLog) {
|
||||
const displayOutput = filterOutputForLog(block.metadata?.id || '', errorOutput, { block })
|
||||
this.callOnBlockComplete(
|
||||
ctx,
|
||||
@@ -277,8 +279,9 @@ export class BlockExecutor {
|
||||
this.sanitizeInputsForLog(input),
|
||||
displayOutput,
|
||||
duration,
|
||||
blockLog!.startedAt,
|
||||
blockLog!.endedAt
|
||||
blockLog.startedAt,
|
||||
blockLog.executionOrder,
|
||||
blockLog.endedAt
|
||||
)
|
||||
}
|
||||
|
||||
@@ -346,6 +349,7 @@ export class BlockExecutor {
|
||||
blockName,
|
||||
blockType: block.metadata?.id ?? DEFAULTS.BLOCK_TYPE,
|
||||
startedAt: new Date().toISOString(),
|
||||
executionOrder: getNextExecutionOrder(ctx),
|
||||
endedAt: '',
|
||||
durationMs: 0,
|
||||
success: false,
|
||||
@@ -409,7 +413,12 @@ export class BlockExecutor {
|
||||
return result
|
||||
}
|
||||
|
||||
private callOnBlockStart(ctx: ExecutionContext, node: DAGNode, block: SerializedBlock): void {
|
||||
private callOnBlockStart(
|
||||
ctx: ExecutionContext,
|
||||
node: DAGNode,
|
||||
block: SerializedBlock,
|
||||
executionOrder: number
|
||||
): void {
|
||||
const blockId = node.id
|
||||
const blockName = block.metadata?.name ?? blockId
|
||||
const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE
|
||||
@@ -417,7 +426,13 @@ export class BlockExecutor {
|
||||
const iterationContext = this.getIterationContext(ctx, node)
|
||||
|
||||
if (this.contextExtensions.onBlockStart) {
|
||||
this.contextExtensions.onBlockStart(blockId, blockName, blockType, iterationContext)
|
||||
this.contextExtensions.onBlockStart(
|
||||
blockId,
|
||||
blockName,
|
||||
blockType,
|
||||
executionOrder,
|
||||
iterationContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,6 +444,7 @@ export class BlockExecutor {
|
||||
output: NormalizedBlockOutput,
|
||||
duration: number,
|
||||
startedAt: string,
|
||||
executionOrder: number,
|
||||
endedAt: string
|
||||
): void {
|
||||
const blockId = node.id
|
||||
@@ -447,6 +463,7 @@ export class BlockExecutor {
|
||||
output,
|
||||
executionTime: duration,
|
||||
startedAt,
|
||||
executionOrder,
|
||||
endedAt,
|
||||
},
|
||||
iterationContext
|
||||
|
||||
@@ -55,7 +55,13 @@ export interface IterationContext {
|
||||
|
||||
export interface ExecutionCallbacks {
|
||||
onStream?: (streamingExec: any) => Promise<void>
|
||||
onBlockStart?: (blockId: string, blockName: string, blockType: string) => Promise<void>
|
||||
onBlockStart?: (
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
blockType: string,
|
||||
executionOrder: number,
|
||||
iterationContext?: IterationContext
|
||||
) => Promise<void>
|
||||
onBlockComplete?: (
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
@@ -97,6 +103,7 @@ export interface ContextExtensions {
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
blockType: string,
|
||||
executionOrder: number,
|
||||
iterationContext?: IterationContext
|
||||
) => Promise<void>
|
||||
onBlockComplete?: (
|
||||
@@ -108,6 +115,7 @@ export interface ContextExtensions {
|
||||
output: NormalizedBlockOutput
|
||||
executionTime: number
|
||||
startedAt: string
|
||||
executionOrder: number
|
||||
endedAt: string
|
||||
},
|
||||
iterationContext?: IterationContext
|
||||
|
||||
@@ -7,7 +7,11 @@ import type { DAG } from '@/executor/dag/builder'
|
||||
import type { EdgeManager } from '@/executor/execution/edge-manager'
|
||||
import type { LoopScope } from '@/executor/execution/state'
|
||||
import type { BlockStateController, ContextExtensions } from '@/executor/execution/types'
|
||||
import type { ExecutionContext, NormalizedBlockOutput } from '@/executor/types'
|
||||
import {
|
||||
type ExecutionContext,
|
||||
getNextExecutionOrder,
|
||||
type NormalizedBlockOutput,
|
||||
} from '@/executor/types'
|
||||
import type { LoopConfigWithNodes } from '@/executor/types/loop'
|
||||
import { replaceValidReferences } from '@/executor/utils/reference-validation'
|
||||
import {
|
||||
@@ -286,6 +290,7 @@ export class LoopOrchestrator {
|
||||
output,
|
||||
executionTime: DEFAULTS.EXECUTION_TIME,
|
||||
startedAt: now,
|
||||
executionOrder: getNextExecutionOrder(ctx),
|
||||
endedAt: now,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ import { DEFAULTS } from '@/executor/constants'
|
||||
import type { DAG } from '@/executor/dag/builder'
|
||||
import type { ParallelScope } from '@/executor/execution/state'
|
||||
import type { BlockStateWriter, ContextExtensions } from '@/executor/execution/types'
|
||||
import type { ExecutionContext, NormalizedBlockOutput } from '@/executor/types'
|
||||
import {
|
||||
type ExecutionContext,
|
||||
getNextExecutionOrder,
|
||||
type NormalizedBlockOutput,
|
||||
} from '@/executor/types'
|
||||
import type { ParallelConfigWithNodes } from '@/executor/types/parallel'
|
||||
import { ParallelExpander } from '@/executor/utils/parallel-expansion'
|
||||
import {
|
||||
@@ -270,6 +274,7 @@ export class ParallelOrchestrator {
|
||||
output,
|
||||
executionTime: 0,
|
||||
startedAt: now,
|
||||
executionOrder: getNextExecutionOrder(ctx),
|
||||
endedAt: now,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -114,6 +114,11 @@ export interface BlockLog {
|
||||
loopId?: string
|
||||
parallelId?: string
|
||||
iterationIndex?: number
|
||||
/**
|
||||
* Monotonically increasing integer (1, 2, 3, ...) for accurate block ordering.
|
||||
* Generated via getNextExecutionOrder() to ensure deterministic sorting.
|
||||
*/
|
||||
executionOrder: number
|
||||
/**
|
||||
* Child workflow trace spans for nested workflow execution.
|
||||
* Stored separately from output to keep output clean for display
|
||||
@@ -227,7 +232,12 @@ export interface ExecutionContext {
|
||||
edges?: Array<{ source: string; target: string }>
|
||||
|
||||
onStream?: (streamingExecution: StreamingExecution) => Promise<void>
|
||||
onBlockStart?: (blockId: string, blockName: string, blockType: string) => Promise<void>
|
||||
onBlockStart?: (
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
blockType: string,
|
||||
executionOrder: number
|
||||
) => Promise<void>
|
||||
onBlockComplete?: (
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
@@ -268,6 +278,23 @@ export interface ExecutionContext {
|
||||
* Stop execution after this block completes. Used for "run until block" feature.
|
||||
*/
|
||||
stopAfterBlockId?: string
|
||||
|
||||
/**
|
||||
* Counter for generating monotonically increasing execution order values.
|
||||
* Starts at 0 and increments for each block. Use getNextExecutionOrder() to access.
|
||||
*/
|
||||
executionOrderCounter?: { value: number }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next execution order value for a block.
|
||||
* Returns a simple incrementing integer (1, 2, 3, ...) for clear ordering.
|
||||
*/
|
||||
export function getNextExecutionOrder(ctx: ExecutionContext): number {
|
||||
if (!ctx.executionOrderCounter) {
|
||||
ctx.executionOrderCounter = { value: 0 }
|
||||
}
|
||||
return ++ctx.executionOrderCounter.value
|
||||
}
|
||||
|
||||
export interface ExecutionResult {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { LOOP, PARALLEL, PARSING, REFERENCE } from '@/executor/constants'
|
||||
import type { ContextExtensions } from '@/executor/execution/types'
|
||||
import type { BlockLog, ExecutionContext } from '@/executor/types'
|
||||
import { type BlockLog, type ExecutionContext, getNextExecutionOrder } from '@/executor/types'
|
||||
import type { VariableResolver } from '@/executor/variables/resolver'
|
||||
|
||||
const logger = createLogger('SubflowUtils')
|
||||
@@ -208,6 +208,7 @@ export function addSubflowErrorLog(
|
||||
contextExtensions: ContextExtensions | null
|
||||
): void {
|
||||
const now = new Date().toISOString()
|
||||
const execOrder = getNextExecutionOrder(ctx)
|
||||
|
||||
const block = ctx.workflow?.blocks?.find((b) => b.id === blockId)
|
||||
const blockName = block?.metadata?.name || (blockType === 'loop' ? 'Loop' : 'Parallel')
|
||||
@@ -217,6 +218,7 @@ export function addSubflowErrorLog(
|
||||
blockName,
|
||||
blockType,
|
||||
startedAt: now,
|
||||
executionOrder: execOrder,
|
||||
endedAt: now,
|
||||
durationMs: 0,
|
||||
success: false,
|
||||
@@ -233,6 +235,7 @@ export function addSubflowErrorLog(
|
||||
output: { error: errorMessage },
|
||||
executionTime: 0,
|
||||
startedAt: now,
|
||||
executionOrder: execOrder,
|
||||
endedAt: now,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
/**
|
||||
* SSE Event types for workflow execution
|
||||
*/
|
||||
|
||||
import type { SubflowType } from '@/stores/workflows/workflow/types'
|
||||
|
||||
export type ExecutionEventType =
|
||||
@@ -83,7 +79,7 @@ export interface BlockStartedEvent extends BaseExecutionEvent {
|
||||
blockId: string
|
||||
blockName: string
|
||||
blockType: string
|
||||
// Iteration context for loops and parallels
|
||||
executionOrder: number
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
@@ -104,8 +100,8 @@ export interface BlockCompletedEvent extends BaseExecutionEvent {
|
||||
output: any
|
||||
durationMs: number
|
||||
startedAt: string
|
||||
executionOrder: number
|
||||
endedAt: string
|
||||
// Iteration context for loops and parallels
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
@@ -126,8 +122,8 @@ export interface BlockErrorEvent extends BaseExecutionEvent {
|
||||
error: string
|
||||
durationMs: number
|
||||
startedAt: string
|
||||
executionOrder: number
|
||||
endedAt: string
|
||||
// Iteration context for loops and parallels
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
@@ -228,6 +224,7 @@ export function createSSECallbacks(options: SSECallbackOptions) {
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
blockType: string,
|
||||
executionOrder: number,
|
||||
iterationContext?: { iterationCurrent: number; iterationTotal: number; iterationType: string }
|
||||
) => {
|
||||
sendEvent({
|
||||
@@ -239,6 +236,7 @@ export function createSSECallbacks(options: SSECallbackOptions) {
|
||||
blockId,
|
||||
blockName,
|
||||
blockType,
|
||||
executionOrder,
|
||||
...(iterationContext && {
|
||||
iterationCurrent: iterationContext.iterationCurrent,
|
||||
iterationTotal: iterationContext.iterationTotal,
|
||||
@@ -257,6 +255,7 @@ export function createSSECallbacks(options: SSECallbackOptions) {
|
||||
output: any
|
||||
executionTime: number
|
||||
startedAt: string
|
||||
executionOrder: number
|
||||
endedAt: string
|
||||
},
|
||||
iterationContext?: { iterationCurrent: number; iterationTotal: number; iterationType: string }
|
||||
@@ -284,6 +283,7 @@ export function createSSECallbacks(options: SSECallbackOptions) {
|
||||
error: callbackData.output.error,
|
||||
durationMs: callbackData.executionTime || 0,
|
||||
startedAt: callbackData.startedAt,
|
||||
executionOrder: callbackData.executionOrder,
|
||||
endedAt: callbackData.endedAt,
|
||||
...iterationData,
|
||||
},
|
||||
@@ -302,6 +302,7 @@ export function createSSECallbacks(options: SSECallbackOptions) {
|
||||
output: callbackData.output,
|
||||
durationMs: callbackData.executionTime || 0,
|
||||
startedAt: callbackData.startedAt,
|
||||
executionOrder: callbackData.executionOrder,
|
||||
endedAt: callbackData.endedAt,
|
||||
...iterationData,
|
||||
},
|
||||
|
||||
@@ -287,6 +287,14 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
|
||||
return entry
|
||||
}
|
||||
|
||||
if (
|
||||
typeof update === 'object' &&
|
||||
update.iterationCurrent !== undefined &&
|
||||
entry.iterationCurrent !== update.iterationCurrent
|
||||
) {
|
||||
return entry
|
||||
}
|
||||
|
||||
if (typeof update === 'string') {
|
||||
const newOutput = updateBlockOutput(entry.output, update)
|
||||
return { ...entry, output: newOutput }
|
||||
@@ -324,6 +332,10 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
|
||||
updatedEntry.success = update.success
|
||||
}
|
||||
|
||||
if (update.startedAt !== undefined) {
|
||||
updatedEntry.startedAt = update.startedAt
|
||||
}
|
||||
|
||||
if (update.endedAt !== undefined) {
|
||||
updatedEntry.endedAt = update.endedAt
|
||||
}
|
||||
@@ -397,9 +409,15 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
|
||||
},
|
||||
merge: (persistedState, currentState) => {
|
||||
const persisted = persistedState as Partial<ConsoleStore> | undefined
|
||||
const entries = (persisted?.entries ?? currentState.entries).map((entry, index) => {
|
||||
if (entry.executionOrder === undefined) {
|
||||
return { ...entry, executionOrder: index + 1 }
|
||||
}
|
||||
return entry
|
||||
})
|
||||
return {
|
||||
...currentState,
|
||||
entries: persisted?.entries ?? currentState.entries,
|
||||
entries,
|
||||
isOpen: persisted?.isOpen ?? currentState.isOpen,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface ConsoleEntry {
|
||||
blockType: string
|
||||
executionId?: string
|
||||
startedAt?: string
|
||||
executionOrder: number
|
||||
endedAt?: string
|
||||
durationMs?: number
|
||||
success?: boolean
|
||||
@@ -20,9 +21,7 @@ export interface ConsoleEntry {
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
/** Whether this block is currently running */
|
||||
isRunning?: boolean
|
||||
/** Whether this block execution was canceled */
|
||||
isCanceled?: boolean
|
||||
}
|
||||
|
||||
@@ -33,14 +32,12 @@ export interface ConsoleUpdate {
|
||||
error?: string | Error | null
|
||||
warning?: string
|
||||
success?: boolean
|
||||
startedAt?: string
|
||||
endedAt?: string
|
||||
durationMs?: number
|
||||
input?: any
|
||||
/** Whether this block is currently running */
|
||||
isRunning?: boolean
|
||||
/** Whether this block execution was canceled */
|
||||
isCanceled?: boolean
|
||||
/** Iteration context for subflow blocks */
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
|
||||
Reference in New Issue
Block a user