Commit Graph
131 Commits
Author SHA1 Message Date
Waleed 3143a15dde feat(uptimerobot): add UptimeRobot v3 integration (#5229)
* feat(uptimerobot): add UptimeRobot v3 integration

- 24 tools across monitors, incidents, maintenance windows, alert contacts,
  public status pages, and account (UptimeRobot v3 REST API, Bearer auth)
- Block with operation-scoped subBlocks, status-page logo/icon file uploads
  via internal multipart routes, and BlockMeta templates + skills
- Registered tools/block, added icon, generated docs
- Updated add-integration/add-block/validate-integration docs links to /integrations

* fix(uptimerobot): address review — heartbeat URL, file/JSON edge cases

- Block: URL is not required for HEARTBEAT monitors (no URL)
- buildMonitorBody: throw on malformed assignedAlertContacts/customHttpHeaders
  JSON instead of silently dropping the field
- PSP route: error (400) when a supplied logo/icon cannot be resolved to a
  stored file instead of silently omitting the image
- PSP route: guard success-path JSON parsing; return a controlled 502 on a
  non-JSON provider response instead of an uncaught 500

* fix(uptimerobot): spec-conformance audit fixes

- pause/start monitor: send Content-Type: application/json (v3 spec requires it
  on these POSTs even with an empty body)
- update maintenance window: drop autoAddMonitors (not in UpdateMaintenanceWindowDto);
  gate the block field to create only

* fix(uptimerobot): rename monitor timeout param to avoid reserved name

The tool runner treats a top-level `timeout` param as the outbound HTTP-client
timeout (ms), so a monitor check-timeout of e.g. 30s would abort the API call in
30ms. Rename the input to `checkTimeout` (block subBlock, tool params, inputs,
numeric coercion) and map it to the API body's `timeout` key in buildMonitorBody.

* fix(uptimerobot): reject empty/non-object PSP responses

A successful PSP create/update must return the PspDto object; an empty or
non-object body now returns a controlled 502 instead of mapping a phantom
status page (id: 0, empty name, null images) back to the workflow.

* fix(uptimerobot): validate core PSP fields before mapping

Reject successful PSP responses that lack a positive numeric id and non-empty
friendlyName (a {} or metadata envelope) with a controlled 502, instead of
mapping a phantom status page.
2026-06-26 18:38:50 -07:00
Waleed 6260eda226 fix(ssr): harden credential query-key factory + fetchers against the 'use client' stub bug (#5206)
* fix(ssr): move credential query-key factory + fetchers to non-client modules

Preventively closes the same 'use client' SSR client-reference-stub class that
crashed the tables page. Server-evaluated modules (the credential block def, the
workflow-comparison helpers) imported workspaceCredentialKeys /
fetchWorkspaceCredentialList / fetchCredentialSetById from 'use client' hook
modules, where they resolve to client-reference stubs on the server (a future
server call path would throw 'X is not a function').

Extract them into non-client hooks/queries/utils/{credential-keys,
fetch-workspace-credentials,fetch-credential-set}.ts (mirroring folder-keys.ts /
fetch-workflow-envelope.ts) and import from there. No behavior change — these
values were only ever called from browser paths.

* docs+ci: codify the 'use client' server-import rule + add check:client-boundary

Document the Next.js rule that server code can only render a 'use client'
export as a component, never call it (server imports resolve to client-reference
stubs that throw — the tables-page crash). Add the rule to
.claude/rules/sim-queries.md + a cross-ref in sim-architecture.md.

Add scripts/check-client-boundary-imports.ts (wired into CI as check:client-boundary)
that flags any value import from a 'use client' module in a server-evaluated,
non-JSX surface (prefetch / route handler / trigger / block definition), so this
class can't silently recur. Escape hatch: // client-boundary-allow: <reason>.
2026-06-24 18:46:18 -07:00
Theodore Li cff7a49310 feat(file): workspace-scoped inline images + public-share cascade (#5203)
* feat(file): workspace-scoped inline images + public-share cascade

Embedded markdown images now resolve only within the document's workspace,
and public file shares cascade to the images the shared document embeds.

- New /api/workspaces/[id]/files/inline (in-app, workspace-scoped) and
  /api/files/public/[token]/inline (public cascade) routes; the public one
  serves an embed only when it is referenced-by-doc, same-workspace, and
  passes a magic-byte image sniff
- Embed srcs (serve-key and view-id forms) rewrite through one scoped inline
  route; one shared isomorphic parser owns the embed grammar for both the
  frontend renderer and the server doc scan
- Accept wf_ file ids on the view/export routes (were 400ing on .uuid())

* feat(file): add Image command to the markdown editor slash menu

- New /Image slash command uploads an image via a file picker and inserts
  it at the caret (same upload+insert path as paste/drop)
- Inserted src is the workspace serve URL, so it renders in-app and
  cascades to public shares like any other embed
- Per-editor handler wired through slash-command storage (the extension
  set is a shared singleton); only active when the editor is editable

* fix(file): export rewrites all embed forms; cap embedded refs combined

Addresses PR review:
- Markdown export now rewrites the in-app `/workspace/<ws>/files/<id>`
  embed form too (not just `/api/files/view/<id>`), so a bundled asset
  never leaves a broken link in an offline export (Bugbot)
- extractEmbeddedFileRefs bounds total references (keys + ids) to 50
  combined rather than 50 each, matching MAX_EMBEDDED_IMAGES intent
2026-06-24 21:07:39 -04:00
Theodore Li 8f312d299b feat(guardrails): PII redaction via Presidio sidecar (native VIN, per-rule language) (#5174)
* fix(logs): run PII redaction over HTTP and fix Presidio provisioning

- resolve the guardrails venv via candidate paths and fail fast instead of
  silently falling back to system python3 (the misleading "Presidio not
  installed" that broke redaction and the guardrails block in deployed runtimes)
- install the en_core_web_lg spaCy model in setup.sh and app.Dockerfile
- route log redaction through an internal /api/guardrails/mask-batch endpoint
  so Presidio always runs in the app container, including async executions that
  persist inside the trigger.dev runtime

* fix(guardrails): chunk + time-bound internal PII mask requests

- chunk maskPIIBatchViaHttp by count (2000) and bytes (256KB) so large
  executions split across requests and never hit the contract's 100k cap
- add AbortSignal.timeout(45s) per request so a slow/unreachable app container
  aborts and the caller scrubs, instead of hanging the trigger.dev job
- catch maskPIIBatch failures in the route: log and return a structured 500
  (broken venv fails loudly server-side; caller still scrubs, no leak)
- add mask-client tests (order across chunks, count split, non-2xx, empty)

* fix(guardrails): mint internal token per mask request

A single token (5min TTL) could expire mid-batch when a large execution
fans out into many sequential chunk requests; mint one per request instead.

* feat(guardrails): run PII via Presidio sidecars + TS recognizer registry

- replace the per-call python3 subprocess (cold spaCy load every call) with
  two long-lived Presidio sidecars (analyzer + anonymizer) reached over HTTP;
  the app image no longer carries Python/Presidio/venv
- add PRESIDIO_ANALYZER_URL / PRESIDIO_ANONYMIZER_URL
- move VIN out of Python into a TS recognizer (check-digit validated) behind a
  CUSTOM_RECOGNIZERS registry so new custom detectors are one entry; masking is
  handled uniformly by the anonymizer
- drive the guardrails block's PII type picker from the shared pii-entities
  catalog (adds VIN, fixes drift) so block + Data Retention never diverge
- delete validate_pii.py, requirements.txt, setup.sh and the Dockerfile venv step

* fix(guardrails): bound-parallelize mask batch; refresh stale comments

- maskPIIBatch runs per-string sidecar calls with bounded concurrency (8) via
  mapWithConcurrency, so a chunk of many small leaves finishes within the 45s
  request timeout instead of aborting and scrubbing; order + fail-on-error kept
- drop stale comments referencing the deleted Python venv / 30s subprocess timeout

* refactor(guardrails): single Presidio image, native VIN, per-rule redaction language

- collapse the analyzer/anonymizer URLs into one PRESIDIO_URL (combined image
  serves /analyze + /anonymize)
- remove the TS VIN recognizer (vin.ts, recognizers.ts) — VIN is now native +
  multi-language in the image; validate_pii is a thin analyze→anonymize client
- trim KR_RRN/TH_TNIN from the catalog (no Korean/Thai model in the image)
- add per-rule redaction language: PII_LANGUAGES catalog drives the contract enum,
  the Data Retention rule modal, and the guardrails block dropdown; resolver +
  logger thread it through to maskPIIBatch (default en), so non-English entity
  rules (e.g. ES_NIF) actually fire instead of silently no-op'ing under en

* fix(guardrails): correct sidecar port (5001) + README for combined image

The combined Presidio image (docker/pii.Dockerfile) serves /analyze + /anonymize
on a single port 5001 with native VIN + multi-language recognizers. Fix the
PRESIDIO_URL default (was 5002) and rewrite the README, which still described two
stock containers and a TS VIN recognizer.

* fix(guardrails): coerce stored redaction language in the resolver

The persist-path resolver accepted any stored language string, so a stale/invalid
code (e.g. a dropped locale) would reach Presidio and scrub the log even though the
admin UI shows English. Coerce against the supported set via a shared
coercePiiLanguage helper (now reused by the data-retention route too), falling back
to en for unknown values.

* fix(guardrails): rename PRESIDIO_URL env var to PII_URL

Match the infra taskdef, which sets PII_URL on the app container for the
combined Presidio sidecar.
2026-06-23 05:29:01 -04:00
Waleed d8da1e2577 fix(state): align server/client state with best practices (query-key bugs, persist hygiene, useState) (#5166)
* fix(queries): close React Query key/fetch-arg drift cache collisions

Several query hooks fetched with an identifier that was absent from their
queryKey, so distinct fetch args shared one cache entry. Thread the missing
args into the key factories and update all callsites/invalidations.

- organization: useOrganization always fetched the ACTIVE org via
  getFullOrganization() while caching under detail(orgId). Pass orgId through
  to the better-auth call (query.organizationId); active-org behavior unchanged.
- logs: logKeys.detail now keys on (workspaceId, logId) to prevent cross-
  workspace collision; updated useLogDetail, useLogByExecutionId, prefetchLogDetail,
  useCancelExecution optimistic path, and external callsites.
- inbox: inboxKeys.taskList now includes cursor/limit (pagination args were sent
  but omitted from the key); keepPreviousData pagination UX preserved.
- a2a: narrow create/update byWorkflows() invalidation to byWorkflow(ws, wf)
  since their responses reliably carry both ids; delete/publish stay broad.

Not bugs (verified, left unchanged):
- kb/connectors update/delete invalidate knowledgeKeys.detail(kbId), which is a
  prefix of connectorKeys.all(kbId) — connector list/detail are invalidated
  transitively by React Query prefix matching.

Harness: add a key-fetch-arg-drift check to check-react-query-patterns.ts that
flags a camelCase identifier the queryFn forwards into the fetch but is absent
from the queryKey (excludes the requestJson contract arg, PascalCase/SCREAMING
constants, and signal/pageParam machinery). Document the rule in sim-queries.md.
tables.useTable annotated rq-lint-allow (tableId globally unique; workspaceId is
only an authz scope).

* fix(stores): whitelist durable fields in persist partialize

chat/terminal/panel persist configs leaked actions and transient state
into localStorage. Replace the chat full-state spread with an explicit
durable whitelist, and add partialize to terminal and panel (which had
none) so isResizing and _hasHydrated are no longer persisted. Panel keeps
activeTab + panelWidth because the layout.tsx blocking script reads them
from panel-state to set data-panel-active-tab before hydration (SSR
tab-flash prevention).

Harden sim-stores doctrine: persist MUST use an explicit partialize
whitelist; never persist transient flags or _hasHydrated.

* fix(state): model component useState as single source of truth

- edit-knowledge-base-modal: reset fields on closed→open via prevOpenRef
  render idiom instead of mirroring props into state through useEffect
  (a prop change while open no longer clobbers in-progress edits)
- use-verification: collapse contradictory isLoading/isVerified/isInvalidOtp
  booleans into a single status enum + errorMessage; consumer derives flags
- contact-form / demo-request-modal: derive busy/success from the mutation
  object; delete duplicated submitSuccess local state
- sim-hooks.md: add state-shape rule (no props-into-state, status enum,
  derive mutation state)

* fix(verify): clear lingering message on complete OTP (restore parity)

* docs(state): convert inline reset comment to TSDoc

* docs(state): tighten harness rules for accuracy (queryFn forwards, partialize whitelist, mutation-flag caveat)

* fix(verify): block auto-verify while a resend is in flight (restore parity)

* fix(logs): key cancel optimistic detail by route workspaceId (not the log row)
2026-06-21 22:44:14 -07:00
Theodore Li 7349bf403f feat(files): password, email-OTP, and SSO auth for public file shares (#5140)
* feat(files): password, email-OTP, and SSO auth for public file shares

* fix(files): suppress filename in share previews for email/sso, not just password

* fix(files): normalize allow-list emails to lowercase; genericize shared SSO denial message

* fix(security): make isEmailAllowed case-insensitive; normalize email at client gates

* test(security): cover isEmailAllowed case-insensitive matching

* fix(security): bind auth cookie to auth type; password endpoint rejects non-password shares

* chore(db): format generated migration meta

* fix(files): share upsert validation returns 400 not 500; disabling always succeeds

* feat(access-control): org admins can restrict allowed file-share auth types
2026-06-19 18:53:12 -04:00
Siddharth Ganesan 5925651cbc feat(vfs): add lazy vfs + remove dynamic fields for prompt caching hits (#5138)
* feat(vfs): add lazy vfs + remove dynamic fields for prompt caching hits

* feat(vfs): send typed workspace snapshot for append-only deltas

Build the workspace inventory from the primary db (fixes replica-lag staleness)
and emit it as a typed VfsSnapshotV1 `vfs` payload alongside the markdown, so the
mothership can diff it into append-only baseline/delta messages. Generate the TS
contract mirror from the Go-owned JSON schema (sync-vfs-snapshot-contract) and
sort connector types so diffs stay byte-stable.

* fix(lint): fix lint

* fix(vfs): forward the typed snapshot through the branch payload builder

The branch buildPayload implementations hand-list the params they pass to
buildCopilotRequestPayload and forwarded workspaceContext but dropped vfs, so the
typed snapshot never reached the Go request (req.Vfs was always nil and the
append-only delta path never engaged). Forward vfs in both the workflow and
workspace branches, and add a regression guard asserting the branch threads it
through (the bug slipped past tests because post.test mocked the payload builder
and payload.test called it directly, bypassing the branch).

* improvement(contracts): update vfs contracts
2026-06-19 15:12:42 -07:00
Theodore Li 208d135dac feat(enrichment): add enrichment details sidebar with cost + provider cascade (#5139)
* feat(enrichment): add enrichment details sidebar with cost + provider cascade

* fix(enrichment): address review — persist detail on cancel/skip, exclude not_run from ran count, refetch on panel open

* fix(enrichment): keep cascade detail sticky on upsert; mark unattempted providers not_run on abort

* fix(enrichment): show Cancelled in details panel for aborted runs
2026-06-19 17:19:13 -04:00
Theodore Li f0b3550729 feat(files): public share links for workspace files (#5130)
* feat(files): public share links for workspace files

* improvement(files): drop reserved public_share columns until used; sync audit mock

* fix(files): share modal tracks authoritative saved state until toggled

* feat(files): per-IP rate limit on public share endpoints

* fix(files): address PR review — public CSV OOM, content cache, share FK, soft-delete filter, download anchor

* fix(files): disable CSV import action in read-only preview (public share)

* refactor(files): drive CSV preview import affordance off readOnly, not disableImport

* fix(files): version public viewer caches by file updatedAt so edits aren't stale

* fix(files): 409 (not corrupt source) when a shared generated doc has no compiled artifact

* feat(files): gate public sharing behind an access-control permission
2026-06-18 21:26:49 -04:00
Theodore Li 63a3e6d2cb feat(files): stream large CSV previews and add import-as-table (#5125)
* feat(files): stream large CSV previews and add import-as-table

* fix(files): validate fileId in csv-preview route, guard double-import, fix sniff perf and toggle flash

* fix(files): scope mothership preview-toggle loading guard to CSV files only
2026-06-17 21:54:18 -04:00
Waleed 8b93e43037 improvement(integrations): validate BigQuery/Forms/PageSpeed + regenerate integration docs (#5109)
* improvement(integrations): validate BigQuery/Forms/PageSpeed + regenerate integration docs

- BigQuery: mark null-defaulted outputs optional (get_table type/numRows/numBytes/creationTime/lastModifiedTime/location, list_datasets location, list_tables type, query totalBytesProcessed)
- Google Forms: add response pagination (pageToken + filter params, nextPageToken output), fix pageSize visibility, advanced-mode pagination subBlocks + filter wandConfig
- PageSpeed: add a 7th BlockMeta template (competitor benchmark)
- Regenerate integration docs; add manual intro sections to new datagma/dropcontact/enrow/icypeas/leadmagic pages

* fix(docs-gen): preserve apostrophes in tool descriptions when generating docs

The doc generator extracted tool descriptions with a character class that
excluded both quote types (['"]([^'"]...)['"]), so a double-quoted description
containing an apostrophe (e.g. "Find someone's email") was truncated at the
apostrophe — the generated docs/catalog showed stubs like "Find someone".

Anchor extraction on the actual opening quote (single/double/backtick), matching
the existing extractDescription helper, in both buildToolDescriptionMap and
extractToolInfo. Regenerated docs restore full descriptions across all affected
integrations (Apollo, Ahrefs, LeadMagic, Findymail, OpenAI, Slack, etc.).

* fix(docs-gen): resolve tools defined in a sibling file + scope params per tool

The doc generator located a tool's definition only by filename convention
(decompress.ts / index.ts), so file_decompress — which lives in compress.ts
alongside file_compress — fell back to index.ts and rendered an empty Input
table. It also read the params block from the first tool in a multi-tool file,
so every tool in such a file inherited the first tool's inputs/outputs.

- getToolInfo: when no candidate file declares the exact tool ID, scan the whole
  tool-prefix directory for the file that does.
- extractToolInfo: read the params block scoped to the specific tool, falling
  back to the full file for tools that inherit params via spread.

Regenerated docs eliminate ~50 empty/incorrect input tables across integrations
(clickhouse, rb2b, reddit, file, etc.); param-less OAuth-only tools correctly
keep an empty input table.
2026-06-16 23:05:30 -07:00
Vikhyath Mondreti 7b4626e547 improvement(perm-groups): allow workspace filter for permission groups (#5070)
* improvement(perm-groups): allow workspace filter for permission groups

* show errors correctly

* address comments

* address concurrent edit concern

* address locks

* address comments"

* index migration safety

* address at route level
2026-06-15 20:52:01 -07:00
Waleed 05e8c7cd71 refactor(connectors): split client metadata from server runtime (#5076)
* refactor(connectors): split client metadata from server runtime + cover node:net in client bundle

The browser build broke with `Cannot find module 'node:net'`. Server-only
SSRF code in `input-validation.server.ts` (`dns/promises`, and since PR #5060
`undici` → `node:net`/`node:tls`) is statically reachable from the client
bundle via the tool/connector registries, which the workflow editor imports
for metadata. Node networking builtins have no browser shim, so Turbopack
cannot compile them for the client.

Two changes:

1. Split each connector's client-safe declarative metadata into a sibling
   `meta.ts` (`<name>ConnectorMeta`), mirroring the `XBlockMeta` /
   `BLOCK_META_REGISTRY` pattern. `connectors/registry.ts` is now the
   client-safe `CONNECTOR_META_REGISTRY` (+ `getConnectorMeta` /
   `getAllConnectorMeta`); the full registry with runtime fns moves to
   `connectors/registry.server.ts`. Client components consume the meta
   registry; the sync engine and knowledge API routes consume the server
   registry. This removes connectors from the client's server-only graph.
   Connector metadata is byte-for-byte identical before/after; runtime fns
   are untouched.

2. Extend the existing #4899 `turbopack.resolveAlias` browser stub — which
   already mapped `dns`/`dns/promises` to an empty module for the browser —
   to also cover `net`/`tls` (+ `node:` variants), since `undici` now pulls
   those in. The remaining tool/provider definitions still reach
   `input-validation.server` server-side; the browser-only stub keeps those
   Node builtins out of the client bundle while the real modules stay on the
   server, so SSRF validation and IP pinning are unaffected.

Connector authoring/validation skills updated to teach the meta.ts split.

* fix(icons): use Square logo glyph only, drop wordmark

* fix(connectors): share Discord max-messages default across meta and runtime

Discord defined DEFAULT_MAX_MESSAGES separately in meta.ts (config placeholder)
and discord.ts (sync behavior), which could drift. Export it from meta.ts and
import it in the runtime, matching the single-source pattern used by the other
connectors (e.g. gmail, intercom).

* refactor(tools): route grafana/agiloft egress server-side, drop SSRF browser shim

Move the server-only SSRF-pinned fetch out of the grafana (update_dashboard,
update_alert_rule) and agiloft (11 record/search tools) definitions and into
internal API routes, the same pattern the rest of the server-side tools (and
agiloft's own attach/retrieve) already use. The tool definitions are now purely
declarative (request → internal route), so they no longer import
`input-validation.server` and the tools registry is fully client-safe.

With connectors (meta split) and these tools no longer reaching server-only code
from the client bundle, the browser no longer pulls in `dns`/`net`/`tls`:

- Add `import 'server-only'` to `input-validation.server.ts` so any future client
  import fails loudly at build time instead of silently bloating the bundle.
- Remove the `turbopack.resolveAlias` browser stub and delete
  `empty-node-fallback.browser.ts` — the root cause is fixed, the shim is gone.

Behavior is unchanged: each route runs the exact merge/validation/fetch logic the
tool ran before (every header, param branch, JSON-parse guard, error string, and
SSRF pinning preserved); only the location of execution moved from the client-
bundled definition to a server route.

* fix(connectors): move onedrive tagDefinitions into meta; drop server-only guard

- onedrive's tagDefinitions lived in the runtime file, so the client meta
  registry returned undefined for it and the add-connector tag opt-out section
  stopped rendering for onedrive. Move it into meta.ts like the other connectors
  so client and server see identical metadata (verified across all 50).
- Remove the 'server-only' import from input-validation.server.ts: the meta/route
  split already keeps it out of the client bundle, and blocks/tools registries
  don't use the guard either.

* fix(grafana): surface upstream error when the prefetch GET fails

Check response.ok on the existing-resource GET in both update routes and return
the upstream status/body, matching how the tool framework surfaced GET errors
before the move to internal routes (the framework checks response.ok before
transformResponse). Without this, a failed prefetch produced a generic
'Failed to fetch existing ...' message and dropped Grafana's error detail.

* fix(grafana): reject invalid panels JSON instead of silently ignoring it

Grafana's dashboard API treats panels as a required array and returns 400 on
invalid JSON; this route already errors on every other JSON param. Return
'Invalid JSON for panels parameter' instead of swallowing the parse error and
proceeding with a misleading success.

* fix(grafana): trim dashboard/alert-rule UID in route URLs (carry over #5082)

PR #5082 added .trim() on dashboardUid/alertRuleUid in the original tool URL
builders. Those tools now build their URLs in the internal routes, so apply the
same trim there to preserve that behavior.

* fix(grafana): route update_folder egress server-side (carry over #5082)

#5082 added a grafana update_folder tool that does SSRF-pinned fetch in its
postProcess, re-introducing the client-bundle leak. Convert it to the internal
API route pattern like the other update tools so the def is declarative and
input-validation.server stays out of the client bundle.

* fix(grafana): surface route failures in transformResponse instead of masking them

The grafana update tools' transformResponse hardcoded success: true and dropped
the route's error, so an upstream/validation failure (HTTP 200 with
{ success: false, error }) was reported to the workflow as a success. Forward
data.success and data.error (matching the agiloft tools) so failures propagate
as before the move to internal routes.
2026-06-15 19:53:37 -07:00
Theodore Li 3fe061e3b3 feat(feature-flags): AppConfig-backed gated feature flags (#5059)
* feat(feature-flags): AppConfig-backed gated feature flags

* fix(ci): repoint 'Validate feature flags' step to env-flags.ts after rename

* improvement(feature-flags): drop in-code defaults; fallback resolves a per-flag secret, gating is AppConfig-only

* improvement(feature-flags): make flag names a closed set so every flag requires a fallback secret

* improvement(feature-flags): single FEATURE_FLAGS registry — each entry defines name, description, and fallback in one place

* improvement(feature-flags): fallback is the env secret key (keyof typeof env), resolved to a boolean
2026-06-15 21:56:12 -04:00
Theodore LiandClaude Opus 4.8 f277f5fba5 feat(db): zero-downtime migration safety lint + db-migrate skill (#5041)
* feat(db): zero-downtime migration safety lint + db-migrate skill

Add scripts/check-migrations-safety.ts (check:migrations), a CI gate that
classifies statements in newly-added migrations into hard errors (rewrite),
annotate-to-acknowledge contract ops (`-- migration-safe: <reason>`), and
backfill warnings. Wire it into test-build.yml. Add the /db-migrate skill as
the judgment half (expand/contract phasing, app-code cross-ref, annotation
authoring).

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

* feat(skills): run cleanup and db-migrate safety checks in /ship

* fix(db): address review — DROP INDEX lock symmetry, RENAME CONSTRAINT false-positive, alter-type literal match

- Non-concurrent DROP INDEX is now a hard error (ACCESS EXCLUSIVE lock), symmetric with CREATE INDEX; DROP INDEX CONCURRENTLY after a COMMIT passes clean. Removes the false-confidence annotate path.
- RENAME rule narrowed to RENAME COLUMN / table RENAME TO; RENAME CONSTRAINT and ALTER INDEX ... RENAME (metadata-only) no longer flagged.
- alter-type regex now requires TYPE to follow the column identifier, so it no longer matches TYPE inside a string default.

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

* fix(db): enforce IF EXISTS on DROP INDEX CONCURRENTLY for replay idempotency

Symmetric with the CREATE INDEX CONCURRENTLY rule: a post-COMMIT DROP INDEX
CONCURRENTLY replays from the top on failure, so without IF EXISTS it aborts
re-dropping an already-gone index.

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

* improvement(skills): gate /ship cleanup on UI changes; default migration base to staging

- /ship runs /cleanup only when the diff touches UI code (.tsx or apps/sim/components|hooks|stores); the six passes are React-only.
- /ship runs check:migrations against origin/staging (the PR base).
- check:migrations default baseRef is now origin/staging instead of origin/main.

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

* docs(db-migrate): add contract-pending TODO convention for deferred drops

Establishes a durable, greppable marker (`contract-pending(<precondition>): ...`)
left on the legacy column in schema.ts when an expand defers a drop, so the
contract phase doesn't rot. The outstanding-work list is `grep -rn contract-pending`;
the contract PR's `-- migration-safe:` annotation references the expand and deletes
the marker.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:00:50 -04:00
Waleed dd32abef1e feat(jsm): add Atlassian Assets (Insight/CMDB) tools for asset management (#5072)
* feat(jsm): add Atlassian Assets (Insight/CMDB) tools for asset management

Add nine JSM Assets tools so workflows can read and write Atlassian Assets
(Insight/CMDB) objects — the foundation for keeping JSM asset tables in sync
for software/hardware asset management.

Tools (wired into the Jira Service Management block):
- jsm_list_object_schemas, jsm_get_object_schema
- jsm_list_object_types, jsm_get_object_type_attributes
- jsm_search_objects_aql (AQL search with pagination)
- jsm_get_object, jsm_create_object, jsm_update_object, jsm_delete_object

Each tool proxies through an internal route that resolves the Jira cloudId and
the Assets workspaceId, then calls the Assets API via the OAuth 2.0 (3LO)
gateway form (/ex/jira/{cloudId}/jsm/assets/workspace/{workspaceId}/v1).

Adds the CMDB OAuth scopes to the jira provider (read/write/delete cmdb-object,
read cmdb-schema/type/attribute) with descriptions, contract schemas for each
route, and block operations/subBlocks/outputs. Bumps the API-validation route
baseline for the nine new routes.

* refactor(jsm): harden Assets param coercion and response typing

- Add toOptionalInt helper so non-numeric pagination inputs never emit NaN
  into the Assets query string (startAt/maxResults/page/resultsPerPage)
- Replace Record<string, any> in mapAssetObject with typed Raw* interfaces

* fix(jsm): validate Assets workspaceId and honor `last` pagination flag

Address review findings on the Assets tools:
- Add validateAssetsWorkspaceId and guard the workspaceId in every Assets
  route before it is interpolated into the API path (mirrors the existing
  cloudId guard) — prevents a crafted workspaceId from escaping the
  workspace-scoped path
- Object schema list now falls back to the `last` flag when `isLast` is
  absent, so pagination doesn't stop early

* feat(jsm): allow overriding the auto-resolved Assets workspace

Atlassian provisions one Assets workspace per site, so workspace discovery
uses values[0] by design. For the rare multi-workspace site, expose an
advanced "Assets Workspace ID" override on the block that flows through to
every Assets operation, and document the single-workspace assumption.

* refactor(jsm): include Assets responses in the JsmResponse union

Append the nine Assets tool response types to JsmResponse for completeness
and consistency with the rest of the JSM tool surface.
2026-06-15 16:40:24 -07:00
Siddharth Ganesan d538b76eda feat(copilot): server-side mothership tool/vfs/file metrics (#5071) 2026-06-15 16:32:35 -07:00
Theodore Li 18edc94b2c fix(billing): deploy modal gates on workspace entitlement, not viewer plan (#5055)
* fix(billing): deploy modal gates on workspace entitlement, not viewer plan

The deploy modal showed the upgrade wall to a free user in a PAID workspace,
because it gated on the viewer's individual plan (useSubscriptionData) while the
server gates on the workspace billed account (rolled-up plan). Add a workspace
api-execution-entitlement endpoint that mirrors isWorkspaceApiExecutionEntitled,
and gate the API/MCP/A2A tabs on it so the UI matches the server exactly.

* fix(billing): key deploy gate on URL workspaceId + refetch entitlement on open

Address review findings:
- key useWorkspaceApiExecutionEntitlement on the URL workspaceId (available on
  mount) instead of workflowWorkspaceId (null until the workflow map resolves),
  so the gate fires immediately instead of leaving the tabs ungated until then
- staleTime 0 so reopening the deploy modal refetches entitlement; a plan upgrade
  happens outside this query's invalidation graph, so the gate self-heals on open

* refactor(billing): workspace owner access state instead of bespoke entitlement endpoint

Replace the single-purpose api-execution-entitlement endpoint with a reusable
workspace-owner billing/access concept — the workspace-scoped counterpart to the
viewer-scoped useSubscriptionData:

- getWorkspaceOwnerSubscriptionAccess(workspaceId): the billed account's rolled-up
  subscription access fields (mirrors getSimplifiedBillingSummary's flag derivation)
- GET /api/workspaces/[id]/owner-billing + useWorkspaceOwnerBilling hook
- deploy modal derives its gate via the existing getSubscriptionAccessState
  (hasUsablePaidAccess) on the owner data, exactly like every other paid feature

Audited the rest of the app: no other UI gates on the viewer's plan where the
server gates on the workspace owner — programmatic execution is the only
workspace-owner-scoped feature; inbox/KB-live-sync/credential-sets all gate
consistently on both sides.

* fix(billing): deploy gate on owner isPaid, not hasUsablePaidAccess

hasUsablePaidAccess rejects past_due and billing-blocked, but the server gate
(isWorkspaceApiExecutionEntitled) allows any paid plan in an entitled status
(active or past_due). Gate on the owner's isPaid so a past_due paid workspace
isn't shown the upgrade wall while the API still works.
2026-06-15 15:36:56 -04:00
Siddharth Ganesan cefb2dc239 improvement(mship): add enrichment tool, clean up dead tools (#5058)
* feat(mothership): add enrichment_run server tool for one-off lookups

Implement the Sim-side handler for the copilot enrichment_run tool: runs the
enrichment provider cascade for a single entity and returns the result inline,
surfacing the hosted-key cost as _serviceCost for per-round billing (matching
the media tools). Registered in the server-tool router.

Regenerate the copilot tool catalog/schemas to include enrichment_run.

* fix(mothership): remove leftover touch_plan tool references

touch_plan was removed from the copilot tool catalog earlier, but the Sim side
still referenced it. Regenerating the generated tool catalog (for enrichment_run)
synced it to the current contract and dropped the stale TouchPlan export, which
broke the build where router.ts still imported it.

Remove the dead touch_plan server tool and its test, drop its router
registration and WRITE_ACTIONS entry, simplify getServerToolRegistry (no more
beta-gated server tools), and clean up stale "use touch_plan" guidance strings.

* chore(mothership): trigger dev redeploy

* improvement(mothership): log billed cost on enrichment_run lookups

* fix(contracts): regenerate mship contracts

* fix(contracts): fix mship contracts
2026-06-15 10:57:29 -07:00
Waleed 940506ad09 feat(square): add Square integration with 34 commerce operations (#5053)
* feat(square): add Square integration with 34 commerce operations

Add a Square integration (API-key auth via personal access token) covering
payments, refunds, customers, locations, orders, invoices, catalog, and
inventory. Catalog image upload routes through an internal API endpoint using
the shared UserFile handling pattern. Adds a dedicated square-errors extractor.

* fix(square): correct catalog image part name and address review feedback

- Fix catalog image upload: Square's multipart part for the binary is `file`,
  not `image_file` (per the live API cURL examples); this would have caused
  upload failures
- Catalog image route: check response.ok before parsing, drop the unreachable
  legacy base64 path, derive MIME from the uploaded file
- Block: split the search query field per operation so placeholders match each
  endpoint's schema; parse each JSON field individually so errors name the field
- Round out coverage: complete_payment version_token; customer nickname/birthday;
  batch inventory states/updated_after/limit

* fix(square): correct canonical file param usage and revert query split

- Read the catalog image file from the canonical `params.file` (the basic/advanced
  inputs are collapsed before the params function runs) instead of the raw
  uploadFile/fileRef ids, which no longer exist at that point — fixes the
  Canonical Param Validation test and a latent upload bug
- Revert the per-operation query split: canonicalParamId is only valid for
  basic/advanced pairs under one condition. Use a single query field with a
  schema-neutral placeholder and a wand prompt that covers each search operation

* chore(square): trigger fresh review

* fix(square): single-location invoice search and guard numeric coercion

- SearchInvoices: Square's invoice filter accepts only one location, so take a
  single locationId (string) instead of an array and wrap it as
  query.filter.location_ids: [locationId]
- Block: fail locally with a clear "<field> must be a valid number" error when
  amount/limit/version/orderVersion are non-numeric instead of forwarding NaN

* fix(square): accept real booleans for autocomplete/includeRelatedObjects

Coerce these from both the dropdown's string values and actual booleans
(which can arrive via connected blocks or templated inputs), so true is not
silently flipped to false.

* fix(square): validate parsed JSON field shapes (array vs object)

parseJsonField now enforces the expected shape so a valid-but-wrong-type value
(e.g. a JSON string where an array is expected for locationIds/objectTypes/
paymentIds/catalogObjectIds/states, or a non-object for order/invoice/etc.)
fails locally with a clear message instead of a confusing Square API error.
2026-06-15 09:55:14 -07:00
Waleed eb1009da1c improvement(react-query): codebase-wide audit — server-state hooks, webhook coherence, resume migration (#5024)
* improvement(react-query): codebase-wide audit — server-state hooks, webhook coherence, resume migration

* chore(react-query): add static pattern linter + address review feedback

- add scripts/check-react-query-patterns.ts (staleTime/signal/key-factory/inline-key
  enforcement) wired into CI as check:react-query; strict zone hooks/queries/**, ratchet elsewhere
- fix(resume): use same-origin relative path for resume POST (getBaseUrl could cross origin
  on whitelabel/preview hosts and drop session cookies) — Cursor Bugbot
- remove explanatory inline comments in favor of TSDoc per repo convention
2026-06-13 10:14:07 -07:00
Waleed 58cff68b5e feat(deployments): add v1 deployment endpoints and Deployments block (#5009)
* feat(deployments): add v1 deployment endpoints and Deployments block

* fix(deployments): require deployed workflow for rollback, normalize warnings, guard orphaned workspaceId

* fix(deployments): workspace-bound tool routes, optional-body parsing, version bounds, and 404 masking

- Tool routes now require the executing workspace ID and reject cross-workspace targets
- v1 deploy/rollback read optional bodies via parseOptionalJsonBody (size-capped, 400 on malformed JSON)
- Version numbers bounded to the Postgres integer range
- v1 mutation routes mask access failures as 404, matching the v1 detail route
- listWorkflowVersions returns description and normalizes admin-api deployedByName (parity with mothership get_deployment_log)
- Workflow selector no longer auto-selects the first workflow (new autoSelectFirstOption opt-out)
- Shared deployment version metadata field schemas across UI/v1/tool contracts

* chore(api-validation): bump route baseline for rebased staging (826)

* fix(docs): use real ID formats in OpenAPI examples

Workflow, workspace, folder, knowledge-base, document, and execution IDs are
plain UUIDv4; workspace file IDs are wf_<shortId>; table and row IDs are
tbl_/row_ + de-dashed UUID. Replaces all fake prefixed example IDs (wf_abc123,
ws_xyz789, exec_..., kb_..., etc.) accordingly and marks the deploy body
description as nullable to match the shared schema.

* feat(deployments): resolve workflow names in block UI, add workflow_undeployed Sim trigger event

- Deployments block now uses the workflow-selector subblock (same as the
  Workflow block), so the canvas tile shows the workflow name instead of the
  raw ID; reverts the now-unneeded dropdown autoSelectFirstOption prop
- Adds workflow_undeployed to the Sim workspace-event trigger, emitted by
  performFullUndeploy through a shared lifecycle-event dispatch loop
2026-06-12 16:38:08 -07:00
Waleed 636bd74f06 fix(integrations): resolve OAuth connect UI by service id instead of display name (#5001)
* fix(integrations): resolve OAuth connect UI by service id instead of display name

* test(integrations): pin OAuth service resolution for all catalog integrations; fix credential branding reverse lookup

* fix(docs-gen): blank string literals and comments before brace scanning in extractOAuthServiceId
2026-06-12 11:38:01 -07:00
Waleed 3db7161b1c feat(integrations): add Vanta integration with compliance, evidence file, people, vendor, vulnerability, and risk tools (#4993)
* feat(integrations): add Vanta integration with compliance, evidence file, people, vendor, vulnerability, and risk tools

* fix(integrations): use write-only scope for vanta document submit and stream-cap document downloads

* fix(integrations): vanta review feedback - unique svg ids, mime type for base64 uploads, auth check consistency

* fix(integrations): bound vanta base64 fileContent size at the contract level

* improvement(integrations): cache vanta tokens and retry once on revocation to avoid concurrent token races

* improvement(integrations): deduplicate in-flight vanta token exchanges

* fix(integrations): hash vanta client secret in token cache keys

* improvement(integrations): expose vanta upload mime type in block and align fileContent bound with the 100MB cap

* chore: resolve api-validation baseline after rebase onto staging (816 + 3 vanta routes)
2026-06-12 10:56:27 -07:00
Theodore LiandClaude Opus 4.8 53fdcab5d6 feat(tables): background jobs (delete/export/backfill on trigger.dev) + tenant-scoped query performance (#4915)
* feat(tables): paginated background row-delete jobs via table_jobs

* fix(tables): address review on async row-delete (filtered count, scoped optimistic clear, Cmd+A select-all, hide delete from tray)

* improvement(tables): filter-aware select-all runs, delete-job read mask, keyset index + autovacuum tuning

* feat(tables): run import/delete/export/backfill jobs on trigger.dev with in-process fallback

* improvement(tables): raise delete page to 10k and export batch to 5k

* improvement(tables): raise CSV import batch to 5k rows (param-cap bounded)

* feat(tables): surface export jobs in the header tray with progress, cancel, and download

* improvement(tables): surface exports as derived tables-scoped toasts instead of the import tray

* Revert "improvement(tables): surface exports as derived tables-scoped toasts instead of the import tray"

This reverts commit 1ea5871610.

* fix(tables): preserve export storage key (NoSuchKey) and unify jobs in one spinner tray

* improvement(tables): jobs tray icon reflects aggregate state (spinner/check/alert)

* fix(tables): restore jobs tray on the tables list (dropped in staging merge)

* improvement(tables): keyset-paginate export row reads (offset paging was O(n^2) over large tables)

* perf(tables): keyset pagination for grid infinite scroll

Default-order row pages now cursor on (order_key, id) instead of OFFSET —
each page is an index seek on tableOrderKeyIdx, where OFFSET re-scans and
discards every prior row (O(N²) across deep scrolls and full drains like
select-all/export-to-clipboard). Sorted views keep offset paging; the
contract refines after+sort as mutually exclusive. v1 public rows API is
unchanged (extends the unrefined base, omits after).

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

* fix(tables): show export in job tray immediately on kickoff

The export-jobs query's poll only self-sustains once a running job is
already in the cache, so a freshly kicked export stayed invisible until
an SSE event or page refresh. Invalidate the tray query on kickoff
success so the icon appears right away.

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

* fix(tables): surface real row/column write errors in toasts

Drizzle wraps DB errors in DrizzleQueryError whose message is the failed
SQL — the real cause (e.g. the row-limit trigger's RAISE) sits on .cause,
so the routes' substring classification never matched and everything fell
through to generic 500s ("Failed to insert row"). Add rootErrorMessage
(cause-chain unwrap) and a shared rowWriteErrorResponse classifier that
consolidates the per-route pattern lists and rewrites the trigger message
into a friendly "Row limit exceeded — capped at N rows". Applied across
the app and v1 row-write routes and the columns route.

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

* perf(tables): tenant-bound filtered row counts (12.7s -> 0.6s)

JSONB filter predicates (->> ILIKE / range casts) are opaque to the
planner: it estimates a handful of matches and picks a parallel seq scan
over the entire shared user_table_rows relation — every tenant's rows —
for the page-0 COUNT(*), so any non-equality filter on a large table cost
10s+ regardless of how few rows matched. Run filtered counts in a
transaction with SET LOCAL enable_seqscan = off, forcing the
tenant-bounded bitmap plan. Unfiltered counts keep their index-only scan.

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

* perf(tables): tenant-bound Cmd+F search and stream its window (75s -> 2s)

Same planner trap as the filtered count, compounded: the lateral
jsonb_each_text ILIKE is unestimatable, so findRowMatches on a 1M-row
table seq-scanned the whole 12M-row shared relation and disk-sorted
~120MB of window input (75s measured). SET LOCAL enable_seqscan=off
bounds the scan to the tenant; on the default order, additionally
penalizing bitmap/sort/parallel steers the planner onto the already-
sorted (table_id, order_key, id) index walk so row_number() streams
with no sort at all (2s measured). Flags only penalize plan shapes —
a custom sort still sorts.

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

* perf(tables): tenant-bound sorted pages and filtered write selections

Extends the seqscan fix to every remaining jsonb-predicate path, all
measured on a 1M-row table in a 12M-row shared relation:
- sorted page query (ORDER BY data->>'col'): 9.7s/page -> 0.76s, and deep
  pages stop spilling ~130MB sorts to disk
- updateRowsByFilter / deleteRowsByFilter row selection: 14.4s -> bounded
- delete-job worker selectRowIdPage with a filter: 12.6s/page -> bounded
- dispatcher filtered-scope window walk: same shape, same fix

Shared withSeqscanOff helper moves to lib/table/planner.ts (service +
dispatcher both consume it; dispatcher can't import service).

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

* perf(tables): tenant-scoped containment index (migration 0232)

The plain GIN on user_table_rows.data matched @> candidates across every
tenant sharing the relation — a hot value in someone else's table
inflated everyone's equality filters (1.07M candidates fetched for a
33k-row match, lossy bitmap, 1.1s). Replace it with btree_gin
(table_id, data jsonb_path_ops): the tenant intersection happens inside
the index and paths are single hashed entries. Rare-equality probe
326ms -> 17ms with zero wasted candidates; unique-constraint checks and
upsert conflict lookups ride the same index. The new index is smaller
than the one it replaces (529MB vs 694MB on the 12M-row dev relation).

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

* perf(tables): tenant-bound unique-constraint checks (3.5s -> <1s per write)

The unique check runs lower(data->>'col') = $1 LIMIT 1 on every insert
and cell edit touching a unique column. The predicate is unestimatable
and a unique (non-conflicting) value never exits early, so the planner
seq-scanned all 12.3M shared-relation rows per check — 3.5s measured.
Tenant-bound both the single and batch variants; the batch path sets the
flag on the caller's transaction when one is supplied (SET LOCAL dies at
its commit, and the statements that follow are tenant-scoped writes).

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

* perf(tables): tenant-bound upsert conflict lookup

Same unestimatable data->>key predicate as the unique checks; an
insert-path upsert has no existing match so the lookup can't exit early
and seq-scans the whole shared relation. The upsert already runs in a
transaction — set the planner flag on it.

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

* refactor(tables): consolidate executor types onto planner exports

service.ts kept a private DbTransaction alias and two inline
typeof db | DbTransaction unions after planner.ts began exporting the
canonical DbTransaction/DbExecutor — import those instead. From the
/simplify review of the perf series; no behavior change.

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

* fix(tests): drop narrow schema mock override in process-contents test

The local vi.mock('@sim/db/schema') stubbed only document/knowledgeBase,
but the file's import graph reaches lib/table/service whose module scope
now references tableJobs. The global schema mock already covers all of
it — rely on it per the testing rules instead of re-mocking.

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

* fix(tables): scope cancels and counts to the filtered selection (review)

Addresses the open Bugbot/Greptile findings on filtered select-all:
- Filtered runs no longer cancel the whole table: cancelWorkflowGroupRuns
  takes a filter — it stops only dispatches with that exact filter scope
  and only in-flight cells on matching rows (semi-join); whole-table and
  differently-scoped dispatches keep running, their cancelled cells
  skipped via cancelledAt > requestedAt.
- Stop on a filtered select-all sends the filter through cancel-runs
  (contract + route + mutation) instead of a table-wide cancel.
- runColumnBodySchema rejects rowIds + filter together (mirrors
  deleteTableRowsBodySchema).
- Select-all delete clears the selection in onSuccess, not at click, so
  a failed kickoff restores both rows and selection.
- Clipboard copy/cut estimates use the filter-aware total (rowTotal)
  instead of the whole-table rowCount.

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

* chore: retrigger CI (Actions dropped the previous push events)

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

* chore: bump api-validation route baseline to 807 (staging route + merge)

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

* fix(tables): release job claim when trigger.dev dispatch fails

If tasks.trigger (or its dynamic imports) throws after
markTableJobRunning, the ghost running row held the table's
one-write-job slot until the stale-job janitor fired (~15-20 min of
409s). All four kickoff routes now release the claim and rethrow; the
backfill runner releases and warns (a failed backfill never fails the
schema change). Greptile P1.

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

* chore(db): squash 0232 into 0231 (one migration for the PR)

Both are branch-only — no environment has applied them through the
migration ledger yet — so the tenant-scoped GIN (btree_gin extension,
index swap) folds into 0231_table_jobs_and_keyset. Snapshot chain
re-pointed; drizzle-kit generate confirms zero drift.

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

* fix(tables): context-menu delete label shows the true select-all count

Under select-all the context menu counted only the loaded page ("Delete
1000 rows" on a 999k-row table) while the action correctly deletes every
matching row via the background job. Delete now gets its own count from
the filter-aware total minus deselections; the run-action labels keep the
loaded-row count since those actions act on loaded rows only.

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

* fix(tables): context-menu bulk actions act on the full select-all scope

Follow-up to the label fix: under select-all the context menu's Run /
Re-run / Stop only acted on the loaded page of rows. They now route
through the same scopes as the action bar — runs dispatch by filter
(whole table when unfiltered), Stop uses the filter-scoped cancel — and
all labels share one true count (filter-aware total minus deselections,
locale-formatted). Like the action bar, filter-scoped runs ignore
deselections (the run API has no exclusion set).

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

* feat(tables): exclusion set for select-all runs and stops

Select-all minus deselected rows now means exactly that for every bulk
action, not just delete. runColumnBodySchema and cancelTableRunsBodySchema
accept excludeRowIds (bounded by MAX_EXCLUDE_ROW_IDS, select-all scope
only); the dispatch scope persists it and the dispatcher window walk,
eager bulk-clear, pre-run cancel, and filter/table-scoped cancel all skip
excluded rows. Client threads exclusions from the selection through the
action bar and the grid context menu, including the optimistic stamps.

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

* fix(tables): spare excluded-row dispatches on Stop; no orphan placeholder table

Two Bugbot findings on the exclusion work:
- Select-all-minus-deselections Stop (no filter) cancelled every active
  dispatch table-wide, killing row-scoped runs on deselected rows.
  markActiveDispatchesCancelled now spares dispatches whose scope.rowIds
  are fully contained in the exclusion set (coalesce(false) keeps
  table-wide dispatches cancellable).
- Create-mode import: a failed trigger.dev dispatch released the job
  claim but left the just-created placeholder table in the workspace.
  Archive it on the failure path (no hard-delete surface exists).

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

* fix(tables): row counts reflect a running delete everywhere

A mid-delete refresh resurrected the old counts: the optimistic update
stripped cached rows but left page-0 totalCount (footer / select-all
label) at the old total, and list/detail counts reported raw row_count
including doomed-but-not-yet-deleted rows.

- onMutate now sets the active view's totalCount to the kept rows and
  decrements the cached detail rowCount by the doomed estimate
- the kickoff persists that estimate on the job (payload.doomedCount,
  clamped server-side); getTableById/listTables subtract the
  not-yet-deleted remainder (doomedCount - rows_processed) while the
  delete runs, so refetched counts match the read path's delete mask

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

* copy(tables): drop background mention from delete confirm

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

* fix(tables): clear select-all immediately when a delete kicks off

The header checkbox lingered as a minus over the optimistically-emptied
grid: rowSelectionCoversAll treats zero rows as not-covered, and the
selection clear waited for the kickoff's onSuccess. Clear at click
(failed kickoffs visibly restore rows + toast; re-selecting is cheap)
and render an empty grid's header checkbox unchecked regardless — a
selection over zero rows is vacuous.

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

* fix(tables): export takes the async path while a delete job runs

The sync/async export choice reads rowCount, which is a doomed-estimate-
adjusted number during a running delete (and the estimate is client-
supplied) — an overstated estimate could route a still-large masked set
through the synchronous stream. Mid-delete exports now always run as a
job: safe at any size, and exports bypass the one-job-per-table gate.

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

* fix(build): stop uploads setup from sweeping the project into route graphs

next build (Turbopack) failed with "Two or more assets with different
content were emitted to the same output path" on the server-root chunk.
Root cause: setup.server.ts's unscoped path.resolve(process.cwd()) made
node-file-tracing sweep the entire project — next.config.ts included —
into every route graph reaching lib/uploads (the files/upload route and,
since the export job, the export-async path). Two producers emitted the
swept config into same-named chunks; staging's latest commits made their
contents diverge and the names collided. Annotate the path derivation
with turbopackIgnore per the NFT warning's own remediation — the build
passes and all ~390 "unexpected file in NFT list" warnings disappear.

Also inline the releaseJobClaim dynamic imports in the kickoff routes to
plain static imports — service is already statically imported there.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 03:03:11 -04:00
Waleed c3b98eb1c4 feat(integrations): add Daytona integration with sandbox lifecycle, code execution, and file tools (#4987)
* feat(integrations): add Daytona integration with sandbox lifecycle, code execution, and file tools

* fix(daytona): address review feedback and harden edge cases

- Pre-check file size via userFile.size before downloading from storage in the upload route
- Tolerate empty response bodies in delete/start/stop sandbox tools
- Preserve explicit timeout 0 for run_code and execute_command
- Default missing exitCode to -1 so unknown state is distinguishable from success
- Reject blank sandbox IDs, clamp list limit to 1-200, trim destination path
- Clarify that toolbox operations require the sandbox ID (not name)
- Bump contract route baseline to 812 for the new daytona upload route

* fix(daytona): cap download size at 100MB and preserve sandbox identity on empty lifecycle responses

* improvement(notion): black icon on white background to match brand

* fix(daytona): reject oversized base64 uploads before decoding

* improvement(landing): move integration last-updated below CTAs and de-emphasize it

* fix(daytona): forward explicit zero cpu/memory/disk values in create sandbox
2026-06-11 19:13:45 -07:00
Waleed 977467970c improvement(integrations): overhaul landing FAQs for SEO/GEO and fix dynamic OG images (#4985)
* improvement(integrations): overhaul landing FAQs for SEO/GEO and fix dynamic OG images

* improvement(integrations): trim comments and fold catalog updatedAt into integrations.json

* fix(integrations): correct FAQ copy for zero-capability and single-tool integrations
2026-06-11 17:54:58 -07:00
Will ChenandClaude Opus 4.8 bc55fc3b50 improvement(docs): builder-first IA reorganization of the English docs (#4896)
* docs: reorganize into topic/ontology IA with a builder-first rewrite

Restructure the English docs from internal product categories into a
topic-based information architecture, and rewrite the conceptual pages
to install a mental model first rather than enumerate features.

Structure & navigation
- Reorder the sidebar to follow how someone builds: Get Started ->
  Workflows -> Tables -> Files -> Knowledge Bases -> Logs ->
  Building agents -> Mothership -> Workspaces -> Platform -> Reference.
- Demote the generated blocks/tools/triggers catalogs to a Reference
  section at the bottom.
- Break up the monolithic execution/ folder into deployment/ and
  logs-debugging/; collapse connections/* and variables/* into single
  pages under workflows/.
- Rename capabilities/ to building-agents/; relabel the integration
  catalog as "Integrations". Remove deprecated copilot and form
  deployment. Redirects added in next.config.ts for every moved URL.

Conceptual rewrites
- Workflows core (index, how-it-runs, data-flow, connections,
  variables): one mental model, one running example, terser prose.
- New building-agents overview distinguishes an agent (a workflow you
  build) from an Agent block (one reasoning step), plus a "choosing
  what to use" guide.
- Concept-trim passes on Knowledge Base, Tables, Blocks, Triggers
  overviews; new task pages for KB, Tables, and Files.
- New code-verified Alerts page.

Infrastructure
- pageType frontmatter (concept/guide/reference) + badge render.
- WorkflowPreview / OutputBundle components to embed real, app-styled
  workflow diagrams (adds framer-motion + reactflow to apps/docs).

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

* feat(docs): spec-driven BlockPreview for block reference heroes

Replace the static screenshot hero on each block reference page with a
<BlockPreview> that renders the block exactly as the builder canvas shows
it — header icon, sub-block rows, and branch/error handles — from a
hand-authored display spec. Static and non-interactive (no ReactFlow), so
it can't be panned or dragged, and self-updating to edit.

- block-display-specs.ts: one editable spec per block (rows, branches, handles)
- block-preview.tsx: static scaled card renderer with decorative handles
- block-icons.tsx: brand glyphs for the core block types; icons.tsx adds WaitIcon
- 14 block + 3 trigger pages swapped from <Image> to <BlockPreview>

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

* fix(docs): correct stale navigation and removed-feature references

Audited the docs against the product changelog (GitHub releases / staging
git history) for content that misleads readers — features that moved, were
renamed, or removed — rather than cosmetic drift. Fixes:

- Skills: no longer a Settings tab. It was promoted to its own workspace
  page (#4354), so "Settings → Skills under the Tools section" sent readers
  to a tab that no longer exists. (skills/index.mdx)
- Env vars: the workspace tab is "Secrets", not "Environment Variables"
  (credentials→secrets rename, #4364). (quick-reference/index.mdx)
- Mothership FAQ pointed to "Settings → Credentials" for integration
  connections; integrations moved to their own page and there is no
  Credentials tab. (mothership/tasks.mdx)
- Vision block was retired (#4684); a tip still named it. Reworded to
  "an Agent using a vision-capable model". (files/passing-files.mdx)
- Getting-started FAQ told new users to "use the Copilot feature" to build
  in natural language — that surface is Mothership. (getting-started)
- Removed the dead "Mod+Y → Go to templates" shortcut; the templates
  gallery was removed (#4354). (keyboard-shortcuts)

Note: MCP "tools" (Settings → Tools, for consuming) and MCP "servers"
(Settings → System, for exposing) are distinct surfaces — both doc
references are correct and were intentionally left as-is.

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

* fix(docs): repair broken /docs-prefixed enterprise links

The enterprise overview linked to /docs/enterprise/* (access-control, sso,
whitelabeling, audit-logs, data-retention, data-drains), but the docs site
is served at root — those 6 links 404'd. Now root-relative /enterprise/*.

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

* fix(docs): refresh stale workflow-preview example blocks

The /workflows diagram blocks are hand-authored (separate from the
spec-driven BlockPreview heroes) and had drifted from the real UI:
- Agent color purple #6f3dfa -> green #33C482 (the var(--brand) rebrand)
- Model gpt-4o -> claude-sonnet-4-6 (current default)
- "Prompt" row -> "Messages" (the actual agent sub-block)
- Start color #34B5FF -> #2FB3FF (real starter bgColor)

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

* fix(docs): align BlockPreview input/output handles to the card edge

The header (input/output) handles are positioned relative to the card and
used a -16px offset, so they floated 8px past the edge. Row/error handles
are -16px relative to a row that's already inset 8px by content padding, so
they sit correctly. Header handles are now -8px, so every handle sticks out
the same 8px and hugs the block edge.

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

* docs(blocks): rewrite Agent reference to match the current block

The page documented the old UI (System/User Prompt, no Files or Skills, Memory
taught as a separate block — contradicting its own FAQ). Rewritten to the real
sub-blocks (Messages, Model, Files, Tools, Skills, Memory, Response Format) in
the builder voice of the workflows exemplars: oriented opening, agent vs
Agent-block callout, outputs table, a live WorkflowPreview example, FAQ kept and
corrected (tool control "Force", not "Required"). pageType: reference.

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

* docs(blocks): rewrite API reference to match the current block

Tightened to the builder voice and the real config (URL, Method, Query Params,
Headers, Body + Advanced timeout/retries/backoff). Dropped the off-topic
"Dynamic URL Construction" / "Response Validation" sections (those are
Function-block techniques, not API config). Outputs table, FAQ kept. The example
is now a live WorkflowPreview (new API_FETCH_WORKFLOW in examples.ts, exported
via the barrel). pageType: reference.

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

* docs(blocks): rewrite Condition reference to match the current block

Tightened to the builder voice: oriented opening (branches on boolean
expressions, no model call, vs Router), the real branch model (if / else if /
else, checked top to bottom), connection-tag expression examples, an error-path
callout, outputs table, and a live branching WorkflowPreview example
(CONDITION_ROUTE_WORKFLOW). FAQ kept. pageType: reference.

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

* docs(blocks): restore Best Practices + multi-example workflows on Condition

Recalibration: reference pages keep genuine substance (Best Practices, every
distinct example), cutting only redundancy and verbose register. Restores the
Best Practices section and turns the three use cases into three rendered
WorkflowPreview examples (route by priority, moderate content, branch
onboarding). Adds CONDITION_MODERATE_WORKFLOW and CONDITION_ONBOARD_WORKFLOW.

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

* docs(blocks): restore Best Practices on Agent reference

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

* docs(blocks): restore Best Practices on API reference

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

* docs(blocks): rewrite Function reference to match the current block

Fixed the verbose register and dropped the duplicated outputs section + the
stale Python screenshot/TODO, while keeping the real substance: JS vs Python
(local vs E2B sandbox), the large-inputs sim.files/sim.values helpers, the
worked loyalty-score example, and Best Practices. The use cases are now two
rendered WorkflowPreview examples (reshape an API response, validate input).
Adds FUNCTION_RESHAPE_WORKFLOW and FUNCTION_VALIDATE_WORKFLOW. pageType: reference.

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

* docs(blocks): rewrite Router reference to match the current block

Cleaned the register, generalized the drifting model list, and folded the
Router-vs-Condition guidance into a callout. Kept the substance (routes as
output ports, NO_MATCH error path, all seven outputs, Best Practices, FAQ). The
three same-shape use cases collapse to one rendered triage WorkflowPreview
(ROUTER_TRIAGE_WORKFLOW), which the prose notes stands for the pattern.
pageType: reference.

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

* docs(blocks): restore the classify and lead-qual examples on Router

I wrongly folded two distinct Router scenarios into a note. Restored all three
as their own rendered WorkflowPreview examples: triage a support ticket,
classify feedback (to child workflows), qualify a lead (sales vs self-serve).
Adds ROUTER_CLASSIFY_WORKFLOW and ROUTER_LEAD_WORKFLOW. (Also exports
RESPONSE_API_WORKFLOW for the next page.)

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

* docs(blocks): rewrite Response reference to match the current block

Cleaned the register and broadened "Variable References" to connection tags
(any output, not just workflow variables). Kept the substance: exit-point
semantics, Builder/Editor mode, status codes, headers, the parallel-branch
warning, Best Practices, FAQ. All three use cases are now rendered
WorkflowPreview examples (API endpoint, webhook ack, status-per-branch). Adds
RESPONSE_API/WEBHOOK/ERROR_WORKFLOW. pageType: reference.

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

* docs(blocks): rewrite Variables reference to match the current block

Cleaned the register, corrected the outputs (each assignment is also exposed as
<variables.name>, not "no outputs"), and kept the substance: assignments
reference earlier outputs and current values, global <variable.name> access,
Best Practices, FAQ. Two use cases now render as WorkflowPreview examples (count
retries, hold config). Adds VARIABLES_RETRY_WORKFLOW and VARIABLES_CONFIG_WORKFLOW.
pageType: reference.

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

* docs(blocks): rewrite Wait reference to match the current block

Corrected a real staleness: the block now has an Async mode that suspends the
run for minutes/hours/days (not a hard 10-minute cap), plus a resumeAt output.
Documents Wait Amount / Unit / Async, the sync-vs-async distinction, all three
outputs, Best Practices, and updated FAQ. Two rendered WorkflowPreview examples
(space out API calls, delayed follow-up). Adds WAIT_RATELIMIT_WORKFLOW and
WAIT_FOLLOWUP_WORKFLOW. pageType: reference.

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

* docs(blocks): polish Credential reference (frontmatter, fold redundant tabs)

The page was already accurate to the block (Select/List operations, the outputs
tabs, the wiring steps). Light touch only: added description + pageType, made the
header consistent, and folded the two identical Gmail/Slack "how to wire" tabs
into one line. Examples stay as labeled flows + the List/ForEach screenshot,
since they use integration blocks and a Loop the WorkflowPreview can't render.

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

* docs(blocks): render the shared-credential example + icon fallback for integrations

Addressing the gap: WorkflowPreview block nodes now fall back to the integration
icon map, so diagrams can show Gmail/Drive/Slack/etc. with their real glyphs, not
just core blocks. Renders the Credential "share one account across blocks" example
as a WorkflowPreview (CREDENTIAL_SHARE_WORKFLOW). The multi-account and
List+ForEach examples stay as labeled flows + screenshot (the latter uses a Loop
container the preview can't render). Also exports EVALUATOR_GATE_WORKFLOW for the
next page.

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

* docs(blocks): rewrite Evaluator reference to match the current block

Cleaned the register, generalized the drifting model list, and documented the
per-metric outputs (<evaluator.metricname>), which the page omitted. Kept the
substance (metrics with name/description/range, structured-output guarantee,
Best Practices, FAQ). The quality-gate example renders as a WorkflowPreview;
the same shape covers the parallel-variations and support-QC patterns, noted in
prose. pageType: reference.

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

* docs(blocks): render the Credential route-by-logic example too

The icon fallback unblocked it: the "route to a different account by logic"
example now renders as a WorkflowPreview (CREDENTIAL_ROUTE_WORKFLOW), a Condition
selecting a production vs staging credential. The List + ForEach example stays a
screenshot because it nests blocks in a Loop container the flat WorkflowPreview
can't represent.

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

* docs(blocks): render Guardrails examples + light accuracy pass

Kept the full substance (four validation types, PII entity/language detail,
the PII screenshot and video, outputs, Best Practices, FAQ). Light fixes:
frontmatter, and generalized the drifting model names (GPT-4o / Claude 3.7) to
"a strong reasoning model" with the current default. The three use cases now
render as WorkflowPreview examples (validate JSON, check grounding, block PII).
Adds GUARDRAILS_JSON/HALLUCINATION/PII_WORKFLOW. pageType: reference.

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

* docs(blocks): render Human-in-the-Loop examples + frontmatter

Kept all the substance (Display Data, Notification, Resume Form, the Approval
Methods and API Execute Behavior tabs, outputs, the paused/resume example).
Added frontmatter and rendered the use cases as WorkflowPreview examples
(approve before publish, two-stage approval, verify extracted data); Quality
Control folds into the approval note as the same approve-then-act shape. Adds
HITL_APPROVAL/MULTISTAGE/VALIDATE_WORKFLOW. pageType: reference.

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

* docs(blocks): render Webhook examples + frontmatter

The page was already accurate (Webhook URL/Payload/Signing Secret/Headers, the
automatic-headers table, HMAC details, outputs, POST-only callout, FAQ). Added
frontmatter and rendered the two use cases as WorkflowPreview examples (notify a
service, fire on a check). Adds WEBHOOK_NOTIFY_WORKFLOW and
WEBHOOK_TRIGGER_WORKFLOW. pageType: reference.

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

* docs(blocks): add example + pageType to Workflow block reference

The page was already accurate and well-structured (Configure It, outputs,
deployment-status badge, execution notes, FAQ). Added pageType: reference and a
rendered WorkflowPreview example showing a parent calling the child workflow
enrich-lead and reading its result. Adds WORKFLOW_CALL_WORKFLOW.

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

* docs(blocks): container rendering for Loop/Parallel + render the Loop example

Adds subflow/container support to WorkflowPreview, modeled on the app's
subflow-node.tsx: a solid-bordered box with a header (icon + name), an internal
"Start" pill whose handle feeds the first nested block, and target/source
handles at the vertical center. PreviewBlock gains size/parentId; edges gain an
optional sourceHandle; nodes render nested children via React Flow parentNode.
Renders the Loop reference's ForEach example (LOOP_WORKFLOW) and keeps the four
loop-type sections + inside/outside referencing + caps. pageType: reference.

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

* docs(blocks): fix the Loop container's Start-pill connector

The Start pill -> first-block edge wasn't rendering: it was a React Flow
parent->child edge (unreliable), and the opaque container body hid it. Nested
blocks now render as absolute-positioned top-level nodes (container below at
zIndex 0, blocks above at zIndex 1), so the connector is an ordinary edge, and
the container body is see-through so it's visible.

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

* docs(blocks): render the Parallel example + frontmatter (last core block)

Reuses the container rendering for the Parallel reference. Kept all substance
(count/collection types, inside/outside referencing, batch size of 20, instance
isolation, the Parallel-vs-Loop table, Best Practices, FAQ). Added frontmatter
and a rendered container WorkflowPreview (PARALLEL_WORKFLOW: distribute tasks,
call concurrently, aggregate <parallel.results>); the two use cases stay as
labeled flows. Adds PARALLEL_WORKFLOW. pageType: reference.

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

* docs(blocks): prose glow-up for Guardrails to match the agent/condition voice

Rewrote the listy register (**Use Cases:** / **How It Works:** / **Configuration:**
scaffolding, "Use this when you need to..." filler) into the plain builder voice,
matching the depth of the Agent/Condition/Function rewrites. Kept every
validation type, option, range, the full PII entity/region list, the screenshot
and video, the outputs table, the rendered examples, Best Practices, and FAQ.

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

* docs(blocks): prose glow-up for Loop to match the agent/condition voice

Rewrote into the plain builder voice and cut the filler: dropped the "Use this
when you need to..." lines and the ASCII "Example: Iteration 1, 2, 3" pseudo-code,
and folded the duplicated Inputs/Outputs tabs into Configuration + Referencing
sections. Kept all four loop types with their screenshots, the inside/outside
reference rules, the 1,000-iteration cap, sequential-vs-parallel guidance, the
rendered example, and FAQ.

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

* docs(blocks): prose glow-up for Parallel to match the agent/condition voice

Same treatment as Loop: plain builder voice, dropped the ASCII pseudo-code and
the duplicated Inputs/Outputs tabs, folded the verbose Advanced Features into
tight Configuration + Referencing sections. Kept both types with screenshots,
the batch-size-of-20 cap, instance isolation, large-result indexing, the
Parallel-vs-Loop table, the rendered example, and FAQ.

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

* docs(blocks): prose glow-up for Human-in-the-Loop

Tightened the register: folded the pause sentence into the intro, made the
section headers consistent (Configuration, Outputs), converted the bold-list
Block Outputs into a table, condensed the Notification channel bullets to a
line, and renamed the second "Example" so it no longer collides with the
rendered Examples. Kept all the substance — Display Data / Notification / Resume
Form, the Approval Methods and API Execute Behavior tabs, the portal video, and FAQ.

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

* docs(blocks): re-enrich Loop prose (fuller, explanatory — not terse)

The first glow-up overcorrected into terse fragments. Restored proper
docs-quality prose at the Agent/Condition level: each loop type now explains
what it does, when to use it, and the relevant reference; Configuration,
Referencing, nesting, and Best Practices give context and the "why," not just
bullets. Same substance, readable depth.

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

* docs(blocks): re-balance Parallel prose to the Agent/Condition register

Calibrated to the level signed off on elsewhere: each concept explained in a
couple of clear sentences with a concrete detail — informative, not terse, not
padded. Kept both types with screenshots, batch-size cap, isolation, large-result
indexing, the Parallel-vs-Loop table, the rendered example, and FAQ.

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

* docs(blocks): restore the Notification channel detail on HITL

The glow-up over-compressed: it flattened the five notification channels (each
with what they do) into one sentence. Restored them as a list in plain voice —
tightening register shouldn't drop genuinely useful reference detail.

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

* docs(blocks): builder-voice polish on the Credential intro

Light touch only — the page was already well-structured and explanatory, so just
led the intro with what the block does (and bolded the name) to match the other
references. No content changed elsewhere.

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

* docs(triggers): rewrite Start trigger in the builder voice

Tightened the register, swapped the <code>&lt;&gt;</code> noise for backticks,
added pageType + an outputs table, and kept all substance: Input Format types,
chat-only outputs (input/conversationId/files), the editor/API/chat tabs, and
best practices.

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

* docs(triggers): rewrite Schedule trigger in the builder voice

Plain voice and clean markdown (dropped the raw <ul>/<div> lists). Kept all
substance: simple intervals, cron examples, timezone, deploy-tied activation,
the 100-failure auto-disable, and FAQ. Added pageType.

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

* docs(triggers): refocus Webhook trigger on the generic (native) trigger

Rewrote in the builder voice and separated out the integration content: the
page now documents the generic Webhook trigger (URL, Input Format, auth, custom
response, outputs, dedup/rate-limit/deploy/no-auto-disable). The "trigger mode
for service blocks" section is reduced to a short pointer + the demo video, and
the long supported-services catalog and vague use-case bullets are dropped in
favor of the Triggers index. Fixed the title (Webhook) and added pageType.

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

* docs(triggers): builder-voice glow-up for RSS

Light pass: added pageType + description, tightened the intro, and presented the
output fields as an <rss.*> outputs table. Kept the polling config, use cases,
the published-after-save callout, and the FAQ (poll cadence, dedup, 25-item cap,
auto-disable, Atom support).

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

* docs(triggers): rewrite Table trigger off the auto-generated card

Replaced the BlockInfoCard/'provides 1 trigger' auto-gen format with a real
builder-voice page: a spec-driven BlockPreview hero (added a 'table' spec),
plain-language Configuration (table, event type, watch columns, include
headers), and a full <table.*> outputs table. pageType: reference.

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

* docs(triggers): frame the index around native triggers + separate the catalog

Reframed "generic" as native (no connected account) and promoted RSS and Table
into the native set alongside Start/Schedule/Webhook — cards, comparison table,
and integration paragraph updated to match. In the sidebar, grouped the five
native triggers under a "Native triggers" header and divided the ~44 service
triggers under "Integration triggers" (nav-only — no files moved, URLs stable;
the move to integrations/ is a later, separate change).

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

* docs: promote Core Blocks + Core Triggers into the Workflows area

Restructured the Documentation sidebar (meta-only — no files moved, URLs stable):
after Deployment, the 16 core block pages now live under a "Core Blocks" section
and the 5 native trigger pages under "Core Triggers", instead of buried in the
bottom Reference catalog. Removed the now-redundant blocks tree from Reference,
and retitled the Reference triggers tree "Integration triggers" so it holds just
the service catalog (the native ones are promoted up top).

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

* docs: merge block/trigger overviews into the Workflows overview; Core accordions

Restructured the sidebar and overview hub (meta + content only, no integration
files moved):

- Folded the /blocks and /triggers overview pages into /workflows: the overview
  now carries the core-block catalog (do work / direct flow / shape run), the
  Integrations-and-triggers families framing, the native + integration trigger
  framing, the trigger comparison, manual-run priority, and email-polling groups.
  Deleted blocks/index.mdx and triggers/index.mdx as redundant.
- Promoted the 16 core blocks into a "Core Blocks" folder accordion and the
  native triggers into a "Core Triggers" accordion, both under Workflows after
  Deployment. Integration triggers stay inside Core Triggers under a labeled
  divider, temporary until they move to integrations/<service> (tabs) later.
- Repointed every /blocks and /triggers index link to the /workflows#blocks and
  /workflows#triggers sections.

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

* docs: split integration triggers into their own Reference accordion

Core Triggers is now the 5 native triggers only. Moved the 43 service triggers
out of triggers/ into a new integration-triggers/ folder, surfaced as an
"Integration triggers" accordion under Reference (an accordion must be its own
folder in Fumadocs). In Workflows, Core Triggers now sits before Core Blocks.
URLs: /triggers/<service> -> /integration-triggers/<service> (native /triggers/*
unchanged); the integrations/<service> tabbed-page migration remains the later step.

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

* docs(workflows): trim the overview back to an introduction

It had drifted from a concept intro into a catalog. Kept the spine (the four
parts with their previews, how-it-runs, workflows-in-context) and compressed the
merged-in material: the full 16-block enumeration becomes a three-kind taxonomy
with examples, the trigger section a short native/integration framing. Cut the
anxious in-between — manual-run trigger priority, the niche email-polling-groups
feature (belongs on the Gmail/Outlook trigger pages), the redundant block-def
line, the Start-outputs callout half, the connections video, and the catalog-y
FAQ items. Dropped the unused Video import.

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

* docs: relocate email-polling + trigger-priority out of the overview

Moved the two bits cut from the workflows overview to durable, generator-safe
homes: email-polling groups -> the Integrations (connecting accounts) page;
manual-run trigger priority -> the Start trigger page. Also added 'table' to the
generator's HANDWRITTEN_TRIGGER_DOCS / SKIP_TRIGGER_PROVIDERS so the hand-written
Table trigger page is no longer overwritten by generate-docs.

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

* feat(docs-gen): emit per-service integration pages (actions + Trigger section)

Rewrites the generator to output one page per service under integrations/
instead of split tools/ + triggers/. Block pass writes the service's actions;
trigger pass appends a '## Triggers' section (badged) to the same page, or writes
a standalone page for trigger-only services. Meta is written after both passes;
hand-written integration pages are preserved; docsUrl repointed to /integrations.

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

* feat(docs): unify tools + triggers into per-service /integrations pages

Encodes the ontology "everything is a block; some blocks are triggers." The
generator now emits one page per service under integrations/ — the service's
Actions plus, when it has one, a Triggers section on the same page — replacing
the split tools/<service> + triggers/<service>. No "Tools" terminology.

- generate-docs.ts: output to integrations/, merge trigger sections into each
  service page (standalone for trigger-only services), Actions heading, table
  block now generated, docsUrl -> /integrations, hand-written pages preserved.
- Nuked tools/ (213) and the interim integration-triggers/ (43); moved the
  custom-tools guide to building-agents/; knowledge/memory/file/table links and
  meta repointed to /integrations.
- Sidebar: integrations catalog now under Reference (was tools); removed the
  Workspaces integrations entry and the integration-triggers tree.
- block-icons: wait uses lucide Clock (the generated icons.tsx no longer carries
  a hand-added WaitIcon). Landing integrations data regenerated.

No redirects (fresh start). Native Core Blocks/Core Triggers unchanged.

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

* fix(docs): recover the hand-written manual-content intros on integration pages

The tools->integrations relocation generated fresh pages, so the generator never
saw the old tools/<service>.mdx to preserve its {/* MANUAL-CONTENT */} sections —
198 curated intros (AgentMail, etc.) were dropped. Reseeded each integrations
page from the pre-move tools page in git, re-ran the generator (which now merges
the manual intro into the new Actions/Triggers format), and repointed /tools/
links inside the recovered prose.

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

* docs(scripts): rewrite the generator README for the integrations model

Brings scripts/README.md current: integration pages are derived from the
apps/sim block/tool/trigger registry (canonical-sources map), the golden rule
not to hand-edit generated pages, the MANUAL-CONTENT escape hatch, which pages
are hand-written/skipped, and the icons.tsx-overwrite gotcha.

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

* docs: regenerate integration docs from staging-synced apps/sim

After merging staging, regenerated so the integration pages reflect current
source: correct block colors/configs (e.g. Gmail #FFFFFF), the new integrations
(sendblue, millionverifier, neverbounce, zerobounce), and staging's icon set.
Pages for integrations staging hid are removed; manual-content intros preserved.

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

* fix(docs-gen): don't let stale-doc cleanup delete hand-written integration pages

Staging's cleanupStaleToolDocs removes any integrations/*.mdx that isn't a visible
tools block — it only guarded `index`, so it deleted the hand-written
google/atlassian service-account pages. Now guards all HANDWRITTEN_INTEGRATION_DOCS.
Restored the two pages, and repointed /integrations/file links to /files (staging
hides the file block, so it has no integration page).

Note: staging recategorized a2a/mysql/postgresql tools -> 'blocks' (and hid file),
so they correctly drop out of the integration catalog and are currently
undocumented — an IA decision to revisit.

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

* fix(docs-gen): stop cleanup/writer filter mismatch from eating manual content

Comprehensive-review findings, all generator-consistency bugs:
- cleanup used staging's isIntegrationBlock while the writer kept the legacy
  filter, so integrations/{knowledge,memory,table}.mdx were deleted then
  regenerated without their manual intros every run. Both now honor a shared
  NATIVE_RESOURCE_BLOCK_TYPES set; intros reseeded.
- Trigger-only services (imap, circleback; category 'triggers') were likewise
  deleted each run; the canonical set now includes visible trigger-category
  blocks, the standalone writer preserves manual content, and their intros are
  reseeded.
- Mapped jsm -> jira_service_management, so JSM triggers merge into the JSM
  integration page instead of an orphan jsm.mdx (removed).
- Repointed lingering bare /tools links to /integrations; added missing
  pageType to integrations/index and building-agents/custom-tools.
Double-regen is now churn-free (idempotent) with all manual content intact.

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

* fix(docs): recover staging's enriched Table doc + never drop manual content

The merge resolution deleted staging's relocated blocks/table.mdx, which carried
substantial enrichment our integrations/table.mdx (reseeded from the older
tools/ version) lacked: Creating Tables (column types/constraints), Filter
Operators, Combining Filters, Sort Specification, Built-in Columns, Limits, and
Notes. Recomposed integrations/table.mdx with that content — Creating Tables
inside the intro manual section, the reference tail in a notes manual section.

Generator fix uncovered en route: a manual section whose insertion anchor is
missing in the generated markdown (e.g. notes with no "## Notes" heading) was
silently dropped on regen. Unplaceable sections now append at the end instead —
manual content is never lost. Verified idempotent across double regeneration.

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

* docs(workspaces): de-philosophize the fundamentals prose

Rewrote in the plain register of the workflows overview: 'draws the boundary
for access' / 'Nothing crosses the boundary' / 'follow the same edge' become
direct statements (only members can access it; a workflow in one workspace
cannot read a table in another). '## The boundary' is now '## Access and
isolation'. All substance kept: every resource type, permission levels,
personal/organization/grandfathered kinds, deployments callout, VISUAL markers.

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

* fix(docs): restore #blocks and #triggers anchors on the workflows overview

The editorial trim renamed '## Blocks' -> '## Kinds of blocks' and
'## Triggers' -> '## How a workflow starts', silently breaking the ten
/workflows#blocks and /workflows#triggers anchor links pointed there when the
old index pages were folded in. Pinned the original ids with explicit heading
anchors. Found by the comparative prose review.

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

* docs: restore the genuinely useful reference bits the rewrite dropped

From the comparative prose review, restored in guidance register (no spec
dumps): temperature tiers on Agent (low/middle/high with ranges), loop/parallel
iteration references in the variables syntax-at-a-glance table, and a short
"Test it" section on the Webhook trigger (curl + check the run in Logs). The
fourth flagged loss (tag-resolver mechanics on connections) turned out to be
already covered — name normalization, case-sensitive paths, missing-output
behavior, and value formatting are all on the page; only the internal resolver
precedence chain was dropped, deliberately.

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

* docs(blocks): rework the Agent intro — encyclopedia register

Replaced the flat opening with a denser, factual one (no metaphor): what the
block does, and its centrality stated as fact — 'Most workflows are built
around one or more Agent blocks.' The agent-vs-Agent-block disambiguation moves
from an info callout into a second paragraph on the block's role in building
agents. Dropped the now-unused Callout import.

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

* docs(integrations): add the HubSpot setup guide for the Marketplace listing

Addresses HubSpot Marketplace review item A1: a public, HubSpot-specific setup
guide following their template — what the app does, install + connect through
the current flow (sidebar Integrations page -> HubSpot -> Add to Sim -> connect
dialog -> HubSpot OAuth), with real screenshots of each step and a placeholder
for the scope-approval shot; configure in a workflow (one-click skills/templates
+ the HubSpot block + trigger mode), use, disconnect (with data consequences),
uninstall from the HubSpot side, troubleshooting. Capability wording is by CRM
object rather than scope enumeration, so it stays accurate after the A2 scope
trim. Lives at /integrations/hubspot-setup, guarded as hand-written,
cross-linked from the HubSpot reference page's intro.

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

* docs(integrations): rewrite the Integrations guide for the sidebar flow

Integrations moved out of Settings to a top-level sidebar page. Rewrote the
guide to the current journey: the Integrations page (Connected/Featured/search),
service pages with one-click skills and templates, + Add to Sim -> connect
dialog (display name + permissions) -> provider OAuth. Replaced the four
Settings-era screenshots with current captures (connect dialog illustrated via
HubSpot); block-side screenshots (account selector, manual credential ID) kept;
one VISUAL marker for the connection detail view pending a fresh capture.
Members/roles, credential-ID, reconnect/disconnect, email polling, and FAQ
substance unchanged apart from navigation.

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

* docs: move Building agents directly after Workflows in the sidebar

The agent-building journey follows straight from workflows (blocks, triggers,
deployment) rather than after the tour of every resource type. Tables/Files/
Knowledge Bases/Logs now follow it. Meta-only reorder.

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

* docs: fill the visual slots coverable by existing components

Six VISUAL markers filled with no new captures needed:
- building-agents overview: rendered the minimal lead-scoring agent
  (Start -> Agent with tool chips -> Response, Agent highlighted) as a
  WorkflowPreview (BUILD_AGENT_WORKFLOW)
- files guide: the read -> summarize -> write chain as a WorkflowPreview
  (FILE_SUMMARY_WORKFLOW)
- tables guide: the query -> classify -> write-back roundtrip as a
  WorkflowPreview (TABLE_ROUNDTRIP_WORKFLOW)
- choosing guide: the six-kind comparison grid as a markdown table
- knowledgebase guide: the Knowledge block's output as an OutputBundle
- workspace fundamentals: removed a duplicate nesting-diagram marker

42 -> 39 VISUAL markers remaining (screenshots + designed diagrams).

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

* docs(components): run-inspector OutputBundle + lightbox with block inspector

Two visual-component upgrades, both mirroring the real app:

- OutputBundle is now a miniature of the run inspector: a Logs column (block
  rows with icon chips and durations, source selected) beside the Output panel's
  typed tree — keys with the app's type-badge semantics (string green, number
  blue, object gray, array purple, boolean orange), chevrons, indent guides,
  primitive values. Styling lifted from the terminal's structured-output.
  Dropped the "Read one value by name" footer (the prose teaches the tag).
  The three usages (data-flow, tables, knowledgebase) get real typed trees;
  data-flow's stale purple/gpt-4o example corrected en route.

- WorkflowPreview gains a lightbox + read-only block inspector: clicking a
  block (or the expand control) opens a 92vw/86vh overlay with zoom and pan,
  and a right-hand inspector panel showing the selected block's full
  configuration — canvas rows truncate, the inspector doesn't. Fields render as
  app-style controls (dropdown/textarea/input by heuristic) with dashed
  dividers, tool chips, and a Connections footer computed from the edges.
  Selection rings without dimming (new selectedBlock option in workflow-data).
  Esc/backdrop closes; body scroll locks while open.

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

* docs: regenerate after staging merge — AppConfig joins integrations/

Staging's new AWS AppConfig integration (#4928) generated its docs into the old
tools/ layout; re-homed to integrations/appconfig.mdx (Actions heading, meta
entry) via the generator. tools/ stays deleted.

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

* docs: redirect the retired tools/ and trigger URLs to integrations/

Revises the earlier fresh-start call: /tools/* are ~200 live, indexed URLs
referenced by deployed app versions' docsLink fields and marketplace listings,
so dropping them cold would 404 from the live product. next.config now 308s:
- /tools -> /integrations, /tools/:slug -> /integrations/:slug
  (custom-tools -> building-agents/custom-tools first)
- old /triggers/<service> -> /integrations/<service>, enumerated so the native
  trigger pages keep resolving; provider-slug mappings for jsm and the
  hyphenated Google/Microsoft slugs
- /blocks and /triggers index URLs -> the workflows overview anchors
Verified every class + native passthroughs against the dev server. Spec updated.

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

* docs(getting-started): rewrite — current UI, cut the post-tutorial padding

The last old-guard page. Accuracy: Agent config now uses Messages (System/User
message) instead of the removed System Prompt/User Prompt fields, the default
model instead of GPT-4o, the banned 'no-code' phrasing is gone, the deploy card
points at /deployment, and frontmatter gets description + pageType. Weight: cut
the 'What You've Built' checklist, the 'Key Concepts You Learned' re-teach
section, the duplicate 'Resources' links, the Start-block hand-holding, and ten
dead icon imports; tightened every step preamble. 203 -> 113 lines with the
full 5-step tutorial, videos, and FAQ intact. (Videos still show the old UI
until the re-recording pass.)

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

* docs: de-fluff the Tier-1 heavy pages (logging, mcp, passing-files, permissions)

From the exhaustive fluff audit, keeping all substance:
- logging: merged the duplicated Console/Logs-page structure, snapshot concept
  stated once instead of three times, cut the generic Best Practices, trivial
  tab walkthrough condensed. Frontmatter added.
- mcp: intro + "What is MCP?" generic bullets folded into two sentences, cut
  the Common Use Cases catalog and the verify-your-config Troubleshooting
  checklists, merged the twice-stated Refresh behavior, security kept as one
  real warning.
- passing-files: marketing opener replaced with a factual lead, fixed the stale
  retired-Vision-block reference (now Agent with a vision model), dropped the
  FAQ item that restated the block catalog verbatim.
- permissions: heading-restating intro replaced with the two-layer model, cut
  the three "Perfect for: stakeholders..." persona lines and the generic Best
  Practices section, dropped the FAQ restating the limits table.
- connectors: audit over-flagged it — the categorized support matrix, API-key
  table, and config examples are genuine reference; frontmatter only.

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

* docs: tier-2 fluff trims (costs, enterprise, mailer, skills)

Conservative sweep from the audit, unambiguous cuts only: the costs CYA opener
and formula restatement, the enterprise marketing intro (now a functional
summary), mailer's restated convenience line and chat-upload comparison, and
skills' third restatement of progressive disclosure. Audit flags screened out
as misfires: mothership/tasks (immediate-vs-scheduled are two facts, not a
duplicate), self-hosting telemetry (real sizing data), and the recently
approved credential/HITL/workflow-block/trigger pages.

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

* docs(skills): update to the Skills tab on the Integrations page + document import

Skills moved again — they now live on the Integrations page's Skills tab in the
workspace sidebar (the doc said "Open the Skills page"). Updated the create flow
(+ Add to Sim -> Add Skill dialog) with fresh screenshots of the tab and both
dialog tabs, and documented the previously-missing Import flow: upload a .md
with YAML frontmatter or a .zip containing SKILL.md, fetch from a GitHub URL, or
paste SKILL.md content (verified against the import route/component; name 64 /
description 1024 limits verified against the contract). Noted the curated
skills suggested on integration pages, cross-linked the Skills tab from the
Integrations guide, and refreshed the location FAQ. Mechanics (progressive
disclosure, load_skill, agent-block attachment) unchanged and still accurate.

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

* docs(building-agents): render the lead-scorer running example on choosing

The page narrated its running example through six sections without ever showing
it. Authored LEAD_SCORER_WORKFLOW (Start -> Enrich workflow-as-tool -> Function
reshape -> Agent with Search/Send Email/CRM tool chips -> Google Sheets append)
and rendered it after the intro, with highlightBlock re-renders in the three
sections that map to a node (deterministic block -> the Sheets append, agent
tool -> the Agent, workflow-as-tool -> Enrich) — the same pattern as the
workflows overview.

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

* docs(tables): rewrite workflow columns around the real lead-scoring example

Rebuilt the page on the ai_startup_customers screenshots instead of captioning
them onto the old hypothetical: one running example throughout — Company Domain
fills domain, Company Info reads it into employee_count/description, Lead Score
Enrichment writes lead_score/priority/score_reasoning. Every section now
describes the actual UI: the grid with group headers, per-row run buttons, and
the 21-running toolbar; the Configure workflow panel (picker, column inputs,
output selection, Auto-run, Run after); the Company Info input/output mapping;
Not found cells explained where the screenshot shows them; the cascade section
describes the example itself. All placeholder markers on the page resolved.

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

* docs: regenerate after staging merge — Slack trigger update + file block re-visible

Staging's mothership v0.2 (#4923) expanded the Slack trigger payload
(interactivity, slash commands: event_type, command, action_id/value/actions,
response_url, trigger_id, callback_id, ...) — regenerated so it lands on the
unified integrations/slack page; the old-layout triggers/slack.mdx from
staging's generator was dropped in the merge. The file block is visible again
upstream, so integrations/file.mdx is back in the catalog.

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

* docs(tables): playbook prose pass on workflow columns + restore File block links

Workflow columns, against the docs-writing playbook: killed the banned
'Term — desc' bullets in the Configure list (term + verb form), restored the one
universal analog (spreadsheet macro), fixed the clipped 'On,/Off,' fragments,
replaced an invented <start.companyDomain> tag with the verified description,
and thinned em-dashes to four page-wide with no clustering. Also repointed
[File] block mentions back to /integrations/file now that the page exists again
(FileV5 is visible upstream); the Files-store links stay on /files.

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

* docs(tables): per-row execution inspection on workflow columns

Two new captures: the cell menu (View execution, Re-run cell, row actions) and
the Log Details trace for a single row's run. New 'Inspecting a row's run'
section ties cell values to real, traceable runs; corrected the re-run guidance
now that Re-run cell exists (the page previously said Run all rows was the only
way to retry).

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

* docs(workflows): drop the confusing 'order by hand' sentence

'You never set the order by hand' read wrong (wiring connections is setting it
by hand), and the replacement was over-explanation. The first sentence already
carries it: Sim works out the order from the connections.

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

* docs(workflows): fix the over-claim about independent blocks

'Two blocks that don't depend on each other run at the same time' is wrong —
independent blocks at different depths run at different times. Concurrency
follows from readiness, not independence: blocks whose dependencies have all
finished run together. Reworded to say that, tied to the image's two agents.

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

* docs(workflows): accuracy audit of how-it-runs against the executor

Verified every claim on the page against apps/sim/executor. One claim was
materially false: "a failed block stops its own path but leaves independent
paths running" — in the engine, an unhandled block failure sets the error flag
and stops scheduling entirely (in-flight blocks finish, nothing new starts);
only a connected error port routes the failure and keeps the run alive. Now
says that. Two imprecisions tightened: a join waits for every feeder *that is
going to run* (deactivated-branch feeders don't hold it up, per the
edge-manager cascade), and Loop also repeats while a condition holds. Confirmed
accurate: per-block readiness scheduling (readyQueue + race, not layers),
branch-skip cascade and empty tags, the 25-hop call-chain cap.

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

* docs(logs): real captures on the overview + prose matched to the UI

The logs-debugging overview had six visual placeholders and no visuals. Three
real captures placed: the workspace Logs page as the hero (rows with status,
credits, trigger, duration), Log Details' Trace tab at the blocks section (the
CRM sync run's spans, with a one-line read of where the time went), and the
editor's live run console at the input/output section. Prose corrected to what
the UI shows: cost is in credits, failed runs are badged Error (dropped the
five-state enum the list doesn't display), and the Trace tab is named. The
row-anatomy marker is covered by the hero; the two designed-diagram markers
(debug-loop flowchart, failed-vs-success comparison) remain.

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

* docs(workflows): one reference syntax, named sources — untangle variables vs connection tags

An exhaustive sweep of "connection tag" found the docs asserting both that a
workflow variable is a connection tag (response.mdx used it as the umbrella for
all angle-bracket references) and that it isn't (variables.mdx). Ruled the
narrow definition canonical — a connection tag reads a block's output; the name
follows the connection — and restructured around the real model:

- variables.mdx: new "One syntax, named sources" section states that everything
  in angle brackets is one mechanism whose first segment names the source, with
  the load-bearing fact stated plainly: `variable` is literal, a connection tag
  starts with the block's own name. The syntax table drops the redundant
  dot-notation row, gets one row per source, and is ordered by resolution
  precedence with the order explained beneath it (absorbing the old Name
  conflicts section). The credentials pointer folds into the env-var section;
  trimmed the "never appears in outputs" overclaim.
- response.mdx: no longer calls a workflow variable a connection tag.
- connections.mdx: the owner page closes the loop — same syntax also reads
  variables and loop/parallel context; a connection tag is the block-output case.

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

* docs(workflows): verify the reference model against the resolver; fix one imprecision

Checked every claim in the new 'One syntax, named sources' section against
apps/sim/executor/variables: resolver chain order is Loop -> Parallel ->
WorkflowVariables -> Env -> Block (matches the table); 'variable'/'loop'/
'parallel' are literal prefixes (REFERENCE.PREFIX); block names normalize via
toLowerCase + strip spaces; an unmatched reference is genuinely left in place
(resolver returns undefined -> the replacer emits the raw match). One claim
tightened: {{KEY}} is a different syntax and can never collide with
angle-bracket references, so the precedence sentence now scopes collisions to
the angle-bracket sources with a concrete example (a block named 'variable').

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

* docs(building-agents): workflow-as-tool is agent-decided, not the Workflow block

The choosing page defined workflow-as-tool as the Workflow block (path-decided),
contradicting its own name and the comparison table's premise. Verified against
the product: workflow_executor is an agent tool — you pick the workflow in the
Agent block's tool list, the model decides when to call it and supplies the
inputMapping (user-or-llm), inputs arrive at the child's Start trigger.

Rewritten agent-first: the section defines it as a workflow handed to an agent
as one callable tool, the lead scorer gains a Deep Enrich workflow tool chip on
the agent (diagram updated), and the deterministic Workflow block becomes the
explicit contrast in a callout — same child workflow, the difference is who
decides, mirroring the block/agent-tool contrast. Table row corrected to
"The agent"; the summary paragraph follows.

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

* docs: theme-aware previews + enrichments vs workflow groups split

Light-mode support for every preview component (WorkflowPreview canvas, nodes,
containers, edges, lightbox, BlockPreview, OutputBundle, BlockInspector): a
wp-scope token block in the docs global stylesheet whose values mirror the OG
repository's globals.css in both modes (surfaces, borders, --workflow-edge,
text tiers, the --badge-* type-badge palette). Every hardcoded hex swapped to a
--wp-* var; brand colors, selection blue, and error red stay literal.

tables/workflow-columns: separated the two group kinds per the contract's
workflowGroupType enum ('manual' | 'enrichment'). New "Two kinds of groups"
section opens with the + New column menu capture (Enrichments above the types,
Workflow below); Enrichments documented from the code-defined registry (company
domain, company info, email verification, phone number, work email) including
the provider-cascade behavior that produces Not found cells; the Company Info
panel capture is now correctly labeled as an enrichment config; workflow groups
keep the Configure workflow panel. Shared machinery generalized under "How
groups run"; the cascade section names which stage is which kind; the two
portrait screenshots render smaller.

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

* docs(tables): don't enumerate the enrichment catalog; don't assert a group's kind

Two corrections on workflow columns: the prose no longer lists the enrichment
catalog (growable, not procedurally tracked — it now describes the category and
points at the Enrichments panel; the provider-cascade/Not-found explanation
stays, it's behavior not catalog), and the page no longer asserts which kind
the example's Company Domain / Company Info groups are (Company Info may be a
user-built workflow, not the built-in). The input/output bindings capture moved
to "How groups run" as the kind-agnostic illustration; only Lead Score — whose
panel shows the workflow picker — is named as a workflow group.

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

* docs(components): per-branch source handles — conditions and routers finally branch

WorkflowPreview's node only ever had one header source handle, so every
condition/router example fanned both edges out of a single point and never
showed the if/else rows the real canvas (and the BlockPreview hero specs)
render. PreviewBlock now supports `branches` (each rendered as a row with its
own right-edge source handle, id `branch-<id>`) and `showError` (red error
handle), mirroring the executor's per-branch condition-true/condition-false and
router-<route> handle model. A block with branches emits from them, not the
header.

Every affected example rewired (13 workflows): the three condition examples,
status-per-branch, credential routing, and the webhook-trigger check route
their edges through branch-if/branch-else with the expression on the If row and
an explicit else; the three router examples list their actual routes as branch
rows (Sales/Support/Billing, Product/Bug report, Enterprise/Self-serve); the
terminal gates (variables retry, evaluator gate, the three guardrails gates)
show dangling if/else branch rows like the canvas does.

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

* docs(components): inspector shows branch rows

Moving condition expressions from rows into branches emptied the lightbox
inspector for condition/router blocks — it only mapped rows to fields. Branches
now map too: each branch renders as a field (If with its expression as code,
else as an empty control, router routes by name).

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

* docs(components): branch handle ids match the app's workflow representation

Verified against the source after the branch-handles work: the canvas emits
condition-${cond.id} handles per condition row (workflow-block.tsx) and Router
V2 uses router-${routeId} port handles, and edges carry those ids as
sourceHandle — the docs' invented branch- prefix was a gratuitous divergence
that the planned fromWorkflowState() adapter would have had to translate. The
node now uses the authored branch id as the handle id directly, and every
example authors ids in the app's own scheme (condition-if/condition-else,
router-<route>), so example edges now match real workflow edges verbatim.

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

* docs: agent skills mint /integrations/ docs links and describe the new output

The add-integration/add-block/validate-integration skills — what Claude Code
follows when integrations land on staging — still taught the old layout:
docsLink templates pointing at docs.sim.ai/tools/{service} and 'generates
tools/{service}.mdx'. Updated so that once this PR merges, the instructions on
staging produce the new way by themselves: /integrations/ docsLinks, the
per-service page description, and the don't-hand-edit/manual-content pointer.

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

* docs(workflows): execution semantics, not simultaneity

The concurrency section drifted into 'run at the same time' framing across two
accuracy passes — but the semantics are non-blocking execution: a block starts
the moment its dependencies finish and waits on nothing else. Section retitled
'Blocks run as soon as they can', the rule stated in two plain sentences, the
duplicated pre-image example narration gone (the post-image caption carries it).

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

* docs(workflows): errors are execution semantics — own section on how-it-runs

Failure behavior was buried inside 'Watching a run' (the live-UI section). Now
a first-class 'When a block fails' section in the execution story: an error
fails the run (in-flight blocks finish, nothing new starts) unless the block's
error port is connected, in which case the run follows the error path.

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

* docs: data-driven additions from the platform-metrics read

Three targeted edits from the sim-internals analysis, each carrying an inline
{/* why */} provenance comment so future editorial passes know the data behind
it:

- workflows/how-it-runs gains "How long a run can take" — run timeouts are the
  only hard-error class provable at scale (2,415 five-minute timeouts in 14
  days); limits verified in lib/core/execution-limits/types.ts (5 min free /
  50 min paid sync, 90 min async, env-overridable).
- getting-started gains an "if the run doesn't go green" callout at the Test
  step — the largest funnel drop is created-workflow -> first-successful-run
  (92% -> 49%), and this is the stall point.
- function/api Best Practices: the existing error-path bullets get a guard
  comment (<1% of deployed workflows connect an error port — under-adopted,
  not under-needed) instead of duplicate bullets.
- visuals manifest: capture priority reordered by integration adoption
  (Sheets, Gmail, Telegram, WhatsApp, ...).

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

* docs: regenerate after staging merge (integration validation batch + Gong tools)

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

* docs: rename Building agents -> Agents; URLs match the settled IA

The section's pages now live where the sidebar says they do:
building-agents/ -> agents/, and the stray top-level /mcp and /skills fold in
as /agents/mcp and /agents/skills (they were always part of the agents story —
the URLs predated the IA settling). Sidebar section header is now "Agents",
link labels updated, and every old URL 308s: /building-agents(/*) -> /agents(/*),
/mcp, /skills, plus the existing capabilities/ and tools/custom-tools redirect
destinations retargeted. Verified: all five new pages render and every old-URL
class redirects correctly.

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

* docs(workflows): connections gets its video, an FAQ, and accurate output examples

The reorg dropped two things from the old tags page that belonged on the
connections reference: the connections.mp4 walkthrough (restored after the
intro) and the FAQ (rebuilt in the robust JSX form — resolver order, name
normalization, env-var syntax pointer, didn't-run behavior, array indexing,
Function-block formatting; answers aligned with the since-verified resolver
facts, including unmatched-references-left-in-place).

Editorial/accuracy pass on the output-shape tabs while in there: stale gpt-4o
and gpt-5 examples now claude-sonnet-4-6, the Agent tokens shape corrected to
the verified { input, output, total } (the page contradicted blocks/agent), and
the dubious cost: [] line dropped — the example now matches the real run
inspector.

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

* docs: regenerate after staging merge — sim trigger, enrichment + logs blocks, re-shown DB integrations

Staging's #4941 added the Sim workspace-event trigger (hand-written page adopted
into Core Triggers), the Enrichment and Logs blocks (category 'blocks' — added
to NATIVE_RESOURCE_BLOCK_TYPES so they live in the integrations catalog like
table/knowledge/memory), and re-categorized mysql/postgresql/sftp/smtp/ssh back
to visible tools (their pages return to the catalog). Generator sets merged as
the union of both sides (sim in HANDWRITTEN_TRIGGER_DOCS + SKIP_TRIGGER_PROVIDERS,
enrichment in the icon allowlist).

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

* docs: regenerate after staging merge (CodePipeline); suppress sim trigger from catalog

The native Sim workspace-event trigger is documented at triggers/sim — the
block writer no longer emits an integrations page for it (skip + canonical-set
exclusion). CodePipeline (#4945) lands in the catalog in the Actions format.

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

* docs(blocks): cross-link the Memory block from the Agent memory section

Final loss audit found the old page's pointer from built-in agent memory to the
standalone Memory block had been dropped; one line restores it.

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

* docs: URLs now mirror the sidebar — sections own their pages

Every page lives at a path matching its meta.json section, done now while none
of these URLs are publicly live (the last free window before merge):

- Workflows owns its accordions: /blocks/* -> /workflows/blocks/*,
  /triggers/{start,schedule,webhook,rss,table,sim} -> /workflows/triggers/*,
  /deployment/* -> /workflows/deployment/*
- Mothership owns Mailer: /mailer -> /mothership/mailer
- Workspaces & Access folds into Platform, sequenced concept-first with the
  reference tail last: /platform/{workspaces,organization,permissions,
  credentials,costs}, then platform/self-hosting/*, platform/enterprise/*
  (from /workspaces/fundamentals+organization, /permissions/roles-and-
  permissions, /credentials, /costs, /self-hosting/*, /enterprise/*)

All internal links swept (0 broken in a full-tree resolver sweep), root
meta.json repointed, and every previously-live URL 308s to its new home —
including retargeted destinations of existing redirects so chains stay
single-hop (verified: /execution/chat reaches /workflows/deployment/chat in
one hop), and the native-trigger rule ordered after the enumerated
integration-trigger redirects so /triggers/gmail still reaches
/integrations/gmail. Production build passes.

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

* docs: untrack .plans/ (local agent planning files)

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

* docs(preview): tool chips use the EMCN ChipTag chrome

The canvas previews' tool chips were ad-hoc (5px radius, header surface, plain
border). The app's canonical chip chrome is the ChipTag family: 20px tall,
rounded-md, px-1, gap-1.5, --surface-5 light / --surface-4 dark with an inset
--border-1 ring and --text-body label. Mirrored those values into --wp-chip-*
tokens (both modes) and restyled the chip; the integration's brand-color icon
square stays, sized to the chip.

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

* chore(blocks): one-time shift of all docsLinks to the new docs URLs

Block definitions are the patterns coding agents copy from, so redirects alone
leave new blocks minting dead conventions. Every docs.sim.ai link in apps/sim
now points at the final URL scheme: /tools/<slug> -> /integrations/<slug>
(433 links), /blocks/<core> -> /workflows/blocks/<core> (knowledge/enrichment/
logs -> /integrations/*), native /triggers/* -> /workflows/triggers/*,
/mcp -> /agents/mcp, /self-hosting + /enterprise -> /platform/*, plus the
llms.txt listings and the blocks.test.ts assertions.

Verified every rewritten target against the docs tree: all resolve except ten
hidden blocks (vision, spotify, thinking, tts...) and a2a whose links were
already dead pre-reorg — no regressions introduced.

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

* docs: ignore .plans/ (local agent planning files)

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

* docs(files): align every File-block claim with the shipped file_v5 block

Accuracy audit against apps/sim/blocks/blocks/file.ts (FileV5Block, the visible
block) and tools/file/*:

- The block has FIVE operations, not four — Get Content was missing entirely.
- Read outputs file objects only; the page claimed it also returned extracted
  text. Text comes from Get Content (contents, per file) or Fetch
  (combinedContent) — table, prose, and the Fetch callout corrected.
- Functions CAN read files: sim.files.readText/readBase64 exist in the sandbox
  (isolated-vm-worker.cjs), so "doesn't reach into workspace storage" is gone;
  the section now teaches Get Content text or sim.files on the file object.
- Workspace file IDs are wf_<shortId> (workspace-file-manager.ts:511), not f_.
- Stale "such as Claude or GPT-4o" vision parenthetical dropped.
- "File block reference" card pointed at /files (the section overview); now
  /integrations/file.
- FILE_SUMMARY example agent consumed <file.combinedContent>, which Read never
  produces — now binds the file object to the Files input.
- passing-files.mdx: combinedContent scoped to Fetch, contents documented.

Verified intact: Write's numeric-suffix collision behavior, Fetch's auth
headers, Append-by-name, and the file-object shape.

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

* docs: keyboard-shortcuts audited against the command registry; cut legacy workspace detail

Every binding verified against commands-utils.ts (the global registry),
workflow.tsx, and table-grid.tsx. Three fixes: tables Mod+A (select all rows)
doesn't exist — the real bindings are Shift+Space (select row, was misworded
as a toggle) and the undocumented Mod+Space (select column); the global
Mod+Shift+A row conflated two commands — add-agent (Mod+Shift+A) and
add-workflow (Mod+Shift+P) are separate. All 29 other documented shortcuts
confirmed accurate, including tables clipboard (native copy/cut/paste events)
and Mod+Y redo (tables only — correctly absent from the workflow editor
section).

Also drops the grandfathered_shared workspace paragraph — internal billing
taxonomy, not something a reader can act on.

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

* docs: apply Theodore's accuracy feedback

- getting-started: workflow creation is the + button next to Workflows in the
  sidebar (no "New Workflow" button exists); Exa/Linkup no longer need
  user-supplied API keys on hosted Sim (apiKey is hideWhenHosted in the Exa
  block) — step and FAQ updated.
- workflows overview: chat and API are entry points of the Start trigger, not
  separate triggers — the "swap in a chat/API trigger" sentence now matches
  triggers/start's own model.
- variables: names cannot contain periods — the resolver reads everything
  after the first dot as a path into the value (executor/variables/resolvers/
  workflow.ts splits on dots) — constraint now stated where name normalization
  is taught.

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

* docs: Python sandbox package list (verified) + agent/agents cross-linking

Function block: the Python callout's "common packages like matplotlib" becomes
the actual package list, grouped by use. Sources verified 2026-06-10 and cited
in an inline provenance comment: E2B's code-interpreter template requirements
(the base Sim's mothership-shell template builds from) plus Sim's three pip
additions (awscli/yq/csvkit, per the copilot repo's template.ts via
sim-internals). Versions omitted so the list doesn't rot on routine bumps.

Agent surfaces deduplicated by direction: blocks/agent's Tools section now
links custom tools and MCP and points at the Agents concept page for tool
sourcing; agents/index drops its duplicated Auto/Force/None enumeration in
favor of the block reference, which owns config mechanics.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 18:39:57 -07:00
Theodore Li 1ff445ae80 feat(codepipeline): add AWS CodePipeline integration with tools and block (#4945)
* feat(codepipeline): add AWS CodePipeline integration with tools and block

* fix(codepipeline): address review feedback on input coercion and error statuses

* chore(hooks): restore use-inline-rename onSave type accidentally swept into previous commit
2026-06-10 14:51:24 -04:00
Vikhyath MondretiandCursor 6abcf82db2 feat(workflows): sim trigger, logs v2 block, toolbar renaming (#4941)
* feat(workflows): sim trigger, logs v2 block, toolbar renaming

* fix(review): bound rule queries, canonical logs params, watched-workflow SQL scoping

Code-review fixes: read the canonical workflowIds param in logs_v2 (the
serializer deletes the source pair ids), aggregate failure-rate in the DB
and switch rule windows to the indexed startedAt column, clamp rule config
to the legacy contract bounds, push no_activity watch scoping into SQL
before the LIMIT, fix the generated sim icon-map key, normalize docs
wording, and drop dead exports.

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

* address comments

* fix(review): integer rule rounding, success-gated workflow labels, display module hygiene

Second-pass review fixes: round integer rule fields so fractional input
never reaches SQL LIMIT, gate workflow-name readiness on a successful
non-placeholder load in both editor and preview (errored loads mislabeled
valid workflows as deleted), lazily read the variables store in preview
rows, move the filter-field JSON preview into the shared display module
and unexport its single-consumer helpers, and align >= boundary copy
(failure rate, error count, cooldown window) with implementation.

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

* chore: sync lockfile after staging merge

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

* fix(workspace-events): keyset-paginate the no_activity subscription scan

A fixed LIMIT 500 with no ORDER BY silently starved subscriptions beyond
the cap once the global count exceeded it. The poll now pages by webhook
id so every subscription is visited each cycle; pagination bounds memory,
not total work.

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

* fix(workspace-events): keyset-paginate the watched-workflow scan

The 500-row LIMIT silently and deterministically excluded high-id
workflows from no_activity coverage in watch-everything subscriptions on
large workspaces. The scan now pages by workflow id, mirroring the
subscription scan; per-workflow checks move into a helper so the
pagination loop stays flat.

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

* fix(workspace-events): skip no_activity subscriptions on the execution-completion path

no_activity is poller-owned and can never fire from a completed execution,
but it passed into the rule branch and cost a pointless cooldown point-read
per subscription on the hottest path. Early-continue alongside the
workflow_deployed guard.

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

* docs(sim-trigger): note failure-based alert conditions evaluate on failed runs

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

* fix(blocks): recategorize Data Enrichment as a core block

It's a Sim-native capability (registry enrichments over a managed provider
cascade, like Search), not a third-party integration. Moves it to Core
Blocks in the toolbar, out of the integrations catalog, and relocates its
docs page to blocks/ with the icon-map allowlist keeping the docs card icon.

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

* fix(blocks): recategorize MySQL, PostgreSQL, SFTP, SMTP, SSH as integrations

External-system connectors with host/credential auth belong under
Integrations, not Core Blocks — consistent with MongoDB, Redis,
ClickHouse, and the other datastore integrations. They already carried
integrationType and /tools docsLinks; the regenerated docs pages turn
those previously-dangling links into real pages, and the blocks join the
integrations catalog and icon maps.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 11:04:12 -07:00
+1 39418f9615 improvement(mothership): v0.2 (#4923)
* CONTRACTS

* updates

* Prompt caching

* Fix regression

* updates to prompt caching

* Prompt caching trace

* VFS updates

* Changelog and plan

* VFS update

* Improvement/mothership (#4775)

* ff

* System role in cache

* Fixes

* Add dynamic user info

* Improve tool search

* improvement(platform): workspace UI/UX overhaul + integrations catalog

Rework the workspace around the AI-workspace model: a Mothership home, a
top-level Skills route, connected-credential and integration-detail pages,
and a polished sidebar/settings surface. Replace the notifications store
with a unified toast system (provider-level dismiss/pause, countdown ring).

Integrations & catalog:
- Add a BlockMeta layer (tags + catalog templates) scoped to catalog-visible
  integrations; every catalog integration carries >=7 grounded templates.
- Rework the taxonomy: each block declares category tools|blocks|triggers.
  3rd-party services are 'tools'; first-party primitives (postgres, mysql,
  knowledge, file, search, stt/tts, image/video generators, thinking, etc.)
  are 'blocks'. Versioned blocks follow the upgrade paradigm (old hidden,
  latest in toolbar/docs).
- Generate integrations.json + tool docs canonically from block configs.

Architecture & cleanup:
- Consolidate block data extraction behind a single latest-version strategy
  (getCanonicalBlocksByCategory; version-consistent getBlockMeta).
- Unify version-suffix handling in @sim/utils/string (stripVersionSuffix /
  isVersionedType, with tests); registry, generate-docs, tools/utils, and
  integrations all route through it.
- Repair latent broken barrels, remove dead code, fix BlockMeta-related type
  errors and 5 broken docs links.

Behavior-preserving for block execution and the toolbar's tool/block listing.

* refactor(platform): remove forms, templates, and creators features

Remove three standalone features and their supporting code:
- Forms: form-deployment pages, API routes, execution path, and docs.
- Templates: the template gallery (landing + workspace) and template APIs.
- Creators: creator-profile routes and contracts.

Add a super-user permissions module (lib/permissions/super-user) and an
organizations API contract; update the audit/db/testing packages, billing,
and the session/theme providers accordingly.

* New doc setup

* Fixes

* Fixes

* Logs tools

* test(workflows): update archiveWorkflow update count after forms removal

The forms feature was removed, dropping the form-table update from
archiveWorkflow. Update the stale assertion from 8 to 7 tx.update calls.

* Add finalization on error

* Fixes

* change dev CI to bun run db:push

* Updates

Please enter the commit message for your changes. Lines starting

* Contrat update

* improvement(nested): subagents

* updates

* upgrade

* improvement(knowledge): polish tag filter dropdowns (#4816)

* improvement(logs): object storage backed tracespans (#4787)

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

* improvement(providers): harden OpenAI-compatible providers + add tests (#4796)

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

* chore(copilot): deprecate mcp server (#4797)

* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza (#4777)

* 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

* fix(tables): reduce column header chevron size and fix sidebar shadow bleed (#4800)

* feat(slack): add install + privacy section to integration landing page (#4799)

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

* improvement(enrichments): align enrichments sidebar with design system (#4801)

* improvement(enrichments): align enrichments sidebar with design system

* fix(enrichments): consistent close button pattern and fix url link hover

* fix(misc): upgrade path change for new better-auth version, billing issue for workflow block agent usage (#4803)

* fix(misc): upgrade path change for new better-auth version, double-billing for workflow block agent usage

* fail loudly if stripe sub id missing

* fix(copilot): seq migration (#4804)

* chore(db): drop redundant idx_webhook_on_workflow_id_block_id index (#4809)

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.

* perf(copilot): read chat transcripts from copilot_messages (R+1 cutover) (#4808)

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

* fix(tables): right-align run/stop in embedded toolbar; workflow cells format like normal cells (#4806)

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

* fix(icons): repair broken integration icon rendering (#4810)

* 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): serialize schema mutations to prevent parallel column clobber (#4812)

* 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(wait): resume live/draft async waits and preserve cell context on chained waits (#4814)

* Make workflow description nullable

* fix(wait): resume live/draft async waits and preserve cell context on chained waits

* improvement(knowledge): polish tag filter dropdowns

* improvement(knowledge): soften filter section labels

* improvement(knowledge): soften list filter labels

* fix(security): harden SSO domain registration, webhook path isolation, and CSV export (#4813)

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

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF (#4818)

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF

* test(security): use real isPrivateOrReservedIP and cover IPv6 edge cases

* improvement(integrations): validate and expand devin, cursor, and greptile (#4820)

* 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(search-replace): don't auto-navigate when content edits invalidate the active match (#4819)

* 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(enrichments): limit company-info to fields both providers return (#4817)

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(files): don't reject external URLs containing '..' in file parse validation (#4821)

* 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

* feat(google-sheets): add row filtering to read with numeric operators (#4822)

* 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(selectors): fetch all pages for paginated dropdown list routes (#4823)

* 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

* fix(sso): re-check domain conflict before write and reject IP-address domains (#4825)

* improvement(copilot): make copilot_messages the sole transcript store, remove JSONB dual-write (#4826)

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.

* feat(tables): expand filter operators (not-contains, starts/ends-with, not-in, empty) (#4827)

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.

* improvement(copilot): stop persisting tool-call result outputs in transcripts (#4829)

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>

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)

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

* fix(knowledge): calendar view sync, deduplicate popover animation classes, type-safe filter cast

* cleanup(knowledge): remove TRIGGER_BORDER_CLASS duplication, inline displayLabel, drop enabledFilterParam alias

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>

* feat(blocks): add BlockMeta to Quiver and Linq; fix invalid block config fields; update skills

Block fixes:
- Add QuiverBlockMeta (tags + 3 templates: icon generator, diagram creator, vectorizer)
- Fix QuiverBlock: remove invalid tags field from BlockConfig, IntegrationType.Design →
  IntegrationType.AI (Design doesn't exist in the enum)
- Fix GreptileBlock: remove invalid tags field from BlockConfig,
  IntegrationType.DeveloperTools → IntegrationType.DevOps
- Fix LinqBlock: remove invalid tags field from BlockConfig (tags belong only in BlockMeta)

Skills:
- add-block: add dedicated BlockMeta section with structure, rules, and registration
  pattern; add BlockMeta checklist items
- add-integration: add BlockMeta to block structure template, add rules clarifying
  that tags must NOT appear on BlockConfig and integrationType must be a valid enum
  value; update registry snippet to include blocksMeta; add checklist items

* fix(integrations): fix category dropdown by defining missing LANDING_INTEGRATIONS_DATA_PATH and regenerating integrations.json

The staging merge introduced landing-content.ts but forgot to define
LANDING_INTEGRATIONS_DATA_PATH in generate-docs.ts, causing the script
to crash before writing integrations.json.

The stale JSON had integrationTypes (plural array) from an older script
version, while the Integration type and workspace UI both read
integrationType (singular string) — so ALL_CATEGORY_SECTIONS bucketed
to undefined and the category filters never appeared in the dropdown.

Fixed by adding the missing path constant and re-running the generator.
integrations.json now has 192 entries with the correct integrationType field.

* fix(sidebar): restore resize handle on all pages

commit 3109104582 wrapped the resize handle in {(isCollapsed ||
isOnWorkflowPage) && ...} and added a useEffect that resets sidebar
width to SIDEBAR_WIDTH.MIN whenever the user navigates away from a
workflow page. Together these made the sidebar non-resizable on Tasks,
Tables, Knowledge Base, and every other non-workflow page.

Restore the staging behavior: always render the resize handle and
remove the effect that forced the width reset on page transitions.

* fix(sidebar): match staging onKeyDown and tabIndex on resize handle

The resize handle was still conditionalizing onKeyDown and tabIndex
on isCollapsed, blocking keyboard accessibility of the separator role
when expanded. Staging always attaches both unconditionally.

onKeyDown={isCollapsed ? handleEdgeKeyDown : undefined} → onKeyDown={handleEdgeKeyDown}
tabIndex={isCollapsed ? 0 : undefined}                 → tabIndex={0}

* feat(integrations): show connected credentials on integration detail page

When navigating to /integrations/google-docs (or any integration), a
Connected section now appears above the templates listing all workspace
credentials tied to that provider. Each row links back to the credential
detail page (/integrations/connected/${id}) for management actions.

Pairs with the earlier change that routes connected items from the
integrations list to the provider detail page instead of directly to
the credential detail page.

* fix(integrations): rename Add in chat to Add to Sim

* fix(skills): rename Add button to Add to Sim

* fix(platform): restore M1/M2/M3 regressions and LazyMotion on landing page

M1 — Invitation guard: re-introduce usePermissionConfig().isInvitationsDisabled
alongside the workspace inviteDisabledReason check. The flag now also
respects NEXT_PUBLIC_DISABLE_INVITATIONS and EE permission-group
disableInvitations, not just billing policy.

M2 — Settings redirects: /settings/integrations and /settings/skills
now server-redirect to /integrations and /skills respectively so old
bookmarks and emails don't silently land on General.

M3 — Starter block search exclusion: restore block.type !== 'starter'
guard in the search store so users cannot add a duplicate Starter block
via the command palette.

LazyMotion: restore LazyMotion + domMax/domAnimation wrappers and m.*
components in landing-preview-panel and landing-preview-home. The
removal was accidental (the full motion bundle was left after an
import cleanup), which caused the entire framer-motion feature set to
load eagerly on the landing page.

* fix(integrations): revert connected list to credential detail; remove settings redirects

* feat(sidebar): restore workspace switcher search with updated styling

Shows a search input in the workspace dropdown when the user has more
than 3 workspaces (WORKSPACE_SEARCH_THRESHOLD). Keyboard navigation:
ArrowDown/Up to move through results, Enter to switch, resets on close.

Styled to match the current branch (border-1/surface-5 tokens, sm text,
11px Search icon) rather than the old staging styles. Highlight state
is wired through chipVariants active prop so it follows the same active
appearance as clicked/hovered items.

* fix(sidebar): clean up workspace search — layout, memo, and effect guard

* fix(sidebar): align workspace rename input selection style with workflow rename

* perf(sidebar): eliminate React re-renders during sidebar drag resize

Previously, every mousemove during resize called setSidebarWidth(), which
both updated the --sidebar-width CSS variable (sync) and set Zustand state
(async). This caused:
  1. A 1-frame transition flash on mousedown — isResizing state had to
     round-trip through React before the is-resizing CSS class was applied,
     so the width transition fired for the first pixel of movement.
  2. A React re-render per pixel dragged — components reading sidebarWidth
     from the store (avatars, usage-indicator) lagged one frame behind the
     container, making the + and ... buttons appear to jump ahead.

New approach:
  - handleMouseDown adds is-resizing directly to the sidebar DOM node before
    any React involvement (synchronous, no frame lag).
  - mousemove writes only to the CSS custom property (zero React renders).
  - mouseup persists the final width to Zustand exactly once.
  - isResizing / setIsResizing state removed from the store and hook — they
    are no longer needed since the class is managed via direct DOM mutation.

* perf(sidebar): add requestAnimationFrame throttle to resize mousemove handler

* fix(sidebar): fix drag-right lag caused by WorkspaceChrome overflow-hidden transition

The sidebar-container's is-resizing class correctly suppressed its own width
transition, but the two wrapper divs in WorkspaceChrome both have
transition-[width]/transition-transform with a 175ms ease. The outer wrapper
also has overflow-hidden, so while the sidebar content was at the correct width
instantly, it was visually clipped by the outer wrapper which was still
animating — causing the + and ... buttons to appear to lag behind the resize
line on drag-right (not on drag-left, since shrinking doesn't clip content).

Fix: add sidebar-shell-outer/sidebar-shell-inner class names to both chrome
wrappers, and suppress their transitions via html.sidebar-resizing rule when
a drag is active. The html.sidebar-resizing class is toggled directly in the
resize hook alongside is-resizing, so it takes effect synchronously on mousedown.

* fix(icons): redesign Download icon to match Upload style; fix Upload/Download confusion

Download icon was missing the tray/shelf line at the bottom that Upload has,
making it look like a plain arrow rather than a matched pair. Updated Download
to use the same viewBox, stroke weight, and three-path structure as Upload
(tray + stem + arrowhead), just pointing down.

Also fix 5 places where Upload (↑) was incorrectly used for download/export
actions:
  - files.tsx: two Download action rows in toolbar and context menu
  - tables/table.tsx: Export CSV toolbar button
  - table-context-menu.tsx: Export CSV context menu item
  - logs.tsx: Export toolbar button
  - landing-preview-logs.tsx: decorative Export button

Import CSV and actual upload actions correctly keep the Upload icon.

* fix(icons): replace Upload with Download on all remaining export/download actions

- panel.tsx: Export workflow dropdown item
- context-menu.tsx: Export in sidebar workflow context menu
- chat.tsx: Export chat button
- output-panel.tsx: Export console CSV button
- terminal.tsx: Export console CSV button
- resource-content.tsx: Export table as CSV + Download file buttons

* fix(icons): fix remaining Upload→Download on download actions in files and logs

- action-bar.tsx: download button in files toolbar
- file-row-context-menu.tsx: Download item in file context menu
- file-download.tsx: both download buttons in log details file viewer

* updated block skills, settings pages, modals, buttons -> chips, blocks missing metadata

* updated skill modal

* improvement(resource-header): refine breadcrumb truncation ux

* Fixes

* improvement(resource): add floating overflow text tooltips

* wire up credits counter

* improvement(resource-header): mute path dropdown title

* refactor(resource-header): share floating-tooltip engine, prune dead overlay tooltips (#4844)

Clean up the breadcrumb truncation feature for reuse and correctness:

- Extract useFloatingTooltip / useIsOverflowing / FloatingTooltip into a shared
  floating-tooltip module. BreadcrumbSegment and FloatingOverflowText now consume
  one implementation instead of duplicating ~150 lines of positioning, velocity,
  overflow-detection, and portal logic.
- Replace the hardcoded terminal-label regex in ResourceHeader with a typed
  `terminal` flag on BreadcrumbItem (set by the document chunk/loading crumbs),
  decoupling the generic header from knowledge-base copy.
- Clear the path-popover close timeout on unmount and reuse the shared
  POPOVER_ANIMATION_CLASSES constant.
- Drop the redundant manual overflow-state writes (fixes a sticky fade mask).
- Revert FloatingOverflowText inside Combobox `overlayContent` back to plain
  truncating spans across files/logs/tables/scheduled-tasks/document: the combobox
  overlay is pointer-events-none, so the tooltip handlers never fired there.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(emcn,resource-header): address PR #4844 review feedback

- useIsOverflowing now uses a callback ref so the ResizeObserver follows the
  element across mount/unmount/reassignment instead of capturing it once at mount.
  Safe for conditionally rendered consumers of the shared hook. (greptile P2)
- Move POPOVER_ANIMATION_CLASSES out of chip-date-picker implementation internals
  into emcn/components/popover/popover-animation.ts, exported from the
  @/components/emcn barrel. Consumers now import from the module boundary.
  (greptile P2)

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

* Prompt surface

* Regenerated contracts

* upgrade table and styling upgrade

* Literalness gaps

* Updates

* Remove prefix for running code

* Display names for reads

* Tool catalog

* fix schema to include integration

* fix(files): align delete icon with tables view (Trash → Trash2)

Co-Authored-By: waleed <waleed@simstudio.ai>

* fix(mothership): preserve blockType for integration contexts in sent messages

Integration mention chips were missing their provider icons in sent messages
because blockType was dropped when mapping ChatContext to messageContexts.
renderIntegrationTile returns null without blockType, silently hiding the icon.

* fix(mothership): allow 'integration' resource type in chat resources API

The VALID_RESOURCE_TYPES allowlist was missing 'integration', causing a
400 error when adding integrations to the Mothership resource tab — so
they never persisted and disappeared on refresh.

* fix(ui): Add "File" title next to file resource header

* fix(ui): fix resource header columns being bolded

* fix(resource): keep the floating tooltip from jumping on click

Gate the focus-driven show behind :focus-visible so a mouse click (which
focuses the trigger) no longer re-shows the tooltip anchored to the element's
bottom edge. On click the tooltip now hides cleanly instead of jumping down;
keyboard focus still shows it.

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

* perf(sidebar): eliminate unnecessary re-renders in workspace switcher for non-search users

- onMouseEnter: only set highlightedIndex when showSearch is true, preventing
  a state update + re-render on every workspace row hover for users with ≤ 3
  workspaces where the search is never shown
- onOpenChange: only reset workspaceSearch and highlightedIndex when showSearch
  is true, since both values are always already at their defaults for non-search
  users and setting them triggers a pointless re-render during dropdown close
- data-workspace-row-idx: only set when showSearch is true since the scroll
  effect that reads this attribute is already gated on showSearch

* feat(search): context-aware cmd-k results on the integrations page

When cmd-k is opened on the integrations page, show two new result
groups: connected accounts (visible even with empty input) and catalog
integrations (appear once the user types). Selecting an OAuth integration
deep-links to its detail page with ?connect=oauth so the connect modal
auto-opens. Non-OAuth integrations navigate to the plain detail page.

Both groups are gated to the integrations page only and respect the
hideIntegrationsTab permission. The credentials fetch shares the same
React Query cache key as the integrations page itself (no double fetch).

* refactor(emcn): make the floating tooltip the one canonical Tooltip

Replace the Radix-based emcn Tooltip with the cursor-following floating tooltip so
every tooltip in the app uses one consistent style. Built on the shared
floating-tooltip engine (relocated into emcn), not a parallel implementation.

- Move the floating-tooltip engine into emcn/components/tooltip and export it from
  the barrel; re-point its consumers (FloatingOverflowText, resource-header)
- Extend the FloatingTooltip bubble to render arbitrary children (+ role/id for
  a11y) so it can back general tooltips, not just overflow text
- Rebuild emcn Tooltip (Root/Trigger/Content/Provider/Shortcut/Preview) on
  useFloatingTooltip — compound API preserved, ~350 call sites unchanged, legacy
  side/align props accepted and ignored (the tooltip follows the cursor). Removes
  @radix-ui/react-tooltip usage (package kept for a later cleanup; react-slot
  retained for asChild)

Note: general tooltips now show instantly (no hover delay) and follow the cursor.

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

* style(emcn): put tooltip text on the design scale (text-caption)

Replace the tooltip's ad-hoc `text-xs` + `leading-[18px]` with the semantic
`text-caption` (12px) font-size token so the text styling is fully on the design
scale and self-documenting, matching how the rest of the system is set up. The
color already used the global `--text-body` token. No visual change (still 12px
with a ~18px line height).

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

* Mship byok

* feat(sidebar): add empty task state and inline task creation

- Show "No tasks yet" in the Tasks section (expanded and collapsed) when the list is empty
- Clicking + now creates a task via the API and navigates directly to it, rather than navigating to home
- Add isCreatingTaskRef guard to prevent double-click from spawning multiple tasks
- Disable + button while creation is pending
- Fall back to home navigation on creation error

* invite, billing, home

* Sandbox warning

* improvement(seats): auto purchase seats on invitations into workspace (#4857)

* improvement(seats): auto purchase seats on invitations into workspace

* improve sampling for seat drift reconciler

* address comments

* feat(knowledge): align connector UI with integrations page styling

- ConnectorTypeCard now matches integration rows: brand-colored rounded-xl tile, ArrowRight, title/subtitle hierarchy
- ConnectorCard icon upgraded from flat surface-4 to branded tile (white icon on brand bg, graceful fallback)
- Connector header badges use chipVariants instead of custom Button classes
- Add-connector search input aligned to integrations style (h-[30px], rounded-lg, border-1)

* fix(icons): trim Folder SVG viewBox to remove right-side whitespace

The folder path only extends to x≈14.33 in a 15-unit viewBox, leaving
~0.5 units of empty space on the right. At 12px rendered size this
produces ~0.4px extra gap (visible as ~1px on retina displays) compared
to solid icons like the workflow color square. Trimming the viewBox to
14.5 units makes the folder fill its chip slot evenly.

* fix(user-input): restore draft text synchronously to preserve contexts on nav

The SSR-safe approach (empty useState + effect restore) created a timing
window where the sync effect in useContextManagement fired with message=''
before the value was set, clearing any restored contexts. Folder and workflow
contexts (not re-added by applyAutoMentions) were lost on every nav-back.

Revert to the staging approach: initialize value synchronously from the
draft store so message is already populated when effects run, matching
the behavior on staging.

* fix(queue): render context chips in queued messages

- Remove plainMentions from queued message rows so context chips render
  with icons, consistent with sent messages
- Fix computeMentionRanges to use '/' prefix for skill contexts (content
  has the slash trigger restored at submit time, not '@')

* fix(mothership): remove integrations from add-resource dropdown

* fix(mothership): comment out integrations from add-resource dropdown

* File serializer

* Skills

* chore(db): drop form, templates, template_creators, template_stars tables

These tables backed the Forms and Templates platform features which were
intentionally removed from this branch. Clean up the DB schema to match.

* chore(db): add migration metadata for 0224 drop tables

* block icons, sidebar, toolbar

* chore: remove remaining dead code for template-profile feature

- Remove 'template-profile' from SettingsSection union type
- Remove 'template-profile' entry from SECTION_TITLES
- Remove now-redundant template-profile guard in settings sidebar
- Remove commented-out template-profile nav item

* fix(multi-select): preserve anchor on range selection for tasks and folders

After a shift+click range, the anchor (lastSelectedTaskId / lastSelectedFolderId)
was being updated to the end of the range (toId). This caused subsequent
shift+clicks to extend from the wrong point instead of the original click.

Standard behavior: anchor stays at the initial click (fromId) so repeated
shift+clicks always expand/contract relative to where you started.

* Update skills

* Load skill tool

* Fix vfs dynamic context encoding

* Media subagent

* feat(emcn): add SearchInput component and unify search bars platform-wide

- Add SearchInput to emcn: 30px chip-family filled search input matching the
  integrations page pattern (border-1, surface-5, leading Search icon)
- Migrate all 22 search bars across settings, EE pages, and integrations to
  SearchInput (only layout classes allowed at callsites)
- Rename Sim Keys -> Sim API Keys in nav/title; page copy now says API key
- Remove components/ui input, label, and verified-badge; migrate consumers
  to emcn equivalents or raw inputs (table cell editor, wand prompt bar)
- Delete dead EE skeleton files (data-drains, data-retention)
- General settings: Home Page chip moves to header left as navigation

* fix(files,tables): restore new-file editor autofocus and CSV import error toasts

Both were dropped on the staging line and regressed vs production (main):

- Files: the new-file editor autofocus chain (files.tsx -> file-viewer ->
  text-editor) was stripped by the react-doctor dead-code pass in #4544,
  which misread the prop-drilled `autoFocus` (consumed by an imperative
  `editor.focus()` effect) as unused. Restored the prop through all three
  layers and the one-shot focus effect so creating a new file focuses the
  editor immediately.
- Tables: CSV import failures were silently logged with no user feedback.
  Restored the per-file and generic `toast.error` surfacing.

* Move superagnet back into superagent

* feat(home): score suggested actions by workspace signals

- Derive the suggestion pool from the curated block template catalog
  (1,343 prompts across 172 blocks) instead of 15 hardcoded entries
- Fix inverted relevance: prompts for connected providers are now boosted
  4x (instantly runnable) instead of excluded; unconnected discounted 0.4x
- Weight by featured (3x), popular category (1.5x), and resource gaps
  (no tables -> boost table starters; has KBs -> dampen KB-creation prompts)
- Weighted sampling without replacement, max one suggestion per block
- Connect rows weighted by catalog template count; 2 for fresh workspaces,
  1 once something is connected
- Key the catalog map by both versioned and base block types so gmail_v2
  templates resolve (gmail, github, notion, linear were silently dropped)
- Replace derive-in-effect state with a useMemo keyed by a shuffle nonce
- Add suggested_action_clicked / suggested_actions_shuffled /
  suggested_actions_toggled PostHog events

* billing, teammates

* improvement(credentials): credentials invites, secrets tab wiring up (#4874)

* improvement(credentials): move away from invite notion

* wire up secrets ui/ux

* address comments

* get consistent styling by removing emcninput + text area

* styling consistency

* remove fallback

* address comment:

* refactor(ui): migrate settings & workspace UI to chip design system

Migrate modals to ChipModal (showDivider, hint, resizable, size, leading,
ChipModalTabs), standardize Chip variants, add ChipCombobox wrapper, and apply
chip inputs/dropdowns across settings, knowledge, logs, tables, inbox, EE tabs.
Render ChipModalTabs as a ChipSwitch segmented control. Align /settings/secrets
detail with /integrations, refresh whitelabeling, and restore file-editor
autofocus and CSV import error toasts.

* Update media

* fix(mothership): restore integrations to useAvailableResources for @ mention

Integrations were fully removed from useAvailableResources which broke
the @ mention menu since user-input shares the same hook. Now integrations
are always included in the hook but excluded at the AddResourceDropdown
component level, keeping them out of the sidebar + menu while remaining
available for @ mention autocomplete.

* refactor(settings): chip design-system consistency pass across all tabs

Extract a shared chip-field shell (CHIP_FIELD_SHELL/CHIP_FIELD_INPUT) mirroring
Input variant='chip' and route secrets, credential detail, and integrations
credential detail through it (30px height, font-medium, focus ring). Add a
Discard action to the secrets header when dirty. Group BYOK providers into
Models/Search & web/Enrichment sections and align its tiles to the integrations
tile.

Normalize list-row typography to text-[14px]/text-[12px] and icon tiles to
rounded-xl + border across api-keys, copilot, custom-tools, mcp,
workflow-mcp-servers, credential-sets, and access-control. Tone the secrets
Details chip and per-row affordances to ghost. Fix token correctness: raw
tailwind colors to design tokens (data-drains), missing chip variant on the
Snowflake role input, ColorInput chip-field reuse (whitelabeling), error token
and icon sizes (workflow-mcp-servers), border token (mcp), no-results sizing
(secrets), row chrome (recently-deleted), hover token and Button to Chip
(access-control), and deduped textarea chrome (sso).

* feat(settings): unify filter dropdowns on ChipSelect (integrations style)

Add a ChipSelect emcn component — a filled chip trigger + chevron opening a
DropdownMenu, matching the integrations category filter — supporting single
select, multi-select (checkbox rows), grouped options, and optional in-menu
search. Migrate every settings/EE filter dropdown off ChipCombobox to it:
audit-logs (resource-type multi + time-range), data-retention, data-drains,
general, admin (grouped tool picker), inbox status filter, and the workflow
MCP-server pickers. The SSO provider-id field stays an editable combobox since
it accepts free-text slugs.

Also fix audit findings: chip the MCP client-secret input, normalize an MCP
error-text size, and drop now-dead destination-icon code in data-drains.

* fix(mentions): require explicit @ for integration mentions; decorate sent messages robustly

- Bare integration names in prose (Monday, Notion, Clay) are no longer
  auto-converted to mentions or chipped — mention treatment is strictly
  opt-in via a token-starting @ (fixes the scunthorpe problem)
- @-prefixed mentions still canonicalize casing (@slack -> @Slack) on both
  the keystroke fast-path and bulk paths (paste, template, draft, STT)
- Sent/queued messages now self-sufficiently decorate @IntegrationName
  tokens via a text scan, covering messages sent before the input pass
  ran or authored outside the chat input
- Integration contexts missing a resolvable blockType (messages persisted
  before blockType was saved) are backfilled by label lookup so their
  mention pills render the brand icon again

* refactor(settings): section the API keys page like secrets

Wrap Workspace, Personal, and the allow-personal-keys toggle in SettingsSection
(muted label + divider) instead of bare bold headers, matching the secrets and
BYOK pages.

* fix(settings): ChipSelect renders above modals + full-width form mode

Raise the ChipSelect menu to --z-popover so it layers above modal surfaces
(--z-modal) instead of opening behind them. Add a fullWidth prop that stretches
the trigger and right-aligns the chevron for form-field use, and apply it to the
workflow MCP-server pickers.

* fix(emcn): ChipSelect uses the emcn flat chevron, not lucide's square one

The lucide ChevronDown is square; rendering it at the chip's 9x7 footprint
stretched it. Switch to the custom emcn ChevronDown (built for that wide aspect),
matching the integrations filter and ChipDropdown.

* fix(emcn): ChipSelect trigger hugs its content (w-fit)

In a stacked form layout the trigger was stretched by align-items: stretch,
leaving an empty gap to the right of the value. Add w-fit so the chip sizes to
its content (a compact pill) everywhere; fullWidth form selects are unaffected.

* fix(emcn): ChipSelect uses a square lucide chevron

Revert to lucide's ChevronDown sized square (size-[14px]) so it renders crisp,
matching the standard select chevron used by Combobox.

* renamed tasks to chats

* rename and file change

* Wspace resource refs

* improvement(billing): wire up billing, org, teammates tabs + remove deprecated subscription tab (#4887)

* improvement(billing): wire up billing, org, teammates tabs + remove depr subscription tab

* pass exec timeout to tool routes

* reuse helper

* address comments

* address disable comment

* Vfs updates

* chore(db): remove migration 0224 to regenerate on top of staging

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

* VFS updates and linter

* fix type errors and regen migration?

* File block v5

* Remove docs

* Fix

* Doccer image

* Nuke the dag

* chore(db): drop branch migration 0226 ahead of staging merge; will regenerate

* chore(db): regenerate migration 0226 after staging merge

* Fix lint

* externalize before compaction in fallback'

* Run workflow improvements

* Loosen run options validation

* Lint

* fix save/discard chips to be consistent

* fix(ui): remove smodal tabs in favor of chip modal tabs

* update byok manager component

* fix(ui): skip auto-scrolling on mouse highlight of workspace

* Add creds to wspace context

* Wscontext

* fix(platform): restore settings redirects, forgot-password Enter submit, and tag tooltip visibility

- Re-add SETTINGS_REDIRECTS so /settings/integrations and /settings/skills
  deep links redirect to their top-level routes instead of rendering an
  empty settings panel (accidentally removed in 86da193cc3 one minute
  after cca5054cf6 added it)
- Add opt-in onSubmit to ChipModalField input/email variants and wire it
  in the forgot-password modal so Enter submits again (lost in the
  ChipModal conversion)
- Knowledge tag tooltip: drop the max-h/overflow-y-auto clamp that the
  pointer-events-none floating tooltip made unreachable; truncate each
  tag row instead so all tags stay visible with bounded height

* feat(usage-limits): org member specific limits (#4893)

* feat(usage-limits): org member specific limits

* revert feature flags

* fix type issues

* address comments

* address comments and address other billing gapes'

* fix

* improvements

* fix remaining credits display

* fix

* pass ws id to route validations

* chore(db): remove migration 0226_third_spot before staging merge

* chore(db): regenerate migration as 0227 after staging merge

* Block

* feat(telemetry): add posthog + audit coverage for new platform actions

Audit log (compliance/permission-relevant only):
- org_seat.provisioned — seat auto-purchased when an invite acceptance
  grows the org (actor = accepting user, includes seat delta)
- org_plan.converted — Pro→Team conversion triggered by invite acceptance
- org_seat.drift_reconciled — hourly cron healed a drifted seat count
- credential_member.added/removed/role_changed — credential sharing
  surface was previously fully unaudited
- table.created — parity with existing table.updated/deleted
- skill updates now record skill.updated instead of mislabeled
  skill.created

PostHog:
- seats_provisioned, credential_shared/unshared,
  environment_updated/deleted (key counts only, never names/values)
- table_import_started/completed — background CSV imports previously had
  zero failure observability
- table_exported, file_downloaded, skill_updated
- credential_connected now fires for OAuth completions (draft-hooks),
  credential_deleted for OAuth disconnects; previously only manual
  credentials were tracked
- table_workflow_run gains deployment_mode (live/deployed/mixed)

Also: logger.warn on all new credential-admin 403 denials (members +
environment routes); invite-created audit enriched with
enforcedFixedSeats/plan.

Deliberately excluded as noise: per-hour dead-letter audit rows (would
re-record the same stuck event every cron run) and a duplicate
system-actor org_plan.converted in the Stripe outbox handler.

* feat(home): fill textarea on suggested prompt click instead of sending

Clicking a prompt action in the Suggested Actions panel now populates
the Mothership user-input textarea (via applyAutoMentions) and focuses
it with the caret at end, rather than immediately submitting. The user
can review, edit, and send manually.

* fix(templates): name owning integration in featured block template prompts

Four featured prompts omitted their owning integration name, making them
unbranded and disconnected from their title. Each prompt now explicitly
names GitHub or Google Sheets so mentionifyIntegrations renders the chip
and the copy reads as a self-contained agent-building instruction.

* fix(templates): rewrite fragment prompts so each names its integration and reads as a complete instruction

Featured and non-featured block template prompts that either omitted
the owning integration's canonical name or were phrased as marketing
fragments rather than natural user instructions have been rewritten.
Each updated prompt now starts with an imperative verb ("Build a
workflow that…"), names the owning integration explicitly so the
@-mention chip renders correctly, and aligns with the entry's title.

Files changed: salesforce, hubspot (×2), github, slack, airtable,
firecrawl, iam.

* fix(templates): name integration in remaining block template prompts

- google_docs.ts: replace 'Google Doc' with 'Google Docs document' in 4 prompts; update title 'Meeting notes to Google Doc' to 'Meeting notes to Google Docs'
- google_sheets.ts: replace 'Google Sheet' with 'Google Sheets spreadsheet/Google Sheets' in 3 non-featured prompts
- slack.ts: replace 'Google Doc' with 'Google Docs document' in 'Daily standup summary'
- stripe.ts: replace 'Google Sheet'/'Slacks' with 'Google Sheets'/'Slack' in 'Weekly metrics report'
- reddit.ts: add 'Reddit' to 3 prompts that only referenced subreddits
- notion.ts: rewrite featured prompt to start with a verb and name Notion
- jira.ts: rewrite featured marketing-fragment prompt to start with a verb
- linear.ts: rewrite featured marketing-fragment prompt to start with a verb
- gmail.ts: rewrite featured marketing-fragment prompt to start with a verb and name Gmail

* fix(icons): convert monochrome dark brand icons to currentColor for dark mode

LinkupIcon, InfisicalIcon, IntercomIcon, LumaIcon, GranolaIcon, OnePasswordIcon,
and RailwayIcon were hardcoded to black/near-black fills/strokes, making them
invisible in bare DARK mode. Convert all to currentColor so they follow the
theme-aware text color.

Add iconColor: '#286efa' to IntercomBlock (Intercom brand blue is a confident
mid-tone, safe on both themes).

Two-tone icons (StagehandIcon, AgentPhoneIcon, QuiverIcon) are left unchanged
because their white details are structural — converting to single-tone would
destroy logo legibility.

* removed color, user input, sidebar, suggested actions, chips/emcn

* removed color migration

* Table bump

* improve(blocks): audit block catalog metadata for accuracy and fill gaps

- Fix template prompts that claimed capabilities blocks don't have
  (fabricated triggers, non-existent tools) across ~98 integrations
- Normalize integration tags to family conventions and valid union values
- Align alsoIntegrations and modules with template prompt content
- Add BlockMeta for circleback, imap, and rss trigger integrations
- Add templates to clickhouse and greptile metas
- Remove duplicate PageSpeed deploy-gate template

* chore(telemetry): drop org_seat.drift_reconciled audit

System self-heal bookkeeping doesn't belong in the user-facing audit
trail — membership and seat-purchase changes are already audited, and
the cron's logger output covers ops visibility.

* chore(db): consolidate branch migrations into single 0227

* fix(emcn): make ChipModal scroll internally when content exceeds viewport

The Modal→ChipModal migration dropped the old ModalBody scroll container:
ChipModal renders ModalContent bare (no overflow-hidden) and its wrappers
had no min-h-0 chain, so tall modals (e.g. New data drain with an S3
destination) overflowed max-h-[84vh] off-screen with no way to scroll.

Complete the flex min-h-0 chain through ChipModal's frame and give
ChipModalBody flex-1 min-h-0 overflow-y-auto — header/footer stay pinned,
body scrolls only when constrained. Short modals are unaffected (max-h
caps, it doesn't stretch), and dropdowns inside the body are Radix-portaled
so the new scroll container cannot clip them. Five modals that had locally
patched this with max-h-[Nvh] overrides keep working unchanged.

* fix(emcn): don't close modal when dismissing a dropdown via outside click

Radix dispatches pointer-down-outside to every open dismissable layer at
once, so clicking outside an open dropdown/select inside a modal closed
both the dropdown and the modal in one jarring step. ModalContent now
prevents its own dismissal while a portaled popper layer is open — the
first outside click closes just the popper, the next one closes the
modal.

* docs(skills): de-duplicate and correct agent docs; canonical styling tokens; mirror sim-sandbox rule

* docs(skills): broaden boundary-raw-fetch scope note, sync cursor rule mirrors

* fix(emcn): harden modal popper guard and exempt caret-anchored dropdowns from body scroll

Adversarial review follow-ups to the ChipModal scroll + dismissal fixes:

- Popper guard now requires data-state="open" inside the popper wrapper,
  so a dropdown that is merely animating closed no longer swallows the
  next outside click on the modal (DropdownMenu has an exit animation
  that keeps its wrapper mounted briefly)
- Port the same guard to SModalContent for consistency
- custom-tool-modal: opt its body out of the chrome scroll container
  (flex-none + overflow-visible); the caret-anchored EnvVar/Tag
  autocomplete dropdowns are absolute-positioned inside the body and
  must spill past its bounds rather than clip against a scroll boundary

* refactor(billing): drop unnecessary useCallback wrappers from event handlers

* fix(data-drains): complete chip migration of destination forms and fill missing placeholders

Finishes the in-flight FormField → ChipModalField conversion for all
destination form specs and adds the placeholders that several inputs
never had (S3 bucket/region/access keys, Azure account key, Datadog API
key, webhook signing secret/bearer token) — the cause of the New Data
Drain modal showing placeholder-less inputs inconsistently.

* fix(emcn): broaden modal popper guard to onInteractOutside

Covers the focusOutside dismissal path too: when a popper's focus scope
unwinds on close, the transient focus shift could still dismiss the
modal (and simultaneous body pointer-events lock teardown could freeze
the page). Same data-state="open" scoping as the pointer guard.

* fix

* fix(emcn): harden modal outside interactions

* Remove migration 0227 to regenerate after merge

Drop the 0227_org_member_usage_limit migration, its snapshot, and journal
entry so it can be regenerated cleanly after merging improvement/platform.

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

* Regenerate migration as 0228 after merging improvement/platform

drizzle-kit generate re-emits the org_member_usage_limit table, document
uploaded_by, and table_run_dispatches triggered_by_user_id changes on top of
improvement/platform's 0227 snapshot. Also collapse the admin.tsx emcn import
left multi-line after merge conflict resolution.

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

* Fix pre-existing type errors surfaced by post-merge type-check

These were present on dev before the merge; the post-merge type-check made
them visible.

- workspace-vfs.ts: materializeEnvironment now emits full oauth integration
  objects (id, providerId, displayName, role) so the summary matches
  WorkspaceMdData['oauthIntegrations'] (consumer reads credentialId from id)
- workflows/utils.ts: narrow resolved workflow rows to non-null workspaceId
  (the inArray filter already guarantees it) so the resolved result type holds

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

* Slack trigger

* fix tool mark complete bugs

* change thinking icon to just be blimp

* fix tests

* MCP fixes

* Fix files

* refactor use chat

* multiple fixes

* Fix deploys

* fix billing, tracing, retry lifecycle issues

* remove stale generated migration

Drop migration 0228 and its metadata so it can be regenerated after merging staging.

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

* merge staging in

* fix sockets err classification

* sockets invite flow fix

* remove stale generated migration 0229 before staging merge

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

* regen migration

* Update bun lock

* Fix schema issues

* Fix lint

* Fix build

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: andres <k62hc5kjst@privaterelay.appleid.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>
Co-authored-by: waleed <waleed@simstudio.ai>
2026-06-09 13:39:51 -07:00
WaleedandTheodore Li 540835a7a5 feat(integrations): add AWS AppConfig integration with tools, block, and docs (#4928)
* feat(integrations): add AWS AppConfig integration with tools, block, and docs

* fix(integrations): preserve latestVersionNumber 0 and tighten locationUri type in AppConfig

* fix(integrations): use valid IntegrationTag values in AppConfig BlockMeta

* feat(appconfig): add list_hosted_configuration_versions operation

* fix(appconfig): stringify configurationVersion in block params

* feat(appconfig): add full CRUD for applications, environments, and configuration profiles

Adds get/update/delete for applications, environments, and configuration
profiles, plus delete_hosted_configuration_version — 10 tools rounding out
the integration to management-grade CRUD completeness.

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-06-09 13:17:40 -07:00
Waleed a72e35e2f4 feat(sendblue): add Sendblue iMessage/SMS integration with tools and triggers (#4917)
* feat(sendblue): add Sendblue iMessage/SMS integration with tools and triggers

* fix(sendblue): address review — status-aware webhook dedup, shared routing map, uniform output casing, api-key authType

* fix(docs-gen): skip nested array `items` descriptor in tool input tables

* chore(sendblue): use SendblueSendStyle type, trim URL identifiers

* fix(sendblue): keep is_outbound routing map local to the webhook handler

Avoids a webhook-providers -> triggers cross-subgraph import (single source
of truth in the handler, its only runtime consumer).

* fix(sendblue): remove invalid `tags` from block config (belongs on BlockMeta)
2026-06-08 19:41:09 -07:00
Theodore LiandClaude Opus 4.8 c90a1eb4a6 feat(tables): row-gutter drag-select, Cmd+F find, and select-all polish (#4901)
* feat(ui): allow dragging on row gutters

* feat(tables): Cmd+F find across cells + row-gutter UI polish

Find:
- New GET /api/table/[tableId]/rows/find endpoint + contract + useFindTableRows hook
- findRowMatches: case-insensitive substring search across every cell via a
  row_number() CTE + jsonb_each_text + ILIKE, returning each matching cell with
  its ordinal in the filtered/sorted view. Shared buildRowOrderBySql keeps the
  find ordinals aligned with the paginated list (with an id tiebreak).
- Cmd/Ctrl+F floating find box (search-on-Enter), cell-by-cell next/prev that
  loads the target row then reveals the cell via the existing anchor scroll.

Gutter UI:
- Row number/checkbox centered in the region left of the run button; the
  select-all header checkbox centers in the same region so they line up.
- emcn Checkbox gains an indeterminate (minus) state; select-all shows the minus
  on any partial selection and clears everything on click.

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

* feat(tables): inline rename from the list context menu

Wire the existing "Rename" item in the per-table context menu to inline rename
(useInlineRename + InlineRenameInput via the Resource name cell's `content`
override), matching the Files list. Enter/blur saves, Esc cancels; replaces the
prior modal.

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

* style(tables): restyle Cmd+F find box to match workflow search

Reuse the workflow search visual language (surface-1 panel, emcn Input, muted
"k of N" counter, size-8 ghost chevron buttons) instead of the flat custom input.

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

* fix(resource): move "Clear sort" to top of the sort menu

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 01:28:12 -04:00
0075ab9cf6 improvement(platform): remove tour, simplify sidebar/header, drop loading skeletons (#4354)
* improvement(platform): workspace UI/UX overhaul + integrations catalog

Rework the workspace around the AI-workspace model: a Mothership home, a
top-level Skills route, connected-credential and integration-detail pages,
and a polished sidebar/settings surface. Replace the notifications store
with a unified toast system (provider-level dismiss/pause, countdown ring).

Integrations & catalog:
- Add a BlockMeta layer (tags + catalog templates) scoped to catalog-visible
  integrations; every catalog integration carries >=7 grounded templates.
- Rework the taxonomy: each block declares category tools|blocks|triggers.
  3rd-party services are 'tools'; first-party primitives (postgres, mysql,
  knowledge, file, search, stt/tts, image/video generators, thinking, etc.)
  are 'blocks'. Versioned blocks follow the upgrade paradigm (old hidden,
  latest in toolbar/docs).
- Generate integrations.json + tool docs canonically from block configs.

Architecture & cleanup:
- Consolidate block data extraction behind a single latest-version strategy
  (getCanonicalBlocksByCategory; version-consistent getBlockMeta).
- Unify version-suffix handling in @sim/utils/string (stripVersionSuffix /
  isVersionedType, with tests); registry, generate-docs, tools/utils, and
  integrations all route through it.
- Repair latent broken barrels, remove dead code, fix BlockMeta-related type
  errors and 5 broken docs links.

Behavior-preserving for block execution and the toolbar's tool/block listing.

* refactor(platform): remove forms, templates, and creators features

Remove three standalone features and their supporting code:
- Forms: form-deployment pages, API routes, execution path, and docs.
- Templates: the template gallery (landing + workspace) and template APIs.
- Creators: creator-profile routes and contracts.

Add a super-user permissions module (lib/permissions/super-user) and an
organizations API contract; update the audit/db/testing packages, billing,
and the session/theme providers accordingly.

* test(workflows): update archiveWorkflow update count after forms removal

The forms feature was removed, dropping the form-table update from
archiveWorkflow. Update the stale assertion from 8 to 7 tx.update calls.

* upgrade

* improvement(knowledge): polish tag filter dropdowns (#4816)

* improvement(logs): object storage backed tracespans (#4787)

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

* improvement(providers): harden OpenAI-compatible providers + add tests (#4796)

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

* chore(copilot): deprecate mcp server (#4797)

* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route

* feat(integrations): hosted API keys for Findymail, Prospeo, and Wiza (#4777)

* 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

* fix(tables): reduce column header chevron size and fix sidebar shadow bleed (#4800)

* feat(slack): add install + privacy section to integration landing page (#4799)

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

* improvement(enrichments): align enrichments sidebar with design system (#4801)

* improvement(enrichments): align enrichments sidebar with design system

* fix(enrichments): consistent close button pattern and fix url link hover

* fix(misc): upgrade path change for new better-auth version, billing issue for workflow block agent usage (#4803)

* fix(misc): upgrade path change for new better-auth version, double-billing for workflow block agent usage

* fail loudly if stripe sub id missing

* fix(copilot): seq migration (#4804)

* chore(db): drop redundant idx_webhook_on_workflow_id_block_id index (#4809)

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.

* perf(copilot): read chat transcripts from copilot_messages (R+1 cutover) (#4808)

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

* fix(tables): right-align run/stop in embedded toolbar; workflow cells format like normal cells (#4806)

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

* fix(icons): repair broken integration icon rendering (#4810)

* 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): serialize schema mutations to prevent parallel column clobber (#4812)

* 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(wait): resume live/draft async waits and preserve cell context on chained waits (#4814)

* Make workflow description nullable

* fix(wait): resume live/draft async waits and preserve cell context on chained waits

* improvement(knowledge): polish tag filter dropdowns

* improvement(knowledge): soften filter section labels

* improvement(knowledge): soften list filter labels

* fix(security): harden SSO domain registration, webhook path isolation, and CSV export (#4813)

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

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF (#4818)

* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF

* test(security): use real isPrivateOrReservedIP and cover IPv6 edge cases

* improvement(integrations): validate and expand devin, cursor, and greptile (#4820)

* 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(search-replace): don't auto-navigate when content edits invalidate the active match (#4819)

* 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(enrichments): limit company-info to fields both providers return (#4817)

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(files): don't reject external URLs containing '..' in file parse validation (#4821)

* 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

* feat(google-sheets): add row filtering to read with numeric operators (#4822)

* 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(selectors): fetch all pages for paginated dropdown list routes (#4823)

* 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

* fix(sso): re-check domain conflict before write and reject IP-address domains (#4825)

* improvement(copilot): make copilot_messages the sole transcript store, remove JSONB dual-write (#4826)

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.

* feat(tables): expand filter operators (not-contains, starts/ends-with, not-in, empty) (#4827)

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.

* improvement(copilot): stop persisting tool-call result outputs in transcripts (#4829)

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>

* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)

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

* fix(knowledge): calendar view sync, deduplicate popover animation classes, type-safe filter cast

* cleanup(knowledge): remove TRIGGER_BORDER_CLASS duplication, inline displayLabel, drop enabledFilterParam alias

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>

* feat(blocks): add BlockMeta to Quiver and Linq; fix invalid block config fields; update skills

Block fixes:
- Add QuiverBlockMeta (tags + 3 templates: icon generator, diagram creator, vectorizer)
- Fix QuiverBlock: remove invalid tags field from BlockConfig, IntegrationType.Design →
  IntegrationType.AI (Design doesn't exist in the enum)
- Fix GreptileBlock: remove invalid tags field from BlockConfig,
  IntegrationType.DeveloperTools → IntegrationType.DevOps
- Fix LinqBlock: remove invalid tags field from BlockConfig (tags belong only in BlockMeta)

Skills:
- add-block: add dedicated BlockMeta section with structure, rules, and registration
  pattern; add BlockMeta checklist items
- add-integration: add BlockMeta to block structure template, add rules clarifying
  that tags must NOT appear on BlockConfig and integrationType must be a valid enum
  value; update registry snippet to include blocksMeta; add checklist items

* fix(integrations): fix category dropdown by defining missing LANDING_INTEGRATIONS_DATA_PATH and regenerating integrations.json

The staging merge introduced landing-content.ts but forgot to define
LANDING_INTEGRATIONS_DATA_PATH in generate-docs.ts, causing the script
to crash before writing integrations.json.

The stale JSON had integrationTypes (plural array) from an older script
version, while the Integration type and workspace UI both read
integrationType (singular string) — so ALL_CATEGORY_SECTIONS bucketed
to undefined and the category filters never appeared in the dropdown.

Fixed by adding the missing path constant and re-running the generator.
integrations.json now has 192 entries with the correct integrationType field.

* fix(sidebar): restore resize handle on all pages

commit 3109104582 wrapped the resize handle in {(isCollapsed ||
isOnWorkflowPage) && ...} and added a useEffect that resets sidebar
width to SIDEBAR_WIDTH.MIN whenever the user navigates away from a
workflow page. Together these made the sidebar non-resizable on Tasks,
Tables, Knowledge Base, and every other non-workflow page.

Restore the staging behavior: always render the resize handle and
remove the effect that forced the width reset on page transitions.

* fix(sidebar): match staging onKeyDown and tabIndex on resize handle

The resize handle was still conditionalizing onKeyDown and tabIndex
on isCollapsed, blocking keyboard accessibility of the separator role
when expanded. Staging always attaches both unconditionally.

onKeyDown={isCollapsed ? handleEdgeKeyDown : undefined} → onKeyDown={handleEdgeKeyDown}
tabIndex={isCollapsed ? 0 : undefined}                 → tabIndex={0}

* feat(integrations): show connected credentials on integration detail page

When navigating to /integrations/google-docs (or any integration), a
Connected section now appears above the templates listing all workspace
credentials tied to that provider. Each row links back to the credential
detail page (/integrations/connected/${id}) for management actions.

Pairs with the earlier change that routes connected items from the
integrations list to the provider detail page instead of directly to
the credential detail page.

* fix(integrations): rename Add in chat to Add to Sim

* fix(skills): rename Add button to Add to Sim

* fix(platform): restore M1/M2/M3 regressions and LazyMotion on landing page

M1 — Invitation guard: re-introduce usePermissionConfig().isInvitationsDisabled
alongside the workspace inviteDisabledReason check. The flag now also
respects NEXT_PUBLIC_DISABLE_INVITATIONS and EE permission-group
disableInvitations, not just billing policy.

M2 — Settings redirects: /settings/integrations and /settings/skills
now server-redirect to /integrations and /skills respectively so old
bookmarks and emails don't silently land on General.

M3 — Starter block search exclusion: restore block.type !== 'starter'
guard in the search store so users cannot add a duplicate Starter block
via the command palette.

LazyMotion: restore LazyMotion + domMax/domAnimation wrappers and m.*
components in landing-preview-panel and landing-preview-home. The
removal was accidental (the full motion bundle was left after an
import cleanup), which caused the entire framer-motion feature set to
load eagerly on the landing page.

* fix(integrations): revert connected list to credential detail; remove settings redirects

* feat(sidebar): restore workspace switcher search with updated styling

Shows a search input in the workspace dropdown when the user has more
than 3 workspaces (WORKSPACE_SEARCH_THRESHOLD). Keyboard navigation:
ArrowDown/Up to move through results, Enter to switch, resets on close.

Styled to match the current branch (border-1/surface-5 tokens, sm text,
11px Search icon) rather than the old staging styles. Highlight state
is wired through chipVariants active prop so it follows the same active
appearance as clicked/hovered items.

* fix(sidebar): clean up workspace search — layout, memo, and effect guard

* fix(sidebar): align workspace rename input selection style with workflow rename

* perf(sidebar): eliminate React re-renders during sidebar drag resize

Previously, every mousemove during resize called setSidebarWidth(), which
both updated the --sidebar-width CSS variable (sync) and set Zustand state
(async). This caused:
  1. A 1-frame transition flash on mousedown — isResizing state had to
     round-trip through React before the is-resizing CSS class was applied,
     so the width transition fired for the first pixel of movement.
  2. A React re-render per pixel dragged — components reading sidebarWidth
     from the store (avatars, usage-indicator) lagged one frame behind the
     container, making the + and ... buttons appear to jump ahead.

New approach:
  - handleMouseDown adds is-resizing directly to the sidebar DOM node before
    any React involvement (synchronous, no frame lag).
  - mousemove writes only to the CSS custom property (zero React renders).
  - mouseup persists the final width to Zustand exactly once.
  - isResizing / setIsResizing state removed from the store and hook — they
    are no longer needed since the class is managed via direct DOM mutation.

* perf(sidebar): add requestAnimationFrame throttle to resize mousemove handler

* fix(sidebar): fix drag-right lag caused by WorkspaceChrome overflow-hidden transition

The sidebar-container's is-resizing class correctly suppressed its own width
transition, but the two wrapper divs in WorkspaceChrome both have
transition-[width]/transition-transform with a 175ms ease. The outer wrapper
also has overflow-hidden, so while the sidebar content was at the correct width
instantly, it was visually clipped by the outer wrapper which was still
animating — causing the + and ... buttons to appear to lag behind the resize
line on drag-right (not on drag-left, since shrinking doesn't clip content).

Fix: add sidebar-shell-outer/sidebar-shell-inner class names to both chrome
wrappers, and suppress their transitions via html.sidebar-resizing rule when
a drag is active. The html.sidebar-resizing class is toggled directly in the
resize hook alongside is-resizing, so it takes effect synchronously on mousedown.

* fix(icons): redesign Download icon to match Upload style; fix Upload/Download confusion

Download icon was missing the tray/shelf line at the bottom that Upload has,
making it look like a plain arrow rather than a matched pair. Updated Download
to use the same viewBox, stroke weight, and three-path structure as Upload
(tray + stem + arrowhead), just pointing down.

Also fix 5 places where Upload (↑) was incorrectly used for download/export
actions:
  - files.tsx: two Download action rows in toolbar and context menu
  - tables/table.tsx: Export CSV toolbar button
  - table-context-menu.tsx: Export CSV context menu item
  - logs.tsx: Export toolbar button
  - landing-preview-logs.tsx: decorative Export button

Import CSV and actual upload actions correctly keep the Upload icon.

* fix(icons): replace Upload with Download on all remaining export/download actions

- panel.tsx: Export workflow dropdown item
- context-menu.tsx: Export in sidebar workflow context menu
- chat.tsx: Export chat button
- output-panel.tsx: Export console CSV button
- terminal.tsx: Export console CSV button
- resource-content.tsx: Export table as CSV + Download file buttons

* fix(icons): fix remaining Upload→Download on download actions in files and logs

- action-bar.tsx: download button in files toolbar
- file-row-context-menu.tsx: Download item in file context menu
- file-download.tsx: both download buttons in log details file viewer

* updated block skills, settings pages, modals, buttons -> chips, blocks missing metadata

* updated skill modal

* improvement(resource-header): refine breadcrumb truncation ux

* improvement(resource): add floating overflow text tooltips

* wire up credits counter

* improvement(resource-header): mute path dropdown title

* refactor(resource-header): share floating-tooltip engine, prune dead overlay tooltips (#4844)

Clean up the breadcrumb truncation feature for reuse and correctness:

- Extract useFloatingTooltip / useIsOverflowing / FloatingTooltip into a shared
  floating-tooltip module. BreadcrumbSegment and FloatingOverflowText now consume
  one implementation instead of duplicating ~150 lines of positioning, velocity,
  overflow-detection, and portal logic.
- Replace the hardcoded terminal-label regex in ResourceHeader with a typed
  `terminal` flag on BreadcrumbItem (set by the document chunk/loading crumbs),
  decoupling the generic header from knowledge-base copy.
- Clear the path-popover close timeout on unmount and reuse the shared
  POPOVER_ANIMATION_CLASSES constant.
- Drop the redundant manual overflow-state writes (fixes a sticky fade mask).
- Revert FloatingOverflowText inside Combobox `overlayContent` back to plain
  truncating spans across files/logs/tables/scheduled-tasks/document: the combobox
  overlay is pointer-events-none, so the tooltip handlers never fired there.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(emcn,resource-header): address PR #4844 review feedback

- useIsOverflowing now uses a callback ref so the ResizeObserver follows the
  element across mount/unmount/reassignment instead of capturing it once at mount.
  Safe for conditionally rendered consumers of the shared hook. (greptile P2)
- Move POPOVER_ANIMATION_CLASSES out of chip-date-picker implementation internals
  into emcn/components/popover/popover-animation.ts, exported from the
  @/components/emcn barrel. Consumers now import from the module boundary.
  (greptile P2)

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

* upgrade table and styling upgrade

* fix schema to include integration

* fix(files): align delete icon with tables view (Trash → Trash2)

Co-Authored-By: waleed <waleed@simstudio.ai>

* fix(mothership): preserve blockType for integration contexts in sent messages

Integration mention chips were missing their provider icons in sent messages
because blockType was dropped when mapping ChatContext to messageContexts.
renderIntegrationTile returns null without blockType, silently hiding the icon.

* fix(mothership): allow 'integration' resource type in chat resources API

The VALID_RESOURCE_TYPES allowlist was missing 'integration', causing a
400 error when adding integrations to the Mothership resource tab — so
they never persisted and disappeared on refresh.

* fix(ui): Add "File" title next to file resource header

* fix(ui): fix resource header columns being bolded

* fix(resource): keep the floating tooltip from jumping on click

Gate the focus-driven show behind :focus-visible so a mouse click (which
focuses the trigger) no longer re-shows the tooltip anchored to the element's
bottom edge. On click the tooltip now hides cleanly instead of jumping down;
keyboard focus still shows it.

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

* perf(sidebar): eliminate unnecessary re-renders in workspace switcher for non-search users

- onMouseEnter: only set highlightedIndex when showSearch is true, preventing
  a state update + re-render on every workspace row hover for users with ≤ 3
  workspaces where the search is never shown
- onOpenChange: only reset workspaceSearch and highlightedIndex when showSearch
  is true, since both values are always already at their defaults for non-search
  users and setting them triggers a pointless re-render during dropdown close
- data-workspace-row-idx: only set when showSearch is true since the scroll
  effect that reads this attribute is already gated on showSearch

* feat(search): context-aware cmd-k results on the integrations page

When cmd-k is opened on the integrations page, show two new result
groups: connected accounts (visible even with empty input) and catalog
integrations (appear once the user types). Selecting an OAuth integration
deep-links to its detail page with ?connect=oauth so the connect modal
auto-opens. Non-OAuth integrations navigate to the plain detail page.

Both groups are gated to the integrations page only and respect the
hideIntegrationsTab permission. The credentials fetch shares the same
React Query cache key as the integrations page itself (no double fetch).

* refactor(emcn): make the floating tooltip the one canonical Tooltip

Replace the Radix-based emcn Tooltip with the cursor-following floating tooltip so
every tooltip in the app uses one consistent style. Built on the shared
floating-tooltip engine (relocated into emcn), not a parallel implementation.

- Move the floating-tooltip engine into emcn/components/tooltip and export it from
  the barrel; re-point its consumers (FloatingOverflowText, resource-header)
- Extend the FloatingTooltip bubble to render arbitrary children (+ role/id for
  a11y) so it can back general tooltips, not just overflow text
- Rebuild emcn Tooltip (Root/Trigger/Content/Provider/Shortcut/Preview) on
  useFloatingTooltip — compound API preserved, ~350 call sites unchanged, legacy
  side/align props accepted and ignored (the tooltip follows the cursor). Removes
  @radix-ui/react-tooltip usage (package kept for a later cleanup; react-slot
  retained for asChild)

Note: general tooltips now show instantly (no hover delay) and follow the cursor.

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

* style(emcn): put tooltip text on the design scale (text-caption)

Replace the tooltip's ad-hoc `text-xs` + `leading-[18px]` with the semantic
`text-caption` (12px) font-size token so the text styling is fully on the design
scale and self-documenting, matching how the rest of the system is set up. The
color already used the global `--text-body` token. No visual change (still 12px
with a ~18px line height).

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

* feat(sidebar): add empty task state and inline task creation

- Show "No tasks yet" in the Tasks section (expanded and collapsed) when the list is empty
- Clicking + now creates a task via the API and navigates directly to it, rather than navigating to home
- Add isCreatingTaskRef guard to prevent double-click from spawning multiple tasks
- Disable + button while creation is pending
- Fall back to home navigation on creation error

* invite, billing, home

* improvement(seats): auto purchase seats on invitations into workspace (#4857)

* improvement(seats): auto purchase seats on invitations into workspace

* improve sampling for seat drift reconciler

* address comments

* feat(knowledge): align connector UI with integrations page styling

- ConnectorTypeCard now matches integration rows: brand-colored rounded-xl tile, ArrowRight, title/subtitle hierarchy
- ConnectorCard icon upgraded from flat surface-4 to branded tile (white icon on brand bg, graceful fallback)
- Connector header badges use chipVariants instead of custom Button classes
- Add-connector search input aligned to integrations style (h-[30px], rounded-lg, border-1)

* fix(icons): trim Folder SVG viewBox to remove right-side whitespace

The folder path only extends to x≈14.33 in a 15-unit viewBox, leaving
~0.5 units of empty space on the right. At 12px rendered size this
produces ~0.4px extra gap (visible as ~1px on retina displays) compared
to solid icons like the workflow color square. Trimming the viewBox to
14.5 units makes the folder fill its chip slot evenly.

* fix(user-input): restore draft text synchronously to preserve contexts on nav

The SSR-safe approach (empty useState + effect restore) created a timing
window where the sync effect in useContextManagement fired with message=''
before the value was set, clearing any restored contexts. Folder and workflow
contexts (not re-added by applyAutoMentions) were lost on every nav-back.

Revert to the staging approach: initialize value synchronously from the
draft store so message is already populated when effects run, matching
the behavior on staging.

* fix(queue): render context chips in queued messages

- Remove plainMentions from queued message rows so context chips render
  with icons, consistent with sent messages
- Fix computeMentionRanges to use '/' prefix for skill contexts (content
  has the slash trigger restored at submit time, not '@')

* fix(mothership): remove integrations from add-resource dropdown

* fix(mothership): comment out integrations from add-resource dropdown

* chore(db): drop form, templates, template_creators, template_stars tables

These tables backed the Forms and Templates platform features which were
intentionally removed from this branch. Clean up the DB schema to match.

* chore(db): add migration metadata for 0224 drop tables

* block icons, sidebar, toolbar

* chore: remove remaining dead code for template-profile feature

- Remove 'template-profile' from SettingsSection union type
- Remove 'template-profile' entry from SECTION_TITLES
- Remove now-redundant template-profile guard in settings sidebar
- Remove commented-out template-profile nav item

* fix(multi-select): preserve anchor on range selection for tasks and folders

After a shift+click range, the anchor (lastSelectedTaskId / lastSelectedFolderId)
was being updated to the end of the range (toId). This caused subsequent
shift+clicks to extend from the wrong point instead of the original click.

Standard behavior: anchor stays at the initial click (fromId) so repeated
shift+clicks always expand/contract relative to where you started.

* feat(emcn): add SearchInput component and unify search bars platform-wide

- Add SearchInput to emcn: 30px chip-family filled search input matching the
  integrations page pattern (border-1, surface-5, leading Search icon)
- Migrate all 22 search bars across settings, EE pages, and integrations to
  SearchInput (only layout classes allowed at callsites)
- Rename Sim Keys -> Sim API Keys in nav/title; page copy now says API key
- Remove components/ui input, label, and verified-badge; migrate consumers
  to emcn equivalents or raw inputs (table cell editor, wand prompt bar)
- Delete dead EE skeleton files (data-drains, data-retention)
- General settings: Home Page chip moves to header left as navigation

* fix(files,tables): restore new-file editor autofocus and CSV import error toasts

Both were dropped on the staging line and regressed vs production (main):

- Files: the new-file editor autofocus chain (files.tsx -> file-viewer ->
  text-editor) was stripped by the react-doctor dead-code pass in #4544,
  which misread the prop-drilled `autoFocus` (consumed by an imperative
  `editor.focus()` effect) as unused. Restored the prop through all three
  layers and the one-shot focus effect so creating a new file focuses the
  editor immediately.
- Tables: CSV import failures were silently logged with no user feedback.
  Restored the per-file and generic `toast.error` surfacing.

* feat(home): score suggested actions by workspace signals

- Derive the suggestion pool from the curated block template catalog
  (1,343 prompts across 172 blocks) instead of 15 hardcoded entries
- Fix inverted relevance: prompts for connected providers are now boosted
  4x (instantly runnable) instead of excluded; unconnected discounted 0.4x
- Weight by featured (3x), popular category (1.5x), and resource gaps
  (no tables -> boost table starters; has KBs -> dampen KB-creation prompts)
- Weighted sampling without replacement, max one suggestion per block
- Connect rows weighted by catalog template count; 2 for fresh workspaces,
  1 once something is connected
- Key the catalog map by both versioned and base block types so gmail_v2
  templates resolve (gmail, github, notion, linear were silently dropped)
- Replace derive-in-effect state with a useMemo keyed by a shuffle nonce
- Add suggested_action_clicked / suggested_actions_shuffled /
  suggested_actions_toggled PostHog events

* billing, teammates

* improvement(credentials): credentials invites, secrets tab wiring up (#4874)

* improvement(credentials): move away from invite notion

* wire up secrets ui/ux

* address comments

* get consistent styling by removing emcninput + text area

* styling consistency

* remove fallback

* address comment:

* refactor(ui): migrate settings & workspace UI to chip design system

Migrate modals to ChipModal (showDivider, hint, resizable, size, leading,
ChipModalTabs), standardize Chip variants, add ChipCombobox wrapper, and apply
chip inputs/dropdowns across settings, knowledge, logs, tables, inbox, EE tabs.
Render ChipModalTabs as a ChipSwitch segmented control. Align /settings/secrets
detail with /integrations, refresh whitelabeling, and restore file-editor
autofocus and CSV import error toasts.

* fix(mothership): restore integrations to useAvailableResources for @ mention

Integrations were fully removed from useAvailableResources which broke
the @ mention menu since user-input shares the same hook. Now integrations
are always included in the hook but excluded at the AddResourceDropdown
component level, keeping them out of the sidebar + menu while remaining
available for @ mention autocomplete.

* refactor(settings): chip design-system consistency pass across all tabs

Extract a shared chip-field shell (CHIP_FIELD_SHELL/CHIP_FIELD_INPUT) mirroring
Input variant='chip' and route secrets, credential detail, and integrations
credential detail through it (30px height, font-medium, focus ring). Add a
Discard action to the secrets header when dirty. Group BYOK providers into
Models/Search & web/Enrichment sections and align its tiles to the integrations
tile.

Normalize list-row typography to text-[14px]/text-[12px] and icon tiles to
rounded-xl + border across api-keys, copilot, custom-tools, mcp,
workflow-mcp-servers, credential-sets, and access-control. Tone the secrets
Details chip and per-row affordances to ghost. Fix token correctness: raw
tailwind colors to design tokens (data-drains), missing chip variant on the
Snowflake role input, ColorInput chip-field reuse (whitelabeling), error token
and icon sizes (workflow-mcp-servers), border token (mcp), no-results sizing
(secrets), row chrome (recently-deleted), hover token and Button to Chip
(access-control), and deduped textarea chrome (sso).

* feat(settings): unify filter dropdowns on ChipSelect (integrations style)

Add a ChipSelect emcn component — a filled chip trigger + chevron opening a
DropdownMenu, matching the integrations category filter — supporting single
select, multi-select (checkbox rows), grouped options, and optional in-menu
search. Migrate every settings/EE filter dropdown off ChipCombobox to it:
audit-logs (resource-type multi + time-range), data-retention, data-drains,
general, admin (grouped tool picker), inbox status filter, and the workflow
MCP-server pickers. The SSO provider-id field stays an editable combobox since
it accepts free-text slugs.

Also fix audit findings: chip the MCP client-secret input, normalize an MCP
error-text size, and drop now-dead destination-icon code in data-drains.

* fix(mentions): require explicit @ for integration mentions; decorate sent messages robustly

- Bare integration names in prose (Monday, Notion, Clay) are no longer
  auto-converted to mentions or chipped — mention treatment is strictly
  opt-in via a token-starting @ (fixes the scunthorpe problem)
- @-prefixed mentions still canonicalize casing (@slack -> @Slack) on both
  the keystroke fast-path and bulk paths (paste, template, draft, STT)
- Sent/queued messages now self-sufficiently decorate @IntegrationName
  tokens via a text scan, covering messages sent before the input pass
  ran or authored outside the chat input
- Integration contexts missing a resolvable blockType (messages persisted
  before blockType was saved) are backfilled by label lookup so their
  mention pills render the brand icon again

* refactor(settings): section the API keys page like secrets

Wrap Workspace, Personal, and the allow-personal-keys toggle in SettingsSection
(muted label + divider) instead of bare bold headers, matching the secrets and
BYOK pages.

* fix(settings): ChipSelect renders above modals + full-width form mode

Raise the ChipSelect menu to --z-popover so it layers above modal surfaces
(--z-modal) instead of opening behind them. Add a fullWidth prop that stretches
the trigger and right-aligns the chevron for form-field use, and apply it to the
workflow MCP-server pickers.

* fix(emcn): ChipSelect uses the emcn flat chevron, not lucide's square one

The lucide ChevronDown is square; rendering it at the chip's 9x7 footprint
stretched it. Switch to the custom emcn ChevronDown (built for that wide aspect),
matching the integrations filter and ChipDropdown.

* fix(emcn): ChipSelect trigger hugs its content (w-fit)

In a stacked form layout the trigger was stretched by align-items: stretch,
leaving an empty gap to the right of the value. Add w-fit so the chip sizes to
its content (a compact pill) everywhere; fullWidth form selects are unaffected.

* fix(emcn): ChipSelect uses a square lucide chevron

Revert to lucide's ChevronDown sized square (size-[14px]) so it renders crisp,
matching the standard select chevron used by Combobox.

* renamed tasks to chats

* rename and file change

* improvement(billing): wire up billing, org, teammates tabs + remove deprecated subscription tab (#4887)

* improvement(billing): wire up billing, org, teammates tabs + remove depr subscription tab

* pass exec timeout to tool routes

* reuse helper

* address comments

* address disable comment

* chore(db): remove migration 0224 to regenerate on top of staging

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

* fix type errors and regen migration?

* chore(db): drop branch migration 0226 ahead of staging merge; will regenerate

* chore(db): regenerate migration 0226 after staging merge

* externalize before compaction in fallback'

* fix save/discard chips to be consistent

* fix(ui): remove smodal tabs in favor of chip modal tabs

* fix(ui): skip auto-scrolling on mouse highlight of workspace

* fix(platform): restore settings redirects, forgot-password Enter submit, and tag tooltip visibility

- Re-add SETTINGS_REDIRECTS so /settings/integrations and /settings/skills
  deep links redirect to their top-level routes instead of rendering an
  empty settings panel (accidentally removed in 86da193cc3 one minute
  after cca5054cf6 added it)
- Add opt-in onSubmit to ChipModalField input/email variants and wire it
  in the forgot-password modal so Enter submits again (lost in the
  ChipModal conversion)
- Knowledge tag tooltip: drop the max-h/overflow-y-auto clamp that the
  pointer-events-none floating tooltip made unreachable; truncate each
  tag row instead so all tags stay visible with bounded height

* chore(db): remove migration 0226_third_spot before staging merge

* chore(db): regenerate migration as 0227 after staging merge

* feat(telemetry): add posthog + audit coverage for new platform actions

Audit log (compliance/permission-relevant only):
- org_seat.provisioned — seat auto-purchased when an invite acceptance
  grows the org (actor = accepting user, includes seat delta)
- org_plan.converted — Pro→Team conversion triggered by invite acceptance
- org_seat.drift_reconciled — hourly cron healed a drifted seat count
- credential_member.added/removed/role_changed — credential sharing
  surface was previously fully unaudited
- table.created — parity with existing table.updated/deleted
- skill updates now record skill.updated instead of mislabeled
  skill.created

PostHog:
- seats_provisioned, credential_shared/unshared,
  environment_updated/deleted (key counts only, never names/values)
- table_import_started/completed — background CSV imports previously had
  zero failure observability
- table_exported, file_downloaded, skill_updated
- credential_connected now fires for OAuth completions (draft-hooks),
  credential_deleted for OAuth disconnects; previously only manual
  credentials were tracked
- table_workflow_run gains deployment_mode (live/deployed/mixed)

Also: logger.warn on all new credential-admin 403 denials (members +
environment routes); invite-created audit enriched with
enforcedFixedSeats/plan.

Deliberately excluded as noise: per-hour dead-letter audit rows (would
re-record the same stuck event every cron run) and a duplicate
system-actor org_plan.converted in the Stripe outbox handler.

* feat(home): fill textarea on suggested prompt click instead of sending

Clicking a prompt action in the Suggested Actions panel now populates
the Mothership user-input textarea (via applyAutoMentions) and focuses
it with the caret at end, rather than immediately submitting. The user
can review, edit, and send manually.

* fix(templates): name owning integration in featured block template prompts

Four featured prompts omitted their owning integration name, making them
unbranded and disconnected from their title. Each prompt now explicitly
names GitHub or Google Sheets so mentionifyIntegrations renders the chip
and the copy reads as a self-contained agent-building instruction.

* fix(templates): rewrite fragment prompts so each names its integration and reads as a complete instruction

Featured and non-featured block template prompts that either omitted
the owning integration's canonical name or were phrased as marketing
fragments rather than natural user instructions have been rewritten.
Each updated prompt now starts with an imperative verb ("Build a
workflow that…"), names the owning integration explicitly so the
@-mention chip renders correctly, and aligns with the entry's title.

Files changed: salesforce, hubspot (×2), github, slack, airtable,
firecrawl, iam.

* fix(templates): name integration in remaining block template prompts

- google_docs.ts: replace 'Google Doc' with 'Google Docs document' in 4 prompts; update title 'Meeting notes to Google Doc' to 'Meeting notes to Google Docs'
- google_sheets.ts: replace 'Google Sheet' with 'Google Sheets spreadsheet/Google Sheets' in 3 non-featured prompts
- slack.ts: replace 'Google Doc' with 'Google Docs document' in 'Daily standup summary'
- stripe.ts: replace 'Google Sheet'/'Slacks' with 'Google Sheets'/'Slack' in 'Weekly metrics report'
- reddit.ts: add 'Reddit' to 3 prompts that only referenced subreddits
- notion.ts: rewrite featured prompt to start with a verb and name Notion
- jira.ts: rewrite featured marketing-fragment prompt to start with a verb
- linear.ts: rewrite featured marketing-fragment prompt to start with a verb
- gmail.ts: rewrite featured marketing-fragment prompt to start with a verb and name Gmail

* fix(icons): convert monochrome dark brand icons to currentColor for dark mode

LinkupIcon, InfisicalIcon, IntercomIcon, LumaIcon, GranolaIcon, OnePasswordIcon,
and RailwayIcon were hardcoded to black/near-black fills/strokes, making them
invisible in bare DARK mode. Convert all to currentColor so they follow the
theme-aware text color.

Add iconColor: '#286efa' to IntercomBlock (Intercom brand blue is a confident
mid-tone, safe on both themes).

Two-tone icons (StagehandIcon, AgentPhoneIcon, QuiverIcon) are left unchanged
because their white details are structural — converting to single-tone would
destroy logo legibility.

* removed color, user input, sidebar, suggested actions, chips/emcn

* removed color migration

* improve(blocks): audit block catalog metadata for accuracy and fill gaps

- Fix template prompts that claimed capabilities blocks don't have
  (fabricated triggers, non-existent tools) across ~98 integrations
- Normalize integration tags to family conventions and valid union values
- Align alsoIntegrations and modules with template prompt content
- Add BlockMeta for circleback, imap, and rss trigger integrations
- Add templates to clickhouse and greptile metas
- Remove duplicate PageSpeed deploy-gate template

* chore(telemetry): drop org_seat.drift_reconciled audit

System self-heal bookkeeping doesn't belong in the user-facing audit
trail — membership and seat-purchase changes are already audited, and
the cron's logger output covers ops visibility.

* chore(db): consolidate branch migrations into single 0227

* fix(emcn): make ChipModal scroll internally when content exceeds viewport

The Modal→ChipModal migration dropped the old ModalBody scroll container:
ChipModal renders ModalContent bare (no overflow-hidden) and its wrappers
had no min-h-0 chain, so tall modals (e.g. New data drain with an S3
destination) overflowed max-h-[84vh] off-screen with no way to scroll.

Complete the flex min-h-0 chain through ChipModal's frame and give
ChipModalBody flex-1 min-h-0 overflow-y-auto — header/footer stay pinned,
body scrolls only when constrained. Short modals are unaffected (max-h
caps, it doesn't stretch), and dropdowns inside the body are Radix-portaled
so the new scroll container cannot clip them. Five modals that had locally
patched this with max-h-[Nvh] overrides keep working unchanged.

* fix(emcn): don't close modal when dismissing a dropdown via outside click

Radix dispatches pointer-down-outside to every open dismissable layer at
once, so clicking outside an open dropdown/select inside a modal closed
both the dropdown and the modal in one jarring step. ModalContent now
prevents its own dismissal while a portaled popper layer is open — the
first outside click closes just the popper, the next one closes the
modal.

* docs(skills): de-duplicate and correct agent docs; canonical styling tokens; mirror sim-sandbox rule

* docs(skills): broaden boundary-raw-fetch scope note, sync cursor rule mirrors

* fix(emcn): harden modal popper guard and exempt caret-anchored dropdowns from body scroll

Adversarial review follow-ups to the ChipModal scroll + dismissal fixes:

- Popper guard now requires data-state="open" inside the popper wrapper,
  so a dropdown that is merely animating closed no longer swallows the
  next outside click on the modal (DropdownMenu has an exit animation
  that keeps its wrapper mounted briefly)
- Port the same guard to SModalContent for consistency
- custom-tool-modal: opt its body out of the chrome scroll container
  (flex-none + overflow-visible); the caret-anchored EnvVar/Tag
  autocomplete dropdowns are absolute-positioned inside the body and
  must spill past its bounds rather than clip against a scroll boundary

* refactor(billing): drop unnecessary useCallback wrappers from event handlers

* fix(data-drains): complete chip migration of destination forms and fill missing placeholders

Finishes the in-flight FormField → ChipModalField conversion for all
destination form specs and adds the placeholders that several inputs
never had (S3 bucket/region/access keys, Azure account key, Datadog API
key, webhook signing secret/bearer token) — the cause of the New Data
Drain modal showing placeholder-less inputs inconsistently.

* fix(emcn): broaden modal popper guard to onInteractOutside

Covers the focusOutside dismissal path too: when a popper's focus scope
unwinds on close, the transient focus shift could still dismiss the
modal (and simultaneous body pointer-events lock teardown could freeze
the page). Same data-state="open" scoping as the pointer guard.

* fix(emcn): harden modal outside interactions

* update audit mock

---------

Co-authored-by: andres <k62hc5kjst@privaterelay.appleid.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: waleed <waleed@simstudio.ai>
2026-06-06 11:39:48 -07:00
WaleedandClaude Opus 4.8 16954e46fe feat(integrations): add ClickHouse block and expand Dagster + Tinybird tools (#4883)
* feat(integrations): add ClickHouse block and expand Dagster + Tinybird tools

* fix(tinybird): fail loudly on invalid query_pipe parameters JSON

parsePipeParameters previously returned {} on any JSON parse error, so a
mistyped 'parameters' input produced a successful pipe call with the dynamic
filters silently dropped. Throw a clear error for non-empty, non-object input
instead; an omitted/empty value still means 'no parameters'.

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

* fix(dagster): guard NaN numeric coercions and bound list_assets pagination

Address PR review:
- Route all block numeric coercions (list_runs limit/createdAfter/createdBefore,
  get_run_logs logsLimit, list_assets assetsLimit) through a toFiniteNumber()
  guard so invalid/wand-generated text becomes undefined instead of NaN.
- list_assets now applies a default page size (100) when no limit is given, so
  paging stays bounded and hasMore is meaningful even when limit is omitted.

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

* fix(dagster): make list_assets hasMore exact via fetch N+1

Address PR review (hasMore true on exact page): request one extra row
(pageSize + 1), use its presence as the authoritative hasMore, slice it off,
and derive the returned cursor from the last RETURNED asset's key path
(JSON-serialized; Dagster normalizes JS/Python whitespace on the way in).
This removes the false-positive hasMore when the final page is exactly full.

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

* fix(clickhouse): enforce read-only query operation and harden WHERE-clause guard

* fix(dagster): make list_runs hasMore exact via fetch N+1

Address PR review (list runs false hasMore): request one extra row
(pageSize + 1), use its presence as the authoritative hasMore, and slice it
off before mapping. Removes the false-positive hasMore (and misleading cursor)
when the final page is exactly `limit` runs long. Mirrors the list_assets fix.

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

* fix(clickhouse): restrict DROP PARTITION to literal values to prevent SQL injection

* fix(clickhouse): reject chained statements in read-only query operation

* fix(clickhouse): force JSON output on query path and ignore comments when detecting chained statements

* fix(tinybird): encode datasource/pipe names in URL paths to prevent traversal

A user-or-llm datasource/pipe name interpolated raw into the URL path (e.g.
'real_ds/../../other') is normalized by the WHATWG URL parser and can target a
different endpoint. Wrap the path segment with encodeURIComponent in the
truncate, delete, and query_pipe URLs. Events/append pass the name via
URLSearchParams, which already encodes, so they were unaffected.

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

* fix(clickhouse): block WITH-led writes/DDL in read-only query operation

* fix(clickhouse): validate column types structurally and normalize FORMAT around SETTINGS

* fix(clickhouse): balance-check ORDER BY/PARTITION BY and skip leading comments in read-only guard

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:49:51 -07:00
Theodore LiandClaude Opus 4.8 3518b999af feat(tables): background import for large CSVs with live progress (#4861)
* feat(tables): background import for large CSVs with live progress

* fix(tables): address review — import heartbeat, overlap guard, column/empty validation

* fix(tables): guard sync import overlap, scope fileKey to workspace, delete-on-replace after download

* fix(tables): stream large CSV imports from storage instead of buffering the whole file

* test(tables): fix async-import route tests for workspace-scoped fileKey + name uniquification

* fix(tables): append imports start after existing rows; reconcile missed import failures in the tray

* fix(tables): delete the uploaded CSV from storage after the import finishes

* fix(tables): validate replace before deleting rows; ignore stale replayed import events by importId

* fix(tables): bind import worker to its importId (no stale-worker clobber/overlap) and destroy storage stream on failure

* feat(tables): byte-based import progress, cancel support, and a start toast that opens the import view

* fix(tables): don't emit ready after cancel; honor cancel during the upload phase

* improvement(tables): use a stop (square) icon for canceling an active import

* fix(tables): make markTableImporting an atomic claim to close the concurrent-import TOCTOU race

* improvement(tables): preview CSV import from a slice, drop client row-count warning

The import dialog parsed the entire file in the browser to show an exact row
count and a row-limit warning. That holds the whole file in memory, blocks the
main thread, and hits V8's ~512MB string ceiling — so the dialog capped the
effective import size well below what the streaming importer handles.

Parse only the first 512KB (headers + sample for the mapping); drop the exact
count and the "would exceed the row limit by N" gate. The DB row-count trigger
already enforces max_rows server-side, so an over-limit import fails fast during
the run with a clear message instead of being blocked by an expensive parse.

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

* fix(tables): gate import ownership every batch and stop canceled imports reappearing

- Worker checked run ownership only at the progress cadence (~every 5k rows), so
  a canceled/superseded import could insert several more batches (incl. the final
  partial batch) before stopping. Move the updateImportProgress ownership gate to
  the top of every flush — a run that lost the table stops within one batch.
- A list/dialog import canceled mid-upload left the server row `importing` until
  the in-flight server cancel landed; hydration re-seeded it from useTablesList,
  so the dismissed import flickered back. Flag the real table id canceled on the
  mid-upload cancel path, skip re-seeding flagged tables in hydration, and clear
  the flag once the server import is terminal.

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

* refactor(tables): drive import tray by polling derived from server, not SSE

Import progress no longer holds an SSE connection per importing table. The tray
now derives its importing rows live from the table list (React Query), polled
only while an import is in flight; the table detail page keeps its own
cell-state SSE for grid refresh.

- store holds only client-only state now: optimistic uploads, which terminal
  completions to surface this session, canceled ids, menu open — no copied
  importStatus/rowsProcessed.
- useWorkspaceImports is the single source: polls via a data-predicate
  refetchInterval, derives rows, and fires completion toasts on the
  importing -> terminal transition.
- kickoff handlers use startUpload/setUploadPercent/endUpload; the invalidated
  list refetch surfaces the server row and polling takes over.
- removes use-hydrate-import-tray + use-import-progress-tracker (folded in).
- trims over-verbose comments across the import paths.

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

* fix(tables): ignore superseded-run import events in the detail SSE cache

applyImport applied every replayed import payload to the detail cache. The SSE
buffer can replay a prior import's terminal event for the same table, stomping a
newer in-flight import's UI. Lock to the active run's importId (and ignore a
replayed terminal before the id is known), matching the guard the header tracker
used to have.

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

* fix(tables): close sync-import TOCTOU by claiming the atomic import gate

The sync import route checked importStatus from a checkAccess snapshot, then
parsed/validated/wrote seconds later without taking the atomic claim. A
concurrent async kickoff (markTableImporting) could slip into that window and
both writers would run together — for replace mode, two delete+insert passes
leave the table indeterminate.

Claim the same atomic gate (markTableImporting) right before the write and
release it in the finally (before the response returns, so a client refetch
never sees the transient status). A row-level FOR UPDATE was avoided on purpose:
it would invert lock order against the position advisory lock / row-count
trigger and risk a deadlock — markTableImporting is the established gate.

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

* fix(multipart): keep abort wired after resolve so a mid-upload disconnect tears down the stream

readMultipart resolves on the file-part header and hands the caller an un-drained
stream, but settle() ran cleanup() and detached the abort listener on that path
too. A client disconnect mid-upload then destroyed nothing — busboy never saw EOF,
the file stream stalled, and the route's `for await` held a request slot until
maxDuration (300s). Re-arm an abort handler scoped to the file stream on resolve,
detached when the stream closes.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:26:06 -04:00
Vikhyath Mondreti 5d9752d563 fix(mothership): connect integrations from chat without state_mismatch (#4848)
* fix(oauth):  skipStateCookieCheck flag change

* browser initated solution

* fix draft timing issue
2026-06-02 12:22:38 -07:00
Waleed 3f3efc98c3 chore(auth): remove deprecated OAuth MCP provider plugin and backing tables (#4847) 2026-06-02 10:46:20 -07:00
Waleed e5a46d7959 feat(linq): add Linq iMessage/SMS/RCS integration (34 tools, block, attachment upload) (#4831) 2026-06-01 13:44:33 -07:00
WaleedandClaude Opus 4.8 403a02c3af feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)
* 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>
2026-06-01 12:17:29 -07:00
Waleed e1e773f487 feat(slack): add install + privacy section to integration landing page (#4799)
* 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.
2026-05-29 16:33:30 -07:00
Vikhyath Mondreti 4b0dab4682 chore(copilot): deprecate mcp server (#4797)
* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route
2026-05-29 14:34:15 -07:00
Theodore LiandClaude Opus 4.7 7f24ae12fd feat(block): Add data enrichment block (#4774)
* 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>
2026-05-28 22:19:08 -04:00
WaleedandClaude Opus 4.8 c898e2e623 feat(integrations): add ZoomInfo, align Wiza, audit Apollo, refresh docs (#4776)
* 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>
2026-05-28 15:30:25 -07:00
WaleedandRheagalFire 81bf93b184 feat(litellm): add LiteLLM as AI gateway provider (#4739)
* feat: add LiteLLM as AI gateway provider

* fix: add litellm to attachments, provider store, utils, and block guards

* fix: add frontend model discovery pipeline for litellm provider

Add API route, contract, query hook case, and ProviderModelsLoader
entry so litellm models are fetched and synced to the store on
workspace load, matching the vllm/ollama/openrouter/fireworks pattern.

Also fixes defaultModel to empty string and adds litellm/ prefix
early-return in blocks/utils.ts (reviewer feedback).

* fix: remove azureEndpoint fallback from LiteLLM provider

Copy-paste artifact from vLLM provider. LiteLLM should only use
LITELLM_BASE_URL, not fall back to azureEndpoint which could cause
requests to be routed to the wrong server.

* fix(litellm): close audit gaps from PR #4644

- byok.ts: add litellm branch to getApiKeyWithBYOK so workflow
  block execution can resolve the proxy key instead of throwing
  "API key is required for litellm ..."
- check-api-validation-contracts.ts: bump route baseline 755 -> 756
  to account for the new /api/providers/litellm/models route
- .env.example: document LITELLM_BASE_URL / LITELLM_API_KEY
- copilot edit-workflow validation: include LiteLLM in the list of
  user-configured prefixed providers shown to the model
- providers/utils.ts: drop stray optional-chain on providers.litellm
  to match the vllm pattern
- lint: apply biome formatting fixes (multi-line if, SVG path,
  multi-line DYNAMIC_MODEL_PROVIDERS)

* fix(litellm): final parity gaps from second audit

- blocks/utils.ts getModelOptions(): include litellm models in the
  combined model dropdown — was previously dropping any
  proxy-discovered models from the agent block model picker.
- get-blocks-metadata-tool.ts mockProvidersState: add litellm bucket
  so the server-side copilot block-metadata fallback can render
  model options when the providers store is not initialized.
- blocks/utils.test.ts: add litellm to mock providers state (initial
  + beforeEach reset) and add a parallel store-bucket guard test
  mirroring the vLLM case.
- providers/utils.test.ts: add parallel getApiKey test for litellm.

* feat(litellm): use official LiteLLM brand icon and color

- icons.tsx: replace the placeholder letterform with the official
  LiteLLM brand mark embedded as a PNG data URI in an SVG image.
- models.ts: set color: #040229 on the litellm provider definition
  to match the brand background.

* chore(litellm): validate /v1/models response with shared schema in initialize()

Match the API route handler — both code paths now run the same
vllmUpstreamResponseSchema.parse() over the upstream /v1/models
JSON instead of a raw type-cast, so malformed upstream payloads
surface a descriptive ZodError instead of a downstream TypeError.

Addresses Greptile review feedback on PR #4739.

---------

Co-authored-by: RheagalFire <arishalam121@gmail.com>
2026-05-26 10:38:24 -07:00
Waleed 21c956cf97 improvement(hubspot): OAuth-native polling trigger replacing webhook flow (#4705)
* improvement(hubspot): OAuth-native polling trigger replacing webhook flow

* feat(hubspot): property autocomplete, multi-filter, property-changed, list-membership, pipeline/owner dropdowns

* fix(hubspot): freeze cursor on failure + request full OAuth scopes

* chore(api): bump API route baseline from 749 to 753 for HubSpot selector routes

* fix(hubspot): make eventType required conditional on visibility

* improvement(hubspot): align trigger name and longDescription with poll-trigger conventions

* fix(hubspot): encodeURIComponent on search path segment for defense-in-depth

* fix(hubspot): cursor-based seed for list_membership polling

* fix(hubspot): Map-backed property snapshot + drop redundant filter parse
2026-05-21 17:25:19 -07:00
Vikhyath Mondreti 47bd7fa345 fix(logs-cleanup): listing active workspaces into mem + download time streaming lims (#4692)
* fix(logs-cleanup): listing active workspaces into mem + download time streaming lims

* fix

* fix ci

* address comments

* add skill

* fix client-server sep

* fix parse bytes enforcement

* address comments, antipatterns

* address slides ssrf comment

* more fixes

* fix tests

* fix
2026-05-21 16:16:03 -07:00
WaleedandClaude Opus 4.7 46db40620f feat(mcp): OAuth 2.1 + PKCE for outbound MCP servers (#4441)
* feat(mcp): OAuth 2.1 support for outbound MCP servers

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Two coupled changes:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Two P1 issues + one cleanup from the bot reviewers:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:44:09 -04:00