* fix(tables): resource-cell icons, embedded filters, run-count + queued fixes
- table-grid: render in-workspace resource URLs (workflow/table/KB/file) as
tagged-resource cells reusing ContextMentionIcon (colored square for
workflows), matching @-mention chips; only the matching list is fetched.
- table-grid: fix row-number sticky cell overflow — reserve the full run/stop
button area (30px, not 16px) so wide row indices don't clip.
- table-grid: show an infinite-scroll loading spinner while the next page
loads instead of looking like the end of the table.
- table: surface sort + filter (and run/stop via the options-bar extras slot)
in the embedded mothership table resource view.
- table-grid/utils: stop the dispatch overlay from optimistically painting
autoRun=false cells Queued for auto-fire dispatches — the dispatcher skips
those groups ('autoRun-off'); manual runs still show Queued (manual-bypass).
- dispatcher: exclude orphan pre-stamps (pending + executionId null) from
countRunningCells so the "X running" badge doesn't stick above zero.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tables): single run/stop control, right-aligned row numbers, View-execution guard
- table: de-duplicate the run/stop control in the embedded mothership view —
drop TableGrid's own embedded run-status bar; it now lives only in the
options bar (left-aligned next to Filter + Sort). Removes the orphaned
RunStatusControl import + onStopAll/cancelRunsPending props.
- data-row: right-align the row number within its box (hugs the right edge,
no hover position jump) with a scaled right inset — 2px for ≤3-digit
indices, 4px for 4+ so narrow columns don't look over-padded.
- table-grid: require a real executionId in the action bar's canViewExecution
flag so an error that never produced an execution (enqueue failure →
status 'error', executionId null) doesn't offer "View execution".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tables): address review — drizzle operators for orphan filter, enabled flag for files query
- dispatcher: replace the `not(and(...)) as SQL` cast in countRunningCells with
`or(ne(status,'pending'), isNotNull(executionId))` — De Morgan equivalent,
fully type-checked, no cast and no hand-written raw SQL.
- workspace-files: add an `enabled` option to useWorkspaceFiles; sim-resource
cell now passes the real workspaceId with `enabled` instead of '' so the
query cache isn't polluted with an empty-key entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tables): "X running" badge counts actual in-flight cells, not dispatch scope
The badge derived from `runningCellCount` (the dispatch-scope estimate =
rows-ahead × groupCount), which over-counts groups that already finished on
rows still inside a dispatch's scope — a cascade where 3 of 4 workflow columns
completed read "4 running" instead of "1". Derive `totalRunning` from the live
`runningByRowId` map instead (the same per-row source the gutter and action-bar
selection already sum), so it reflects cells actually in flight and updates
per-cell via SSE rather than only on dispatch-window events.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
copilot_messages had no column preserving message order: created_at (set
from each message's timestamp) ties at millisecond granularity in 58% of
chats, and some chats have out-of-order timestamps within their array. The
only other tiebreaker, id, is a random UUID — so ORDER BY created_at, id
renders same-timestamp user/assistant pairs swapped. This blocks the R+1
read cutover.
Add an integer seq = the message's 0-based index within the chat's JSONB
array (ground-truth order), backfilled inline in migration 0219 (no script
for self-hosters or us). Reads will use ORDER BY seq NULLS LAST, created_at,
id at cutover; reads still come from JSONB after this PR.
Design:
- seq is a tiebreaker, not the sole sort key (concurrent-append/NULL safety).
- Nullable now; defer NOT NULL so rolling-deploy old pods don't fail inserts.
- replace (update-messages snapshot) overwrites seq = array index
(re-densifies after a mid-conversation delete); append preserves existing
seq via COALESCE and assigns base+idx from a single MAX(seq) read (never
MAX+i in SQL — multi-row batches would collide). The non-atomic
read-then-insert window is documented and bounded by the read tiebreak +
snapshot re-densify.
- Dedupe message ids before insert (87 prod chats carry dup ids; a repeated
id in one INSERT...ON CONFLICT would otherwise throw).
- Backfill picks first-occurrence per (chat,id), gap-free via ROW_NUMBER;
validated on staging data (0-based, contiguous, 0 bad ranges).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(auth): block signup spam by denylisting shared MX backends
Signup-spam bots rotate throwaway domains rapidly but funnel them through a
small number of shared catch-all mail providers. Across the current wave, 85%
of bot domains resolved to just two MX backends (smtp.215.im,
email.gravityengine.cc), while every domain differed — so the resolved MX host
is a far more durable signal than the domain itself.
Add a server-only MX validator (validateSignupEmailMx) that resolves the
domain's MX records during /sign-up/email and rejects:
- domains with no MX record (no_mx)
- domains whose MX backend is on the denylist (blocked_mx_backend)
Seeded with the two observed backends; extend at runtime via
BLOCKED_EMAIL_MX_HOSTS. Fail-open on DNS timeout/transient error so legitimate
users are never blocked by a resolver blip; kill switch via
DISABLE_SIGNUP_MX_VALIDATION. Returns a clean 403 (APIError), not a 500.
* refactor(auth): make MX signup validation opt-in (SIGNUP_MX_VALIDATION_ENABLED)
Aligns with the sibling feature SIGNUP_EMAIL_VALIDATION_ENABLED (disposable
blocking via harmony), which is also opt-in. Default-off avoids adding a DNS
dependency to the signup path and prevents surprise signup blocking on
self-hosted deployments with non-standard mail setups (internal domains, or a
too-broad MX entry catching legit shared infra like Cloudflare Email Routing).
Enable on hosted/abuse-targeted deployments via SIGNUP_MX_VALIDATION_ENABLED;
the flag doubles as the kill switch, so the separate DISABLE_ flag is removed.
* fix(auth): clear MX-lookup timeout to avoid dangling timer on success
* refactor(auth): remove hardcoded MX denylist defaults
The MX-backend denylist is now entirely operator-supplied via
BLOCKED_EMAIL_MX_HOSTS. Sim is open source, so no specific mail backends are
named in the repo, the env example, or the tests — deployments configure their
own list out of band (e.g. via secrets). The no-MX hygiene check is unchanged;
with an empty denylist no backend is blocked.
* feat(tables): freeze columns
* fix(tables): sticky meta-header row for frozen workflow groups, remove dead handleChangeType
* fix(tables): scope frozenOffsets dep to frozen column widths only
* fix(tables): restore frozenColumns on delete-column undo/redo
* fix(tables): restore useMemo for isAllRowsSelected (O(n) computation)
* fix(tables): use current frozenColumns on delete-column redo, not stale snapshot
* fix(tables): clean up frozenColumns on create-column undo
* fix(tables): merge frozen state on delete-column undo instead of overwriting
* fix(tables): add previousFrozenColumns to test fixture for delete-column action
* fix(tables): skip frozen state update on delete-column redo when column was not frozen
* refactor(tables): rename frozen columns to pinned, fix sticky-zone UX
- rename frozenColumns → pinnedColumns across types, contract, undo
actions, grid state/refs/props, and dropdown labels
- add Pin / PinOff emcn icons; use them in the column menu in place of
Lock / Unlock
- pinned body cells render at z-[6], above the cell selection border
(z-[5]), so the blue selection border can't draw on top of the
sticky-left zone
- restrict column drag-reorder to within the pinned or unpinned zone in
both handleColumnDragOver and handleScrollDragOver; cross-zone drop
indicators are suppressed
- on unpin, slide the column to the first unpinned slot so the sticky
zone stays contiguous; consolidates pin and unpin into one branch
that always re-enforces pinned-at-front
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tables): biome formatting + tighten pinned-zone comments
- collapse two onPinToggle JSX props that biome wanted on a single line
- drop a WHAT comment in handleScrollDragOver; tighten the why-comments
in handlePinToggle, handleColumnDragOver, and handleColumnDragEnd so
they describe the invariant being protected instead of narrating the
recent change
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tables): re-sort reorder-columns undo to keep pinned-at-front
If the user reordered, then pinned a column, then undid the reorder, the
restored snapshot could leave a currently-pinned column in the middle of
columnOrder. pinnedOffsets walks displayColumns left→right and assigns
sticky `left` from checkboxColWidth — a pinned column in the middle gets
a sticky offset as if it were at the front, causing it to jump over its
left neighbors on horizontal scroll.
Re-sort the restored order with pinned entries pulled to the front before
applying. Mirrors the belt-and-suspenders re-sort in handleColumnDragEnd.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(enrichment): workflow Enrichment block + /api/tools/enrichment/run
Add a generic Enrichment workflow block that runs a code-defined enrichment
(Work Email, Phone Number, Company Domain, Company Info, …) and returns its
outputs — usable in workflows, not just tables.
- New internal endpoint POST /api/tools/enrichment/run (checkInternalAuth +
contract) runs the same runEnrichment provider cascade; injects the
workspace's hosted/BYOK key via executeTool.
- New tool enrichment_run posts to it and surfaces hosted-key cost on the
output so the workflow logging session bills it.
- New block blocks/blocks/enrichment.ts generated from the enrichment registry:
operation dropdown = enrichments, per-enrichment conditional inputs, union of
conditional outputs. New registry entries appear automatically.
- EnrichmentRunContext.tableId/rowId made optional (workflow path has no row).
- Register tool + block; bump api-validation route baseline; add EnrichmentIcon
and generated docs page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: re-trigger CI
* refactor(enrichment): share mapFieldType helper between block and tool
* fix(enrichment): reserved output keys (matched/provider) win over enrichment outputs
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* make zoominfo icon a bit bigger
* feat(integrations): add RB2B integration
Add the RB2B AI workspace integration for person-level visitor identification and B2B enrichment via the RB2B API (api.rb2b.com).
- 15 tools: credit check, IP to HEM/MAID/company, email/HEM to business profile/best LinkedIn/LinkedIn slug/MAID, email to last active date, and LinkedIn to business profile/best personal email/personal email/hashed emails/mobile phone, plus LinkedIn slug search
- Single API-key block with an operation dropdown, conditional inputs, and outputs covering every tool
- RB2B icon and generated docs
* fix(integrations): address RB2B PR review
- email_to_activity now has its own plaintext Email field (canonicalParamId) instead of sharing the Email-or-MD5 input, since it does not accept MD5 hashes
- RB2BIcon uses useId() for its clipPath id instead of a hardcoded id, matching the convention used by other icons
* fix(integrations): map RB2B email_to_activity input via params, not canonicalParamId
The canonical-pair validation test requires canonical members to share the same
condition. Replace the cross-operation canonicalParamId reuse with two distinct
subblock ids (email for HEM ops, emailAddress for email_to_activity) and a small
tools.config.params remap to the email tool param.
The hooks.before middleware threw plain Errors for the four auth-policy
gates (registration disabled, email/password disabled, login allowlist,
blocked signup domains). better-auth surfaces an uncaught hook Error as a
generic 500 SERVER_ERROR, so users hitting these gates saw 'Failed to
create account' with no actionable message.
Throw APIError('FORBIDDEN', { message }) instead so the endpoints return a
clean 403 with the policy message, which the client surfaces directly.
Internal/server failures (email send, provider userinfo fetch, ID-token
parse) intentionally remain plain Errors so they continue to surface as
500s.
* feat(slack): scope private channel visibility to installing user
* improvement(slack): warn on usr_ marker parse miss, drop dead bot prefix
* chore(slack): trim verbose comment
* feat(integrations): add ZoomInfo, align Wiza, audit Apollo, refresh docs
- Add ZoomInfo integration: search/enrich contacts & companies, intent, news (6 tools), proxy route, block, and icon
- Validate and align Wiza tools/block/outputs against live API docs
- Audit Apollo tools: tighten params, outputs, and types
- Update tool docs (.mdx), icons, icon mappings, and integrations.json
* fix(zoominfo): use useId for ZoomInfoIcon clipPath to avoid duplicate DOM ids
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(apollo): address PR review on sequence add and bulk enrich
- sequence_add_contacts: send large contact_ids/label_names arrays in the
POST body (Rails merges query + body params) to avoid reverse-proxy URL
length limits; keep scalar settings in the query string
- organization_bulk_enrich: add back-compat shim mapping the legacy
`organizations` ({name, domain}[]) subBlock value to the new `domains`
string array so saved workflows keep running
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(integrations): unique ZoomInfo icon clip id, numeric employee range filters
- ZoomInfoIcon: derive clipPath id from useId() so multiple instances don't collide
- ZoomInfo company search: send employeeRangeMin/Max as numbers, matching revenueMin/Max
* fix(zoominfo): send employeeRange filters as strings per API schema
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(zoominfo): send contactAccuracyScoreMin as string per Contacts Search schema
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(apollo): harden people_search pagination, correct bulk-update output docs
- people_search: read pagination from both the nested `pagination` object
(legacy /mixed_people/search) and top-level fields, avoiding silent
fallback to defaults
- account_bulk_update: correct output descriptions — accounts support up to
1000 per request and async is opt-in (not auto-triggered at 100 like contacts)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(wiza): correct company enrichment credits shape in output docs
Company enrichment returns api_credits { total, company_credits }, not the email/phone/scrape breakdown used by individual reveals. Description-only fix verified against docs.wiza.co.
* fix(apollo): send sequence add contact_ids/label_names as query params per docs
Apollo documents every field for emailer_campaigns/:id/add_contact_ids as a query parameter with no request body. Append contact_ids[]/label_names[] to the query string instead of the JSON body to match the documented contract.
* feat(apollo): expose account_stage_id uniform field for bulk update accounts
Apollo documents account_stage_id as a Body Param for /accounts/bulk_update ('when using account_ids, apply this account stage to all accounts'). Adds it to the tool params, body builder, type, block subBlock, and params mapper alongside name/owner_id.
* docs(apollo): correct contact_update typed_custom_fields description
Apollo's update-a-contact endpoint documents typed_custom_fields, so drop
the inaccurate "not officially documented" caveat.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(zoominfo): default required outputFields on enrich; parse nested API error object
- ZoomInfo enrich endpoints require outputFields; send a curated default set when omitted so requests don't fail
- extractZoomInfoError now reads the GTM REST nested error object ({error:{code,message}}) instead of dropping it to a generic HTTP message
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(wiza): add wandConfig to complex prospect-search filter fields
Adds AI-assist wandConfig (json-object) with format examples to the structured filter inputs (job_title, job_company, past_company, company_industry, location, company_location) and the full filters object, completing the wandConfig checklist item for the Wiza block.
* fix(findymail): surface API .error messages and alphabetize registry
- transformResponse error branches now fall back to the response body's
`error` field before the generic status string, so Findymail's actual
messages ("Not enough credits" on 402, "Subscription is paused" on 423,
"One identifier is required..." on 422) reach the user instead of a
bare "Findymail API error: <status>". Applied to all 11 tools.
- alphabetize the findymail entries in tools/registry.ts to match the
already-alphabetical import block and the integration guideline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(billing): migrate hot path writes away from user_stats
* fix period start and end sot
* address comments
* Remove stale billing migration
Co-authored-by: Cursor <cursoragent@cursor.com>
* regen migrations
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Splits copilot chat messages out of the copilot_chats.messages JSONB
column into a dedicated copilot_messages table. JSONB stays canonical
during R+0 — every write path dual-writes to the new table best-effort
(try/catch + log warn, never throws).
Migration 0217 creates the table + indexes and inline-backfills history
from JSONB so OSS self-hosters don't need to run a separate script.
Write paths covered:
- post.ts (user message append)
- terminal-state.ts (assistant turn finalize)
- update-messages/route.ts (snapshot replace)
- inbox/executor.ts (background turn)
- fork/route.ts (chat clone)
- superuser/import-workflow/route.ts (chat import)
Each call threads chatModel + streamId where relevant; ON CONFLICT DO
UPDATE preserves existing stream_id / model via COALESCE.
For pre-R+1 reconciliation, run:
bun apps/sim/scripts/copilot-messages-reconcile.ts [--since='7 days']
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(mothership): persist queued messages, edit-in-place preserves order
* fix(mothership): pause drain while head is in edit, restore handoff cleanup on edit, merge on migrate
* improvement(mothership): strip editing on persist; tighten comments to codebase style
* improvement(mothership): omit editing from partialize entirely
* fix(mothership): honor user removal during dispatch in failure-restore path
* chore(auth): upgrade better-auth 1.3.12 → 1.6.11
* chore(auth): address Greptile review — broaden change-email type + migration newline
* fix(auth): correct oneTimeToken expiresIn unit (minutes, not seconds)
Better-auth's oneTimeToken expiresIn is in minutes (multiplied by 60_000ms
internally). Sim's existing 24*60*60 evaluated to ~60 days of token
lifetime instead of the intended 24 hours. Tokens are one-time-use and
typically consumed within seconds of generation (Socket.IO handshake),
so this tightens an unused security window without affecting UX.
* improvement(cron): fire-and-forget for cron-invoked endpoints
* fix(cron): add staleness takeover to single-flight guard
* improvement(cron): drop single-flight guard, rely on DB row claiming
* fix(tables): coerce row values to column types on write instead of failing
* fix(tables): persist coerced values in upsert match + bulk update, normalize Date to ISO
* fix(tables): guard date coercion against out-of-range values
* improvement(schedu
les): retries, concurrency limits
* improvement(schedules): remove stale generated migration
Drop the pre-merge generated 0213 migration so it can be regenerated after syncing with staging.
Co-authored-by: Cursor <cursoragent@cursor.com>
* address comments
* fix pinned version for lru cache
* retryable errs cleanup
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(db): disable statement_timeout for migrations
* fix(ci): route migration workflow through guarded migrate.ts
* feat(tables): workflow-column run fixes + bounded "run N rows"
- Pass group.autoRun as the add-group dispatch flag so an autoRun=false
column no longer opens a no-op dispatch that flashes the run-count badge.
- Scope the context-menu re-run to the right-clicked workflow cell's group
(cascading to dependents) instead of every group on the row.
- Add an extensible per-dispatch row cap (DispatchLimit { type:'rows', max })
surfaced as "Run 10 / 1,000 empty rows" in the group header; dispatcher
stops after N eligible rows. New limit/processed_count columns on
table_run_dispatches.
- Fix stranded "Queued" cells: the cascade owner now treats a queued marker
(orphan pre-stamp) as a manual run so autoRun=false requested groups are
picked up, and drains late markers before releasing the row lock.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(db): regenerate dispatch limit migration on staging chain (0214)
Re-numbers the table_run_dispatches limit/processed_count columns from the
collided 0212 to 0214 after merging staging (which added its own 0212/0213).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(tables): lint formatting
* fix(tables): address PR review on dispatch cap + cascade drain
- Don't consume the row cap when batchEnqueueAndWait fails; a transient
failure no longer completes a capped dispatch with zero rows started.
- Outer cascade-drain loop only re-drives a genuine queued marker, not any
eligible group, so an empty-output group can't re-run forever.
- completeDispatch forwards limit on the terminal SSE event.
- Extract shared LIMITED_RUN_PRESETS for the Run-N-rows menu items.
* chore(lint): format generated tool-schemas-v1
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(tables): native enrichments sidebar + workflow input mapping
Add a Clay-style enrichments catalog to the table view and wire per-row
input mapping into workflow-backed columns.
- New "Enrichments" entry in the New-column dropdown opens a sliding panel
listing curated enrichment templates; picking one swaps to the workflow
config in-place (no cross-slide) with a back button.
- Type the workflow sidebar as manual | enrichment; enrichment hides the
launch + add-column-inputs affordances.
- Add a "Workflow inputs" advanced panel mapping Start-block input fields to
table columns (left-of-workflow columns only), with name-match auto-fill
and collapsible input-mapping-style rows.
- Persist type + inputMappings on the workflow group (types, contract, route,
service, hook) — jsonb, no migration.
- Consume inputMappings at run time: when present, feed Start-block fields
from the mapped columns; otherwise fall back to name-match spread.
- Clean up inputMappings on column rename/delete (stripGroupDeps + renameColumn).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(emcn): extract CollapsibleCard and reuse for input mapping
Pull the collapsible field-card markup (surface-4 header + surface-2 body,
click/keyboard toggle, truncated title + optional badge) into a shared
`CollapsibleCard` emcn component, and use it in the workflow-builder input
mapping rows and the table sidebar's input-mapping panel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(tables): code-defined enrichment registry run directly per row
Enrichments are now TS configs in apps/sim/enrichments/ (registry, like
connectors) that run directly per table row via the existing run/dispatch/
cell-write rails — no workflow execution.
- enrichments/{types,registry} + work-email (heuristic) and phone-number (stub).
- WorkflowGroup gains enrichmentId; WorkflowGroupOutput gains outputId
(workflowId/blockId/path kept required, '' for enrichment groups).
- Executor branches on group.type === 'enrichment' → maps inputMappings →
enrich() → outputs by outputId → cell-write. Missing required inputs skip
(blank cell) instead of erroring.
- Sidebar lists the registry; enrichment-config panel maps inputs to columns
and creates the enrichment group (no workflow UI).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(enrichments): provider fallback cascade + hosted-key usage source
Replace each enrichment's single enrich() with an ordered providers[]
fallback cascade. Providers are plain data ({ id, label, toolId,
buildParams, mapOutput }) so the catalog stays client-safe; the
server-only runner (run.ts) calls executeTool per provider, first
non-empty result wins, misses/errors fall through, all-miss = blank cell.
Wire four enrichments on the hosted-safe providers (Hunter, PDL):
- Work Email (fullName, companyDomain): Hunter -> PDL
- Phone Number (fullName, companyDomain): PDL
- Company Domain (companyName): PDL
- Company Info (domain): PDL -> Hunter
Person enrichments take a single canonical fullName (Clay-style); Hunter
gets first/last via splitName(), PDL takes name directly.
Add 'enrichment' to usage_log_source enum (+ migration) so hosted-key
tool cost from these per-row calls can be billed to the table owner.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(enrichments): bill hosted-key cost; surface provider errors; abort safety
- runEnrichment now returns { result, cost, error }: accumulates hosted-key
cost across the cascade, and sets `error` only when every provider that ran
errored (auth/rate-limit/outage) vs a clean miss.
- Executor records the cost to the table owner (createdBy) via recordUsage
(source 'enrichment'); billing failures are logged, never error the cell.
- F1: all-providers-errored now writes status 'error' instead of a blank
'completed' cell that looked like "no data found".
- F2: re-check the abort signal after the cascade so a cancel mid-tool-call
isn't recorded as a completed empty cell.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(tables): present enrichment columns as first-class in the grid
- Meta-header shows the enrichment's name + icon (Mail/Phone/Globe/Building2)
instead of "Workflow" + a color chip.
- Per-column header icon uses the enrichment's icon (via columnSourceInfo)
instead of the generic play icon.
- Hide "View execution" for enrichment cells in both the row context menu and
the action bar (no workflow execution exists to open); also hide the
meta-menu "View workflow" item for enrichment groups.
- Clicking an enrichment column header now opens the enrichments sidebar in
edit mode (pre-filled input mappings, Update via useUpdateWorkflowGroup)
instead of the workflow "Configure workflow" sidebar.
- Enrichment config lets the user name each output column (editable per-output,
deduped defaults) since enrichments can produce multiple columns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tables): enrichment columns use type icon; output names editable
- Drop the per-column enrichment icon (it duplicated the meta-header icon).
Enrichment output columns now render the standard column-type icon (Text,
etc.) — the enrichment's icon stays only on the group meta-header.
- Make output column names editable in the enrichment config edit mode too;
changed names rename their columns via useUpdateColumn (the rename cascades
into the group's output refs server-side). Validation excludes the output's
own current name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tables): wrap enrichment catalog descriptions instead of truncating
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tables): edit enrichment output columns via the plain column editor
Edit column on an enrichment output now opens the normal column-config sidebar
(rename / type / unique) instead of the workflow 'Configure output column'
panel, which showed workflow-only fields and blocked a simple rename.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(copilot): list_enrichments + add_enrichment table tools
Let the copilot enumerate the code-defined enrichment registry and add an
enrichment column to a table (validating required input mappings against the
table's columns), backed by the same workflow-group machinery the UI uses.
* fix(enrichments): address PR review feedback
- Guard the enrichment cell path on `enrichmentId` so a group typed
'enrichment' without a registry id falls through to the workflow path
instead of erroring.
- Clear stale output values when skipping a row for missing required inputs,
so the auto cascade re-enriches once inputs return (was left completed+filled).
- Write a terminal state on abort in the enrichment path (matches the workflow
path) so a cancel between run and terminal-write can't leave the cell running.
- Edit mode: apply the group update (mappings/deps/auto-run) before column
renames so the primary edit lands even if a rename fails.
- Disable Save once validation has surfaced a missing required input.
- Use the workflowGroupById map instead of O(n) find in the context-menu and
action-bar hot paths.
* chore(commands): add /add-enrichment command
Guides adding a code-defined table enrichment to the registry, with a required
step to verify each provider tool has hosted-key support and chain to
/add-hosted-key when it doesn't.
* fix(enrichments): address second-pass PR review
- updateWorkflowGroup output diff now keys on outputId (falling back to
blockId::path) so enrichment outputs — which share empty blockId/path —
no longer collapse to one key and drop sibling columns.
- Enrichment terminal write now clears output columns absent from the result,
so a partial/empty re-run doesn't leave stale values.
- Editing a group whose enrichment was removed from the registry shows an
explanatory panel instead of silently falling through to the new-enrichment
catalog.
* feat(tables): show "Not found" badge for empty completed enrichment cells
An enrichment that runs to completion but matches nothing now renders a gray
"Not found" badge (like the Queued/Waiting cell states) instead of a blank
cell, so a real miss is distinguishable from an unrun cell. Scoped to
enrichment output columns; an empty string no longer counts as a value.
* fix(enrichments): don't re-run completed no-match enrichments on auto cascade
A completed enrichment with empty outputs is a real no-match result, not an
unfinished run. Eligibility now treats an enrichment's completed status as
terminal (regardless of output fill), so the auto cascade stops re-invoking
billable provider calls on every no-match row each dispatch. Input changes
still clear the exec entry, so genuine re-runs are unaffected; manual Run all
still re-runs.
* fix(enrichments): treat provider 404 as no-match, not a cell error
Providers like People Data Labs signal 'no record found' with HTTP 404, which
executeTool surfaces as a failed ToolResponse (status on output.status). The
cascade now treats a 404 as a clean miss — falls through to the next provider
and lets the cell render 'Not found' — instead of marking the cell errored.
Auth/rate-limit/5xx still propagate as real errors.
* fix(tools): surface HTTP status on error ToolResponse output
executeTool's catch handled Error instances in its first branch and only
extracted status/statusText/data for non-Error object throws — so HTTP errors
(thrown as Error instances carrying .status) lost their status on the returned
output. Surface it for Error instances too, so callers can branch on the
status (e.g. the enrichment cascade treating a provider 404 as a no-match).
* fix lint
* Revert ff
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cron schedules all fire on the same boundary (e.g. every :00), stampeding
the Postgres connection pool at the top of each minute/hour. Spread each due
schedule's start across a [0, 30s) window via trigger.dev's delay option
(no compute billed during the delay). Wires the previously-unused
EnqueueOptions.delayMs through the trigger.dev backend.
* Add queueing for hosted keys
* feat(rate-limiter): FIFO queue for hosted-key per-workspace fairness
Replace the per-call distributed lock with a Redis-backed FIFO queue so
callers within a workspace get strict ordering instead of racing the
bucket. Adds heartbeat-based crash recovery and dead-head reaping in a
single Lua script. Bumps Exa search hosted RPM from 5 to 60.
* fix(rate-limiter): bound hosted-key queue wait to execution budget; fix heartbeat + telemetry
Tie the per-workspace hosted-key queue wait to the surrounding execution
budget instead of a flat 5-minute cap. acquireKey now accepts the execution
AbortSignal (threaded from ExecutionContext): when present, the wait is
bounded by the run's actual plan timeout / cancellation, with the enterprise
async ceiling as a backstop; when absent it falls back to MAX_QUEUE_WAIT_MS.
This lets long-running async (Trigger.dev) runs use their full budget while
no longer letting a single queued call burn a short sync run's entire budget.
Also addresses Greptile review:
- P1: share one lastHeartbeatAt across all wait phases and cap every sleep to
HEARTBEAT_REFRESH_INTERVAL_MS so a long low-RPM retryAfterMs can no longer
let the head's heartbeat lapse mid-wait and break FIFO ordering.
- P2: derive hostedKeyQueueWaited telemetry reason from the actual bottleneck
(queue_position / dimension / actor_requests) instead of hardcoding it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(rate-limiter): make hosted-key queue waits abort-interruptible
Replace the plain capped sleeps in the queue-head and bucket-capacity wait
loops with an interruptibleSleep that resolves early when the execution
AbortSignal fires (timeout or cancellation), cleaning up its own timer and
listener. Previously a cancelled/timed-out run could overshoot by up to the
heartbeat cap (~10s) before the loop re-checked its budget; now it wakes
within a tick. The cap remains for heartbeat renewal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(db): disable statement_timeout for migrations
* fix(ci): route migration workflow through guarded migrate.ts
* feat(hosted-keys): add Hunter.io and People Data Labs hosted key support
* improvement(api): use HttpError base class for typed-error status mapping
* chore(api): drop unreachable runtime check after HttpError instanceof guard
* feat: add LiteLLM as AI gateway provider
* fix: add litellm to attachments, provider store, utils, and block guards
* fix: add frontend model discovery pipeline for litellm provider
Add API route, contract, query hook case, and ProviderModelsLoader
entry so litellm models are fetched and synced to the store on
workspace load, matching the vllm/ollama/openrouter/fireworks pattern.
Also fixes defaultModel to empty string and adds litellm/ prefix
early-return in blocks/utils.ts (reviewer feedback).
* fix: remove azureEndpoint fallback from LiteLLM provider
Copy-paste artifact from vLLM provider. LiteLLM should only use
LITELLM_BASE_URL, not fall back to azureEndpoint which could cause
requests to be routed to the wrong server.
* fix(litellm): close audit gaps from PR #4644
- byok.ts: add litellm branch to getApiKeyWithBYOK so workflow
block execution can resolve the proxy key instead of throwing
"API key is required for litellm ..."
- check-api-validation-contracts.ts: bump route baseline 755 -> 756
to account for the new /api/providers/litellm/models route
- .env.example: document LITELLM_BASE_URL / LITELLM_API_KEY
- copilot edit-workflow validation: include LiteLLM in the list of
user-configured prefixed providers shown to the model
- providers/utils.ts: drop stray optional-chain on providers.litellm
to match the vllm pattern
- lint: apply biome formatting fixes (multi-line if, SVG path,
multi-line DYNAMIC_MODEL_PROVIDERS)
* fix(litellm): final parity gaps from second audit
- blocks/utils.ts getModelOptions(): include litellm models in the
combined model dropdown — was previously dropping any
proxy-discovered models from the agent block model picker.
- get-blocks-metadata-tool.ts mockProvidersState: add litellm bucket
so the server-side copilot block-metadata fallback can render
model options when the providers store is not initialized.
- blocks/utils.test.ts: add litellm to mock providers state (initial
+ beforeEach reset) and add a parallel store-bucket guard test
mirroring the vLLM case.
- providers/utils.test.ts: add parallel getApiKey test for litellm.
* feat(litellm): use official LiteLLM brand icon and color
- icons.tsx: replace the placeholder letterform with the official
LiteLLM brand mark embedded as a PNG data URI in an SVG image.
- models.ts: set color: #040229 on the litellm provider definition
to match the brand background.
* chore(litellm): validate /v1/models response with shared schema in initialize()
Match the API route handler — both code paths now run the same
vllmUpstreamResponseSchema.parse() over the upstream /v1/models
JSON instead of a raw type-cast, so malformed upstream payloads
surface a descriptive ZodError instead of a downstream TypeError.
Addresses Greptile review feedback on PR #4739.
---------
Co-authored-by: RheagalFire <arishalam121@gmail.com>
* feat(zoom): add KB connector for cloud recording transcripts, fix refresh token rotation
* fix(zoom): trim maxRecordings within page, relax VTT cue-id parsing
* fix(zoom): widen incremental sync overlap to 30 days for late transcripts
* fix(files): never dedup external URL fetches by path filename
External URL fetches in the file parse route were keyed on the path
filename, so two distinct URLs whose paths ended in `image.png` (e.g.
every Slack clipboard paste) collided in the workspace cache and
returned stale bytes from a prior fetch.
Extracts the fetch + save flow into `fetchExternalUrlToWorkspace` so
the broken dedup pattern can't be reintroduced. The helper always
downloads; `uploadWorkspaceFile` handles name collisions on save by
suffixing (`image.png` -> `image (1).png` -> ...).
* fix(files): distinguish non-member from read-only in workspace-save skip log
Splits the workspace-save skip branch so non-members (permission === null)
log 'user is not a workspace member' instead of the misleading 'lacks
write permission'. Adds a test covering the null case so the relaxed
behavior (vs. the prior route's hard 'File not found') is explicit.
* improvement(kb-connectors): align connector UI surfaces and strip redundant description suffix
- Fix icon colors to use --text-icon token across connector cards and modal
- Switch Reconnect/Update access buttons from active to primary variant
- Lift search box surface from --surface-2 to --surface-3 so it's visible against modal bg
- Replace raw <button> elements with emcn Button in base.tsx
- Shrink Connected Sources modal from lg to md
- Strip " to/into/from your knowledge base" from all 27 connector descriptions to prevent overflow
* fix(resource): replace raw pagination button with emcn Button
* fix(resource): prevent permission-gated breadcrumb items from flashing on load
* fix(resource): self-remove keydown listener on Escape and include session loading in isLoading guard