Skip to content

feat(workflows): sim trigger, logs v2 block, toolbar renaming#4941

Open
icecrasher321 wants to merge 6 commits into
stagingfrom
feat/hooks-wf
Open

feat(workflows): sim trigger, logs v2 block, toolbar renaming#4941
icecrasher321 wants to merge 6 commits into
stagingfrom
feat/hooks-wf

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

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

  • New feature

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

icecrasher321 and others added 6 commits June 9, 2026 20:05
…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>
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jun 10, 2026 5:38am

Request Review

@cursor

cursor Bot commented Jun 10, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Removes the entire workspace notification delivery path (APIs, UI, background jobs) in favor of in-process workspace events—a breaking change for customers using Logs notifications; also touches auth on log APIs and webhook routing for internal triggers.

Overview
Replaces workspace log notifications with workflow-native alerting. The Logs-page notification UI, workspace notification REST APIs, workflow notification email previews, and the workspace-notification-delivery background job are removed. Real-time reactions move to a new Sim trigger (sim_workspace_event): deployed workflows run in-process on execution success/error, deploys, and the former alert rules (failures, latency, cost, inactivity, etc.), with HTTP webhook delivery blocked for internal sim/table trigger paths. No-activity evaluation is wired through /api/workspace-events/poll (cron lock/key renamed).

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 ChipTags plus +N; subblock summary text is centralized in lib/workflows/subblocks/display for canvas and preview. Log detail-by-execution accepts session or internal auth.

Reviewed by Cursor Bugbot for commit 7313ce2. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

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

  • Sim trigger: New workspace-events emitter wires into the execution logger and deploy orchestration; cooldown state survives redeploys via the new sim_trigger_state table; forged HTTP deliveries to internal trigger paths are blocked with a 404 in the webhook route.
  • Logs v2 block: LogsV2Block (Query Logs + Get Run Details) with workflow multi-select, time-range presets, and cost/duration filters; shared display.ts helpers eliminate duplicated display logic between the canvas editor and the read-only preview.
  • Cleanup: All notification tables, APIs, background jobs, email templates, and frontend components are removed; migration 0231 drops them and creates sim_trigger_state.

Confidence Score: 4/5

Safe to merge with one fix: fetchNoActivitySubscriptions in the no-activity cron poller needs an ORDER BY before the cap so subscriptions are processed deterministically rather than randomly at scale.

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 fetchNoActivitySubscriptions: it applies a hard 500-row LIMIT with no ORDER BY, meaning which subscriptions get processed at scale is non-deterministic across cron runs. The developer clearly understood this pattern (they added ORDER BY with an explanatory comment in the companion fetchWatchedWorkflows), so it appears to be an oversight. All other new code — the Logs v2 block, display helpers, migration, and deploy-event emission — looks correct.

apps/sim/lib/workspace-events/no-activity.ts — the fetchNoActivitySubscriptions query is the only file that needs a fix before merge.

Important Files Changed

Filename Overview
apps/sim/lib/workspace-events/emitter.ts New event emitter for workspace events; loop prevention and atomic cooldown claim are correct, but no_activity subscriptions are silently iterated and discarded on every execution completion (unnecessary DB round-trips).
apps/sim/lib/workspace-events/no-activity.ts New no-activity cron poller; fetchNoActivitySubscriptions is missing ORDER BY so results above the 500 cap are non-deterministic — some subscriptions could never fire. fetchWatchedWorkflows in the same file correctly uses ORDER BY with the same cap logic.
apps/sim/lib/workspace-events/state.ts New cooldown state helpers; uses atomic upsert with setWhere condition to prevent double-firing under concurrency.
apps/sim/lib/workspace-events/subscriptions.ts Fetches workspace-scoped Sim trigger subscriptions with deployment version join; input bounds clamping looks correct.
apps/sim/lib/workspace-events/rules.ts Rule evaluation functions for all alert conditions; correctly excludes Sim-trigger executions from statistics and uses DB-side aggregates on the hot path.
apps/sim/triggers/sim/workspace-event.ts New Sim trigger configuration with all event types and rule subblocks; outputs are derived from the shared SIM_EVENT_PAYLOAD_FIELDS constant.
apps/sim/triggers/constants.ts Adds INTERNAL_TRIGGER_PROVIDERS set and isInternalTriggerProvider guard to block forged HTTP deliveries to sim/table trigger paths.
apps/sim/app/api/webhooks/trigger/[path]/route.ts Correctly rejects HTTP deliveries to internal trigger paths (sim, table) with 404 to prevent event forgery.
apps/sim/blocks/blocks/logs.ts Adds LogsV2Block with Query Logs and Get Run Details operations, workflow multi-select, time-range presets, and cost/duration filters.
apps/sim/tools/logs/query_runs.ts New Query Logs tool; correctly converts credit cost values to dollars before passing to the API endpoint.
apps/sim/tools/logs/get_run_details.ts New Get Run Details tool; uses checkSessionOrInternalAuth and passes workspaceId for authorization scoping.
apps/sim/lib/workflows/subblocks/display.ts New shared display helpers extracted from workflow-block.tsx; pure functions shared between canvas editor and preview, with type guards for all subblock shapes.
packages/db/migrations/0231_sim_trigger_workspace_events.sql Creates sim_trigger_state table, drops notification tables and enums; straightforward schema migration with foreign key and cascade.
apps/sim/lib/workflows/orchestration/deploy.ts Adds fire-and-forget emitWorkflowDeployedEvent call after full deploy and version activation; void-cast correctly so failures never affect the deploy path.
apps/sim/app/api/workspace-events/poll/route.ts New cron-auth-gated poll endpoint for no-activity events; uses Redis distributed lock to prevent concurrent runs.

Sequence Diagram

sequenceDiagram
    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
Loading

Comments Outside Diff (2)

  1. apps/sim/lib/workspace-events/no-activity.ts

    P1 Adding ORDER BY on webhook.id (or any stable column) makes the capped scan deterministic across cron invocations, consistent with the same design reasoning documented in fetchWatchedWorkflows.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  2. apps/sim/lib/workspace-events/emitter.ts

    P2 Adding an early continue for no_activity subscriptions mirrors the existing guard for workflow_deployed and avoids three unnecessary DB round-trips (cooldown read, rule evaluation, cooldown claim) on every execution completion for subscriptions that the emitter can never fire.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "chore: sync lockfile after staging merge" | Re-trigger Greptile

Comment on lines +33 to +68
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

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.

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

Comment on lines +143 to +160
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,

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

Comment on lines +113 to +142
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,

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

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.

1 participant