The live SSE path counts pending pre-stamps (isExecInFlight) but
countRunningCells excluded them, so each per-window refetch reset the badge
from ~20 to 0 (visible flicker now that the control stays shown via
hasActiveDispatch). Include unclaimed pre-stamps in byRowId when a dispatch is
active; keep excluding them only in the no-dispatch fallback (orphan case).
Adds an optional aiDisclaimer field to the integration landing content (types + data), rendered as an independent 'AI-generated content' section and baked into integrations.json via docs-gen. Populates Slack to satisfy Slack's AI-components guideline (disclaimer on the landing page).
* fix(tables): reliable stop-all, accurate "X running", and rate/usage gating for cell runs
Stop-all:
- Make the cancellation guard status-based (not executionId-scoped) so a
`cancelled` tombstone stamped while a cell is still a dispatcher pre-stamp
(null executionId) keeps the cell dead — fixes function-execute cells that
resurrected after Stop all. Consolidated into shared isExecCancelled /
isExecCancelledAfter predicates in deps.ts, reused by the in-memory guard,
the SQL guard, the dispatcher tombstone filter, the worker, and resume.
- Add an explicit pre-execution cancellation read so a cell that dequeues
after Stop all (e.g. from the trigger.dev queue) never runs.
- Resume worker aborts a cancelled paused/awaiting cell before resuming;
cancelWorkflowGroupRuns marks paused executions cancelling.
"X running":
- Emit a dispatch SSE at dispatch start so auto-fired/capped runs surface
immediately; show the control whenever a dispatch is active.
Checkbox dependency:
- Treat boolean `false` as an unmet dependency so unchecking never reruns
dependents — only checking does. deriveExecClearsForDataPatch no longer
re-arms a downstream group whose deps are unmet after the patch.
Rate / usage gating:
- Route table cell execution through preprocessExecution (billing actor =
workspace billed account, usage limit, per-plan timeout), keeping draft.
- Rate limit: pace & retry per cell (async counter) so rows aren't skipped.
- Usage limit: halt the dispatch without marking cells and emit a
usageLimitReached event; the client shows an Upgrade prompt that routes to
subscription settings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tables): dedupe usage-limit event + release rate-limited cells on cancel
Addresses PR review:
- Usage limit: only the cell that transitions the dispatch active→complete
(via completeDispatchIfActive) emits usageLimitReached, so concurrent cells
don't fire up to 20 identical "upgrade" toasts.
- Rate-limit retry: re-check the cancelled tombstone after each sleep so a
Stop All mid-wait releases the concurrency slot promptly (signal never fires
on the trigger.dev backend).
* fix(tables): jitter rate-limit retry backoff to avoid thundering herd
Passing the bucket's shared resetAt as retryAfterMs made backoffWithJitter
return a fixed clamped value (no jitter, attempt ignored), so all concurrent
cells retried in lockstep. Pass null to get jittered exponential backoff.
* fix(tables): unstick cells + resync counter on usage-limit halt
Addresses PR review:
- Clear each blocked cell's pre-stamp on a 402 so it reverts to un-run instead
of being stuck "Queued" (no error/cancelled badge); covers auto-fire cells
with no owning dispatch.
- Client re-syncs run-state counts and refetches rows on usageLimitReached so
the stale "X running" / Stop-all control clears and queued cells drop.
- Make usageLimitReached.dispatchId optional; client only touches the dispatch
overlay when present.
* fix(tables): don't emit stale dispatching event after a mid-window halt
If a cell halts the dispatch mid-window (usage limit), re-read the dispatch
status after the batch and bail instead of emitting a per-window 'dispatching'
event that would arrive after the client dropped the dispatch and re-add it
(flickering 'X running' back).
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(tables): enforce plan limits in mothership user_table tool
* improvement(tables): truncate over-limit CSV imports to the plan cap instead of rejecting
* fix(tables): log rollback failures and surface a clear reason on failed CSV import
* fix(deps): upgrade vitest to ^4.1.0 to patch critical Vitest UI advisory (GHSA-5xrq-8626-4rwp)
- Bump vitest and @vitest/coverage-v8 to ^4.1.0 across all workspaces (only patched release for the critical 'Vitest UI server arbitrary file read/execute' advisory; no 3.x backport exists)
- Widen @sim/testing peer range to ^3.0.0 || ^4.0.0
- Migrate constructor mocks to class expressions: vitest 4 uses Reflect.construct for mocks invoked with new, and arrow/function implementations are not constructable (function expressions also get reverted to arrows by biome's useArrowFunction)
- Remove deprecated test.poolOptions from apps/sim/vitest.config.ts (options are now top-level in vitest 4)
* fix(deps): exclude vulnerable vitest 4.0.x from @sim/testing peer range
Tighten the v4 arm of the peer range to >=4.1.0 <5.0.0 so the peer
requirement cannot be satisfied by the unpatched 4.0.x builds that
GHSA-5xrq-8626-4rwp affects.
* fix(testing): make vitest 4 constructor mocks type-check cleanly
- logging-session & mcp-oauth mocks: a class passed to mockImplementation has
a construct signature that isn't assignable to its (...args) => any parameter,
failing tsc. Use named function declarations instead (constructable via
Reflect.construct, assignable to mockImplementation, and not rewritten to
arrows by biome's useArrowFunction).
- database.mock.ts: vitest 4's generic vi.fn typings no longer break the
self-referential cycle on the transaction callback's tx param; loosen tx and
annotate the callback's return type to resolve the implicit-any errors.
* test(isolated-vm): de-flake queue-capacity scheduler tests
The 'queue is full' and 'per-owner queued limit' tests relied on
'await sleep(1)' to assume the first request had reached the queue before
submitting the overflow request. The first request only enqueues after an
async spawn-failure chain (acquireWorker -> spawn exit -> resolve null ->
enqueue), which isn't guaranteed within 1ms under CI load — the overflow
request then found an empty queue and hit the 200ms queue-wait timeout
instead of the capacity rejection.
Replace the wall-clock barrier with a deterministic, event-driven one: hold
the single global concurrency slot (IVM_MAX_CONCURRENT=1) with an active
worker and await an explicit 'dispatched' signal (fired when the worker
receives its execute message, after the scheduler counts it active). The
follow-up requests then deterministically hit the synchronous enqueue path.
Also drops the queue-wait timeout from 200ms to 50ms, so the tests run faster.
Two bugs in the CSV-import path:
- addTableColumnsWithTx rebuilt the schema with only `columns`, dropping
`workflowGroups` (and any other top-level schema fields). Importing CSV
into a table that has workflow groups erased the group config. Spread
`table.schema` first so siblings survive.
- batchInsertRowsWithTx fired fireTableTrigger and scheduleRunsForRows from
inside the caller's transaction. Both read through the global db
connection, so they could run before the inserts committed and see no
rows. Extracted the dispatch into dispatchAfterBatchInsert; non-tx
wrapper fires it after `db.transaction(...)` resolves, and the CSV
import route does the same after its tx.
* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers
* fix(providers): guard Ollama streaming fast-path with hasActiveTools
Match Together/Baseten/Fireworks: when tools are supplied but all are
filtered out (usageControl 'none'), take the single streaming call instead
of an extra non-streaming round-trip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(providers): filter non-chat model types from Together model list
* refactor(providers): dedupe Ollama Cloud upstream schema
ollamaCloudUpstreamResponseSchema was byte-for-byte identical to
ollamaUpstreamResponseSchema (both /api/tags endpoints return the same
{ models: [{ name }] } shape). Drop the duplicate and reuse the shared schema.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Opening a Mothership task could take many seconds because a single persisted
assistant message in copilot_messages.content can reach hundreds of MB, almost
entirely inside contentBlocks[].toolCall.result.output (e.g. a get_workflow_logs
or run_workflow result). The DB query is ~2ms; the cost is detoasting that
payload, shipping it to the browser, and parsing it.
These outputs are dead weight on the Sim side: they are never rendered (the
thread shows only tool name/title/status) and never replayed to the model (the
upstream copilot service owns conversation memory). So drop result.output before
it is persisted, keeping result.success/error plus the tool metadata.
- add stripToolResultOutput() in persisted-message.ts
- apply it in messages-store toRow (covers every write path) and in
loadCopilotChatMessages (existing rows render fast on read)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add does-not-contain ($ncontains), starts-with ($startsWith), ends-with
($endsWith), not-in-array ($nin, previously executed server-side but unexposed
in the UI), and is-empty/is-not-empty ($empty) filter operators end-to-end —
SQL builder, condition types, query-builder converters/constants, the filter
UI, the Table tools/block descriptions, and docs.
Also fix correctness bugs in the filter builder surfaced by the wider operator
set:
- Same-column AND rules (e.g. age > 18 AND age < 65, or name startsWith 'A'
AND name endsWith 'Z') silently overwrote each other because the AND group
was keyed by column name. They now merge into one operator object, which
also makes Filter -> rules -> Filter round-trip losslessly for multi-operator
columns.
- $nin values were not split into an array like $in, and textual-match values
like "123" were numeric-coerced (breaking the ILIKE path).
- A non-boolean $empty operand from the raw API silently inverted the check; it
now coerces 'true'/'false' strings and otherwise returns a 400.
Stop writing/reading the legacy copilot_chats.messages JSONB column now that
reads are cut over to copilot_messages. Make appendCopilotChatMessages the
primary write (throws on failure instead of swallowing), repoint peripheral
readers (workspace VFS, chat cleanup, data drains, fork, superuser import) to
copilot_messages, and persist the assistant turn inside finalizeAssistantTurn's
transaction so it commits atomically with the stream-marker clear. The column
itself is dropped in a follow-up migration after this bakes.
* 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>