Skip to content

v0.6.32: BYOK fixes, ui improvements, cloudwatch tools#4060

Merged
waleedlatif1 merged 11 commits intomainfrom
staging
Apr 9, 2026
Merged

v0.6.32: BYOK fixes, ui improvements, cloudwatch tools#4060
waleedlatif1 merged 11 commits intomainfrom
staging

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@waleedlatif1 waleedlatif1 commented Apr 8, 2026

…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
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Apr 9, 2026 5:20am

Request Review

* 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>
@waleedlatif1 waleedlatif1 changed the title v0.6.32: hitl streaming, BYOK fixes, ui improvements v0.6.32: BYOK fixes, ui improvements, cloudwatch tools Apr 8, 2026
…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
@waleedlatif1 waleedlatif1 marked this pull request as ready for review April 9, 2026 05:30
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 9, 2026

PR Summary

Medium Risk
Medium risk because it changes core execution/resume/streaming plumbing (including new async job queueing) and billing cost behavior for BYOK streaming responses, which can affect workflow runtime correctness and cost reporting.

Overview
Human-in-the-loop resume now supports sync, SSE streaming, and true async execution, driven by executionMode stored in the execution snapshot and respected by POST /api/resume/... (including a new polling endpoint and updated docs). Async resumes are dispatched via a new resume-execution job type with BullMQ/Trigger.dev plumbing, plus new worker processor support.

Streaming execution was refactored so createStreamingResponse is executor-agnostic (takes an executeFn), properly handles paused results, and only emits selected outputs for completed blocks; chat/form/execute routes were updated accordingly.

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 401, speech token uses server-side usage limit checks with a UI callback to navigate to subscription settings, whitelabeling removes logo flash by fetching org settings server-side and disabling SSR for settings UI, auto-layout preserves subblock state, JSM request creation supports form answers with better error messages, and various UI/doc tweaks (SVG uploads, tool cost display, excluded output types, version strings).

Reviewed by Cursor Bugbot for commit db23078. Configure here.

@waleedlatif1 waleedlatif1 merged commit f8f3758 into main Apr 9, 2026
27 checks passed
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 9, 2026

Greptile Summary

This release bundles several fixes and features: BYOK cost-zeroing for streamed workflows (via a zeroCostForBYOK property interceptor on the execution output), server-side whitelabeling settings to eliminate logo flash, async/streaming HITL resume support, a new CloudWatch Publish Metric operation, improved JSM form-based request support, and removal of the now-dead getUserSubscriptionState / hasExceededCostLimit code paths.

Confidence Score: 4/5

Safe 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 completedBlockIds filtering can silently drop partial block output. All are bounded in impact and the developer has already noted the BYOK limitation with a warning log. No P0/P1 issues found.

apps/sim/providers/index.ts (zeroCostForBYOK silent no-op), apps/sim/app/api/resume/.../route.ts (implicit branch ordering)

Vulnerabilities

No security concerns identified. AWS credentials in the new put-metric-data route are validated server-side and never logged. The checkInternalAuth guard is correctly applied. Socket-token auth error handling correctly surfaces UNAUTHORIZED as 401 rather than 500 without leaking session details.

Important Files Changed

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
Loading

Reviews (1): Last reviewed commit: "fix(jsm): improve create request error h..." | Re-trigger Greptile

Comment on lines +71 to +78
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),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Comment on lines 168 to +170
const isApiCaller = access.auth?.authType === AuthType.API_KEY
const snapshotConfig = isApiCaller ? getStoredSnapshotConfig(enqueueResult.pausedExecution) : {}
const executionMode = isApiCaller ? (snapshotConfig.executionMode ?? 'sync') : undefined
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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') {

Comment on lines 108 to +111
}

if (!completedBlockIds.has(blockId)) {
continue
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Fix All in Cursor

❌ 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']
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db23078. Configure here.

configurable: true,
enumerable: true,
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db23078. Configure here.

executionMode: 'sync',
},
executionId
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db23078. Configure here.

if (value?.toolCost && typeof value.toolCost === 'number') {
toolCost = value.toolCost
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit db23078. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants