v0.6.32: BYOK fixes, ui improvements, cloudwatch tools#4060
v0.6.32: BYOK fixes, ui improvements, cloudwatch tools#4060waleedlatif1 merged 11 commits intomainfrom
Conversation
…ntext (#4055) Auto-layout was reading from getWorkflowState() without merging subblock store values, then persisting stale subblock data to the database. This caused runtime-edited values (e.g. router_v2 context) to be overwritten with their initial/empty values whenever auto-layout was triggered.
…ver-side (#4057) * fix(whitelabeling): eliminate logo flash by fetching org settings server-side * improvement(whitelabeling): add SVG support for logo and wordmark uploads * skelly in workspace header * remove dead code * fix(whitelabeling): hydration error, SVG support, skeleton shimmer, dead code removal * fix(whitelabeling): blob preview dep cycle and missing color fallback * fix(whitelabeling): use brand-accent as color fallback when workspace color is undefined * chore(whitelabeling): inline hasOrgBrand
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
* fix(error): catch socket auth error as 4xx * Switch to type guard --------- Co-authored-by: Theodore Li <theo@sim.ai>
* fix(billing): skip billing on streamed workflows with byok * Simplify logic * Address comments, skip tokenization billing fallback * Fix tool usage billing for streamed outputs * fix(webhook): throw webhook errors as 4xxs (#4050) * fix(webhook): throw webhook errors as 4xxs * Fix shadowing body var --------- Co-authored-by: Theodore Li <theo@sim.ai> * feat(enterprise): cloud whitelabeling for enterprise orgs (#4047) * feat(enterprise): cloud whitelabeling for enterprise orgs * fix(enterprise): scope enterprise plan check to target org in whitelabel PUT * fix(enterprise): use isOrganizationOnEnterprisePlan for org-scoped enterprise check * fix(enterprise): allow clearing whitelabel fields and guard against empty update result * fix(enterprise): remove webp from logo accept attribute to match upload hook validation * improvement(billing): use isBillingEnabled instead of isProd for plan gate bypasses * fix(enterprise): show whitelabeling nav item when billing is enabled on non-hosted environments * fix(enterprise): accept relative paths for logoUrl since upload API returns /api/files/serve/ paths * fix(whitelabeling): prevent logo flash on refresh by hiding logo while branding loads * fix(whitelabeling): wire hover color through CSS token on tertiary buttons * fix(whitelabeling): show sim logo by default, only replace when org logo loads * fix(whitelabeling): cache org logo url in localstorage to eliminate flash on repeat visits * feat(whitelabeling): add wordmark support with drag/drop upload * updated turbo * fix(whitelabeling): defer localstorage read to effect to prevent hydration mismatch * fix(whitelabeling): use layout effect for cache read to eliminate logo flash before paint * fix(whitelabeling): cache theme css to eliminate color flash before org settings resolve * fix(whitelabeling): deduplicate HEX_COLOR_REGEX into lib/branding and remove mutation from useCallback deps * fix(whitelabeling): use cookie-based SSR cache to eliminate brand flash on all page loads * fix(whitelabeling): use !orgSettings condition to fix SSR brand cache injection React Query returns isLoading: false with data: undefined during SSR, so the previous brandingLoading condition was always false on the server — initialCache was never injected into brandConfig. Changing to !orgSettings correctly applies the cookie cache both during SSR and while the client-side query loads, eliminating the logo flash on hard refresh. * fix(editor): stop highlighting start.input as blue when block is not connected to starter (#4054) * fix: merge subblock values in auto-layout to prevent losing router context (#4055) Auto-layout was reading from getWorkflowState() without merging subblock store values, then persisting stale subblock data to the database. This caused runtime-edited values (e.g. router_v2 context) to be overwritten with their initial/empty values whenever auto-layout was triggered. * fix(whitelabeling): eliminate logo flash by fetching org settings server-side (#4057) * fix(whitelabeling): eliminate logo flash by fetching org settings server-side * improvement(whitelabeling): add SVG support for logo and wordmark uploads * skelly in workspace header * remove dead code * fix(whitelabeling): hydration error, SVG support, skeleton shimmer, dead code removal * fix(whitelabeling): blob preview dep cycle and missing color fallback * fix(whitelabeling): use brand-accent as color fallback when workspace color is undefined * chore(whitelabeling): inline hasOrgBrand --------- Co-authored-by: Theodore Li <theo@sim.ai>
…4062) * fix(subscription-state): remove dead code, change token route check * update tests * remove mock * improve ux past usage limit
* improvement(hitl): support streaming, async, update docs * update docs * fix tests * fix abort signal passthrough * module level const * fix form route * address comments * fix build
* fix(hitl): async resume * fix
* feat(block): Add cloudwatch publish operation * fix(integrations): validate and fix cloudwatch, cloudformation, athena conventions - Update tool version strings from '1.0' to '1.0.0' across all three integrations - Add missing `export * from './types'` barrel re-exports (cloudwatch, cloudformation) - Add docsLink, wandConfig timestamps, mode: 'advanced' on optional fields (cloudwatch) - Add dropdown defaults, ZodError handling, docs intro section (cloudwatch) - Add mode: 'advanced' on limit field (cloudformation) - Alphabetize registry entries (cloudwatch, cloudformation) - Fix athena docs maxResults range (1-999) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cloudwatch): complete put_metric_data unit dropdown, add missing outputs, fix JSON error handling - Add all 27 valid CloudWatch StandardUnit values to metricUnit dropdown (was 13) - Add missing block outputs for put_metric_data: success, namespace, metricName, value, unit - Add try-catch around dimensions JSON.parse in put-metric-data route for proper 400 errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cloudwatch): fix DescribeAlarms returning only MetricAlarm when "All Types" selected Per AWS docs, omitting AlarmTypes returns only MetricAlarm. Now explicitly sends both MetricAlarm and CompositeAlarm when no filter is selected. Also fix dimensions JSON parse errors returning 500 instead of 400 in get-metric-statistics route. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cloudwatch): validate dimensions JSON at Zod schema level Move dimensions validation from runtime try-catch to Zod refinement, catching malformed JSON and arrays at schema validation time (400) instead of runtime (500). Also rejects JSON arrays that would produce meaningless numeric dimension names. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cloudwatch): reject non-numeric metricValue instead of silently publishing 0 Add NaN guard in block config and .finite() refinement in Zod schema so "abc" → NaN is caught at both layers instead of coercing to 0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cloudwatch): use Number.isFinite to also reject Infinity in block config Aligns block-level validation with route's Zod .finite() refinement so Infinity/-Infinity are caught at the block config layer, not just the API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Theodore Li <teddy@zenobiapay.com> Co-authored-by: Waleed Latif <walif6@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ssion support (#4066) * fix(jsm): improve create request error handling, add form-based submission support * refactor(jsm): extract parseJsmErrorMessage helper to deduplicate error handling * fix(jsm): remove required on summary for advanced mode, add JSON.parse error handling * fix(jsm): include description in requestFieldValues gate for form-only requests
PR SummaryMedium Risk Overview Streaming execution was refactored so Adds CloudWatch “Publish Metric” support end-to-end (tool + API route + block UI/docs + integrations metadata) and improves CloudWatch error handling defaults. Includes several fixes/improvements: BYOK streaming now forces zero model cost while preserving tool costs (and tokenization skips recalculation when billing already set pricing), socket-token auth errors map to Reviewed by Cursor Bugbot for commit db23078. Configure here. |
Greptile SummaryThis release bundles several fixes and features: BYOK cost-zeroing for streamed workflows (via a Confidence Score: 4/5Safe to merge; all three findings are P2 and none block correct operation in the common path. The BYOK streaming cost-zeroing has a known edge-case silent failure, the resume-route branch condition relies on implicit ordering, and the apps/sim/providers/index.ts (zeroCostForBYOK silent no-op), apps/sim/app/api/resume/.../route.ts (implicit branch ordering)
|
| Filename | Overview |
|---|---|
| apps/sim/providers/index.ts | Adds zeroCostForBYOK to intercept cost writes for BYOK streaming; silently no-ops if execution.output is not yet initialised at intercept time. |
| apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts | Adds streaming and async resume modes; logic is correct but the executionMode !== 'async' guard implicitly relies on the stream branch returning early. |
| apps/sim/lib/workflows/streaming/streaming.ts | Refactors createStreamingResponse to accept an executeFn callback; adds completedBlockIds tracking and paused-status forwarding in the final SSE event. |
| apps/sim/lib/billing/core/subscription.ts | Removes dead getUserSubscriptionState and hasExceededCostLimit functions; clean dead-code removal with no regressions. |
| apps/sim/tools/cloudwatch/put_metric_data.ts | New CloudWatch PutMetricData tool; well-structured with correct user-only visibility for AWS credentials. |
| apps/sim/app/api/tools/cloudwatch/put-metric-data/route.ts | New route for CloudWatch PutMetricData; uses Zod validation, checkInternalAuth, and correctly handles unit and dimensions. |
| apps/sim/ee/whitelabeling/components/branding-provider.tsx | Replaces cookie-based brand cache with server-fetched initialOrgSettings prop; eliminates logo flash on initial load. |
| apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts | Applies mergeSubblockState after layout and on revert to prevent losing router context subblock values. |
| apps/sim/background/resume-execution.ts | New background task for async HITL resume; fetches paused execution by ID and delegates to PauseResumeManager. |
| apps/sim/lib/tokenization/streaming.ts | Adds early-exit guard in processStreamingBlockLog when cost already has billing-layer pricing (BYOK zero cost sentinel). |
Sequence Diagram
sequenceDiagram
participant Client
participant ResumeAPI as POST /api/resume/[...]
participant HITL as PauseResumeManager
participant Queue as BullMQ / Trigger.dev
participant Worker as Resume Worker
Client->>ResumeAPI: POST with resumeInput
ResumeAPI->>ResumeAPI: validateWorkflowAccess + preprocessExecution
ResumeAPI->>HITL: enqueueResumeInput()
HITL-->>ResumeAPI: { pausedExecution, resumeExecutionId, ... }
alt executionMode = stream (API caller)
ResumeAPI->>HITL: startResumeExecution(onStream, onBlockComplete)
HITL-->>ResumeAPI: ReadableStream (SSE)
ResumeAPI-->>Client: 200 SSE stream
else executionMode = sync (API caller)
ResumeAPI->>HITL: startResumeExecution()
HITL-->>ResumeAPI: ExecutionResult
ResumeAPI-->>Client: 200 JSON result
else executionMode = async (API caller)
ResumeAPI->>Queue: enqueue resume-execution job
Queue-->>ResumeAPI: jobId
ResumeAPI-->>Client: 202 { jobId, statusUrl }
Queue->>Worker: processResume(job)
Worker->>HITL: startResumeExecution()
HITL-->>Worker: ExecutionResult
else non-API caller (dashboard)
ResumeAPI->>HITL: startResumeExecution() fire-and-forget
ResumeAPI-->>Client: 200 JSON (immediate)
end
Reviews (1): Last reviewed commit: "fix(jsm): improve create request error h..." | Re-trigger Greptile
| if (!output || typeof output !== 'object') { | ||
| logger.warn('zeroCostForBYOK: output not available at intercept time; cost may not be zeroed') | ||
| return | ||
| } | ||
|
|
||
| let toolCost = 0 | ||
| Object.defineProperty(output, 'cost', { | ||
| get: () => (toolCost > 0 ? { ...ZERO_COST, toolCost, total: toolCost } : ZERO_COST), |
There was a problem hiding this comment.
zeroCostForBYOK silently no-ops when output is not yet initialised
If a provider returns a StreamingExecution where execution.output is null / undefined at the moment zeroCostForBYOK runs (i.e. the output object is created lazily during streaming rather than eagerly in executeRequest), the function logs a warning and returns without setting up the Object.defineProperty cost interceptor. The cost getter is never installed, so subsequent writes from the streaming callbacks reach output.cost unfiltered and BYOK users may be billed for model tokens they shouldn't owe.
The warning makes clear the developer is aware of this race, but there is no fallback — the cost is simply not zeroed. Consider deferring the intercept to the first write via a Proxy on the execution object itself, or asserting that all providers eagerly initialise output before returning StreamingExecution.
| const isApiCaller = access.auth?.authType === AuthType.API_KEY | ||
| const snapshotConfig = isApiCaller ? getStoredSnapshotConfig(enqueueResult.pausedExecution) : {} | ||
| const executionMode = isApiCaller ? (snapshotConfig.executionMode ?? 'sync') : undefined |
There was a problem hiding this comment.
executionMode !== 'async' matches 'stream' after stream branch already returned
The condition on the second branch (executionMode !== 'async') evaluates to true for executionMode === 'stream' — it only works correctly because the stream branch returns before reaching this line. The implicit coupling makes the flow fragile: if a future refactor wraps these branches in a shared helper or reorders them, the sync path will silently execute for stream mode too. Using an explicit executionMode === 'sync' guard here (or restructuring as if/else if) would make the intent unambiguous.
| const isApiCaller = access.auth?.authType === AuthType.API_KEY | |
| const snapshotConfig = isApiCaller ? getStoredSnapshotConfig(enqueueResult.pausedExecution) : {} | |
| const executionMode = isApiCaller ? (snapshotConfig.executionMode ?? 'sync') : undefined | |
| if (isApiCaller && executionMode === 'sync') { |
| } | ||
|
|
||
| if (!completedBlockIds.has(blockId)) { | ||
| continue |
There was a problem hiding this comment.
Blocks that stream but never fire
onBlockComplete are dropped from selectedOutputs
completedBlockIds is only populated inside onBlockCompleteCallback. A block that produces streamed text but whose onBlockComplete never fires (e.g. error mid-stream, abort signal) will not be in the set, so its output key is silently excluded from the selectedOutputs final result even though streaming chunks were already sent to the client. For the HITL / resume streaming path this is the desired truncation behaviour, but it may surprise callers of the normal execute path when a partially-streamed block disappears from the final final SSE event.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit db23078. Configure here.
| const logger = createLogger('ProfilePictureUpload') | ||
| const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB | ||
| const ACCEPTED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg'] | ||
| const ACCEPTED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml'] |
There was a problem hiding this comment.
SVG uploads may pose stored XSS risk
Medium Severity
Adding image/svg+xml to accepted image types for profile pictures and logos introduces a stored XSS vulnerability. SVG files can embed JavaScript, and the file serving endpoint delivers them inline without sanitization. This means direct navigation to an uploaded SVG's URL can execute scripts in the application's origin, even though <img> tags safely block execution.
Reviewed by Cursor Bugbot for commit db23078. Configure here.
| configurable: true, | ||
| enumerable: true, | ||
| }) | ||
| } |
There was a problem hiding this comment.
BYOK cost interceptor may silently fail on streaming
High Severity
zeroCostForBYOK reads response.execution?.output at the moment the provider returns the StreamingExecution, but for streaming providers the output object is often not yet populated at that point. If output is undefined or null, the function logs a warning and returns without installing the getter/setter interceptor—meaning the BYOK user's model costs are never zeroed and they get billed as if using a platform key. The developer clearly anticipated this risk (the warning message says "cost may not be zeroed"), but it represents a billing-correctness gap for any provider whose streaming path lazily initialises execution.output.
Reviewed by Cursor Bugbot for commit db23078. Configure here.
| executionMode: 'sync', | ||
| }, | ||
| executionId | ||
| ), |
There was a problem hiding this comment.
Form route SSE parser never captures final output
Low Severity
The form route consumes the SSE stream looking for data.type === 'complete' || data.output at the top level, but createStreamingResponse emits the final event as { event: 'final', data: { success, output, … } }. The top-level parsed object has event and data keys—not type or output—so lastOutput always remains null. This is inert today since lastOutput isn't used in the success response, but it's dead code that will silently fail if ever relied upon.
Reviewed by Cursor Bugbot for commit db23078. Configure here.
| if (value?.toolCost && typeof value.toolCost === 'number') { | ||
| toolCost = value.toolCost | ||
| } | ||
| }, |
There was a problem hiding this comment.
BYOK setter ignores zero toolCost due to truthiness
Low Severity
In zeroCostForBYOK, the setter uses value?.toolCost && typeof value.toolCost === 'number' to capture tool costs. Because && short-circuits on falsy values, a toolCost of 0 is never captured. If a provider first writes a cost object with a positive toolCost and later overwrites it with toolCost: 0, the stale positive value persists in the closure, causing the getter to return an inflated total.
Reviewed by Cursor Bugbot for commit db23078. Configure here.


Uh oh!
There was an error while loading. Please reload this page.