* fix(selectors): fetch all pages for paginated dropdown list routes
Dropdown selectors fetched only the first page of paginated provider
APIs, silently hiding results past page one. Add bounded server-side
draining to the list routes across Microsoft Graph, Google, Notion,
Atlassian, Linear, AWS CloudWatch, and offset/token REST APIs, plus a
shared client-side drain cap in the selector hook. Response shapes,
stored values, and tool execution are unchanged; CloudWatch list tools
still honor a caller-supplied limit. Also fixes the Word file picker
that was searching for .xlsx files.
* fix(selectors): harden JSM and Monday pagination draining
- JSM service-desk/request-type drains advance `start` by the actual row
count returned (not the fixed page size) and stop on an empty page, so a
short non-final page can't skip items.
- Monday boards drain now checks `response.ok` per page, surfacing a
mid-drain HTTP failure instead of treating it as an empty final page and
returning a partial 200.
* docs(selectors): clarify JSM drain advances start by actual row count
The offset-advancement fix (advance `start` by the rows returned, not the
fixed page size) landed in 7b19788a8; update the TSDoc to match so it no
longer reads as advancing by `limit`.
* fix(selectors): drain fetchPage in direct fetchList callers
Making `fetchList` optional left three direct callers (outside the
useSelectorOptions hook) calling it unguarded, which broke the build's
type check. Route them through a shared `loadAllSelectorOptions` helper
that uses `fetchList` when present and otherwise drains `fetchPage`.
This also prevents a regression: `confluence.spaces` / `knowledge.documents`
now paginate via `fetchPage` only, and these callers (search/replace,
value resolution) would otherwise have silently returned no options.
* chore(selectors): rename MAX_PAGE_PAGES to MAX_NOTION_PAGES for readability
* feat(google-sheets): add row filtering to read with numeric operators
Adds client-side row filtering to the Google Sheets read (v2) operation.
Filter the returned rows by a header column using text operators
(contains, not_contains, exact, not_equals, starts_with, ends_with) and
numeric/ordering operators (gt, gte, lt, lte). Filtering lives in a pure,
unit-tested helper (filterSheetRows) and runs over the fetched read range;
an optional `filter` output reports whether the column was found and how
many rows matched.
Also hardens the surrounding tools:
- trim spreadsheetId in write/update/append URL builders (matches read)
- URL-encode the v1 read default range
- expose valueInputOption for the update operation in the block
Backwards compatible: with no filter requested, read output is byte-
identical and the `filter` field is omitted. The filterMatchType union is
widened additively (4 -> 10 values).
* fix(google-sheets): correct filter metadata for missing column and header-only sheets
- matchedRows is now 0 (not totalRows) when the filter column is not found,
so it no longer contradicts applied=false / columnFound=false
- columnFound now reflects an actual header lookup for empty/header-only
sheets instead of being hardcoded true
- add tests covering header-only and empty sheets with present/absent columns
* fix(files): don't reject external URLs containing '..' in file parse validation
The file block's file_fetch operation rejected any external URL whose path
contained '..' (e.g. Slack files-pri slugs with a literal '...') with
'Access denied: path traversal detected'. Traversal checks only apply to
local paths — external http(s) URLs are fetched with SSRF protection
downstream and are never resolved against the filesystem, so they now
short-circuit as valid. Internal /api/files/serve/ URLs keep full traversal
protection.
* test(files): fix external-URL assertion to handle undefined error
* test(files): assert success explicitly in external-URL traversal test
* fix(files): keep traversal protection for https URLs matching internal serve paths
Hunter's company dataset returns null industry/foundedYear for many large companies (verified against the live API for Microsoft, Amazon, Google), so under the first-non-empty-wins cascade those columns appeared inconsistently across rows. Limit company-info outputs to employee count and description — the fields Hunter and PDL both reliably return — so every row is consistent. employeeCount is a string so Hunter's range bucket and PDL's exact count share the column.
* fix(search-replace): don't auto-navigate when content edits invalidate the active match
* fix(search-replace): clear afterReplaceIndexRef on apply failure and zero matches
* fix(search-replace): remove duplicate setActiveSearchTarget(null) on close
* fix(search-replace): move afterReplaceIndexRef write inside handleApply past the guard
* fix(search-replace): auto-navigate when hydration resolves with no prior active match
* chore(search-replace): remove inline comments
* fix(search-replace): revert !activeMatchId guard that caused immediate re-navigation after deselect
* improvement(integrations): validate and expand devin, cursor, and greptile
- devin: fix missing org_id path segment on all session endpoints, add 7 session sub-resource tools (list messages/attachments, get/append/replace tags, archive, terminate), pagination, and is_archived output
- cursor: add get_api_key_info, list_models, list_repositories tools
- greptile: align block and docs
- normalize array outputs to default [] and tighten types
* refactor(cursor): simplify list_repositories v2 array normalization
Collapse the redundant `?? []` + `Array.isArray` double-guard into a
single Array.isArray check, per PR review feedback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(devin): scope session-tag mapping to tag ops and normalize array tag inputs
- Only map sessionTags into the tools tags param for append/replace operations,
preventing stale sessionTags state from clobbering create_session tags
- Fall back to a wired tags value when sessionTags is empty for tag operations
- Normalize tag inputs (string or wired string[]) via normalizeTags so array
values from other blocks no longer throw on .split
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(cursor): restore base64 file data in legacy download_artifact metadata
The legacy CursorBlock exposes only content + metadata (no v2 file
output), so metadata.data was the only way legacy-block workflows could
access downloaded artifact bytes. Restore the base64 data field and
document it in the outputs/type instead of dropping it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(devin): coerce terminateArchive to archive flag for boolean-wired input
* docs(integrations): regenerate tool docs for new devin and cursor operations
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF
* test(security): use real isPrivateOrReservedIP and cover IPv6 edge cases
* fix(security): harden KB file access, SSO domain registration, webhook path isolation, env secrets, and CSV export
* fix(sso): scope domain conflict query with indexed lower(domain) filter
Address PR review: avoid a full-table scan on every SSO provider
registration by filtering candidate rows in SQL with
lower(domain) = <normalized>, keeping the in-memory ownership check.
Also tighten the normalizeSSODomain TSDoc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: condense env route security comments
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* icons update
* chore(security): tighten inline comments in CSV export and KB file authorization
Condense verbose comment blocks to concise TSDoc/single-line form; no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): validate internal serve origin in KB file authorization
Replace the bypassable isInternalFileUrl substring check in resolveInternalKbKey
with an origin allow-list (base URL, internal API base URL, TRUSTED_ORIGINS).
A crafted external host whose path is /api/files/serve/<victim-key> no longer
resolves to the victim key. Relative same-origin URLs are unaffected.
* style(sso): use idiomatic sql lower() comparison for domain conflict query
Match the repo's prevailing `sql`lower(col) = value`` idiom for the
case-insensitive SSO domain conflict lookup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): align workspace env admin gate with hasWorkspaceAdminAccess
Use the same admin check the secrets UI uses (owner, admin permission, or
org-admin) so owners and org-admins are not wrongly denied their own decrypted
workspace secrets, while read-only members remain restricted to names only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(sso): rely on lower(domain) match for conflict detection, drop dead in-memory recheck
Address PR review: the SQL `lower(domain) = <normalized>` predicate already
excludes rows that the in-memory `normalizeSSODomain(...) === domain` recheck
claimed to catch, making that recheck dead/misleading code. Match on the
canonical lower-cased domain and filter purely by ownership. Malformed legacy
values (wildcards, schemes, ports) never match an email domain at sign-in, so
excluding them is not a gap. Test DB mock now applies the lower() predicate so
the casing-variant case is genuinely exercised.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): scope webhook deploy path conflict to active webhooks
findConflictingWebhookPathOwner omitted the isActive filter that the
runtime dispatcher (findAllWebhooksForPath) applies, so an inactive but
non-archived webhook from another workflow (e.g. after undeploy or
failure auto-disable) would permanently block any new deployment on that
path even though it never receives deliveries. Align the guard with the
runtime isActive + archivedAt filter; the earliest-owner runtime check
remains the authoritative cross-tenant protection. Also trims verbose
TSDoc on the webhook path-isolation helpers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): exclude archived workflows from webhook deploy path conflict
findConflictingWebhookPathOwner now joins workflow and filters
isNull(workflow.archivedAt), matching the runtime dispatcher
(findAllWebhooksForPath). A webhook on an archived workflow can never
receive deliveries at runtime, so it must not block legitimate path reuse
with a 409.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): anchor KB file ownership to earliest document in any state
A KB file's owner is now the earliest document referencing its key regardless of
state (active/archived/deleted/excluded); access is granted only when that owning
document is still active. Closes the residual where an attacker could plant an
active document to claim a file whose original document was archived or deleted.
* updated greptile icon
* revert(security): drop KB file authorization changes
Reverts the knowledge-base file-access work (origin-pinning / owner-pinning /
origin allow-list in verifyKBFileAccess) and its test. The other hardening fixes
(SSO domain registration, webhook path isolation, workspace env secrets, CSV
export) are unchanged. apps/sim/app/api/files/authorization.ts is restored to its
origin/staging baseline.
* fix(sso): treat caller's own user-scoped provider as owned during conflict check
Self-hosters often register SSO user-scoped via the CLI script (no
SSO_ORGANIZATION_ID). If they later enable organizations and reconfigure the
same domain org-scoped through the UI, the conflict check previously treated
their own user-scoped row as another tenant's and returned a misleading 409.
Recognize the caller's own user-scoped provider as owned so that migration is
allowed, while still blocking another user's or another org's domain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* revert(security): remove workspace-env admin gate
Defer to a credential-based access model (separate change). Restores
GET /api/workspaces/[id]/environment to main behavior and removes the test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(security): consolidate webhook path-collision check into one helper
Extract findConflictingWebhookPathOwner to lib/webhooks/utils.server.ts as
the single source of truth for cross-tenant path-collision detection, used by
both webhook creation paths (deploy sync and the manual /api/webhooks route).
This also repairs two latent issues in the manual route's previous inline
check, which queried with limit(1) and only webhook.archivedAt:
- limit(1) inspected one arbitrary row, so a same-workflow row could mask a
foreign collision (false negative). The shared helper scans all matching
rows.
- It omitted isActive/workflow.archivedAt, so inactive or archived-workflow
webhooks (which never receive deliveries) permanently blocked path reuse.
The helper mirrors the runtime dispatcher's filter.
Same-workflow webhook reuse for upsert is now a separate, explicit lookup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Make workflow description nullable
* fix(tables): serialize schema mutations to prevent parallel column clobber
* fix(tables): load workflow outside schema lock; use DbOrTx for getTableById
* fix(tables): scale idle timeout in updateColumnType to avoid aborting large type changes
* fix(tables): skip stale remap types when workflowId changes concurrently
* fix(tables): scale idle timeout in updateColumnConstraints for large tables
* fix(icons): repair broken integration icon rendering
Two distinct bugs left integration icons broken on the /integrations page
(visible at 32-40px, hidden at the toolbar's 16px):
1. Corrupted SVG paths (Notion, Greptile, Granola, Calendly, Grafana, Bedrock):
over-minified data dropped elliptical-arc flag digits (e.g. `A1 1 0 5.9 7`
instead of `A1 1 0 0 0 5.9 7`); Granola's cubic stream was truncated. Browsers
abort path parsing at the first invalid arc flag, so each rendered as a fragment
or blank. Replaced with correct path data from canonical sources, preserving each
icon's existing fill/gradient and bgColor.
2. Invisible glyph (Bright Data): its icon uses fill='currentColor' but bgColor was
'#FFFFFF', and every surface forces text-white on the glyph - white-on-white.
Changed bgColor to Bright Data's brand blue (#3d7ffc) so the white glyph reads,
matching the white-glyph-on-brand-chip convention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(icons): restore Calendly dual-tone brand colors
Addresses review feedback: the previous fix replaced the broken Calendly icon
with a monochrome #006BFF path, dropping the cyan #0ae8f0 accent from the
original dual-tone mark. Restored the two-tone logo (blue + cyan) using clean,
valid path data, cropped to a tight square viewBox so it fills the chip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(icons): enlarge icons, fix Zoom contrast and Quiver chip
- Zoom: glyph was blue-on-blue (#0B5CFF on #2D8CFF chip); switched to
currentColor so it renders as a white glyph on the blue chip.
- Quiver: chip bgColor #000000 -> #FFFFFF to match the icon's near-white box,
and enlarged the mark slightly (viewBox crop).
- Enlarged (tightened viewBox, verified no clipping): RevenueCat, Prospeo,
Granola, Firecrawl, Enrich.so, and the AWS icons (RDS, DynamoDB, SQS,
CloudFormation, Athena, CloudWatch, SES, Bedrock, S3).
- ZoomInfo left unchanged: it is a full red rounded-square logo that already
fills its frame, so a crop would clip it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(icons): use Bright Data wordmark on white chip; repair Circleback
- Bright Data: replaced the flame glyph with the official two-tone 'bright data'
wordmark (provided asset), centered in a symmetric viewBox. Reverted the chip
bgColor from #3d7ffc to #FFFFFF since the blue wordmark is invisible on a blue
chip (the wordmark is designed for a light background).
- Circleback: a minifier had rounded the pattern's image scale to scale(0),
collapsing the embedded logo to zero size (invisible). Restored the correct
scale (1/280 = 0.00357142857) so the C. mark renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(docs): sync Quiver block color card to white chip
Reflects the Quiver bgColor change (#000000 -> #FFFFFF) in the docs block info card.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(icons): enlarge AWS/Cloudflare/Dagster icons, fully white Zoom
- Enlarged (tighter viewBox, render-verified, no clipping): Cloudflare, Dagster,
and the red AWS icons AWS IAM, Identity Center, Secrets Manager, SES, STS.
Identity Center was anomalously small (filled ~32% of its frame); the group is
now sized consistently (~80% fill).
- Zoom: the camera lens triangle was still #0B5CFF (blue-on-blue); switched it to
currentColor so the whole camera renders white on the blue chip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(wiza): consolidate individual reveal into a single operation
Merges the separate Start/Get Individual Reveal operations into one Individual
Reveal operation in the Wiza docs and integrations data (operationCount 5 -> 4).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(icons): size remaining AWS icons to match the set (~80% fill)
Bring RDS, DynamoDB, SQS, CloudFormation, Athena, CloudWatch and S3 up to the
same ~80% fill as the AWS IAM/Identity Center/Secrets Manager/SES/STS group, so
all AWS icons are visually consistent. Bedrock left as-is (already ~92% fill).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(icons): use Bright Data flame mark, enlarge ZoomInfo
- Bright Data: the full 'bright data' wordmark was illegible at chip size.
Replaced with just the flame-'i' brand mark (blue #4280f6 on the white chip),
centered.
- ZoomInfo: cropped the viewBox toward the white 'Zi' so it's larger; the red
rounded-square background still fills the chip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(icons): enlarge CrowdStrike icon
The falcon mark sat small in its chip because the icon used a wide 768x500
viewBox (letterboxed in the square chip). Switched to a square viewBox centered
on the mark so it fills ~80%, consistent with the other icons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tables): right-align run/stop in the embedded table toolbar
Add a right-aligned `trailing` slot to ResourceOptionsBar and move the embedded
mothership table's run/stop control into it, so Filter + Sort stay left-aligned
and run/stop sits opposite on the right. No-op for the search-bearing consumers
(logs, resource list), which don't pass `trailing`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tables): workflow-output cells format values like normal cells
Workflow-output columns short-circuited in resolveCellRender and rendered their
value as plain text, so a sim-resource URL / external URL / JSON / date produced
by a workflow never got the chip, favicon link, or typed formatting a normal
cell gets. Factor value formatting into a shared `resolveValueKind` helper used
by both the workflow-value branch and the plain-cell branch; the workflow branch
keeps the typewriter reveal for plain streaming text via a `typewriter` flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tables): detect resource/URL links on workflow output regardless of column type
Workflow output columns default to `json` (columnTypeForLeaf), so routing their
values through the type-based formatter (a) gated chip/URL promotion behind
`column.type === 'string'` — a URL produced by a json-typed output never became
a chip — and (b) JSON.stringify'd plain string values, adding quotes and losing
the typewriter reveal. Detect links (sim-resource chip / favicon URL) on the
value string directly for workflow outputs, falling back to the plain `value`
kind; plain cells keep the type-based formatting. Addresses Greptile P2 on #4806.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(copilot): read chat transcripts from copilot_messages, not JSONB
Flip user-facing chat reads from the legacy copilot_chats.messages JSONB
array (5.7GB, 99% TOAST) to the normalized copilot_messages table via a
new loadCopilotChatMessages helper ordered by seq NULLS LAST, created_at,
id — the verified canonical order. Both chat-detail getters
(getAccessibleCopilotChat, getAccessibleCopilotChatWithMessages) now drop
the messages column from their metadata select (no more whole-array
detoast on every load) and assemble the transcript from the table after
authorization. This cascades to the copilot + mothership GET endpoints
and to resolveOrCreateChat's conversationHistory (the LLM payload).
The normalize/effective-transcript pipeline is source-agnostic
(copilot_messages.content == a JSONB array element), so transcripts are
byte-identical. Dual-write and the JSONB column stay in place as the
internal-logic source and fallback; removing JSONB writes is a later step.
Prod integrity verified before cutover: 0 messages missing, 0 NULL-seq,
0 dup keys/seq, 0 orphans, order-parity vs JSONB = 0 mismatches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(copilot): cover auth-deny on a found row skips the messages query
Address PR review: exercise the `if (!authorized) return null` contract —
when the chat row exists but authorization fails, the getter returns null
and never issues the copilot_messages read.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Removed because (workflow_id, block_id) is a left-prefix of idx_webhook_on_workflow_id_block_id_updated_at_desc, which fully covers it. The dropped index was non-unique and enforced no constraint.
* feat(slack): add install + privacy section to integration landing page
Adds a hand-authored, slug-keyed landing-content module (separate from the generated integrations.json so it survives regeneration) and renders an install walkthrough + privacy-policy link on integration pages when present. Also refreshes generated docs (data-enrichment entry, icon mappings, tool mdx).
* fix(landing): render privacy section independently, align CTA analytics label
* docs(landing): clarify the Slack install button is behind sign-in
* refactor(landing): bake integration landing content into generated json via docs-gen
Moves landing content (install walkthrough + privacy) out of a render-time augment and into the generation pipeline: generate-docs reads the pure-data content map and writes landingContent into integrations.json, so the page reads a single source (integration.landingContent). Canonical types live in integrations/data/types.ts.
* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza
Add hosted-key support across all credit-consuming Findymail, Prospeo, and Wiza operations so Sim provides the key when a workspace has not brought its own. Register the three BYOK providers, consolidate Wiza's two-step reveal into a single polling wiza_individual_reveal op, and hide the API key field on hosted Sim for hosted operations.
* fix(integrations): harden Wiza reveal polling, soften enrichment getCost guards
Address Greptile + Cursor Bugbot review on #4777: return explicit failures from the Wiza individual_reveal poller instead of throwing (thrown errors were swallowed into a false queued success), short-circuit when the initial reveal is already terminal, tolerate transient 5xx/429 during polling, and return 0 (not throw) from Findymail getCost when the contacts/employees array is absent.
* chore(integrations): biome formatting after wiza merge resolution
* fix(wiza): type isTerminalReveal param structurally for next build typecheck
* feat(enrichments): add Findymail, Prospeo, Wiza to work-email waterfall
* feat(enrichments): add Wiza + Prospeo phone reveal to phone-number waterfall
* feat(enrichments): opportunistic identifiers + LinkedIn URL input across work-email & phone cascades
* improvement(providers): harden OpenAI-compatible providers + add tests
* fix(vllm): let tool-loop errors propagate instead of returning silent partial success
* fix(litellm): force tool_choice 'none' on final structured-output call
The deferred final call used tool_choice 'auto', so the model could emit
another tool_calls round instead of the structured answer, leaving content
stale. Use 'none' (matching vLLM/Fireworks) on both the streaming and
non-streaming final calls so the model must return the structured response.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(providers/ollama): drop tools from post-tool streaming call
Ollama ignores tool_choice (not in its supported fields), so vLLM/Fireworks'
tool_choice:'none' guard is a no-op here. Omit tools from the final streaming
payload instead so the summarization turn can't emit dropped tool calls.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(litellm): spread payload into deferred final call so reasoning_effort carries over
The non-streaming deferred finalPayload hand-picked fields and dropped
reasoning_effort (and any future payload field), diverging from the streaming
path which spreads ...payload. Spread payload here too for consistency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(providers/ollama): restore enrichment TSDoc block
Keeps parity with sibling Chat Completions providers (cerebras/mistral/xai).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(fireworks): restore TSDoc on utils helpers
Restore the TSDoc blocks on supportsNativeStructuredOutputs,
createReadableStreamFromOpenAIStream, and checkForForcedToolUsage —
TSDoc is the codebase documentation standard and should not have been
stripped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(litellm): remove inline rationale comments (codebase uses TSDoc)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(providers/ollama): drop orphaned enrichment TSDoc
The block documented a function that now lives in trace-enrichment.ts, so it
documents nothing in this file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* improvement(logs): obj storage backed tracespans
* fix storage write context
* fix tests
* address comments
* address comments
* chore(db): remove migration 0219 to regenerate after staging merge
Drops the 0219_robust_shard SQL, its snapshot, and the journal entry so the
trace-spans/cost schema migration can be regenerated on top of the latest
staging migration chain (avoids a number collision with staging's migrations).
Co-authored-by: Cursor <cursoragent@cursor.com>
* improvement(billing): accurate per-member usage via shared ledger helper
Per-member/per-user usage in the org-member routes now adds the usage_log
ledger to the currentPeriodCost baseline (which is no longer incremented),
via a shared getOrgMemberLedgerByUser helper to avoid repeating the
subscription→period→ledger lookup across the admin and member-facing routes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* regen migrations
* update migration
* address comments
* more code cleanup
* incorrect type cast
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(access-control): add per-model denylist to permission groups
* fix(access-control): default deniedModels in response schema, hide blocked badge on disabled rows, trim comments
* chore(access-control): reuse canonical DYNAMIC_MODEL_PROVIDERS from providers/models
* 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