Skip to content

Commit 8cce371

Browse files
committed
Merge remote-tracking branch 'origin/staging' into improvement/snapshot-service
2 parents 9a8f053 + 57f0837 commit 8cce371

64 files changed

Lines changed: 15685 additions & 2040 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/en/quick-reference/index.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,11 @@ A quick lookup for everyday actions in the Sim workflow editor. For keyboard sho
335335
<td>Access previous versions in Deploy tab → **Promote to live**</td>
336336
<td><ActionImage src="/static/quick-reference/promote-deployment.png" alt="Promote deployment to live" /></td>
337337
</tr>
338+
<tr>
339+
<td>Add version description</td>
340+
<td>Deploy tab → Click description icon → Add or generate description</td>
341+
<td><ActionVideo src="quick-reference/deployment-description.mp4" alt="Add deployment version description" /></td>
342+
</tr>
338343
<tr>
339344
<td>Copy API endpoint</td>
340345
<td>Deploy tab → API → Copy API cURL</td>

apps/sim/app/_styles/globals.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
--panel-width: 320px; /* PANEL_WIDTH.DEFAULT */
1515
--toolbar-triggers-height: 300px; /* TOOLBAR_TRIGGERS_HEIGHT.DEFAULT */
1616
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
17-
--terminal-height: 155px; /* TERMINAL_HEIGHT.DEFAULT */
17+
--terminal-height: 206px; /* TERMINAL_HEIGHT.DEFAULT */
1818
}
1919

2020
.sidebar-container {

apps/sim/app/api/schedules/[id]/route.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ describe('Schedule PUT API (Reactivate)', () => {
344344
expect(nextRunAt).toBeGreaterThan(beforeCall)
345345
expect(nextRunAt).toBeLessThanOrEqual(afterCall + 5 * 60 * 1000 + 1000)
346346
// Should align with 5-minute intervals (minute divisible by 5)
347-
expect(new Date(nextRunAt).getMinutes() % 5).toBe(0)
347+
expect(new Date(nextRunAt).getUTCMinutes() % 5).toBe(0)
348348
})
349349

350350
it('calculates nextRunAt from daily cron expression', async () => {
@@ -572,7 +572,7 @@ describe('Schedule PUT API (Reactivate)', () => {
572572
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
573573
expect(nextRunAt.getTime()).toBeLessThanOrEqual(beforeCall + 10 * 60 * 1000 + 1000)
574574
// Should align with 10-minute intervals
575-
expect(nextRunAt.getMinutes() % 10).toBe(0)
575+
expect(nextRunAt.getUTCMinutes() % 10).toBe(0)
576576
})
577577

578578
it('handles hourly schedules with timezone correctly', async () => {
@@ -598,8 +598,8 @@ describe('Schedule PUT API (Reactivate)', () => {
598598

599599
// Should be a future date at minute 15
600600
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
601-
expect(nextRunAt.getMinutes()).toBe(15)
602-
expect(nextRunAt.getSeconds()).toBe(0)
601+
expect(nextRunAt.getUTCMinutes()).toBe(15)
602+
expect(nextRunAt.getUTCSeconds()).toBe(0)
603603
})
604604

605605
it('handles custom cron expressions with complex patterns and timezone', async () => {

apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,24 @@ import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/
99

1010
const logger = createLogger('WorkflowDeploymentVersionAPI')
1111

12-
const patchBodySchema = z.object({
13-
name: z
14-
.string()
15-
.trim()
16-
.min(1, 'Name cannot be empty')
17-
.max(100, 'Name must be 100 characters or less'),
18-
})
12+
const patchBodySchema = z
13+
.object({
14+
name: z
15+
.string()
16+
.trim()
17+
.min(1, 'Name cannot be empty')
18+
.max(100, 'Name must be 100 characters or less')
19+
.optional(),
20+
description: z
21+
.string()
22+
.trim()
23+
.max(500, 'Description must be 500 characters or less')
24+
.nullable()
25+
.optional(),
26+
})
27+
.refine((data) => data.name !== undefined || data.description !== undefined, {
28+
message: 'At least one of name or description must be provided',
29+
})
1930

2031
export const dynamic = 'force-dynamic'
2132
export const runtime = 'nodejs'
@@ -88,33 +99,46 @@ export async function PATCH(
8899
return createErrorResponse(validation.error.errors[0]?.message || 'Invalid request body', 400)
89100
}
90101

91-
const { name } = validation.data
102+
const { name, description } = validation.data
103+
104+
const updateData: { name?: string; description?: string | null } = {}
105+
if (name !== undefined) {
106+
updateData.name = name
107+
}
108+
if (description !== undefined) {
109+
updateData.description = description
110+
}
92111

93112
const [updated] = await db
94113
.update(workflowDeploymentVersion)
95-
.set({ name })
114+
.set(updateData)
96115
.where(
97116
and(
98117
eq(workflowDeploymentVersion.workflowId, id),
99118
eq(workflowDeploymentVersion.version, versionNum)
100119
)
101120
)
102-
.returning({ id: workflowDeploymentVersion.id, name: workflowDeploymentVersion.name })
121+
.returning({
122+
id: workflowDeploymentVersion.id,
123+
name: workflowDeploymentVersion.name,
124+
description: workflowDeploymentVersion.description,
125+
})
103126

104127
if (!updated) {
105128
return createErrorResponse('Deployment version not found', 404)
106129
}
107130

108-
logger.info(
109-
`[${requestId}] Renamed deployment version ${version} for workflow ${id} to "${name}"`
110-
)
131+
logger.info(`[${requestId}] Updated deployment version ${version} for workflow ${id}`, {
132+
name: updateData.name,
133+
description: updateData.description,
134+
})
111135

112-
return createSuccessResponse({ name: updated.name })
136+
return createSuccessResponse({ name: updated.name, description: updated.description })
113137
} catch (error: any) {
114138
logger.error(
115-
`[${requestId}] Error renaming deployment version ${version} for workflow ${id}`,
139+
`[${requestId}] Error updating deployment version ${version} for workflow ${id}`,
116140
error
117141
)
118-
return createErrorResponse(error.message || 'Failed to rename deployment version', 500)
142+
return createErrorResponse(error.message || 'Failed to update deployment version', 500)
119143
}
120144
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
2626
id: workflowDeploymentVersion.id,
2727
version: workflowDeploymentVersion.version,
2828
name: workflowDeploymentVersion.name,
29+
description: workflowDeploymentVersion.description,
2930
isActive: workflowDeploymentVersion.isActive,
3031
createdAt: workflowDeploymentVersion.createdAt,
3132
createdBy: workflowDeploymentVersion.createdBy,

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx

Lines changed: 18 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -57,40 +57,6 @@ function useSetToggle() {
5757
)
5858
}
5959

60-
/**
61-
* Generates a unique key for a trace span
62-
*/
63-
function getSpanKey(span: TraceSpan): string {
64-
if (span.id) {
65-
return span.id
66-
}
67-
const name = span.name || 'span'
68-
const start = span.startTime || 'unknown-start'
69-
const end = span.endTime || 'unknown-end'
70-
return `${name}|${start}|${end}`
71-
}
72-
73-
/**
74-
* Merges multiple arrays of trace span children, deduplicating by span key
75-
*/
76-
function mergeTraceSpanChildren(...groups: TraceSpan[][]): TraceSpan[] {
77-
const merged: TraceSpan[] = []
78-
const seen = new Set<string>()
79-
80-
groups.forEach((group) => {
81-
group.forEach((child) => {
82-
const key = getSpanKey(child)
83-
if (seen.has(key)) {
84-
return
85-
}
86-
seen.add(key)
87-
merged.push(child)
88-
})
89-
})
90-
91-
return merged
92-
}
93-
9460
/**
9561
* Parses a time value to milliseconds
9662
*/
@@ -116,34 +82,16 @@ function hasErrorInTree(span: TraceSpan): boolean {
11682

11783
/**
11884
* Normalizes and sorts trace spans recursively.
119-
* Merges children from both span.children and span.output.childTraceSpans,
120-
* deduplicates them, and sorts by start time.
85+
* Deduplicates children and sorts by start time.
12186
*/
12287
function normalizeAndSortSpans(spans: TraceSpan[]): TraceSpan[] {
12388
return spans
12489
.map((span) => {
12590
const enrichedSpan: TraceSpan = { ...span }
12691

127-
// Clean output by removing childTraceSpans after extracting
128-
if (enrichedSpan.output && typeof enrichedSpan.output === 'object') {
129-
enrichedSpan.output = { ...enrichedSpan.output }
130-
if ('childTraceSpans' in enrichedSpan.output) {
131-
const { childTraceSpans, ...cleanOutput } = enrichedSpan.output as {
132-
childTraceSpans?: TraceSpan[]
133-
} & Record<string, unknown>
134-
enrichedSpan.output = cleanOutput
135-
}
136-
}
137-
138-
// Merge and deduplicate children from both sources
139-
const directChildren = Array.isArray(span.children) ? span.children : []
140-
const outputChildren = Array.isArray(span.output?.childTraceSpans)
141-
? (span.output!.childTraceSpans as TraceSpan[])
142-
: []
143-
144-
const mergedChildren = mergeTraceSpanChildren(directChildren, outputChildren)
145-
enrichedSpan.children =
146-
mergedChildren.length > 0 ? normalizeAndSortSpans(mergedChildren) : undefined
92+
// Process and deduplicate children
93+
const children = Array.isArray(span.children) ? span.children : []
94+
enrichedSpan.children = children.length > 0 ? normalizeAndSortSpans(children) : undefined
14795

14896
return enrichedSpan
14997
})
@@ -573,7 +521,19 @@ const TraceSpanNode = memo(function TraceSpanNode({
573521
return children.sort((a, b) => parseTime(a.startTime) - parseTime(b.startTime))
574522
}, [span, spanId, spanStartTime])
575523

576-
const hasChildren = allChildren.length > 0
524+
// Hide empty model timing segments for agents without tool calls
525+
const filteredChildren = useMemo(() => {
526+
const isAgent = span.type?.toLowerCase() === 'agent'
527+
const hasToolCalls =
528+
(span.toolCalls?.length ?? 0) > 0 || allChildren.some((c) => c.type?.toLowerCase() === 'tool')
529+
530+
if (isAgent && !hasToolCalls) {
531+
return allChildren.filter((c) => c.type?.toLowerCase() !== 'model')
532+
}
533+
return allChildren
534+
}, [allChildren, span.type, span.toolCalls])
535+
536+
const hasChildren = filteredChildren.length > 0
577537
const isExpanded = isRootWorkflow || expandedNodes.has(spanId)
578538
const isToggleable = !isRootWorkflow
579539

@@ -685,7 +645,7 @@ const TraceSpanNode = memo(function TraceSpanNode({
685645
{/* Nested Children */}
686646
{hasChildren && (
687647
<div className='flex min-w-0 flex-col gap-[2px] border-[var(--border)] border-l pl-[10px]'>
688-
{allChildren.map((child, index) => (
648+
{filteredChildren.map((child, index) => (
689649
<div key={child.id || `${spanId}-child-${index}`} className='pl-[6px]'>
690650
<TraceSpanNode
691651
span={child}

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { ScrollArea } from '@/components/ui/scroll-area'
1919
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
2020
import { cn } from '@/lib/core/utils/cn'
21+
import { filterHiddenOutputKeys } from '@/lib/logs/execution/trace-spans/trace-spans'
2122
import {
2223
ExecutionSnapshot,
2324
FileCards,
@@ -274,16 +275,13 @@ export const LogDetails = memo(function LogDetails({
274275
return isWorkflowExecutionLog && log?.cost
275276
}, [log, isWorkflowExecutionLog])
276277

277-
// Extract and clean the workflow final output (remove childTraceSpans for cleaner display)
278+
// Extract and clean the workflow final output (recursively remove hidden keys for cleaner display)
278279
const workflowOutput = useMemo(() => {
279280
const executionData = log?.executionData as
280281
| { finalOutput?: Record<string, unknown> }
281282
| undefined
282283
if (!executionData?.finalOutput) return null
283-
const { childTraceSpans, ...cleanOutput } = executionData.finalOutput as {
284-
childTraceSpans?: unknown
285-
} & Record<string, unknown>
286-
return cleanOutput
284+
return filterHiddenOutputKeys(executionData.finalOutput) as Record<string, unknown>
287285
}, [log?.executionData])
288286

289287
useEffect(() => {

apps/sim/app/workspace/[workspaceId]/logs/logs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function Logs() {
7878
// eslint-disable-next-line react-hooks/exhaustive-deps
7979
}, [])
8080

81-
const [isLive, setIsLive] = useState(false)
81+
const [isLive, setIsLive] = useState(true)
8282
const [isVisuallyRefreshing, setIsVisuallyRefreshing] = useState(false)
8383
const [isExporting, setIsExporting] = useState(false)
8484
const isSearchOpenRef = useRef<boolean>(false)

0 commit comments

Comments
 (0)