feat(workflows): sim trigger, logs v2 block, toolbar renaming#4941
feat(workflows): sim trigger, logs v2 block, toolbar renaming#4941icecrasher321 wants to merge 6 commits into
Conversation
…low SQL scoping Code-review fixes: read the canonical workflowIds param in logs_v2 (the serializer deletes the source pair ids), aggregate failure-rate in the DB and switch rule windows to the indexed startedAt column, clamp rule config to the legacy contract bounds, push no_activity watch scoping into SQL before the LIMIT, fix the generated sim icon-map key, normalize docs wording, and drop dead exports. Co-authored-by: Cursor <cursoragent@cursor.com>
…splay module hygiene Second-pass review fixes: round integer rule fields so fractional input never reaches SQL LIMIT, gate workflow-name readiness on a successful non-placeholder load in both editor and preview (errored loads mislabeled valid workflows as deleted), lazily read the variables store in preview rows, move the filter-field JSON preview into the shared display module and unexport its single-consumer helpers, and align >= boundary copy (failure rate, error count, cooldown window) with implementation. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx # apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryHigh Risk Overview Adds operator/docs surface for log automation. New Logs block documentation (query run IDs + fetch run details/trace) and Sim trigger docs; execution API/logging docs now steer readers to the Sim trigger instead of workspace notification webhooks. Editor and canvas polish (no behavior change to execution). Workflow toolbar labels become Core Blocks / Integrations; multi-select dropdowns show at most two Reviewed by Cursor Bugbot for commit 7313ce2. Configure here. |
Greptile SummaryThis PR replaces the notifications feature with a Sim workspace-event trigger system and adds a Logs v2 block. The Sim trigger lets deployed workflows react to workspace events (execution success/error, deploy, and alert conditions like latency spikes, cost thresholds, and inactivity) delivered via the existing polled-webhook pipeline with per-block cooldowns and loop prevention.
Confidence Score: 4/5Safe to merge with one fix: The workspace-event emitter, cooldown state, and rule evaluation are all well-designed — loop prevention is correct, the atomic upsert prevents double-fires, and forged HTTP deliveries to internal trigger paths are blocked. The behavioral issue is in apps/sim/lib/workspace-events/no-activity.ts — the Important Files Changed
Sequence DiagramsequenceDiagram
participant Source as Source Workflow
participant Logger as ExecutionLogger
participant Emitter as WorkspaceEventEmitter
participant Subs as Subscriptions DB
participant Rules as RuleEvaluator
participant State as CooldownState (sim_trigger_state)
participant Proc as WebhookProcessor
participant Queue as Job Queue
participant Target as Target Workflow
Source->>Logger: execution completes
Logger->>Logger: persist execution log
Logger-->>Emitter: emitExecutionCompletedEvent(log) [fire-and-forget]
Emitter->>Emitter: check log.trigger ≠ 'sim' (loop guard)
Emitter->>Subs: fetchSimTriggerSubscriptions(workspaceId)
Subs-->>Emitter: active subscriptions
loop for each subscription
Emitter->>Emitter: check eventType, scope, status filters
alt rule-based event
Emitter->>State: readLastFiredAt(workflowId, blockKey, '')
State-->>Emitter: lastFiredAt
Emitter->>Rules: evaluateRule(eventType, config, context)
Rules->>Rules: query DB aggregates (failures, latency, cost)
Rules-->>Emitter: ruleFired
Emitter->>State: claimCooldown (atomic upsert + setWhere)
State-->>Emitter: claimed (bool)
end
Emitter->>Proc: processPolledWebhookEvent(webhook, workflow, payload)
alt shouldExecuteInline
Proc->>Target: execute directly
else queue routing
Proc->>Queue: enqueue webhook-execution job
Queue->>Target: execute (async)
end
end
Note over Emitter,Target: no_activity path runs via cron → pollNoActivityEvents() instead
|
| async function fetchNoActivitySubscriptions(): Promise<SimSubscription[]> { | ||
| const rows = await db | ||
| .select({ webhook, workflow }) | ||
| .from(webhook) | ||
| .innerJoin(workflow, eq(webhook.workflowId, workflow.id)) | ||
| .leftJoin( | ||
| workflowDeploymentVersion, | ||
| and( | ||
| eq(workflowDeploymentVersion.workflowId, workflow.id), | ||
| eq(workflowDeploymentVersion.isActive, true) | ||
| ) | ||
| ) | ||
| .where( | ||
| and( | ||
| eq(webhook.provider, SIM_TRIGGER_PROVIDER), | ||
| eq(webhook.isActive, true), | ||
| isNull(webhook.archivedAt), | ||
| eq(workflow.isDeployed, true), | ||
| isNull(workflow.archivedAt), | ||
| sql`${webhook.providerConfig}->>'eventType' = 'no_activity'`, | ||
| or( | ||
| eq(webhook.deploymentVersionId, workflowDeploymentVersion.id), | ||
| and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId)) | ||
| ) | ||
| ) | ||
| ) | ||
| .limit(MAX_SUBSCRIPTIONS_PER_POLL) | ||
|
|
||
| return rows | ||
| } | ||
|
|
||
| /** | ||
| * Resolves the workflows a no_activity subscription watches: deployed, active | ||
| * workflows in the subscriber's workspace, minus the subscriber itself, | ||
| * narrowed to the explicit selection when one is set (empty selection watches | ||
| * everything). Deployed-only keeps never-runnable draft workflows from |
There was a problem hiding this comment.
Missing ORDER BY in
fetchNoActivitySubscriptions — non-deterministic pagination above cap
fetchNoActivitySubscriptions applies .limit(MAX_SUBSCRIPTIONS_PER_POLL) (500) with no ORDER BY, so once the table exceeds 500 rows the database can return any 500 in any order. This means some subscriptions may never be processed across any poll cycle. Compare this with fetchWatchedWorkflows in the same file, which has an explicit comment: "The ORDER BY keeps the capped scan deterministic across polls" — and uses .orderBy(asc(workflow.id)). The same reasoning applies here: without ordering, subscriptions added after the cap is reached risk being starved indefinitely. A no_activity trigger configured for a critical workflow could silently stop firing.
| if (subscriptions.length === 0) return result | ||
|
|
||
| if (subscriptions.length >= MAX_SUBSCRIPTIONS_PER_POLL) { | ||
| logger.warn(`no_activity subscription scan hit the ${MAX_SUBSCRIPTIONS_PER_POLL} cap`) | ||
| } | ||
|
|
||
| for (const subscription of subscriptions) { | ||
| const config = parseSubscriptionConfig(subscription.webhook.providerConfig) | ||
| if (!config || config.eventType !== 'no_activity') continue | ||
|
|
||
| const workspaceId = subscription.workflow.workspaceId | ||
| if (!workspaceId) continue | ||
|
|
||
| const blockKey = subscription.webhook.blockId ?? subscription.webhook.path | ||
| const cooldownMs = noActivityCooldownMs(config) | ||
|
|
||
| const watched = await fetchWatchedWorkflows( | ||
| workspaceId, |
There was a problem hiding this comment.
MAX_WORKFLOWS_PER_SUBSCRIPTION cap silently excludes high-ID workflows when "all workflows" is selected
When a subscription watches every workflow (empty workflowIds), fetchWatchedWorkflows limits results to 500 sorted by asc(workflow.id). Workflows whose IDs sort after the first 500 (by string comparison on the short IDs) are permanently excluded from no-activity checks without any warning. The cap comment in fetchNoActivitySubscriptions warrants a similar note here, or at least a cap-hit log warning (as done for subscriptions on line 154) so operators can observe the truncation.
| if (config.eventType === 'execution_error' && context.status !== 'error') continue | ||
|
|
||
| if (isSimRuleEventType(config.eventType)) { | ||
| const blockKey = subscriptionBlockKey(subscription) | ||
|
|
||
| const lastFiredAt = await readLastFiredAt(subscription.webhook.workflowId, blockKey, '') | ||
| if (isWithinCooldown(lastFiredAt, SIM_RULE_COOLDOWN_MS)) continue | ||
|
|
||
| const ruleFired = await evaluateRule(config.eventType, config, context) | ||
| if (!ruleFired) continue | ||
|
|
||
| const claimed = await claimCooldown( | ||
| subscription.webhook.workflowId, | ||
| blockKey, | ||
| '', | ||
| SIM_RULE_COOLDOWN_MS | ||
| ) | ||
| if (!claimed) continue | ||
|
|
||
| logger.info(`Sim trigger rule ${config.eventType} fired`, { | ||
| subscriberWorkflowId: subscription.webhook.workflowId, | ||
| sourceWorkflowId: log.workflowId, | ||
| executionId: log.executionId, | ||
| }) | ||
| } | ||
|
|
||
| const payload = buildExecutionEventPayload({ | ||
| event: config.eventType as Parameters<typeof buildExecutionEventPayload>[0]['event'], | ||
| workflowName: workflowContext.workflow.name, | ||
| context, |
There was a problem hiding this comment.
no_activity subscriptions are fetched and iterated but silently dropped on every execution completion
Every subscription with eventType === 'no_activity' passes the initial if (config.eventType === 'workflow_deployed') continue guard, then enters the rule branch where evaluateRule immediately returns false. The subscription is never dispatched, but the emitter still spends one readLastFiredAt + one evaluateRule call + one claimCooldown attempt per no-activity subscription per execution completion. Adding an early continue for no_activity (analogous to the workflow_deployed guard above) would skip the unnecessary DB round-trips on what is intended to be a very hot path.
Summary
Replace the notifications feature with a Sim trigger block — deployed workflows now run as side-effects of workspace events (execution success/error, deploys, and the ported alert rules: consecutive failures, failure/latency/cost thresholds, latency spikes, error counts, inactivity) with per-block cooldowns, credit-denominated thresholds, and loop prevention; all notification UI, APIs, tables, and background jobs are removed (migration 0231, requires the workspace-events-poll cron repoint in infra).
Add a Logs v2 block and canvas polish — Query Logs (run IDs with the full Logs-page filter set) + Get Run Details (trace spans, credit costs) for building log-driven automations; toolbar renamed (Blocks → Core Blocks, Tools → Integrations), multi-select inputs now summarize with +N chips, and shared subblock display resolvers keep editor/preview hydration consistent.
Type of Change
Testing
Tested manually
Checklist