Commit Graph
4646 Commits
Author SHA1 Message Date
Waleed b6679a9880 feat(google-slides): complete API surface for branded slide generation (#4678)
* feat(google-slides): complete API surface for branded slide generation

* fix(google-slides): address PR review — explicit videoId mapping, fast base64 export, remove dead utility

* fix(google-slides): declare z-order operation output in block outputs map
2026-05-20 13:46:40 -07:00
Waleed 7e678552d6 improvement(elevenlabs): wire stability and similarity_boost end-to-end (#4679)
* improvement(elevenlabs): wire stability and similarity_boost end-to-end

* fix(elevenlabs): guard NaN in voice settings and always send both knobs together
2026-05-20 13:46:29 -07:00
Waleed a1b2130256 improvement(knowledge): batch trigger dispatch, prune redundant DB roundtrips (#4680)
* improvement(knowledge): batch trigger dispatch, prune redundant DB roundtrips

Connector sync was dispatching Trigger.dev document-processing jobs one
HTTP roundtrip at a time. processDocumentsWithQueue now uses
tasks.batchTrigger when Trigger.dev is available, collapsing N roundtrips
to ceil(N/1000). Idempotency keys protect against duplicate runs on retry.

Also trims DB roundtrips inside the sync loop:
- Per-batch isConnectorDeleted + isKnowledgeBaseDeleted collapsed into a
  single checkSyncLiveness JOIN (one SELECT instead of two per batch).
- Dropped redundant pre-upload isKnowledgeBaseDeleted checks from
  addDocument/updateDocument: the batch-boundary liveness check already
  catches pre-batch deletions and the in-tx FOR UPDATE is authoritative
  for races during the batch.
- Removed dead processDocumentsWithTrigger helper (never called).

* refactor(knowledge): split dispatch helpers, drop dead trigger branch

- Use the canonical DocumentProcessingPayload from the task module instead
  of the duplicate DocumentJobData interface in service.ts
- Pass typeof processDocumentTask as a generic to tasks.batchTrigger so the
  payload shape is type-checked against the task definition
- Inline TRIGGER_BATCH_SIZE provenance (Trigger.dev SDK 4.3.1+ doc'd cap,
  we're on 4.4.3)
- Split direct vs trigger dispatch into dispatchInProcess and
  dispatchViaBatchTrigger; collapse the all-failed throw into a single
  check on the combined dispatched counter
- Remove dispatchDocumentProcessingJob — its trigger branch is no longer
  reachable now that batchTrigger handles the trigger path, and the direct
  branch is inlined

* improvement(knowledge): log Trigger.dev batchIds for audit trail

tasks.batchTrigger returns a batchId per call. Collecting and logging
them after dispatch makes it possible to look up or cancel batches in
the Trigger.dev dashboard when investigating stuck or missing documents.

* improvement(knowledge): thread requestId through direct dispatch logs

Symmetry polish: dispatchInProcess now includes [requestId] in its error
log so direct-mode failures are correlatable the same way trigger-mode
failures already are.

* improvement(knowledge): trim verbose comments

Tightens TSDoc on processDocumentsWithQueue, TRIGGER_BATCH_SIZE,
checkSyncLiveness, and the idempotency-key inline comment.
2026-05-20 13:45:42 -07:00
Waleed c3815509b2 fix(landing-nav): scroll to top on route change in shared shells (#4676)
* fix(landing-nav): scroll to top on route change in shared shells

* fix(landing-nav): hoist popstate flag to module scope and skip on hash anchors

* fix(landing-nav): use popstate timestamp window so flag self-expires

* fix(landing-nav): use -Infinity sentinel and consume popstate timestamp on use

* fix(landing-nav): skip scroll on initial mount so reload restoration wins

* fix(landing-nav): gate initial-mount skip on document.readyState

* refactor(landing-nav): simplify scroll-to-top to conventional pattern
2026-05-20 12:48:48 -07:00
Theodore Li af8025c411 fix(table): dispatcher cold-start, live run counter, smooth typewriter (#4675)
* fix(table): cut dispatcher cold-start by lazy-loading heavy import chains

The trigger.dev table-run-dispatcher spent ~6s in module init before its
first batchTriggerAndWait — it imports lib/table/service for getTableById,
which eagerly imported lib/table/trigger → @/lib/webhooks/processor →
webhook-execution + executor, dragging the entire workflow-execution stack
into the dispatcher container even though it never fires a trigger.

- trigger.ts lazy-imports the webhook processor + polling utils inside
  fireTableTrigger (the only consumer), so importing service no longer pulls
  the executor.
- buildEnqueueItems only imports the cell job (for the inline `runner`) on the
  database backend; the trigger.dev backend triggers by task id and ignores
  runner.

* fix(table): run counter + gutter Stop update instantly on Run

The "X running" badge, per-row gutter Stop button, and runningByRowId map
stayed at zero after clicking Run until a manual refetch. useRunColumn
optimistically stamped cells pending in the rows cache but never bumped the
activeDispatches counter — so when the dispatcher's real pending SSE arrived,
applyCell saw the cell was already in-flight (wasInFlight === isInFlight) and
skipped the counter delta. The optimistic stamp ate the transition.

- onMutate now bumps runningCellCount / runningByRowId by the cells it stamps,
  snapshotting prior run-state for rollback on error.
- onSuccess seeds the dispatch into the overlay list from the response instead
  of invalidating activeDispatches (a refetch would reset the optimistic
  counter to the server's still-zero count before the dispatcher stamps).

* fix(table): drive cell typewriter with rAF so concurrent reveals stay smooth

The character-by-character reveal used a per-cell setInterval. When many cells
reveal at once (a Run-all completing in waves), the independent interval
callbacks fire at uncoordinated times and each forces its own render +
layout/paint — O(cells) reflows over an un-virtualized grid, so it degrades as
more cells fill. Switch to requestAnimationFrame: all cells' callbacks run
before one paint, so React batches them into a single render + paint per frame
regardless of cell count. Reveal length is derived from elapsed time, so a
dropped frame catches up instead of slowing the animation.

* fix(table): roll back optimistic run counter when no dispatch is created

useRunColumn.onSuccess returned early on a null dispatchId (no matching
groups / eligible rows) without undoing the onMutate counter bump — and no
SSE would arrive to correct it, leaving the counter permanently inflated.
Restore the pre-mutation run-state on that path, mirroring onError.

* chore(table): tighten inline comments on dispatcher cold-start fixes
2026-05-20 15:15:45 -04:00
WaleedandClaude Opus 4.7 46db40620f feat(mcp): OAuth 2.1 + PKCE for outbound MCP servers (#4441)
* feat(mcp): OAuth 2.1 support for outbound MCP servers

* fix(mcp): tighten OAuth refresh race and session-error detection

Re-load the OAuth row inside withMcpOauthRefreshLock so concurrent
callers observe predecessor-written tokens instead of a stale snapshot
loaded before lock acquisition. Without this, the second caller's
provider held a rotated-out refresh token and the SDK tripped
invalid_grant, forcing reauthorization.

Switch isSessionError to match the SDK's typed StreamableHTTPError
(code 404/400) instead of substring-checking arbitrary error messages,
removing false positives on URLs that happen to contain those digits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(mcp): tighten OAuth callback contract and registration metadata

- Validate callback query params via mcpOauthCallbackContract instead of
  raw searchParams.get, matching the rest of the MCP route surface.
- Drop non-RFC-7591 application_type field from dynamic client registration
  to avoid rejection by strict authorization servers.
- Collapse the pre-lock OAuth row load in createClient — the row is now
  loaded exclusively inside withMcpOauthRefreshLock, removing a redundant
  query and a stale-snapshot path.

* fix(mcp): narrow workspaceId before async closure in OAuth createClient

* fix(mcp): return authType from create-server endpoint

The POST /api/mcp/servers handler omitted authType from the success
response, so useCreateMcpServer always saw data.data.authType as
undefined and never triggered the OAuth popup after creating an
OAuth-protected server. Thread authType through performCreateMcpServer
into the response so the client can decide whether to auto-start OAuth.

* fix(mcp): mirror server null normalization in optimistic oauthClientId update

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): revert optimistic oauthClientId to undefined to match McpServer type

The response contract preprocesses null → undefined, so McpServer.oauthClientId
is string | undefined. Using null broke type checking.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): tighten OAuth probe signal and clear stale popup interval

- probe: only classify as OAuth on resource_metadata or scope params.
  Bare `Bearer error="invalid_token"` is generic and used by API-key servers,
  so it must not auto-flip the auth type to OAuth.
- popup hook: clear any existing close-watcher interval before overwriting
  when startOauthForServer is invoked twice for the same serverId.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): normalize empty-string oauthClientId at route boundary

Orchestration already converts falsy → null via `|| null` (server-lifecycle.ts),
so the DB was never receiving an empty string. Tightening the route layer to
match the same convention keeps the boundary contract consistent and avoids
relying on downstream normalization.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(canvas): expand MCP tool params into per-row labels on block tile

The MCP Tool block on the workflow canvas previously crammed every selected-
tool parameter into a stringified blob under the `Tool` row. Now, when a tool
is selected, the tile reads the cached `_toolSchema` and emits one labeled
SubBlockRow per parameter (matching the Exa block's per-param layout). Labels
reuse `formatParameterLabel` for parity with the editor panel; values pass
through the existing `getDisplayValue` so booleans/numbers/arrays render
identically to other blocks. Deterministic tile height counts expanded rows
so the tile sizes correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(logs): show MCP icon and strip prefix in trace tool spans

Tool spans for MCP calls were rendering the raw id (e.g.
`mcp-f908f259-planetscale_list_organizations`) with the default blank-
square icon. Now they read just the tool name and render the MCP block's
icon and bgColor, matching how workflow-execute tools render.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): lift near-black trace icon backgrounds for dark-mode contrast

Block bgColors below a small luminance threshold (e.g. the MCP block's
#181C1E) rendered nearly invisible against the dark-mode surface
(--bg: #1b1b1b). Adds a tiny adjustBgForContrast helper that floors each
RGB channel at 0x33 only when luminance is below 30,000, leaving every
branded color above that band untouched. Applied to both the trace tree
row and the detail pane.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): fall back to neutral gray for near-black trace icon bgs

#333333 was still too close to the dark-mode surface to read. For bgs
below the luminance threshold (e.g. the MCP block's #181C1E) we now fall
back to DEFAULT_BLOCK_COLOR (#6b7280) — the same neutral the renderer
uses for blocks with no distinct identity. Clearly visible in both
themes; brighter brand colors still pass through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(db): drop 0209_mcp_oauth migration ahead of staging merge

Staging shipped 0209_smiling_fixer; the MCP OAuth migration will be
regenerated on top of staging as 0210.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(db): regenerate MCP OAuth migration as 0210

Re-runs drizzle-kit generate on top of staging's 0209_smiling_fixer.
Same schema (mcp_server_oauth table + mcp_servers.auth_type / oauth_*
columns) as the dropped 0209_mcp_oauth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(audit): bump route baseline 748 → 749 after staging merge

The post-merge route count is 749 (this branch's OAuth start/callback
plus staging's new route). I had set the baseline to 748 in the merge
conflict resolution — bumping to match reality so the strict audit
passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: remove source-command skill files committed by accident

These were untracked-then-accidentally-staged in 05c4bc19e via a wide
`git add -A`. They aren't part of this PR's scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 12:10:04 -07:00
WaleedandClaude Opus 4.7 d9dd7a3e55 fix(cors): re-enable credentials on chat/form embed CORS policy (#4673)
* fix(cors): re-enable credentials on embed CORS policy

Chat and form embeds authenticate via the chat_auth_<id> / form auth
cookie set by setDeploymentAuthCookie. The previous PR set
Access-Control-Allow-Credentials: false on these routes, which made the
browser drop the auth cookie and produce 401s on subsequent embed calls
after login. Restore credentials: true (matching pre-consolidation
behavior) while keeping reflected origin and Vary: Origin.

The wildcard fallback when Origin is absent now also drops credentials
to stay CORS-spec-compliant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(cors): trim verbose comments in proxy

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(cors): restore concise TSDoc on proxy CORS helpers

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 10:55:26 -07:00
Theodore LiandClaude Opus 4.7 f0311a6f5e feat(table): chunked dispatcher + workflow cascade (#4672)
* feat(table): chunked dispatcher for workflow-column runs

Replaces the all-rows-at-once runWorkflowColumn with a row-window dispatcher
backed by a new table_run_dispatches row. Each user click inserts a dispatch
row and triggers a trigger.dev task that crawls the table 20 rows at a time,
re-enqueueing itself between windows. The HTTP/Mothership entrypoints return
{ dispatchId } immediately instead of holding the request open for minutes
on multi-thousand-row dispatches.

- Per-row cancel stamps cancelledAt; the dispatcher skips cells whose
  cancelledAt > dispatch.requestedAt so a mid-cascade cancel sticks even
  under isManualRun.
- Table-wide cancel marks active dispatches cancelled atomically so the
  dispatcher bails on its next iteration.
- New 'dispatch' SSE event variant plumbed; client ignores for v1.

* fix(table): eager bulk clear on column run so cells flip immediately

Run-column with run-mode 'all' wasn't visually flipping rows that already
had data — the cell renderer's "value wins" branch kept showing the prior
output behind the queued/running state. The dispatcher only cleared one
window of rows at a time, so most of the column stayed stale until the
cursor walked to it.

Now:
- Dispatcher's `pending → dispatching` transition runs a single SQL UPDATE
  that wipes targeted `data` output columns and `executions[gid]` across
  every targeted row (mode-aware: 'incomplete' skips fully-filled rows).
- Per-window clear in `dispatcherStep` is gone — rows are pre-cleared,
  the loop only filters cancel tombstones / unmet deps and enqueues.
- Optimistic patch in `useRunColumn` mirrors the bulk clear by nulling
  output values in the cached row, so the UI flips queued/running
  instantly without waiting for the SSE catch-up.

* fix(table): bulk clear honors in-flight execs under mode: 'incomplete'

The eager bulk clear for mode: 'incomplete' only skipped rows that were
already fully filled, so two overlapping dispatches could race — dispatch B
would nuke executions[gid] on a row dispatch A had just stamped 'queued',
flickering the cell and potentially confusing the worker.

Skip any row whose targeted group is currently queued/running/pending — an
'incomplete' run shouldn't touch what another dispatch is actively working
on. The per-walk 'in-flight' eligibility skip already handles rows that
flip in-flight between the clear and the cursor reaching them.

* refactor(table): dispatcher uses batchTriggerAndWait + tag-based cancel

Switch the per-window cell fan-out from fire-and-forget tasks.trigger to
tasks.batchTriggerAndWait. The dispatcher is now a single long-lived
trigger.dev task that loops dispatcherStep until the table is exhausted;
trigger.dev CRIU-checkpoints the parent during each wait so we don't pay
compute while cells execute. Queue depth is bounded at WINDOW_SIZE per
dispatch — no more flooding trigger.dev with a million queued runs.

- dispatcher.ts builds payloads via the new shared buildPendingRuns helper
  and calls tasks.batchTriggerAndWait directly. Pre-stamps each cell to
  `queued` (jobId=null) so the UI flips instantly.
- table-run-dispatcher.ts is now a plain while-true loop. No
  RUN_BUDGET_MS, no self-re-enqueue, no cold-start tax per window.

Cancel:
- New cancelCellRunsByTags(tags) paginates runs.list + runs.cancel(id).
- cancelWorkflowGroupRuns fires the tag-sweep alongside the per-jobId
  queue.cancelJob path (preserved for auto-fire cells that have real
  jobIds from single tasks.trigger calls).
- Trigger.dev acks the cancel → batchTriggerAndWait resumes → dispatcher
  observes the dispatch-row cancel flag → exits.

Side fixes:
- getAsyncBackendType returns 'trigger-dev' whenever taskContext.isInsideTask
  is true, regardless of TRIGGER_DEV_ENABLED env. The preview/dev-sim
  worker silently routing cell jobs to DatabaseJobQueue (no poller) is
  fixed without any env config change.
- runWorkflowColumn skips the dispatcher entirely when trigger.dev is
  disabled, running cells inline via DatabaseJobQueue.runInline. HTTP
  response returns dispatchId: null in that mode.
- runColumnContract response schema updated to dispatchId.nullable().

* fix(table): show Stop button on optimistic-pending row cells

isExecInFlight required a jobId for `pending` status, gating it as "real
backend pending" vs "optimistic flag only." The row-gutter Stop button
keyed on this — so a freshly clicked Play sat as `pending` (no jobId) and
the user couldn't cancel it until the server-side `queued` stamp arrived
via SSE. With the dispatcher pre-batch stamping cells as `queued` (not
`pending`) and no per-cell jobIds under batchTriggerAndWait, the gap was
worse.

Drop the jobId requirement. `pending` now counts as in-flight everywhere.
Cancel writes `cancelled` to the cell exec authoritatively whether or not
a real trigger.dev run exists yet — cancelling an optimistic cell means
"don't run this," which is correct.

Also collapse isOptimisticInFlight into isExecInFlight since the two
helpers are now identical.

* refactor(table): loop-in-cell cascade + dispatcher-everywhere routing

Two coupled changes:

1. Cell-task runs the row's full cascade in-process. executeWorkflowGroupCellJob
   acquires a Redis lock per (tableId, rowId) with heartbeat (10s/30s TTL),
   then loops through eligible workflow groups for the row. One cell-task =
   one row's full cascade, not N. Resume worker holds the same lock and
   continues the cascade after a HITL resume. Shared withCascadeLock helper
   in lib/table/cascade-lock.ts.

2. Every cell-enqueue goes through the dispatcher. The implicit
   scheduleRunsForRows reactor in service.ts is removed — 8 callsites
   (insertRow, batchInsertRows, upsertRow, updateRowsByFilter,
   batchUpdateRows, addWorkflowGroup, updateWorkflowGroup) now fire
   runWorkflowColumn with mode: 'incomplete', isManualRun: false. HTTP
   routes that call updateRow directly also fire runWorkflowColumn
   afterwards. scheduleRunsForTable / scheduleRunsForRowIds deleted;
   scheduleRunsForRows demoted to private (only the TRIGGER_DEV_ENABLED=false
   fallback uses it). skipScheduler flag dropped from UpdateRowData /
   BatchUpdateByIdData — no longer meaningful since there's nothing implicit
   to suppress.

Plumbed isManualRun through the dispatch row (new is_manual_run column,
default true) so auto-fire callers honor autoRun: false and don't re-run
completed cells.

Stamp 'pending' (not 'queued', executionId: null) before
batchTriggerAndWait — cell-task writes its own 'queued' on lock acquire.

Small UI polish: row gutter Play button spacing, "Delete workflow" →
"Delete column" label, optimistic-pending cells now show Stop button
(isExecInFlight no longer requires jobId).

* fix(table): SQL cancellation guard allows worker to claim a null-execId cell

The dispatcher's pre-batch `pending` stamp leaves executionId unset so any
cell-task that wins the cascade lock can claim the cell. The cancellation-
guard SQL clause was rejecting these claims because it tested
`executions->gid IS NULL` (whole exec missing) but the pre-stamp leaves
the exec present with executionId=null.

Add a third carve-out: `executions->gid->>'executionId' IS NULL`. Now the
guard reads "write allowed if no exec exists, OR no executionId is set
yet, OR the executionId matches ours."

Symptom: every cell-task's first markWorkflowGroupPickedUp call would log
"SQL guard saw cancelled" and skip, leaving cells stuck at the dispatcher's
pending stamp.

* fix(table): dispatcher cursor starts at -1 so position 0 is included

The dispatcher's row-window SELECT is `position > cursor` for exclusive
lower-bound semantics. With cursor initialized to 0, position-0 rows were
never picked up — every dispatch silently skipped the table's first row.

Start cursor at -1 instead. First window's filter `position > -1` matches
position 0; subsequent iterations advance to `lastPosition` which then
correctly excludes already-processed rows.

* refactor(table): align optimistic UI with new dispatcher; sticky cancel via 'new' mode

Fix 0: new `DispatchMode = 'new'` for auto-fire callsites. Eligibility skips
rows with any prior `executions[gid]` entry — cancelled / errored / completed
cells stay sticky until a manual run. Dispatcher's windowed SELECT pushes
`NOT jsonb_exists_any(...)` to SQL so CSV imports into mostly-attempted
tables don't pay a per-window load+JS-filter. `batchInsertRows` drops its
`rowIds` payload (keeps dispatch scope tiny on big imports).

Fix A/B/D: client optimistic patches now mirror the backend's actual
invariants. `useCreateTableRow.onSuccess` stamps eligible groups via
`optimisticallyScheduleNewlyEligibleGroups` so newly-inserted rows show
`Queued` instantly. `useCancelTableRuns.onMutate` distinguishes optimistic-
only pending (`executionId == null` — strip silently) from real worker
claims (stamp cancelled; SSE will reconcile). Drop `onSettled` invalidation
on `useUpdateTableRow` / `useBatchUpdateTableRows` to kill the
delete-cell flicker.

Fix C: active-dispatches overlay. New `listActiveDispatches` helper,
contract, and `GET /api/table/[tableId]/dispatches` route. `kind:'dispatch'`
SSE events carry scope+cursor+mode on every transition. New
`useActiveDispatches` hook + `resolveCellExec` synthesize a virtual
`pending` exec for cells in an active dispatch's scope ahead of cursor —
queued indicators now survive page refresh during long Run-all dispatches.
`cancelWorkflowGroupRuns` emits `kind:'dispatch',status:'cancelled'`
events so the overlay clears without a refetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(table): unify trigger.dev and inline dispatcher paths

`runWorkflowColumn` now always inserts a `table_run_dispatches` row and
drives the dispatcher state machine. The trigger.dev / in-process branch
narrows to a single line: trigger.dev fires `tableRunDispatcherTask` (which
calls the new `runDispatcherToCompletion`), the inline path calls the same
helper fire-and-forget. Deletes `scheduleRunsForRows` and
`stampQueuedOrCancel` — the inline-fallback no longer duplicates window
walking, SSE emission, or cancel.

The dispatcher's window-execute call goes through `JobQueueBackend`:
- New `batchEnqueueAndWait` interface method.
- Trigger.dev impl wraps `tasks.batchTriggerAndWait` behind a
  `taskContext.isInsideTask` guard (clear error if called from outside a
  task).
- Database impl skips `async_jobs` entirely — `Promise.all` over
  `options.runner(payload, signal)` per item, with per-cell AbortControllers
  tracked by `cancelKey` for cancel.

`cancelInlineRun` moves to the interface as `cancelByKey` so
`cancelWorkflowGroupRuns` no longer reaches into the database backend.

Fix `mode: 'new'` SQL filter:
- `${array}::text[]` interpolated as a tuple-cast which Postgres rejected
  ("cannot cast type record to text[]") and every inline dispatch silently
  failed. Switched to `ARRAY[${sql.join(...)}]::text[]`.
- Predicate was `jsonb_exists_any` ("any one targeted group present"),
  which excluded rows that needed at least one group re-run after a
  downstream output was deleted. Switched to `jsonb_exists_all` — per-group
  JS eligibility handles the rest.

Cascade-loop workflowId bug: `runRowCascadeLoop` was not threading the new
group's `workflowId` when advancing across groups. The cell-task ran the
previous group's workflow against the next group's cell, terminating
`completed` with empty `accumulatedData`. Fixed by tracking
`currentWorkflowId` alongside `currentGroupId` / `currentExecutionId`.

Client optimistic-patch tightening:
- `useRunColumn.onMutate` mirrors server eligibility — skip cells with
  unmet deps so unmet rows don't flash Queued and get stuck (no SSE will
  arrive for cells the server skipped).
- `resolveCellExec` overlay synthesizes a virtual `pending` only when
  `areGroupDepsSatisfied` is true. Rows with unmet deps render Waiting,
  matching the dispatcher's actual behavior.

Cleanup from /simplify pass:
- Use `generateShortId(20)` instead of
  `generateId().replace(/-/g, '').slice(0, 20)`.
- Inline `batchEnqueueAndWait` no longer allocates synthetic ids
  (returned `string[]` is unused).
- Flattened the per-cell `tracked` array — only push entries that
  registered controllers, drop the null placeholders.
- Extracted `runDispatcherToCompletion` to share the loop between the
  trigger.dev wrapper and the in-process path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(table): backend running counter, dep-aware retrigger, sidebar polish

Counter (Fix 1): top-right "X running" + per-row badge are now
backend-bootstrapped via a count on `user_table_rows.executions ->> 'status'
= 'running'` returned alongside active dispatches. SSE `kind: 'cell'` events
compute a delta from `prev → next` status to keep the cache live; cell
events for rows outside the loaded page slice trigger a run-state refetch.
On `pruned` we invalidate the cache. Counts only worker-claimed `running`
cells — optimistic queued/pending no longer inflate the badge, and rows
outside the loaded page slice are counted too.

Sidebar (Fix 2 + 3a): `Run after` no longer ticks every column by default
for new groups (empty list). Save is disabled with an inline error when
auto-run is on with zero deps. `edit-group` mode anchors the left-of-current
filter to the group's leftmost column, so a workflow can only depend on
columns to its left.

Reorder scrub (Fix 3b): `updateTableMetadata` walks the schema's workflow
groups when `columnOrder` is in the patch and drops any dep whose new
position lands at or after the group's leftmost column (uses the existing
`stripGroupDeps` helper). Metadata + schema updates land atomically.

Server returns ordered columns (Fix 3b cont'd): `getTableById` /
`listTables` now sort `schema.columns` by `metadata.columnOrder` before
returning, via a new `applyColumnOrderToSchema` helper. Every consumer
(grid, sidebar, copilot, mothership) gets one ordered list — the sidebar's
leftmost-group-column anchor now points at the right index.

Dep-aware retrigger (Fix 4): editing a value that a downstream workflow
depends on now re-runs that workflow.
- `deriveExecClearsForDataPatch` returns
  `{ executionsPatch, inFlightDownstreamGroups }`. Walks
  `schema.workflowGroups[].dependencies.columns` for every column in the
  patch, clears terminal-state downstream entries, and reports in-flight
  entries.
- `updateRow` calls `cancelWorkflowGroupRuns` + `runWorkflowColumn`
  (`mode: 'incomplete' + isManualRun: true`) for in-flight downstream
  groups, then always fires `runWorkflowColumn({ mode: 'new' })` for the
  cleared groups. Skips both when `executionsPatch` is provided by the
  caller — those are cell-task / cancel writes that would otherwise spawn
  a recursive flood of dispatches per partial-write.
- `cancelWorkflowGroupRuns(tableId, rowId, { groupIds? })` accepts a
  per-group filter so the cancel only touches the affected groups, not
  every in-flight cell on the row.
- `pickNextEligibleGroupForRow` now treats a dispatcher pre-stamp
  (`pending` + `executionId: null`) as claimable — the cascade-loop is the
  real owner. Without this, the dispatcher's pre-stamp of downstream
  groups made the cascade-loop see them as "in-flight" and skip them,
  stranding `pending` cells forever.
- `optimisticallyScheduleNewlyEligibleGroups` extends the cache patch to
  flip dep-touched groups to `pending` regardless of their current status,
  matching the server's cancel-then-rerun behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(table): paused workflow cells route through executeResumeJob; render Pending + viewable

Three connected issues with workflows that pause mid-cell (e.g. wait blocks):

1. `/api/resume/poll` (the time-pause auto-resumer) called
   `PauseResumeManager.startResumeExecution` directly, bypassing
   `executeResumeJob` from `background/resume-execution.ts`. The wrapper is
   where the cell-context restoration + cascade-loop continuation lives —
   without it, the resumed workflow ran to completion but never wrote the
   terminal state back to the table cell. Cell stays `pending` forever
   even though the underlying execution finished.

   Fix: dynamically import `executeResumeJob` and use it for the
   `'starting'` branch. Same primitive the trigger.dev `resumeExecutionTask`
   wraps — calling it directly handles both trigger.dev-disabled local dev
   and trigger.dev-enabled prod identically.

2. The cell renderer mapped `status: 'pending'` to `kind: 'queued'` (gray
   "Queued" badge) regardless of whether the run had started. A HITL-paused
   run has `status: 'pending'` + `jobId` prefixed `paused-` + a real
   `executionId` — semantically very different from "queued, hasn't run."
   Now renders as `pending-upstream` (the existing Pending pill) for
   paused-jobId rows.

3. Right-click "View execution" was disabled for `pending` cells (gated to
   `completed | error | running`), so users couldn't open the trace for a
   paused execution. Paused runs have a viewable trace (the executionId is
   real and the log row exists). Both the per-row context menu and the
   action-bar derivation now recognize `pending` + `paused-` jobId as a
   started run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(table): typewriter reveal for SSE-driven workflow cell values

Workflow-output cells now reveal their text character-by-character when an
SSE update lands, while page reloads and virtualization remounts still paint
the value instantly. A first-render guard inside the new useTypewriter hook
distinguishes hydration from live updates with no plumbing through the cell
tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(table): address bugbot/greptile review feedback

Two P1 issues + one cleanup from the bot reviewers:

1. **Double-dispatch + completed-output wipe.** Both PATCH row routes
   (`app/api/table/[tableId]/rows/[rowId]` and
   `app/api/v1/tables/[tableId]/rows/[rowId]`) were firing a second
   `runWorkflowColumn({ mode: 'incomplete' })` after `updateRow` returns.
   `updateRow` already fires `mode: 'new'` internally for user edits, so
   the second call created a concurrent dispatch. Worse, the
   `mode: 'incomplete'` path's `bulkClearWorkflowGroupCells` wipes ALL
   targeted output columns on any row where any one column is empty —
   meaning sibling-group completed outputs could be erased. Removed both
   route-level calls; auto-dispatch lives entirely in `updateRow`.

2. **`runWorkflowColumn` log-spamming on plain tables.**
   `if (targetGroups.length === 0) throw new Error(...)` fired on every
   row insert/update for tables without any workflow groups (the
   majority). Every caller wraps with `.catch(logger.error)`, so each
   PATCH produced an error-level log. Return `{ dispatchId: null }`
   silently — manual `runWorkflowColumn` callers pass `groupIds`
   explicitly so they can't reach this branch.

3. **`isManualRun` plumbed through dispatch SSE events.** Late-arriving
   `kind: 'dispatch'` events for dispatches not in the initial fetch
   were hardcoding `isManualRun: false`. Added the field to the event
   shape, emit it from `dispatcherStep` (pending → complete, dispatching
   transitions) and `markActiveDispatchesCancelled`, and consume it in
   the SSE handler with a sensible fallback for legacy emits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(table): row executions sidecar + left-to-right dep retrigger + cancel counter refresh

Split per-row workflow-group execution state out of the user_table_rows.executions
JSONB column into a new table_row_executions sidecar keyed by (row_id, group_id).
Dispatcher filters, "X running" counter, bulk clears, and the cancellation guard
all hit indexed columns instead of walking JSONB. Wire shape unchanged — server
merges sidecar rows back into row.executions on the way out.

Also:
- deriveExecClearsForDataPatch now walks workflowGroups left-to-right with a
  propagating dirtied-column set so transitive dep chains (edit col A → group 1
  re-runs → group 2 depends on group 1's output → group 2 re-runs) collapse to
  a single forward pass.
- useCancelTableRuns.onSettled invalidates the activeDispatches query so the
  top-right counter and row gutter Stop button refetch from the server after
  any Stop (per-cell, row, or table-wide). countRunningCells is the source of
  truth; client no longer needs duplicate state.

Three migrations on this branch (0209 + 0210 + new sidecar) collapsed into one
since the feature is unreleased.

* fix(table): address remaining cursor/greptile review feedback

- Mothership update_row no longer double-dispatches. updateRow already fires
  the auto-cascade internally; the second `mode: 'incomplete'` call here
  raced with it and could bulk-clear sibling-group outputs.
- SSE dispatch events no longer dropped when the activeDispatches cache is
  cold. Seed an empty TableRunState if the initial fetch hasn't landed yet
  so the queued overlay doesn't lose the first dispatch event.
- batchUpdateRows now runs cancel+rerun for per-row in-flight downstream
  groups, mirroring updateRow. Without this, dep edits in a batch left
  running workflows reading stale upstream values.

* fix(table): cancel prior runs, scope batch insert dispatch, recover orphan pre-stamps

Addresses cursor + greptile review feedback on table dispatcher edge cases:

- Manual table-wide Run-all / Run-column now cancels prior active dispatches
  AND in-flight cell workers before bulk-clearing. Without this, mode:'all'
  deleted running sidecar rows out from under their workers (which kept
  writing into the wiped state) and a second Run-all could enqueue overlapping
  cells racing on the same rows. Row-scoped manual calls (dep-edit cascade)
  are excluded — those already cancel their own scope.
- batchInsertRowsWithTx now scopes its auto-dispatch to the newly-inserted
  row ids. Without this, after the sidecar migration the NOT EXISTS filter
  matches every existing row (zero sidecar entries), so a CSV import would
  walk the entire table dispatching workflow runs on every pre-existing row.
- classifyEligibility carve-out: pending + executionId=null is an orphan
  pre-stamp (cascade-lock contention, batchEnqueueAndWait failure, etc.),
  treated as claimable so future dispatchers can re-stamp instead of skipping
  it as 'in-flight' forever. Matches pickNextEligibleGroupForRow's logic.
- On batchEnqueueAndWait failure, dispatcherStep now sweeps the orphan
  pre-stamps it wrote for the failed batch so the cells don't render Queued
  forever; the next user action picks them up cleanly.

* fix(table): row-scoped Refresh cancels in-flight; counter includes queued/pending

- runWorkflowColumn now cancels prior in-flight cells for row-scoped manual
  runs too (context-menu Refresh on a row subset, action-bar Refresh on
  selected rows). Previously only the table-wide path cancelled, so a
  row-scoped Refresh would bulk-clear running sidecar rows without aborting
  workers. Per-row cancel skips markActiveDispatchesCancelled so unrelated
  dispatches keep running.
- countRunningCells now counts all in-flight statuses (queued / running /
  pending) instead of just running. The row gutter Run/Stop button reads
  this map — with the old behavior, clicking Play during the queued window
  would re-enqueue an already-queued cell. SSE applyCell handler updated
  to use isExecInFlight so client deltas track the same semantics.

* fix(table): per-row Stop tombstones ahead-of-cursor rows during Run-all

Per-row Stop only cancelled sidecar rows already in flight. A row the
dispatcher hadn't reached yet had no exec record, so Stop was a no-op there
— the dispatcher would later walk to it, classify the group eligible, and
re-fire workflows the user thought they stopped.

cancelWorkflowGroupRuns now, for a per-row cancel, checks active dispatches
whose scope covers the row and writes `cancelled` tombstones (cancelledAt =
now) for the at-risk groups that don't already have a sidecar entry. The
dispatcher's existing `cancelledAt > dispatch.requestedAt` filter then skips
them when the cursor arrives. onConflictDoNothing guards against clobbering
a concurrently-written entry; the active-dispatch check avoids stamping
spurious cancels on idle rows.

* fix(table): seed dispatch overlay on Run; surface batch-enqueue failures as error

- useRunColumn.onSuccess invalidates the activeDispatches query so the
  resolveCellExec queued overlay populates immediately for ahead-of-cursor
  rows (scrolled-in / refetched), instead of waiting for the first dispatch
  SSE. Targeted at activeDispatches only — the rows cache stays owned by
  useTableEventStream.
- On batchEnqueueAndWait failure, dispatcherStep now flips the orphan
  pre-stamps to a terminal `error` state and emits a cell SSE event, rather
  than deleting them. The cursor still advances past the window, but the
  dropped cells are now visible (Error pill) instead of silently empty, stay
  out of the in-flight set, and re-run on the next manual run.

* fix(table): seed dispatch overlay on Run; surface batch-enqueue failures as error

- useRunColumn.onSuccess invalidates activeDispatches so the resolveCellExec
  queued overlay populates immediately for ahead-of-cursor rows instead of
  waiting for the first dispatch SSE. Rows cache stays owned by SSE.
- On batchEnqueueAndWait failure, dispatcherStep flips orphan pre-stamps to a
  terminal error state (+ cell SSE) instead of deleting them, so the dropped
  window is visible (Error pill) rather than silently empty and re-runs on the
  next manual run.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:44:09 -04:00
Waleed d55f40ce14 fix(blocks): preserve agent block color (#4671) 2026-05-19 18:05:38 -07:00
Vikhyath Mondreti 974a18dd61 improvement(media-blocks): new versions of image and video gen with latest models + fixes (#4667)
* improvement(media-blocks): new versions of image and video gen with latest models + fixes

* respect versioning for icons

* fix integration routes

* address comments

* address api mismatches

* more ltx 2.3 durations

* typing tightness
2026-05-19 16:42:04 -07:00
WaleedandClaude Opus 4.7 e40c91588f fix(workflow-search): unclip block-name highlight shadow on the left (#4670)
The editor title h2 uses truncate (overflow:hidden) so the search highlight's -3px box-shadow was getting clipped on the left, exposing the mark's sharp edge. Replaces overflow:hidden with overflow:clip plus a 3px overflow-clip-margin so the shadow can bleed past the clip boundary without shifting the title text or breaking truncation.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 16:32:05 -07:00
Waleed 972ec5f216 fix(branding): align auth and deploy UI colors (#4669) 2026-05-19 16:18:07 -07:00
WaleedandClaude Opus 4.7 d7d58cec36 improvement(workflow-search): include block names in in-workflow search (#4668)
* improvement(workflow-search): include block names in in-workflow search

Adds block names to the workflow search index alongside subblock content. Selecting a block-name match navigates to the block and highlights its title in the editor header with the same orange treatment used for subblock labels.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(panel-store): derive ActiveSearchTargetKind from WorkflowSearchTarget

Replaces the hand-maintained literal union with a derived type so adding a new target kind in search-replace/types.ts automatically propagates here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 15:44:34 -07:00
Waleed 6c755cbb59 feat(integrations): add Gong incident.io Railway and New Relic (#4663)
* feat(integrations): add Gong incident.io Railway and New Relic

* fix(railway): preserve explicit empty variable values

* fix(incidentio): fail on invalid workflow JSON

* fix(new-relic): validate custom attributes JSON

* fix(integrations): address incident workflow review fixes

* chore(docs): apply lint formatting

* chore: refresh integration docs and validation fixes

* more

* fix(integrations): address PR review comments

* fix(gong): align list calls block validation
2026-05-19 14:12:28 -07:00
df1e2ddc2e feat(azure-devops): block and trigger (#4664)
* azure devops logo on white

* generated ADO tool docs

* generated ADO tool docs

* added ADO to registries

* ADO workflow triggers

* ADO workflow triggers

* tool layer for ADO, checks passed and manual verified

* ADO workflow triggers

* block layer for ADO

* ADO icon svg

* generated docs for ADO triggers

* committing the tests for azure devops tools and blocks

* Update apps/sim/triggers/azure_devops/utils.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update apps/sim/tools/azure_devops/update_work_item.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update apps/sim/triggers/azure_devops/utils.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* comma syntax error patched

* azure devops: validate-integration fixes + manual description

- bgColor switched from white to Azure DevOps brand color #0078D4 (block + mdx)
- WIQL query_work_items: hydrate ALL matched IDs by chunking through batches
  of 200 instead of silently truncating; check response.ok on the follow-up
  fetch and surface a clear error on 4xx/5xx; trim org/project; expose
  totalMatched in metadata so users can see pre-hydration count
- Add MANUAL-CONTENT-START:intro section to the azure_devops.mdx docs page
- Update unit tests for new chunking behavior and update-work-item validation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* azure_devops: second-pass audit fixes + formatter cleanup

- Add types barrel export to tools/azure_devops/index.ts
- Normalize comment endpoint path casing (/workItems/ -> /workitems/)
- Update test assertions to match normalized path
- Biome formatter reflow across tools, triggers, registry, and docs icon

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* azure_devops: address PR review comments

- Fix bgColor #FFFFFF -> #0078D4 in integrations.json and triggers/azure_devops.mdx
- Bump File tool operationCount from 4 to 5 (Read, Fetch, Get, Write, Append)
- Apply .trim() to org/project across all 15 remaining tools (consistency with query_work_items)
- Fix Found ${data.count} -> Found ${data.count ?? items.length} fallback in list_builds, list_pipelines, list_pipeline_runs content strings

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* idemtpotency

* azure_devops: address bugbot review comments

- triggers/utils: match build.complete result case-insensitively, accept stopped/cancelled in addition to failed/canceled/partiallySucceeded so PascalCase and legacy Azure DevOps payloads aren't dropped
- get_work_items_batch: chunk comma-separated IDs into 200-batch loops with proper status checks (was failing or returning incomplete data on >200 IDs)
- Add tests for both behaviors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* azure_devops: address additional bugbot comments

- Block update_work_item now forwards areaPath; the Area Path subblock condition expanded to include update operation
- get_build_timeline.failedRecords now also flags partiallySucceeded and succeededWithIssues, normalized case-insensitively. Output description and added a focused test

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* azure_devops: address more bugbot comments

- Webhook provider extractIdempotencyId returns null when subscriptionId or notificationId is missing/empty, preventing the literal "azure_devops:undefined:undefined" key from collapsing unrelated deliveries into duplicates
- Get Work Items Batch validates that at least one non-empty ID is supplied before issuing the API request, throwing a clear error instead of hitting an empty ids= query
- Tests cover both behaviors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* azure_devops: pin add_comment to documented api-version 7.0-preview.3

Microsoft's Add Comments docs only publish 7.0-preview.3 (the 7.2 view falls back to the 7.0 page). Get Comments stays on the documented 7.2-preview.4. Matches what's strictly in the Azure DevOps REST API reference rather than relying on undocumented version behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Marcus Chandra <mzxchandra@gmail.com>
Co-authored-by: mzxchandra <129460234+mzxchandra@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 14:10:42 -07:00
WaleedandClaude Opus 4.7 49f70bc635 fix(docker): restore NEXT_PUBLIC_APP_URL build arg with dummy fallback (#4665)
* fix(docker): restore NEXT_PUBLIC_APP_URL build arg with dummy fallback

getBaseUrl() in lib/core/utils/urls is evaluated at module load during
next build's page-data collection and throws if NEXT_PUBLIC_APP_URL is
unset. PR #4658 removed the build arg, breaking the Docker build at the
"/_not-found" page-data collection step.

Restore the dummy localhost fallback (mirroring DATABASE_URL). The CORS
fix from #4658 is preserved: next.config.ts no longer reads
NEXT_PUBLIC_APP_URL at build time, and no module-level expression
captures getBaseUrl() — every caller invokes it at request time, where
getEnv() reads the deployed container env. The dummy localhost value
cannot leak into runtime CORS response headers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(docker): trim verbose comment on build-time env args

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 13:34:49 -07:00
Vikhyath Mondreti 6414b9381d improvement(cleanup): cleanup refs along in logs cleanup job (#4661)
* improvement(cleanup): cleanup refs along in logs cleanup job

* address comments

* cleanup code
2026-05-19 12:19:54 -07:00
Waleed b98164fd3d feat(wiza): add Wiza integration for B2B prospect enrichment and search (#4662)
* feat(wiza): add Wiza integration for B2B prospect enrichment and search

* fix(wiza): coerce reveal id to string, skip empty filters in prospect search

* fix(wiza): throw on invalid JSON in advanced filter fields instead of silently dropping
2026-05-19 12:06:19 -07:00
WaleedandClaude Opus 4.7 ef14b2b580 fix(security): remove localhost CORS origin, consolidate CORS in proxy (#4658)
* fix(security): remove localhost CORS origin, consolidate CORS in proxy

Move all /api/* CORS handling from next.config.ts to proxy.ts so the
runtime can resolve allowed origin per-request instead of baking it at
build time (which produced "Access-Control-Allow-Origin: http://localhost:3000"
with credentials:true in production).

- proxy.ts: per-route CORS policy table covering auth, MCP, form, and
  workflow execute endpoints; OPTIONS preflight short-circuit; Vary:
  Origin when origin is not '*'; form routes defer to route handler's
  addCorsHeaders to avoid double-setting
- next.config.ts: drop all /api/* Access-Control-Allow-* headers; keep
  COEP/COOP/CSP
- deployment.ts: addCorsHeaders sets Vary: Origin alongside reflected
  Allow-Origin
- Dockerfile: drop NEXT_PUBLIC_APP_URL build placeholder (Zod has
  skipValidation:true; build path doesn't read it)
- Remove 8 dead OPTIONS handlers and their preflight tests now that the
  proxy handles preflight uniformly

* refactor(cors): consolidate API CORS into proxy as single source of truth

Move CORS for /api/chat/* and /api/form/* into the proxy policy table with
reflected-origin + credentials:false, and delete the per-route addCorsHeaders
helper. Routes no longer set CORS headers — the proxy is the only writer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(cors): convert proxy CORS policy chain to a rule table + add tests

Replace the if/else chain in resolveApiCorsPolicy with a CORS_RULES table
so each route's policy lives in one place and is trivially scannable.
Add proxy.test.ts covering each rule and the wildcard-with-credentials
invariant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cors): scope embed CORS rule to /api/{chat,form}/[identifier] only

The embed policy (reflected origin, credentials:false) was matching
workspace-internal session-authed routes — /api/chat, /api/chat/manage/*,
/api/chat/validate, and the form equivalents — which need the default
credentialed policy. Tighten the matcher to the embed paths only and add
tests covering the exclusion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(cors): replace embed-path regex with explicit segment check

The regex form `^/api/(chat|form)/(?!manage|validate)[^/]+(/(otp|sso))?$`
was opaque on review and would silently exclude any future identifier
subroute outside the hard-coded (otp|sso) group from the embed policy.
Replace it with an imperative segment check and a named
EMBED_RESERVED_SEGMENTS Set, so the policy boundary is visible at the
top of the function and adding a reserved subpath is a one-line diff.
Add a test asserting that future identifier subroutes also get the
embed policy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cors): allow PUT in embed CORS policy for OTP verification

Both /api/chat/[identifier]/otp and /api/form/[identifier]/otp export
PUT for OTP code verification. The embed policy advertised only
GET/POST/OPTIONS, so cross-origin embed clients failed preflight on
verify. Add PUT and assert it in the embed policy test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 12:04:23 -07:00
Waleed 3930485dff feat(models): add gemini 3.5 flash (#4660) 2026-05-19 11:19:44 -07:00
Vikhyath Mondreti 279010a2e7 improvement(execution): memory usage for aggregated results (#4650)
* improvement(execution): memory usage for aggregated results

* progress

* address comments

* loop/parallel results compaction

* address comment

* remove build files, harden edge cases

* remove hotpath serialiazation

* display change to make use of only preview

* materialize refs before sending in response block

* preserve exact large-value access through workflow materialization

* address comments

* progress

* fix notif error + sync manifest undefined exit

* fix streaming ref materialization

* fix tests
2026-05-18 23:55:06 -07:00
Waleed 6827be7f43 feat(google_docs): opt-in Markdown formatting for create operation (#4656)
* feat(google_docs): opt-in Markdown formatting for create operation

* fix(google_docs): harden multipart boundary handoff and align postProcess guard
2026-05-18 19:33:33 -07:00
Waleed a0d9e4dc90 fix(tables): type-aware SQL casts for range filters on date columns (#4657)
* fix(tables): type-aware SQL casts for range filters on date columns

* improvement(table): tighten filter-cast types & workspace guards

- Drop redundant tableId/workspaceId from BulkUpdateData and BulkDeleteData; service uses table.id / table.workspaceId so column metadata and DB scope can't drift apart.
- Add missing workspace-id guards to copilot user-table cases (insert_row, batch_insert_rows, update_row, batch_update_rows, rename); collapse duplicated rename check.
- Add service-level integration tests that buildFilterClause/buildSortClause receive table.schema.columns from queryRows, updateRowsByFilter, deleteRowsByFilter.

* improvement(table): cast jsonb date filters/sorts to timestamptz

::timestamp strips timezone offsets from ISO strings, making comparisons
depend on the server TimeZone setting. ::timestamptz preserves the offset
so chronological comparisons are correct regardless of server config.

* improvement(table): correct JSDoc examples for required columns arg

* improvement(table): validate range operator value types at SQL builder
2026-05-18 19:26:51 -07:00
Waleed 0c1167d76b improvement(workspace): fix resource table column proportions and toast stacking (#4655)
* improvement(workspace): fix resource table column proportions and toast stacking

* fix(resource): restore scrollbar-gutter stable on table scroll container

* fix(resource): remove scrollbar-gutter stable — single-table layout doesn't need it

* fixed files cols

* fix(findymail): add required enabled field to wandConfig entries

* fix(findymail): remove optional from block outputs — not valid on BlockConfig output type

* fixes

* fix(files): use folderSizeMap for sort value so size sort matches display

* files ref
2026-05-18 17:49:51 -07:00
Waleed f3cf8fc55d feat(findymail): add Findymail B2B contact data integration (#4654)
* feat(findymail): add Findymail B2B contact data integration

Adds 11 tools covering verified email lookup (by name, LinkedIn, domain
roles), email verification, reverse email lookup with profile enrichment,
company info, employee discovery, phone lookup, technology stack
detection, and credit checks. Single API-key block with operation
dropdown, gradient-rendered icon, and generated docs.

* fix(findymail): handle HTTP errors and surface last_detected_at

- All 11 tools now check response.ok and return success:false with the API error message on non-2xx responses
- search_technologies now maps last_detected_at to match lookup_technologies and the shared output schema
- Restore file_v3 in docs icon-mapping (translated docs still reference it)

* improvement(findymail): exclude operation from params transform

Match the convention used by enrich/apify/box/calendly — destructure out
operation before forwarding the rest to the tool call, so the operation
key doesn't leak into the tool payload.
2026-05-18 12:53:32 -07:00
Waleed b276672867 feat(prospeo): add Prospeo integration for B2B contact enrichment and search (#4653)
* feat(prospeo): add Prospeo integration for B2B contact enrichment and search

Adds 8 operations: enrich person/company, bulk enrich person/company,
search person/company, search suggestions, and account information.
Uses X-KEY header auth.

* refactor(prospeo): extract shared parse helpers into utils.ts
2026-05-18 12:08:00 -07:00
Waleed cf146931dd improvement(memory): replace unbounded server caches with lru-cache to fix heap growth (#4652)
* fix(memory): prune toolSchemaCache and semaphores to prevent heap growth

toolSchemaCache (lib/copilot/chat/payload.ts): module-level Map keyed by
userId:workspaceId never deleted expired entries, only checked TTL on read.
With 100K+ unique user/workspace pairs each holding 50-200KB of tool schemas,
this was the primary driver of the 24MB -> 25GB heap growth observed in
CloudWatch. Add a setInterval sweep every 30s (matching the TTL) with .unref()
so it does not prevent graceful shutdown.

semaphores (lib/core/async-jobs/backends/database.ts): acquireSlot created
Semaphore entries that releaseSlot never deleted. With per-execution UUID keys
(e.g. scheduleJobId), each scheduled workflow run would add a permanent entry.
Store the concurrency limit on the Semaphore struct and delete the entry from
the Map when all slots are free and no waiters remain.

validatorCache (lib/copilot/tools/server/generated-schema.ts): validated as
bounded (93 tools x 2 schema kinds = 186 max entries, ~2-9MB). No fix needed.

isolated-vm nativeContexts: validated as deferred GC, self-healed by worker
rotation at MAX_EXECUTIONS_PER_WORKER=200. externalMB spikes trace to
concurrent isolate heaps at peak load (128MB limit x active isolates), not a
reference leak. No fix needed.

* fix(memory): prune effectiveEnvCache and instrument cache sizes in telemetry

effectiveEnvCache (lib/environment/utils.ts): same unbounded accumulation
pattern as toolSchemaCache — module-level Map keyed by userId:workspaceId
with a 15s TTL that is only checked on read, never proactively evicted.
Adds a periodic sweep matching the TTL interval with .unref().

cache-registry (lib/monitoring/cache-registry.ts): lightweight registry
so modules can expose their cache sizes to telemetry without coupling.
toolSchemaCache and effectiveEnvCache both register on module load.

memory-telemetry: emits cacheSizes in every Memory snapshot log so
CloudWatch can confirm the caches stay bounded post-deploy.

* improvement(memory): replace manual TTL Maps with lru-cache for toolSchemaCache and effectiveEnvCache

Replaces the homegrown Map + setInterval sweep pattern with LRUCache from
the lru-cache npm package, which is the standard Node.js solution for
bounded in-process caching with TTL.

Changes per cache:
- Removes manual ToolSchemaCacheEntry / EffectiveEnvCacheEntry types
- Removes setInterval sweep timers (and the .unref() boilerplate)
- Removes the two-phase promise->value entry update inside the IIFE
- Stores Promise<T> directly — in-flight and resolved states share one type
- max: 200 (toolSchemaCache) / max: 500 (effectiveEnvCache) as hard ceilings
- TTL behaviour and concurrent-request deduplication are preserved exactly
- cache-registry .size reporting works unchanged via lru-cache's .size prop

* fix(memory): remove redundant waiters guard in releaseSlot
2026-05-18 11:58:25 -07:00
Waleed 268fa0e97e fix(knowledge): preserve scroll position when toggling tokenizer in chunk viewer (#4643)
* fix(knowledge): preserve scroll position when toggling tokenizer in chunk viewer

* fix(knowledge): skip scroll restore on initial mount of chunk editor

* chore(dev): add dev:clean script to purge Turbopack cache
2026-05-18 10:36:47 -07:00
Waleed 3979476432 improvement(workspace): allocate more space to name column in resource tables (#4645) 2026-05-18 10:19:01 -07:00
Vikhyath Mondreti 42bbb8aa49 improvement(mothership): abort path race preventing persistence (#4647)
* improvement(mothership): abort path race preventing persistence

* address comments

* address bugbot comment
2026-05-17 15:30:42 -07:00
Vikhyath Mondreti 4dc78cb82b improvement(redis-cleanup): schedule, async workflow, hitl base64 cache cleanup (#4646)
* improvement(redis-cleanup): schedule, async workflow, hitl bae64 cache cleanup

* address comments
2026-05-17 15:05:01 -07:00
WaleedandClaude Opus 4.7 08eeecbebe fix(security): KB fileUrl LFI, MCP/Agiloft SSRF pinning, form OTP, KB authz (#4639)
* fix(security): KB fileUrl LFI, MCP/Agiloft SSRF pinning, form OTP, KB authz

* fix(otp): don't leak caught error.message; fail-closed on DB retry exhaust

- Chat/form OTP routes: replace `error.message || fallback` with generic
  `Failed to process request` in 500 responses (logger still captures detail).
- otp.ts incrementOTPAttempts DB path: on MAX_RETRIES exhaustion, delete the
  verification row and return `'locked'` instead of trusting a possibly-
  undercounted final read.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): use undici fetch directly in pinned-fetch for typed dispatcher

Replace `globalThis.fetch` + double-cast with `undici.fetch` so the
`dispatcher` option is part of the real type contract. This guarantees
pinning won't silently break if a future runtime swaps the underlying
fetch implementation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(build): keep agiloft/grafana tool configs client-safe

Tool config files are statically reachable from the client bundle (via
tools/registry.ts → tools/{service}/index.ts). Importing
`@/lib/core/security/input-validation.server` from these files pulled
`node:dns/promises` into the Turbopack client bundle and broke the build.

Split agiloft utils into client-safe (`utils.ts`, plain fetch + sync
`validateExternalUrl`) and server-only (`utils.server.ts`, DNS-pinned
variants). Routes that need TOCTOU protection import the pinned helpers;
the executor-side tool path falls back to sync URL validation (matches
the supabase precedent and pre-PR baseline).

Grafana update tools likewise switch from `secureFetchWithValidation`
(server-only) to inline sync `validateExternalUrl` + plain fetch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(knowledge): case-insensitive scheme checks for fileUrl

Boundary schema accepted uppercase schemes (e.g. HTTPS://, DATA:) via the
case-insensitive http regex, but the processor's case-sensitive
startsWith('data:') / startsWith('http') / startsWith('https://') checks
rejected them with a confusing "Unsupported fileUrl scheme" error.
Aligns processor checks to the schema using case-insensitive regex per
RFC 3986 §3.1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): annotate undici/DOM type-bridge double-casts in pinned-fetch

Strict audit was failing on two new `as unknown as` casts in pinned-fetch.ts.
They bridge DOM `RequestInit`/`Response` ↔ undici equivalents (structurally
compatible at runtime since Node's global fetch is undici) and are required
to satisfy the FetchLike contract. Annotate so they count as documented
exemptions instead of new violations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 23:05:52 -07:00
Waleed fd12137c28 improvement(copilot): drop unused columns from mothership chat detail reads (#4640) 2026-05-16 23:05:41 -07:00
Theodore Li 4945d55671 fix(redis): apply TLS SNI override to pub/sub clients (#4638)
* fix(redis): apply TLS SNI override to pub/sub clients too

Pub/sub clients in lib/events/pubsub.ts build their own ioredis instances
directly via new Redis(redisUrl, ...) because pub/sub needs dedicated
connections (can't multiplex on the shared client from getRedisClient).
That path skipped the resolveTlsOptions helper added for trigger.dev's
PrivateLink VPCE IP, so every pub/sub channel hit
'Hostname/IP does not match certificate's altnames' on connect.

Export the helper as resolveRedisTlsOptions and use it from pubsub.ts.

* refactor(redis): share connection defaults via one helper

Extract keepAlive/connectTimeout/enableOfflineQueue + TLS SNI into a
single getRedisConnectionDefaults helper. Main client and pub/sub
clients both spread it; caller-specific retry/timeout policy stays
per-caller (pub/sub still needs maxRetriesPerRequest: null and a
different retry strategy for SUBSCRIBE).

* fix(pubsub): surface TLS config errors instead of silently degrading

resolveRedisTlsOptions (via getRedisConnectionDefaults) throws if
REDIS_TLS_SERVERNAME is missing for an IP-based rediss:// URL. Calling
it inside the constructor let createPubSubChannel's try/catch swallow
the error and fall back to in-process EventEmitter — silent
cross-replica pub/sub breakage in prod. Resolve defaults before the
try so config errors propagate; only catch genuine runtime construction
failures.
2026-05-17 01:51:17 -04:00
Theodore LiandClaude Opus 4.7 bd9e692b91 feat(redis): TLS SNI override for IP-based REDIS_URL + zod schema fixes (#4635)
* feat(redis): allow TLS SNI override for IP-based REDIS_URL

When trigger.dev's hosted workers reach our ElastiCache via PrivateLink,
their REDIS_URL contains the VPCE-assigned IP, not a DNS name. Default
ioredis TLS verification fails because the ElastiCache cert is issued for
the cluster's DNS, not the IP.

Add REDIS_TLS_SERVERNAME env var; when REDIS_URL is rediss:// + IP host,
pass `tls: { servername }` to ioredis so cert hostname verification
matches against the DNS name instead. Throws at client construction if
REDIS_TLS_SERVERNAME is unset in this scenario (fail fast — no silent
TLS bypass).

No-op for in-VPC connections (DNS host), so the always-on Sim app keeps
using default verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(confluence-schemas): expose extendable bases before .superRefine

confluenceCommentScopedSchema and confluenceBlogPostScopedSchema were
built with .extend(...).superRefine(...). superRefine returns a
ZodEffects which has no .extend method, so the three downstream
.extend() calls (confluenceUpdateCommentBodySchema,
confluenceGetBlogPostBodySchema, confluenceUpdateBlogPostBodySchema)
threw at module-init time.

Next.js lazy-loads route code per-request and never executed this
top-level chain, hiding the issue. Trigger.dev's bundler eagerly
evaluates all task-reachable modules at startup, which is why the
trigger.dev deploy surfaced it as "confluenceCommentScopedSchema.extend
is not a function" across every background task that transitively
imports this file.

Fix: introduce un-superRefined base schemas and use them as the .extend
target downstream; apply superRefine after each .extend so validation
behavior is preserved for every consumer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(storage-transfer): use z.string().url() for Zod 3 compat

z.url() is Zod 4 top-level syntax. The hoisted node_modules/zod
resolves to v3.25.76 (despite apps/sim/package.json declaring 4.3.6 —
a workspace resolution conflict), so z.url is undefined at runtime.

Trigger.dev's bundler eagerly evaluates all task-reachable modules at
startup and hits this with `external_exports.url is not a function`.
Next.js dev only evaluates routes per-request so the call site never
fires.

Quick fix: revert to the chained .string().url() form which works on
both Zod 3 and Zod 4 (deprecated in 4 but still supported). The
underlying version-resolution conflict is a separate cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mongodb-schemas): expose extendable base before .refine

mongoConnectionBodySchema was built with z.object(...).refine(...). Five
downstream schemas (mongodbQueryBodySchema, mongodbExecuteBodySchema,
mongodbInsertBodySchema, mongodbUpdateBodySchema, mongodbDeleteBodySchema)
.extend() that result, which threw at module-init in the trigger.dev
bundle (same root cause as the confluence and storage-transfer fixes:
.refine returns ZodEffects with no .extend method, and the resolved
zod is v3 even though package.json declares v4).

Fix: keep the un-refined mongoConnectionBaseSchema for downstream
.extend() targets. The pairing-validation refine isn't reattached
because the downstream extensions were never actually evaluating it
(module init threw before they could).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(redis,mongodb): address PR review comments

- redis.ts: move resolveTlsOptions call outside the try/catch in
  getRedisClient so config errors surface instead of being swallowed
  into a silent null return.
- mongodb.ts: re-attach mongoUsernamePasswordPaired .refine after each
  of the five downstream .extend()s. Mirrors the confluence pattern
  and restores the pairing constraint that the original chain dropped.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:12:13 -04:00
Siddharth Ganesan 67e18f747d fix(migrations): remove duplicate column add (#4632) 2026-05-16 10:46:02 -07:00
WaleedandClaude Opus 4.7 ff23546f30 fix(workflows): exclude block locked from diff detection (#4631)
Toggling a block's locked state is UI metadata and should not register
as a workflow drift/diff. Strip locked from hasBlockChanged,
computeFieldDiff, the compare.ts blockFields list, and from
extractBlockFieldsForComparison so it's also excluded from the
normalized stringify-based block equality check used by drift detection
and hashing.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 19:23:03 -07:00
Siddharth Ganesan 3712d1e2df feat(mship): make mship block stream output (#4626)
* Mship block stream

* Improvements

* Fixes

* Fix stream

* Fix

* Fix
2026-05-15 19:17:56 -07:00
Waleed f8ae249e7d improvement(executor): faster, more responsive workflow cancellation (#4630)
* improvement(executor): faster, more responsive workflow cancellation

* fix(executor): publish cancellation event on Redis write failure

* fix(tests): migrate handler test assertions to executeTool options object

* chore(tests): rename stale 'cancellation check interval' test after polling removal
2026-05-15 19:14:29 -07:00
Theodore Li fffb87901d feat(wait): Async toggle, chained-wait resume fix, execution status API (#4514)
* fix(wait): poll partially_resumed rows so chained waits resume

The chained-pause flow leaves a row in 'partially_resumed' status (wait1 done, wait2 still waiting). The poll's WHERE filter only matched 'paused', so wait2 was never picked up. Include 'partially_resumed' in the filter.

* feat(wait): make in-process threshold env-overridable for local testing

Adds WAIT_INPROCESS_MAX_MS env var (default 300000ms = 5 min). Lower it locally (e.g. 5000) to exercise the suspend/cron-resume path with short waits.

* feat(wait): add Suspend Workflow toggle, restore 5-min in-process default

* fix(wait): address bot review — setNextResumeAt + suspend unit default

- setNextResumeAt now matches paused OR partially_resumed; otherwise the
  cron poller can't null nextResumeAt after dispatching a chained-wait
  row, so it keeps reappearing in every poll batch until execution ends
  (flagged by both greptile and bugbot)
- Suspend mode now defaults missing timeUnitLong to 'minutes' instead of
  falling back to 'seconds' and immediately erroring (flagged by bugbot)

* improvement(wait): hint the wait-amount cap on the input

Restores the pre-#4331 description on the Wait Amount field so the limit is visible before submit instead of only at runtime. Mentions both the 5 min default and the 30 day cap with Suspend Workflow.

* refactor(wait): rename Suspend Workflow toggle to Async

* fix(wait): reword cap errors to say 'async mode'

* feat(workflows): add GET /workflows/[id]/executions/[executionId] status endpoint

Normalized status (pending|running|paused|completed|failed|cancelled)
across workflowExecutionLogs and pausedExecutions in a single response.
Surfaces paused-state details (resumeAt, pauseKind, blockedOnBlockId)
when a row exists in pausedExecutions, and the error string for failed
runs. finalOutput is opt-in via ?includeOutput=true.

* feat(workflows): support ?selectedOutputs= on execution status endpoint

Returns per-block outputs filtered by selectedOutputs paths (same
shape as the execute endpoint). Reads from executionData.traceSpans,
walks children recursively, and resolves dot-paths into each block's
output. Bare blockId returns the full output.

* fix(wait): drop dead tooltip prop on Async switch

Switch sub-blocks return null from renderLabel (sub-block.tsx:238), so
the tooltip never reached the user. The trade-off explanation already
lives in longDescription and bestPractices. Flagged by bugbot.

* docs(api): document GET /workflows/[id]/executions/[executionId]

Adds the WorkflowExecutionStatus schema and the getWorkflowExecution
operation to the OpenAPI spec, including completed/paused/failed
response examples and the includeOutput + selectedOutputs query params.
Registers the page in the Workflows section of the API reference.

* docs(api): tighten getWorkflowExecution descriptions
2026-05-15 22:09:06 -04:00
Waleed f76e8e6dcc improvement(copilot): trim copilot_chats reads to lean projections (#4629)
* improvement(copilot): trim copilot_chats reads to lean projections

* fix(copilot): exercise idempotent-delete guard via the lean auth mock
2026-05-15 18:54:53 -07:00
Waleed 674dd8d91f fix(mcp): map validation and conflict orchestration errors to 400/409 (#4628)
* fix(mcp): map validation and conflict orchestration errors to 400/409

* fix(mcp): migrate workflow-server routes to mcpOrchestrationStatus
2026-05-15 18:49:07 -07:00
WaleedandClaude Opus 4.7 93f7be4097 improvement(redis): strip idempotency body and cap mothership stream zsets (#4625)
* improvement(redis): strip idempotency body and cap mothership stream zsets

* chore(redis): trim verbose comments on idempotency body-strip

* test(buffer): pin exact ZREMRANGEBYRANK stop arg

Pinning -5_001 (= -(DEFAULT_EVENT_LIMIT) - 1) so the off-by-one
boundary is directly validated; expect.any(Number) would have passed
a wrong formula like -eventLimit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 17:46:19 -07:00
Waleed 8d7bbbc670 chore(utils): migrate to shared random/ID utilities and add enforcement linting (#4623)
* chore(utils): migrate to shared random/ID utilities and add enforcement linting

- Replace all Math.random(), crypto.randomUUID(), crypto.randomBytes(), nanoid, and uuid usages with shared @sim/utils/random and @sim/utils/id helpers across 72 files
- Add new @sim/utils exports: deepClone, omit, filterUndefined (object), truncate (string), backoffWithJitter, parseRetryAfter (retry), getErrorMessage (errors)
- Sweep all getErrorMessage, sleep, deepClone callsites across 500+ files to use shared utilities
- Add Biome noRestrictedImports rule to catch nanoid, uuid, and crypto named imports at lint time
- Add scripts/check-utils-enforcement.ts to catch Math.random and crypto.* global property access
- Add check:utils script to package.json

* chore(utils): replace deepClone wrapper with structuredClone built-in

deepClone() was a one-line wrapper around structuredClone(), which is
universally available in Node 17+ and all modern browsers. Removing the
abstraction reduces indirection and means contributors don't need to
learn a project-specific name for a well-known built-in.

- Remove deepClone from packages/utils/src/object.ts and index.ts
- Replace all 17 call sites with structuredClone() directly
- Update check:utils script suggestion text
- Update CLAUDE.md and global.md docs

* fix(utils): add missing biome noRestrictedImports rule and correct truncate docs

- Add noRestrictedImports to biome.json under style — bans nanoid and uuid
  package imports at lint time (crypto.randomUUID/randomBytes are caught by
  the check:utils grep script which handles global property access)
- Correct truncate() TSDoc and parameter name: sliceLength makes it clear
  that total output length is sliceLength + suffix.length, matching the
  behavior all callers were already written to expect

* fix(utils): add missing getErrorMessage imports at 4 call sites

The sweep agents added getErrorMessage calls without the corresponding
import in 4 files, causing test failures. Added the missing imports.

* fix(utils): fix build errors from getErrorMessage sweep and retry.ts Turbopack issue

- Fix retry.ts cross-file import: Turbopack cannot resolve './random.js' for
  internal package imports; inline the jitter crypto call directly
- Add missing getErrorMessage imports to 32 files where the sweep added calls
  without the corresponding import (caught by type-check and test runs)
- Remove accidental getErrorMessage import from crowdstrike/query/route.ts
  which has its own domain-specific getErrorMessage for parsing CrowdStrike's
  JSON error format
- Fix use-sub-block-value.ts type error from structuredClone narrowing:
  add 'as T' cast at emitValue callsite (safe — valueCopy is always a
  structural copy of newValue)

* fix(tools): use toError in crowdstrike catch block instead of local getErrorMessage

The catch block was calling the local getErrorMessage function which
parses CrowdStrike API JSON responses, not JavaScript Error objects.
Use toError(error).message to correctly extract the message from a
caught value in this context.
2026-05-15 17:31:27 -07:00
Theodore Li c403fafd11 fix(cloudwatch): use PutAlarmMuteRule for mute/unmute with duration window (#4621)
* fix(cloudwatch): use PutAlarmMuteRule for mute/unmute with duration window

* fix(cloudwatch): drop AWS StartDate to avoid race with at() trigger
2026-05-15 19:39:46 -04:00
Waleed 0dc1611f39 improvement(providers): align attachment dispatch to vendor SDK types (#4619)
* improvement(providers): align attachment dispatch to vendor SDK types

Post-merge audit of #4610 surfaced three follow-ups:

1. xAI Grok vision was blocked. Grok runs through the OpenAI-compatible
   chat-completions endpoint, so removing xAI from UNSUPPORTED_FILE_PROVIDERS
   and routing it through the image-only branch restores image attachments
   on vision models.

2. Azure OpenAI chat-completions deployments blocked any file attachment.
   Added a per-message image_url parts path; documents still require the
   Responses API endpoint and throw a clear, actionable error.

3. Wire shapes were loosely typed (Record<string, unknown> arrays).
   Replaced with `satisfies` clauses against each vendor SDK union at every
   push site: OpenAI Responses/Chat, Anthropic ContentBlockParam, Gemini
   Part, Bedrock ContentBlock members. AnthropicImageMediaType now derives
   from Base64ImageSource['media_type'] so it tracks SDK updates.

Also collapsed the validation cascade into an exhaustive switch with
`never` enforcement, and dropped the redundant per-provider
formatMessagesForProvider call from xai/index.ts (providers/index.ts
already runs the dispatcher centrally).

* fix(providers): restore getProviderAttachmentMaxBytes export and xAI message dispatch

- Restore `getProviderAttachmentMaxBytes` — still consumed by agent-handler.ts
  for per-provider attachment size limits in file hydration
- Restore `formatMessagesForProvider(allMessages, 'xai')` — providers/index.ts
  does NOT dispatch centrally on this branch; each OpenAI-compat provider
  formats its own messages. Without it, xAI Grok vision drops image attachments

* fix(providers): tighten ResponsesInputItem content type to SDK ResponseInputContent

Build fix: buildOpenAIMessageContent returns ResponseInputContent[] which
isn't assignable to Record<string, unknown>[] (ResponseInputText lacks an
index signature). Align the type to the SDK shape.
2026-05-15 15:28:49 -07:00
Siddharth Ganesan cb9c2d5452 improvement(files): validations (#4620)
* Fix

* Fixes
2026-05-15 15:28:48 -07:00
WaleedandCursor a64b338236 fix(files): fixed resource spacing on files directories pages (#4618)
* fix(files): fixed resource spacing on files directories pages

* fix(build): align turbo schema version

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 12:48:15 -07:00
Waleed d3d8f9c861 fix(logs,workspace): prevent cancelled status overwrite on race and move impersonation banner (#4617)
- Guard completeWithError against overwriting a cancelled execution status — cancel route writes cancelled to DB optimistically, but a block error racing the 500ms Redis check could finalize with failed before the engine detects cancellation
- Add tests covering the guard: cancelled DB status skips the write, non-cancelled proceeds normally, DB failure falls through to cost-only fallback, and subsequent attempts are deduped after guard marks session complete
- Move ImpersonationBanner from workspace root into components/ folder
2026-05-15 12:14:37 -07:00
c9118e775b feat(files): folders, multiselect, vfs update (#4572)
* v0.6.29: login improvements, posthog telemetry (#4026)

* feat(posthog): Add tracking on mothership abort (#4023)

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(login): fix captcha headers for manual login  (#4025)

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* feat(files): folders + vfs update

* address comments

* address comments

* cleanup unnused code

* address comments

* perf improvements

* address next set

* cycle detect

* error handling

* path improvements

* cleanup, best practices

* react query best practices: targeted invalidation, optimistic updates, key factory hierarchy

- Add workspaceLists(workspaceId) intermediate key level to both workspaceFilesKeys
  and workspaceFileFolderKeys so invalidation targets only the affected workspace
  instead of all workspaces
- Replace all lists() invalidation calls with workspaceLists(workspaceId) across
  every mutation (upload, rename, delete, restore, update content, folder mutations)
- Add optimistic updates to useRenameWorkspaceFile and useUpdateWorkspaceFileFolder
  with onMutate snapshot, onError rollback, onSettled reconciliation
- Move storage key into the content() factory as optional param so query keys are
  always built through the factory (useWorkspaceFileContent, useWorkspaceFileBinary)
- Fix AnimatePresence wrapping in FilesActionBar so exit animation fires on deselect
- Fix ResourceColGroup to use percentage weights instead of pixel widths to prevent
  horizontal scroll on narrow viewports

* add shift-click range selection and selection-aware context menu for files

- Extend SelectableConfig.onSelectRow with optional shiftKey param; DataRow captures shiftKey before onCheckedChange fires via a ref so the Radix Checkbox interaction chain stays intact
- Implement shift-click range selection in files.tsx using lastSelectedIndexRef; tracks last-selected index in visibleRowIds to compute the range
- Reset lastSelectedIndexRef on deselect and select-all
- Add selectedCount prop to FileRowContextMenu; hide Open and Rename when multiple items are selected, show "Delete N items" / "Download N items" labels in multi-select mode

* add Move submenu to file context menu and fix shift-click anchor update

- Add nested Move submenu to FileRowContextMenu using DropdownMenuSub/SubTrigger/SubContent; shows available folders filtered by selection, converts '__root__' -> null for moving to the root level
- Add handleContextMenuMove in files.tsx that calls moveItems.mutateAsync directly (no modal) and clears selection on success
- Fix shift-click range selection: update lastSelectedIndexRef after range select so chained shift-clicks extend from the new anchor point correctly

* fix move submenu: use folder names with tree-ordered indentation instead of stale paths

- Compute folder depth from parentId chain client-side (avoids stale server-computed path field)
- Tree-order folders so parents appear before their children, sorted by sortOrder then name
- Show folder.name instead of folder.path so optimistic renames are reflected immediately
- Indent each folder by depth * 12px in the submenu so po/shit renders as 'shit' indented under 'po'
- MoveOption gains optional depth field; contextMenuMoveOptions is a separate memo from moveFolderOptions (modal keeps its existing path-label behavior)

* fix shift-click anchor drift and remove dead stopPropagation constant

- Remove dead stopPropagation const in resource.tsx (replaced by handleSelectRowClick)
- Reset lastSelectedIndexRef when visibleRowIds changes so search/filter/folder navigation doesn't leave a stale anchor that produces wrong ranges on the next shift-click
- Update lastSelectedIndexRef in handleRowContextMenu when right-clicking resets selection to a single item, so the anchor matches the newly-selected row
- Add visibleRowIds to handleRowContextMenu deps (now reads it to compute anchor index)
- Remove moveItems.mutateAsync from handleContextMenuMove deps per project convention (.mutateAsync is stable in TanStack v5)

* complete workspace files feature: audit logs, posthog events, folder restore, empty state, keyboard shortcuts, storage indicator, breadcrumb rename

- Audit + PostHog: wire file_renamed, file_deleted, file_moved, file_bulk_deleted, folder_created, folder_renamed, folder_deleted, folder_moved events to all file/folder API routes
- Add AuditAction.FOLDER_UPDATED, FILE_MOVED, FOLDER_MOVED to audit types
- Folder restore: server function, contract, API route (POST /files/folders/[folderId]/restore), hook (useRestoreWorkspaceFileFolder), Recently Deleted integration with new File Folders tab
- Empty state: contextual emptyMessage passed to <Resource> based on search/filters/folder context
- Keyboard shortcuts: Delete/Backspace deletes selection, Escape deselects, Cmd+A selects all (list view only, input-aware guard)
- Storage indicator: useStorageInfo drives compact "used / limit" display in file list header via leadingActions
- Breadcrumb rename: current folder breadcrumb gains Rename dropdown + inline editing via breadcrumbRename (useInlineRename)
- Resource: thread leadingActions prop from ResourceProps to ResourceHeader

* cleanup: accessibility, emcn design tokens, react best practices across workspace UI

- Add sr-only ModalDescription to dialogs/modals for accessibility
- Replace hardcoded colors and z-indices with design token CSS variables
- Apply emcn design review fixes across tables, knowledge, logs, settings, workflows

* fix audit and posthog: FOLDER_RESTORED action on restore, fire folder_moved event separately from file_moved

* sidebar: add Files section with nested folder tree; polish move UX and cleanup

- Files section in sidebar shows folder/file tree with expand/collapse,
  matching Workflows section structure; collapsed sidebar shows flyout menu
- Move action bar now uses nested DropdownMenuSub tree instead of flat modal
- Context menu and action bar share renderMoveOption from move-options.tsx
- FolderInput added to emcn icons barrel; all FolderInput imports migrated
- Drag ghost uses CSS vars (--border, --shadow-medium, --z-toast)
- Selection pruning converted from useEffect to render-time comparison
- Keyboard listener stabilized with handleBulkDeleteRef pattern
- toError() used consistently in restore and move route handlers

* remove Files section from sidebar

* restore Files nav item in sidebar workspace section

* fix infinite re-render on files page - revert selection pruning to useEffect

* add filefolder resource type for ingesting workspace file folders

* export filefolder tree types; add toast feedback for file/folder mutations

* regenerate migration as 0208 after rebase onto staging

* add workspaceFileFolder to schema mock

* add FILE_MOVED, FOLDER_MOVED, FOLDER_UPDATED to audit mock

* add filefolder ChatContext kind and wire through schema and resolver

* add filefolder to AgentContextType

* add filefolder to chat context kind registry; fix resolver to use workspaceFiles table

* add .deepsec to gitignore

* cleanup: effect, emcn tokens, mutation error handling

- Replace selection-pruning useEffect with inline state adjustment during render
- Fix drag overlay using invalid --accent HSL token → --brand-secondary; z-50 → z-[var(--z-dropdown)]
- Move static inline styles on context menu trigger div to className
- Add missing onError toast to useUpdateWorkspaceFileFolder, useRestoreWorkspaceFileFolder, useRestoreWorkspaceFile

* lint

* fix: remove duplicate handleCopilotStopGeneration from rebase

* feat(copilot): folder-aware file context in WORKSPACE.md

* feat(copilot): add move operation to file manage API

* fix(files): make targetFolder optional in move file contract

* perf(files): parallelize buffer fetches, fix N+1 folder queries, stabilize drag useMemo

- download route: fan out all fetchWorkspaceFileBuffer calls with Promise.all
  before zip assembly so 100 files resolve in one round-trip instead of sequentially
- getWorkspaceFileFolder: replace per-ancestor SELECTs with a single workspace-wide
  folder load + buildWorkspaceFileFolderPathMap, making depth irrelevant to query count
- ensureWorkspaceFileFolderPath: pre-load all workspace folders in one SELECT before
  the segment loop; resolve existing segments from an in-memory map; only hit the DB
  to CREATE missing segments; conflict retry path preserved and also updates the map
- files.tsx rowDragDropConfig: move activeDropTargetId into a ref so the useMemo
  does not recompute on every drag-over event

* fix(files): remove files/ path stripping, fix stale path in optimistic update

- splitWorkspaceFilePath: remove the unconditional .replace(/^files\//, '')
  that clobbered paths for files inside a folder literally named "files"
- useUpdateWorkspaceFileFolder: when a name update is in flight, recompute
  the path field for the renamed folder (replace last segment) and propagate
  the new prefix to all descendant folders so breadcrumbs stay correct
  during the optimistic window

* fix(files): revert broken ref opt, clean 409 on restore, null parentId on orphaned restore

- files.tsx: revert the activeDropTargetId ref optimization — the ref doesn't
  trigger re-renders so the drop-target highlight never updated during drag;
  activeDropTargetId is back in state and in the rowDragDropConfig deps
- restore/route.ts: catch Postgres 23505 unique-constraint violation and
  return a clean 409 instead of leaking the raw error as 400
- restoreWorkspaceFileFolder: check if the parent folder is still archived
  before restoring; if it is, restore to root (parentId: null) so the folder
  is never orphaned under an archived parent

* feat(search): show folder path for files in cmd-k modal, strip extraneous comments

- FileItem interface with folderPath?: string[] added to search modal utils
- MemoizedFileItem component renders folder breadcrumb identically to
  MemoizedWorkflowItem — truncated path segments on the right with / separators
- FilesGroup rewritten as a dedicated memo component (was createIconGroup factory)
  so it accepts FileItem[] and includes folderPath segments in the search value
- searchModalFiles in sidebar splits f.folderPath string into string[] segments
- search-modal.tsx typed to FileItem and includes folderPath in filterAndSort
- Remove self-explanatory "Phase 1" section label from download route
- Remove redundant TSDoc on the unique index in db schema

* fix(workspace-files): audit fixes — transaction, status codes, contract refinements, guards

* fix(vfs): pass folderPath separately so buildWorkspaceMd groups files correctly

* fix(types): narrow unknown fileInput with Record cast after object guard

* fix(routes): replace instanceof Error with toError() across new workspace file routes

* improvement(files): cleanup pass — remove unnecessary useCallbacks, consolidate emcn icon imports

- Remove useCallback from 5 drag-event handlers in DataRow (passed to native <tr> elements, no observer)
- Remove stable useCallback fns from 3 useMemo deps arrays in files.tsx (editingId/editValue remain)
- Merge all @/components/emcn/icons subpath imports into barrel (files.tsx, action-bar, file-row-context-menu)

* fix(files): apply activeSort to folders, reject drop onto current parent folder

- visibleFolders now respects activeSort column (name/updated/created) and direction
  so folder ordering stays consistent with file ordering
- isInvalidDropTarget now returns true when all dragged items are already direct children
  of the target folder, preventing a no-op move mutation

* fix breadcrumb

* add new tools to rename, create, delete folders

* move more ui actions into orchestration dir

* address comments

* fix params

* fix tests

* address comments

* improve error codes

* address comments

* address more nits

* fix mcp server error code

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: waleed <walif6@gmail.com>
2026-05-15 12:05:03 -07:00