Compare commits

..

167 Commits

Author SHA1 Message Date
Vikhyath Mondreti
489f2d3bd0 v0.6.60: copilot security improvements, slack canvas ops, retention jobs fixes 2026-04-27 12:37:28 -07:00
Theodore Li
65e17de065 fix(retention-job): add chunking strategy for cleanup (#4305)
* fix(retention-job): add chunking strategy for cleanup

* change stats to be perjob not per chunk
2026-04-27 15:13:00 -04:00
Vikhyath Mondreti
79ff5d80b3 improvement(slack): channel selector for list canvasses (#4307) 2026-04-27 12:11:14 -07:00
Vikhyath Mondreti
2a52141d2f feat(slack): canvas related operations (#4306)
* feat(slack): canvas related operations

* extract shared code
2026-04-27 12:00:40 -07:00
Theodore Li
76ad59fd7d fix(stream): Avoid bun memory leak bug from TransformStream (#4255)
* Avoid bun memory leak bug from TransformStream

* fix(executor): skip content persistence when stream consumer exits early

Previously, if the onStream consumer caught an internal error without
re-throwing, the block-executor would treat the shortened accumulator
as the complete response, persist a truncated string to memory via
appendToMemory, and set it as executionOutput.content.

Track whether the source ReadableStream actually closed (done=true) in
the pull handler. If onStream returns before the source drains, skip
content persistence and log a warning — the old tee()-based flow was
immune to this because the executor branch drained independently of
the client branch.

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

* fix lint

* fix(executor): early-return when no streamed content to make onFullContent symmetric

Previously, executionOutput.content was guarded by `if (fullContent)`
but `onFullContent` fired regardless. The agent-handler implementor
defensively bails on empty/whitespace content, but that's a callee
contract, not enforced at the call site — future implementors could
spuriously persist empty assistant turns to memory.

Hoist the `!fullContent` check to a single early return, so both the
output write and the callback share the same precondition.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:23:36 -04:00
Waleed
c32c1cb917 fix(security): patch copilot tool & multipart upload IDORs (#4304)
* fix(security): patch copilot tool & multipart upload IDORs

- multipart upload: bind upload session to (userId, workspaceId, key)
  via short-lived HMAC-signed token; require workspace write access at
  initiate; source key/uploadId/context from verified token (never
  client) at get-part-urls/complete/abort
- copilot knowledge-base tools: gate all 11 read/write/tag/connector
  ops with checkKnowledgeBaseAccess / checkKnowledgeBaseWriteAccess
- copilot user-table tools: add workspace-id check to get, get_schema,
  add/rename/delete/update_column to match existing op pattern
- copilot manage-credential: add full ownership/write-permission auth
  via getCredentialActorContext (previously had no auth)
- copilot restore-resource: verify workspace ownership and write
  permission for workflow, table, knowledgebase, file, and folder
  restores
- copilot folder rename/move: verify both folderId and parentId belong
  to the caller's workspace via new verifyFolderWorkspace helper
- copilot get-job-logs: verify schedule belongs to caller's workspace

* fix(security): address PR review — document IDOR, log count, token split

- knowledge-base delete_document/update_document: verify each document
  belongs to the claimed knowledgeBaseId via checkDocumentWriteAccess
  (was: trusted args.knowledgeBaseId without binding it to the document)
- multipart batch complete: log verifiedEntries.length instead of raw
  client-supplied data.uploads.length
- upload-token: reject tokens with !=2 dot-delimited segments

* fix(security): close folder workspace bypass when workspaceId is falsy
2026-04-27 11:05:22 -07:00
Vikhyath Mondreti
58a3ae2aa4 v0.6.59: gpt 5.5, security hardening, parallel subagents rendering 2026-04-27 10:02:06 -07:00
Vikhyath Mondreti
50e74f75ef fix(mothership): parallel subagent rendering, exec stream re-attach (#4299)
* fix(mothership): parallel subagent rendering

* fix rendering logic

* address comments

* cleanup dead code

* address mothership legacy key

* prevent id collision

* rollout stream edge case

* address bugbot comments"

* remove unused fallbacks

* fix execution stream attach, cleanup comments

* remove debug logs

* cleanup failed reconnect
2026-04-27 00:40:39 -07:00
Waleed
60652e621c fix(security): credential-set invite email check + shopify authorize XSS (#4302) 2026-04-26 20:52:42 -07:00
Waleed
8863f1132a feat(models): add gpt-5.5 models (#4300)
* feat(models): add gpt-5.5 models

* fix(models): address gpt-5.5 review feedback

* fix(models): align gpt-5.5 pro controls
2026-04-25 19:13:29 -07:00
Vikhyath Mondreti
d6c1bc2fef v0.6.58: queue abort state machine improvement, contributing guide 2026-04-24 18:52:13 -07:00
Vikhyath Mondreti
d93a6f57bc chore(guide): update contributing guide (#4296)
* chore(guide): update contributing guide

* fix md
2026-04-24 18:47:57 -07:00
Vikhyath Mondreti
df581c3efb fix(mothership): queue supersede crash (#4297)
* fix(mothership): queue supersede crash

* add test

* abort observed marker
2026-04-24 18:31:46 -07:00
Theodore Li
595c4c3613 Merge pull request #4293 from simstudioai/staging
v0.6.57: mothership reliability, ashby refactor, tables row count, copilot id fix, bun upgrade
2026-04-24 16:59:47 -07:00
Vikhyath Mondreti
f16d17ba49 improvement(mothership): do not silently re-route missing stream id (#4295) 2026-04-24 16:37:40 -07:00
Waleed
e6fefc863c fix(table-block): resolve canonical tableId in filter/sort builders (#4294)
The visual filter/sort builders read the selected tableId from subBlock id
'tableId', but the Table block stores it under 'tableSelector' (basic) or
'manualTableId' (advanced) via canonicalParamId. The lookup always returned
null, so useTable was disabled and the column picker always showed
"no options available".

Adds useCanonicalSubBlockValue that resolves by canonicalParamId through
the canonical index, mirroring the pattern used by dropdown dependsOn.
2026-04-24 16:24:49 -07:00
Waleed
5fba724818 chore(bun): bump bun to 1.3.13 (#4291) 2026-04-24 15:59:10 -07:00
Waleed
60b80ec172 improvement(tables): race-free row-count trigger + scoped tx timeouts (#4289)
* improvement(tables): race-free row-count trigger + scoped tx timeouts

* fix(tables): close upsert race + serialize replaceTableRows

Two concurrency bugs flagged by review:

1. `upsertRow` insert path: removing FOR UPDATE broke serialization between
   the initial existing-row SELECT and the INSERT. Two concurrent upserts
   on the same conflict target could both find no match, then both insert,
   producing a duplicate that bypasses the app-level unique check. Fixed
   by re-checking for the matching row *after* acquiring the per-table
   advisory lock; if a racing tx already inserted, switch to UPDATE.

2. `replaceTableRows`: under READ COMMITTED each tx's DELETE uses its own
   MVCC snapshot, so two concurrent replaces could each DELETE only the
   rows visible at their start, then both INSERT, leaving the table with
   the union of both row sets. Fixed by acquiring the per-table advisory
   lock at the start of the tx to serialize replaces against each other
   and against auto-position inserts.

Also updated a stale docstring on `upsertRow` that still referenced the
removed FOR UPDATE.

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

* fix(tables): serialize explicit-position inserts with advisory lock

The `(table_id, position)` index is non-unique. Concurrent explicit-
position inserts at the same slot can both observe the slot empty, both
skip the shift, then each INSERT — producing duplicate `(table_id,
position)` rows.

Take the per-table advisory lock in the explicit-position branches of
`insertRow` and `batchInsertRows` (the auto-position branches already do
this). Updated the test that asserted the explicit path skipped the lock,
and added coverage for `batchInsertRows` with explicit positions.

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

* refactor(tables): dedupe upsert UPDATE path + extract nextAutoPosition

Two pure cleanups on the round-1 changes:

1. Extract `nextAutoPosition(trx, tableId)` — the `SELECT coalesce(max(
   position), -1) + 1` pattern was repeated in three call sites
   (`insertRow` auto branch, `batchInsertRows` auto branch, `upsertRow`
   insert branch). One helper, same behavior.

2. Consolidate `upsertRow` update path. The initial-SELECT match and the
   post-lock re-select match previously had two literal duplicates of the
   same UPDATE + return block. Resolve `matchedRowId` first, then run one
   UPDATE branch. Lock is still only acquired when we don't find a match
   on the first pass.

No behavior change. 98/98 table unit tests unchanged.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 15:33:17 -07:00
Vikhyath Mondreti
af4be770a1 improvement(mothership): treat error as terminal event (#4290) 2026-04-24 14:51:26 -07:00
Waleed
f330fe22a2 refactor(ashby): align tools, block, and triggers with Ashby API (#4288)
* refactor(ashby): align tools, block, and triggers with Ashby API

Audit-driven refactor to destructure rich fields per Ashby's API docs,
centralize output shapes via shared mappers in tools/ashby/utils.ts,
and align webhook provider handler with trigger IDs via a shared
action map. Removes stale block outputs left over from prior flat
response shapes.

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

* fix(ashby): remove stale noteId output and reject ping events

- Remove stale `noteId` output descriptor from block (create_note
  now returns `id` at the top level via the shared note mapper).
- Explicitly reject `ping` events in the webhook matchEvent before
  falling back to the generic triggerId check, so webhook records
  missing triggerId cannot execute workflows on Ashby ping probes.

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

* fix(ashby): trim optional ID params in create/update tools

Optional ID params in create_application, change_application_stage,
and update_candidate were passed through to the request body without
.trim(), unlike their required ID siblings. Normalize to prevent
copy-paste whitespace errors.

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

* fix(ashby): add subblock migration for removed filterCandidateId

Add SUBBLOCK_ID_MIGRATIONS entry so deployed workflows that previously
used the `filterCandidateId` subBlock on `list_applications` don't break
after the field was removed (Ashby's application.list doesn't filter by
candidateId). Also regenerate docs to sync noteId removal.

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

* fix(ashby): final API alignment from parallel validation

- create_candidate: email is optional per Ashby docs (only name is
  required); tool, types, and block all made non-required.
- list_applications: guard NaN when createdAfter can't be parsed so we
  don't send a bad value to Ashby's API.
- webhook provider: replace createHmacVerifier with explicit
  fail-closed verifyAuth that 401s when secretToken, signature header,
  or signature match is missing (was previously fail-open on missing
  secret).

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

* fix(ashby): preserve input.data path in webhook formatInput

Restore the explicit `data` key alongside the spread so deployed
workflows that reference `input.data.application.*`, `input.data.candidate.*`,
etc. keep working. The spread alone dropped those paths.

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

* refactor(ashby): drop legacy input.data key from webhook formatInput

Keep formatInput aligned with the advertised trigger outputs schema
(flat top-level entities) and drop the legacy input.data.* compat path.
Every field declared in each trigger's outputs is now populated 1:1 by
the data spread plus the explicit action key — no undeclared keys.

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

* fix(ashby): trim remaining ID body params for parity

Add .trim() on sourceId (create_candidate), jobId (list_applications),
applicationId and interviewStageId (list_interviews) to match the
trim-on-IDs pattern used across the rest of the Ashby tools and guard
against copy-paste whitespace.

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

* update docs

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:29:26 -07:00
d 🔹
efc868263a fix(copilot): replace crypto.randomUUID() with generateId() per project rule (#4268)
AGENTS.md / CLAUDE.md forbid crypto.randomUUID() (non-secure contexts throw
TypeError in browsers). Four copilot server-side files still violated this
rule, left over after PR #3397 polyfilled the client.

Routes through request lifecycle, OAuth draft insertion, persisted message
normalization, and table-row generation now use generateId from @sim/utils/id,
which is a drop-in UUID v4 producer that falls back to crypto.getRandomValues
when randomUUID is unavailable.

Refs #3393.
2026-04-24 11:59:48 -07:00
Vikhyath Mondreti
56044776d5 improvement(mothership): stream retry state machine, progressive re-rendering (#4287)
* improvement(mothership): stream rety state machine, progressive re-rendering

* address comments
2026-04-24 11:56:53 -07:00
Theodore Li
04f1d015f3 fix(mothership): Use heartbeat mechanism for chat locks (#4286) 2026-04-24 14:36:50 -04:00
Waleed
3422f64c5f Merge pull request #4285 from simstudioai/staging
fix(db): revert statement_timeout startup options breaking pooled connections (#4284)
2026-04-23 23:32:23 -07:00
Waleed
ccb5f1e690 fix(db): revert statement_timeout startup options breaking pooled connections (#4284) 2026-04-23 23:31:52 -07:00
Waleed
6066fc1960 v0.6.56: data retention improvements, tables column double click resize, subagent thinking, files sorting, agentphone integration 2026-04-23 23:09:57 -07:00
Waleed
91ccbb9921 fix(agentphone): fix image (#4281) 2026-04-23 16:54:32 -07:00
Theodore Li
dcbe7c69b0 feat(ui): Show subagent logs in bounded vertical view (#4280)
* feat(ui): render subagent actions in bounded box.

* Add gradients and scroll bar

* fix lint
2026-04-23 18:41:53 -04:00
Theodore Li
c22ac38ab0 Set statement timeout of 90 seconds (#4276) 2026-04-23 18:27:15 -04:00
Waleed
cdde8cbd66 feat(agentphone): add AgentPhone integration (#4278)
* feat(agentphone): add AgentPhone integration

* fix(agentphone): validate numeric inputs and metadata JSON

* chore(agentphone): remove dead from fallback in get_number_messages

* fix(agentphone): drop empty-string updates in update_contact

* fix(agentphone): scope limit/offset to list ops and revert stray IdentityCenter change

* lint
2026-04-23 15:20:19 -07:00
Waleed
0ae19dab85 feat(files): default sort by updated and add updated sort option (#4279)
* feat(files): default sort by updated and add updated sort option

* feat(files): show Last Updated column

Matches the visible-column pattern already used on Knowledge and Tables
so users can see the value they're sorting by.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 15:02:07 -07:00
Waleed
3b11c814f8 fix(tables): account for letter-spacing and displayed content in column auto-resize (#4277) 2026-04-23 14:41:40 -07:00
Theodore Li
b86ebb35fd fix(api): Pass archivedAt to list table response (#4275)
* fix(retention): switch data retention to be org-level

* fix lint

* cleanup mothership ran logs

* fix cleanup dispatcher

* fix ui flash for data retention settings

* fix lint

* remove raw sql string interprolation

* fix(api): return archivedAt for list tables route
2026-04-23 14:32:37 -04:00
Theodore Li
65972f2fa3 fix(retention): switch data retention to be org-level (#4270)
* fix(retention): switch data retention to be org-level

* fix lint

* cleanup mothership ran logs

* fix cleanup dispatcher

* fix ui flash for data retention settings

* fix lint

* remove raw sql string interprolation
2026-04-23 02:41:49 -04:00
Waleed
7ca736a7a1 v0.6.55: standardize monorepo conventions, api key hash, thinking text for mothership 2026-04-22 23:25:27 -07:00
Vikhyath Mondreti
5f0f0edd63 improvement(repo): separate realtime into separate app (#4262)
* improvement(repo): restructuring to make realtime image narrower scoped

* improvements

* chore(repo): rebase fixes and quality improvements for realtime split

Addresses merge-time issues and gaps from the realtime app split:
- Retarget stale vi.mock paths to @sim/workflow-persistence/subblocks
- Restore README branding, fix AGENTS.md script reference
- Restore TSDoc on workflow-persistence subblocks helpers
- Use toError() from @sim/utils/errors in save.ts
- Add vitest config + local mocks so @sim/audit tests run standalone
- Move socket.io-client to devDependencies in apps/realtime
- Add missing package COPY steps to docker/app.Dockerfile
- Add check:boundaries/check:realtime-prune scripts and wire into CI

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

* refactor(security): consolidate crypto primitives into @sim/security

Move general-purpose crypto primitives out of apps/sim into the
@sim/security package so both apps/sim and apps/realtime can share them.

@sim/security exports (all pure, dependency-free):
  ./compare    safeCompare (constant-time HMAC-wrapped equality)
  ./encryption encrypt/decrypt (AES-256-GCM, iv:cipher:tag format)
  ./hash       sha256Hex
  ./tokens     generateSecureToken (base64url)

Migrate apps/sim call sites to use these + @sim/utils helpers:
  crypto.randomUUID()            -> generateId() from @sim/utils/id
  createHash('sha256').digest    -> sha256Hex
  timingSafeEqual on hashed hex  -> safeCompare
  new Promise(setTimeout)        -> sleep from @sim/utils/helpers

No behavior change: encryption format, digest output, and token
length are preserved exactly.

* refactor(copilot): use toError in remaining otel/finalize sites

Replace the last two `error instanceof Error ? error : new Error(String(error))`
patterns with toError from @sim/utils/errors. Completes the sweep of clean
candidates — no behavior change.

* refactor(security): consolidate HMAC-SHA256 primitives into @sim/security

Adds hmacSha256Hex and hmacSha256Base64 to @sim/security/hmac and migrates
15 webhook providers plus 5 other hot paths (deployment token signing,
outbound webhook requests, workspace notification delivery, notification
test route, Shopify OAuth callback) off bare `createHmac` calls. Secret
parameter accepts `string | Buffer` to cover base64-decoded Svix-style
secrets (Resend) and MS Teams' HMAC scheme. AWS SigV4 signing in S3 and
Textract tools intentionally retains direct `createHmac` usage — its
multi-step key derivation chain doesn't fit a generic helper.

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

* chore(packages): post-audit test + packaging polish

- Add safeCompare unit tests (identity, length mismatch, hex-nibble diff).
- Add Buffer-secret cases to hmac tests to lock in Svix/MS-Teams contract.
- Declare `reactflow` as a peerDependency on @sim/workflow-types — only used for type imports.
- Add a barrel export to @sim/workflow-persistence for consumers that prefer package-level imports; subpath exports retained.
- Document the data-field invariant in load.ts for loop/parallel subflow patching.

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

* chore(realtime): address PR review feedback

- Remove redundant SOCKET_PORT=3002 env from Dockerfile runner stage
  (env.PORT already defaults to 3002 via zod schema).
- Reorder PORT fallback so an explicitly-set SOCKET_PORT wins over
  the schema default for PORT; keeps SOCKET_PORT functional as an
  override instead of dead code.
- Add dedicated type-check CI step for @sim/realtime so TS errors
  surface pre-deploy (the Dockerfile runs source TS via Bun and has
  no implicit build-time type check).

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

* chore(realtime): remove unused SOCKET_PORT env var

SOCKET_PORT has lived in the socket server since the June 2025 refactor
but was never actually set in any deploy config — docker-compose.prod,
helm values/templates, .env.example, and docs all use PORT or the 3002
default exclusively. No self-hoster was ever pointed at SOCKET_PORT, so
removing it is safe.

Simplifies realtime port resolution to `env.PORT` (zod-validated with a
3002 default) and drops the orphaned sim-side schema entry.

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

---------

Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 23:06:16 -07:00
Waleed
bed5e95742 fix(selectors): enable search on all picker and selector subBlocks (#4269) 2026-04-22 20:40:59 -07:00
Theodore Li
f7ab39984c feat(ui): add thinking ui to mothership (#4254)
* feat(ui): Add thinking ui

* fix tests

* Remove duplicate helper for block timing

* fix lint

* fix endedAt timestamp bug

* fix stuck subagent thinking
2026-04-22 19:52:56 -04:00
Theodore Li
8ce56fe1f2 fix(auth): add api key auth via sha256 hash lookup (#4266)
* fix(auth): add api key auth via sha256 hash lookup

* Remove promise all logic

* Restore feature flag

* fix feature flag

* Combine auth and hash gate
2026-04-22 18:30:37 -04:00
Vikhyath Mondreti
64cfda523b v0.6.54: mothership tracing, db pool size increase 2026-04-22 14:01:10 -07:00
Vikhyath Mondreti
8c9ddefc53 fix(otel): chat root OTel span on all early-return paths (#4265) 2026-04-22 13:54:47 -07:00
Theodore Li
d927d8bdff fix(db): raise db pool size (#4263)
* fix(db): raise db pool size

* Raise socket connections

* bump up connection size even more
2026-04-22 14:25:13 -04:00
Siddharth Ganesan
0aeab026a8 feat(observability): add mothership tracing (#4253) 2026-04-22 09:06:01 -07:00
Vikhyath Mondreti
7c619e78d8 Merge pull request #4261 from simstudioai/staging
v0.6.54: migration error logs
2026-04-21 22:11:47 -07:00
Vikhyath Mondreti
41a1b50ace improvement(migrations): log better errors (#4260) 2026-04-21 22:06:05 -07:00
Waleed
bbf400ff13 v0.6.53: permissions groups migration, docs updates 2026-04-21 21:19:20 -07:00
Waleed
7941dcde98 fix(docs): update simstudio.ai URLs to sim.ai in SSO docs (#4257)
* fix(docs): update simstudio.ai URLs to sim.ai in SSO docs

* improvement(docs): remove plan defaults table from data retention docs

* improvement(docs): consolidate self-hosting info at bottom of enterprise docs

* improvement(docs): reduce callout and FAQ overuse in enterprise docs

* improvement(docs): restore FAQs and genuine-gotcha callouts
2026-04-21 21:18:32 -07:00
Vikhyath Mondreti
51ace655e4 fix(migration): permission group migration error (#4258) 2026-04-21 21:10:50 -07:00
Waleed
ca3bbf14b0 v0.6.52: data retention, docs updates, slack manifest generator, security hardening, contact page, 404 page, access control, SES, SNS 2026-04-21 20:25:26 -07:00
Waleed
c2529c3f2c fix(settings): hide data-retention nav item when user lacks enterprise plan (#4256) 2026-04-21 20:19:04 -07:00
Waleed
45bf396968 fix(deps): bump drizzle-orm 0.45.2 + adopt MCP SDK 1.25.3 native types (#4252)
* fix(deps): bump drizzle-orm to 0.45.2 (GHSA-gpj5-g38j-94v9)

Resolves Dependabot alert #98. Drizzle ORM <0.45.2 improperly escaped
quoted SQL identifiers, allowing SQL injection via untrusted input
passed to APIs like sql.identifier() or .as().

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

* chore(mcp): adopt native SDK types after @modelcontextprotocol/sdk 1.25.3 bump

Replace hand-written schema/annotation shapes with the SDK's exported
Tool, JSONRPCResultResponse, and Tool['annotations'] types so changes
upstream flow through automatically.

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

* refactor(types): use drizzle $inferSelect for row types

Replace hand-written interfaces that duplicated schema shape with
typeof table.$inferSelect aliases for webhook, workflow, and
workspaceFiles rows. Also simplify metadata insert/update to use
.returning() instead of field-by-field copies.

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

* fix(uploads): fall through to INSERT if restore-deleted row races a hard delete

If a hard delete races between the initial SELECT and the restore UPDATE,
.returning() yields no row. Previously the function would return undefined
and silently violate the Promise<FileMetadataRecord> contract. Now the
function falls through to the INSERT path, which already handles
uniqueness races via the 23505 catch.

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

* chore(uploads): align metadata.ts with global standards

Replace dynamic uuid import with generateId() per @sim/utils/id
convention, narrow the error catch off `any`, and convert the inline
comment to TSDoc.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:52:15 -07:00
Waleed
193f06fcf5 fix(aws): add validateAwsRegion to all AWS route schemas to prevent SSRF (#4250)
* fix(aws): add validateAwsRegion to all AWS route schemas to prevent SSRF

* fix(validation): add mx and eu-isoe prefixes to validateAwsRegion regex

* test(validation): add mx-central-1, eu-isoe-west-1, and us-iso-west-1 region test cases

* fix(aws): eliminate double validateAwsRegion call and fix regex alternation order

- Replace double-call .refine() pattern with single-call + static message across all 61 AWS routes
- Reorder regex alternation to put longer prefixes first (eu-isoe before eu, us-isob/us-iso/us-gov before us) for engine-agnostic correctness
2026-04-21 19:22:50 -07:00
Waleed
34cfc2689a improvement(contact): add Turnstile CAPTCHA, honeypot, and robustness fixes (#4248)
* improvement(contact): add Turnstile CAPTCHA, honeypot, and robustness fixes

- Add Cloudflare Turnstile with graceful degradation: when the widget
  fails to load (ad blockers, iOS privacy, corporate DNS), submissions
  fall through to a tighter rate-limit bucket rather than hard-blocking
- Add honeypot field to filter automated submissions without user impact
- Add separate CAPTCHA_UNAVAILABLE_RATE_LIMIT bucket (3/min) for the
  no-captcha path so spam via ad-blocker bypass remains expensive
- Pass expectedHostname to verifyTurnstileToken to close cross-site
  token reuse gap
- Add SITE_HOSTNAME as module-level constant (avoid URL parsing per req)
- Wire onExpire/onError/onUnsupported callbacks so token expiry during
  slow form-filling falls back gracefully instead of showing a captcha error
- Add getResponsePromise(30_000) timeout to prevent indefinite hang on
  network blips
- Add size: 'invisible' to Turnstile options (required for execute mode)
- Move turnstile.ts to lib/core/security/ alongside csp/encryption/input-validation
- Switch all CSS to --landing-* variables throughout contact form
- Move error display inline next to label with truncation in LandingField
- Add labelClassName prop to LandingField for context-specific overrides
- Simplify contact page to single-column max-w-[640px] layout

* fix(contact): fall through to no-captcha rate limit on Cloudflare transport errors

* chore(contact): remove extraneous comments from route

* fix(contact): remove forced min-height on success state, let content flow naturally

* fix(contact): cast CONTACT_TOPIC_OPTIONS to satisfy Combobox mutable type

* fix(contact): disable submit during CAPTCHA resolution window, add relative to form
2026-04-21 18:25:48 -07:00
Waleed
2d94b3729d feat(integrations): AWS SES, IAM Identity Center, and enhanced IAM/STS/CloudWatch/DynamoDB (#4245)
* feat(integrations): add AWS SES, IAM Identity Center, and enhanced IAM/STS/CloudWatch/DynamoDB integrations

- Add AWS SES v2 integration with 9 operations (send email, templated, bulk, templates, account)
- Add AWS IAM Identity Center integration with 12 operations (account assignments, permission sets, users, groups)
- Add 3 new IAM tools: list-attached-role-policies, list-attached-user-policies, simulate-principal-policy
- Fix DynamoDB duplicate subBlock IDs, add operation-scoped field names, add subblock migrations
- Add authMode: AuthMode.ApiKey to DynamoDB block
- Fix CloudWatch routes: toError, client.destroy(), withRouteHandler, auth outside try
- Fix STS/DynamoDB/IAM routes: nullable Zod schemas, withRouteHandler adoption
- Fix Identity Center: list_instances pagination, list_groups instanceArn condition
- Add subblock migrations for renamed DynamoDB fields (key, filterExpression, etc.)
- Apply withRouteHandler to all new and existing AWS tool routes

* docs(ses): add manual intro section to SES docs

* fix(dynamodb): add legacy fallbacks in params for subblock migration compatibility

Workflows saved with the old shared IDs (key, filterExpression, etc.) that migrate
to get-scoped slots via subblock-migrations still work correctly on update/delete/scan/put
operations via fallback lookups in tools.config.params.

* feat(contact): add contact page, migrate help/demo forms to useMutation (#4242)

* feat(contact): add contact page, migrate help/demo forms to useMutation

* improvement(contact): address greptile review feedback

- Map contact topic to help email type for accurate confirmation emails
- Drop Zod schema details from 400 response on public /api/contact
- Wire aria-describedby + aria-invalid in LandingField for both forms
- Reset helpMutation on modal reopen to match demo-request pattern

* improvement(landing): extract shared LandingField component

* fix(landing): resolve error-page crash on invalid /models and /integrations routes (#4243)

* fix(layout): use plain inline script for PublicEnvScript to set env before chunks eval on error pages

* fix(landing): handle runtime env race on error-page renders

React skips SSR on unhandled server errors and re-renders on the client
(see vercel/next.js#63980, #82456). Root-layout scripts — including the
runtime env script that populates window.__ENV — are inserted but not
executed on that client re-render, so any client module that reads env
at module evaluation crashes the render into a blank "Application error"
overlay instead of rendering the styled 404.

This replaces the earlier PublicEnvScript tweak with the architectural
fix:

- auth-client.ts: fall back to window.location.origin when getBaseUrl()
  throws on the client. Auth endpoints are same-origin, so this is the
  correct baseURL on the client. Server-side we still throw on genuine
  misconfig.
- loading.tsx under /models/[provider], /models/[provider]/[model], and
  /integrations/[slug]: establishes a Suspense boundary below the root
  layout so a page-level notFound() no longer invalidates the layout's
  SSR output (the fix endorsed by Next.js maintainers in #63980).
- layout.tsx: revert disableNextScript — the research showed this
  doesn't actually fix error-page renders. The real fix is above.

* improvement(landing): use emcn Loader in scoped loading.tsx, trim auth-client comment

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

---------

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

* fix(iam): correct MissingContextValues mapping in simulatePrincipalPolicy

* fix(aws): add conditionExpression migration fallback for DynamoDB delete, fix SES pageSize min

* fix(aws): deep validation fixes across SES, IAM, Identity Center, DynamoDB integrations

- IAM: replace non-existent StatementId with SourcePolicyType in simulatePrincipalPolicy
- IAM: add .int() constraint to list-users/roles/policies/groups Zod schemas
- IAM: remove redundant manual requestId from all 21 IAM route handlers
- SES: add .refine() body validation to create-template route
- SES: make bulk email destination templateData optional, only include ReplacementEmailContent when present
- SES: fix pageSize guard to if (pageSize != null) to correctly forward 0
- SES: add max(100) to list-templates pageSize, revert list-identities to min(0) per SDK
- STS: fix logger.error calls to use structured metadata pattern
- Identity Center: remove deprecated account.Status fallback, use account.State only
- DynamoDB: convert empty interface extends to type aliases, remove redundant error field, fix barrel to absolute imports

* regen docs

* fix(iam): add .int() constraint to maxSessionDuration in create-role route

* fix(ses): forward pageSize=0 correctly in listIdentities util

* fix(aws): add gradient background to IdentityCenterIcon, fix listTemplates pageSize guard

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 16:30:43 -07:00
Vikhyath Mondreti
aee6189d14 improvement(access-control): migrate to workspace scope (#4244)
* improvement(access-control): migrate to workspace scope

* fix edge cases

* update docs

* prep merge

* regen migrations

* address comments

* add ws id, user constraint

* address more comments

* address ui comments

* address more comments
2026-04-21 15:53:17 -07:00
Emir Karabeg
3a0e7b89a4 seo(robots): disallow tag-filtered blog URLs from crawlers (#4247) 2026-04-21 15:13:08 -07:00
Waleed
d185953248 improvement(landing): scope navbar/footer to (shell) route group, align scoped 404s with root (#4246)
* improvement(landing): scope navbar/footer shell to (shell) route group, align scoped 404s with root

Move integrations and models page routes into a `(shell)` route group so the Navbar+Footer layout wraps pages but not `not-found.tsx`. This lets scoped 404s render the same `<AuthBackground>` + Navbar treatment as the root `/` 404, instead of appearing inside the landing CTA footer.

Extract the shared 404 markup into `<NotFoundView>` so root, integrations, and models 404s share a single source of truth. Route URLs are unchanged — route groups are URL-transparent.

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

* fix(landing): convert relative imports to absolute in integrations (shell) page

Build failed because the move into the (shell) route group invalidated relative `./components/...` and `./data/...` imports. CLAUDE.md mandates absolute imports throughout — switching these resolves the Turbopack build errors.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 14:03:51 -07:00
Waleed
42ef2b1dbb fix(landing): resolve error-page crash on invalid /models and /integrations routes (#4243)
* fix(layout): use plain inline script for PublicEnvScript to set env before chunks eval on error pages

* fix(landing): handle runtime env race on error-page renders

React skips SSR on unhandled server errors and re-renders on the client
(see vercel/next.js#63980, #82456). Root-layout scripts — including the
runtime env script that populates window.__ENV — are inserted but not
executed on that client re-render, so any client module that reads env
at module evaluation crashes the render into a blank "Application error"
overlay instead of rendering the styled 404.

This replaces the earlier PublicEnvScript tweak with the architectural
fix:

- auth-client.ts: fall back to window.location.origin when getBaseUrl()
  throws on the client. Auth endpoints are same-origin, so this is the
  correct baseURL on the client. Server-side we still throw on genuine
  misconfig.
- loading.tsx under /models/[provider], /models/[provider]/[model], and
  /integrations/[slug]: establishes a Suspense boundary below the root
  layout so a page-level notFound() no longer invalidates the layout's
  SSR output (the fix endorsed by Next.js maintainers in #63980).
- layout.tsx: revert disableNextScript — the research showed this
  doesn't actually fix error-page renders. The real fix is above.

* improvement(landing): use emcn Loader in scoped loading.tsx, trim auth-client comment

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 12:43:43 -07:00
Waleed
2456128fb7 feat(contact): add contact page, migrate help/demo forms to useMutation (#4242)
* feat(contact): add contact page, migrate help/demo forms to useMutation

* improvement(contact): address greptile review feedback

- Map contact topic to help email type for accurate confirmation emails
- Drop Zod schema details from 400 response on public /api/contact
- Wire aria-describedby + aria-invalid in LandingField for both forms
- Reset helpMutation on modal reopen to match demo-request pattern

* improvement(landing): extract shared LandingField component
2026-04-21 12:20:09 -07:00
Theodore Li
699bbfd16f feat(log): Add wrapper function for standardized logging (#4061)
* feat(log): Add wrapper function for standardized logging

* Add all routes to wrapper, handle background execution

* fix lint

* fix test

* fix test missing url

* fix lint

* fix tests

* fix build

* fix(build): unmangle generic in admin outbox requeue route

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:21:13 -04:00
Waleed
0e1ff0a1ac improvement(enterprise): slack wizard UI, enterprise docs, data retention updates (#4241)
* improvement(enterprise): slack wizard UI, enterprise docs, data retention updates

* improvement(docs): add enterprise screenshots to sso, access-control, whitelabeling pages

* form

* fix(enterprise): address PR review — h-full for recently-deleted, shared SettingRow, toast UX, stale form fix, emcn tokens

* fix(whitelabeling): scope drop zone to thumbnail only, not full upload row

* fix(whitelabeling): remove drop image text from drag overlay

* fix(config): add DATA_RETENTION_ENABLED to env schema to fix build type error

* fix(testing): add isDataRetentionEnabled to feature flags mock

* improvement(docs): remove redundant requirements section from data-retention page

* improvement(docs): remove requirements sections from all enterprise doc pages

* improvement(docs): add screenshot to audit-logs page

* fix(data-retention): bypass enterprise gate when billing is disabled for self-hosted
2026-04-20 23:24:14 -07:00
Waleed
94202ed229 improvement(knowledge): show selector with saved option in connector edit modal (#4240)
* improvement(knowledge): show selector with saved option in connector edit modal

* fix(kb-connectors): clear canonical siblings when non-canonical dep changes; share selector field

* refactor(kb-connectors): extract canonical-field logic into useConnectorConfigFields hook

* fix(kb-connectors): only merge changed fields into sourceConfig on edit save

Avoids writing spurious empty-string keys for untouched optional fields when
another field triggers a save.

* refactor(kb-connectors): tighten state primitives in modals

- edit modal: replace useMemo([]) + eslint-disable with useState lazy
  initializer for initialSourceConfig — same mount-once semantics
  without the escape hatch.
- add modal: drop useCallback on handleConnectNewAccount (no observer
  saw the reference) and inline the one call site.
2026-04-20 20:49:58 -07:00
Theodore Li
802f4cf0fc feat(jobs): Add data retention jobs (#4128)
* feat(jobs): Add data retention jobs

Add 3 cron-triggered cleanup jobs dispatched via Trigger.dev (or inline fallback):
- cleanup-soft-deletes: hard-deletes soft-deleted workspace resources past retention
- cleanup-logs: deletes expired workflow execution logs + S3 files
- cleanup-tasks: deletes expired copilot chats, runs, feedback, inbox tasks

Enterprise admins can configure per-workspace retention via Settings > Data Retention.

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

# Conflicts:
#	packages/db/migrations/meta/0192_snapshot.json
#	packages/db/migrations/meta/_journal.json
#	packages/db/schema.ts

* Cleanup orphaned using ids, not timestamp sorting

* fix lint
2026-04-20 20:24:39 -04:00
Waleed
ac4ccfcac8 fix(billing): close TOCTOU race in subscription transfer, centralize stripe test mocks (#4239)
* fix(billing): close TOCTOU race in subscription transfer, centralize stripe test mocks

* more mocks

* fix(testing): provide complete Stripe.Event defaults in createMockStripeEvent

* fix(testing): make dbChainMock .for('update') chainable with .limit()

* fix(billing): gate subscription transfer noop behind membership check

Previously the 'already belongs to this organization' early return fired
before the org/member lookups, letting any authenticated caller probe
sub-to-org pairings without being a member of the target org. Move the
noop check after the admin/owner verification so unauthorized callers
hit the 403 first.
2026-04-20 16:46:28 -07:00
Waleed
0cd14f4ac9 improvement(sso): fix provider lookup, migrate UI to emcn, add enterprise SSO docs (#4238)
* improvement(sso): fix provider lookup, migrate UI to emcn, add enterprise SSO docs

* fix(sso): add org membership guard on providers route, fix idpMetadata round-trip

* fix(sso): add org membership guard on register route, fix SP entityID, remove fullError leak

* fix(sso): fix SAML script callbackUrl and SP entityID to use app base URL

* fix(sso): correct SAML callback URL path in script header comment

* fix(sso): restrict SSO provider read/write to org owners and admins

* docs(sso): restructure page, fix provider guide accuracy, add external doc links

* fix(sso): correct SAML callback path and generate idpMetadata from cert+entryPoint

* fix(sso): always require NEXT_PUBLIC_APP_URL for SAML SP metadata entityID

* fix(sso): scope provider query to org only when organizationId is provided

* fix(sso): escape XML special chars in script idpMetadata generation

* fix(sso): final audit corrections — saml mapping, xml escaping, self-hosted org guard

* fix(sso): redact oidc client secret in providers response, add self-hosted org admin guard

* fix(sso): scope redacted-secret lookup to caller's org or userId

* fix(sso): null out oidcConfig on parse failure to prevent unredacted secret leak

* fix(sso): use issuer as entityID in auto-generated idp metadata xml
2026-04-20 16:45:37 -07:00
Vikhyath Mondreti
d9209f9588 improvement(governance): workspace-org invitation system consolidation (#4230)
* workspace re-org checkpoint

* admin route reconciliation

* checkpoint consistency fixes

* prep merge

* regen migration

* checkpoint

* code cleanup

* update docs

* add feature for owner to leave + admin route

* address comments

* fix new account race

* address comments
2026-04-20 14:45:07 -07:00
Theodore Li
2ae1ad293f feat(ui): Add slack manifest generator (#4237)
* feat(slack): add manifest copying

* Try using wizard modal

* clean up modal

* feat(ui): Add wizard emcn component

* Made modal subblock more generic

* Fix test

* Address greptile comments

* fix handle copy behavior

* Add secret input emcn type

* Lint code
2026-04-20 16:50:30 -04:00
Waleed
febc36ff9c fix(security): enforce URL validation across connectors, providers, and auth flows (SSRF + open-redirect hardening) (#4236)
* fix(workday): validate tenantUrl to prevent SSRF in SOAP client

* fix(workday): use validation.sanitized in buildWsdlUrl

* fix(security): enforce URL validation across connectors, providers, auth

- Azure OpenAI/Anthropic: validate user-supplied azureEndpoint with validateUrlWithDNS to block SSRF to private IPs, localhost (in hosted mode), and dangerous ports.
- ServiceNow connector: enforce ServiceNow domain allowlist via validateServiceNowInstanceUrl before calling the instance URL.
- Obsidian connector: validate vaultUrl with validateUrlWithDNS and reuse the resolved IP via secureFetchWithPinnedIPAndRetry to block DNS rebinding between validation and request.
- Signup + verify flows: pass redirect/callbackUrl/redirectAfter and stored inviteRedirectUrl through validateCallbackUrl; drop unsafe values and log a warning.
- lib/knowledge/documents/utils.ts: add secureFetchWithPinnedIPAndRetry wrapper around secureFetchWithPinnedIP (used by Obsidian).

* fix(obsidian): use isomorphic SSRF validation to unblock client build

The Obsidian connector is reachable from client bundles via `connectors/registry.ts` (the knowledge UI reads metadata like `.icon`/`.name`). Importing `validateUrlWithDNS` / `secureFetchWithPinnedIP` from `input-validation.server` pulled `dns/promises`, `http`, `https`, `net` into client chunks, breaking the Turbopack build:

  Module not found: Can't resolve 'dns/promises'
  ./apps/sim/lib/core/security/input-validation.server.ts [Client Component Browser]
  ./apps/sim/connectors/obsidian/obsidian.ts [Client Component Browser]
  ./apps/sim/connectors/registry.ts [Client Component Browser]

Once that file polluted a browser context, Turbopack also failed to resolve the Node builtins in its legitimate server-route imports, cascading the error across App Routes and Server Components.

Fix: switch the Obsidian connector to the isomorphic `validateExternalUrl` + `fetchWithRetry` helpers, matching the pattern used by every other connector in the registry. This keeps the core SSRF protections:
  - hosted Sim: blocks localhost, private IPs, HTTP (HTTPS enforced)
  - self-hosted Sim: allows localhost + HTTP, still blocks non-loopback private IPs and dangerous ports (22, 25, 3306, 5432, 6379, 27017, 9200)

Drops the DNS-rebinding defense specifically (the IP-pinned fetch chain). The trade-off is acceptable because the vault URL is entered by the workspace admin — not arbitrary untrusted input — and hosted deployments already force the plugin to be exposed through a public URL (tunnel/port-forward), making rebinding a narrow threat.

Also reverts the `secureFetchWithPinnedIPAndRetry` wrapper in `lib/knowledge/documents/utils.ts` (no longer needed, and its `.server` import was the original source of the client-bundle pollution).

* fix(servicenow): propagate URL validation errors in getDocument

Match listDocuments behavior — invalid instance URL should surface as a
configuration error rather than being swallowed into a "document not found"
null response during sync.

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

* fix(obsidian): drop allowHttp to restore HTTPS enforcement in hosted mode

allowHttp: true permitted plaintext HTTP for all hosts in all deployment
modes, contradicting the documented policy. The default validateExternalUrl
behavior already allows http://localhost in self-hosted mode (the actual
Obsidian Local REST API use case) via the built-in carve-out, while correctly
rejecting HTTP for public hosts in hosted mode — which prevents leaking the
Bearer access token over plaintext.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 10:54:03 -07:00
Waleed
5f56e46758 v0.6.51: tables improvements, billing fixes, 404 pages, code hygiene 2026-04-19 23:35:29 -07:00
Waleed
5cf7e8d546 improvement(codebase): migrate tests to dbChainMock, extract react-query hooks (#4235)
* improvement(codebase): migrate tests to dbChainMock, extract react-query hooks

Migrate 97 test files to centralized dbChainMock/dbChainMockFns helpers from
@sim/testing — removes hoisted chain-wiring boilerplate.

Extend dbChainMock to cover insert/update/delete/transaction/execute patterns.
Extract useGitHubStars and useVoiceSettings react-query hooks from inline fetches.
Centralize additional mocks (authMockFns, hybridAuthMockFns) and update docs.

* fix(github-stars): centralize fallback via initialData, remove stale constants

Move the placeholder star count into useGitHubStars as initialData with
initialDataUpdatedAt: 0 so `data` is always a narrowed string while still
refetching on mount. Fixes two Bugbot issues: stale '25.8k' in chat.tsx
(vs '27.8k' in navbar) and empty-string return in fetchGitHubStars that
bypassed `??` fallbacks in consumers.

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

* fix(testing): wire dbChainMock.db to shared transaction and execute fns

dbChainMock.db.transaction was an inline vi.fn() separate from the exported
dbChainMockFns.transaction, so dbChainMockFns.transaction.mockResolvedValueOnce
and assertions silently targeted the wrong instance. dbChainMock.db also
omitted execute, so tests for any module that calls db.execute (logging-session,
table service, billing balance) would throw TypeError. Both mocks now reference
the module-level constants so overrides and resetDbChainMock affect the same fn.

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

* fix(chat,testing): memoize welcome message and add selectDistinct to dbChainMock.db

Why:
- Welcome ChatMessage was rebuilt inline each render, producing a fresh
  timestamp and new array identity — cascading to ChatMessageContainer
  and VoiceInterface props on every tick.
- dbChainMockFns exports selectDistinct/selectDistinctOn but the
  dbChainMock.db object omitted them, so tests that stub those builders
  hit undefined on the mocked module.

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

* fix(chat): re-attach scroll listener once container mounts

The scroll effect's empty dep array meant it ran only on the first
render, when `chatConfig` is still loading and the component returns
`<ChatLoadingState />` — so `messagesContainerRef.current` was null and
the listener was never attached. Depend on the gating conditions that
control which tree renders, so the effect re-runs once the real
container is in the DOM (and re-attaches when toggling in/out of voice
mode).

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

* fix(chat): reset chat state on identifier change via key prop

Keying `<ChatClient>` on `identifier` guarantees a full remount on
route transitions between chats, so `conversationId`, `messages`, and
every other piece of local state start fresh — no reset effect
required.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 23:05:06 -07:00
Waleed
1ced54a77c fix(settings): restore paste-to-destructure for workspace secrets, cleanup hooks and design tokens (#4231)
* fix(settings): restore paste-to-destructure for workspace secrets, cleanup hooks and design tokens

Restores the env-var paste feature (KEY=VALUE → split rows, multi-line
→ multi rows) for workspace secrets that was lost when the unified
Credentials tab was split into Secrets and Integrations. Adds
`parseEnvVarLine`, `parseValidEnvVars`, and `handleWorkspacePaste` with
full support for export prefix, quoted values, inline comments, and
base64 false-positive guards. Also adds consistent value masking
(show on focus / mask on blur) to new workspace input rows.

Cleans up ~20 unnecessary `useCallback` wrappers, fixes a direct state
mutation in `handleSingleValuePaste`, moves `e.preventDefault()` inside
the `parsedVars.length > 0` guard, replaces all hardcoded hex colors
with CSS variable tokens, converts template-literal classNames to `cn()`,
and replaces raw `<button>` with emcn `Button`.

* fix(settings): fix handlePaste silent swallow, quote-strip bug, and credential sync efficiency

- Move e.preventDefault() inside parsedVars guard in handlePaste so KEY= lines
  don't silently discard input (mirrors handleWorkspacePaste fix from same PR)
- Add value.length >= 2 guard before quote-stripping to prevent single-char
  values like KEY=\" from being stripped to empty and silently dropped
- Introduce createWorkspaceEnvCredentials and deleteWorkspaceEnvCredentials
  for delta-aware credential sync (O(k) instead of O(n*m) for env var mutations)
- Fix createWorkspaceEnvCredentials early-return bug that skipped credential
  record creation when workspace had zero members
- Update credentials/[id] DELETE to use deleteWorkspaceEnvCredentials instead
  of full syncWorkspaceEnvCredentials
- Optimize syncWorkspaceEnvCredentials to fetch workspace+member IDs in parallel
  once instead of once per credential

* fix(settings): normalize Windows line endings in paste handlers

* fix(settings): eliminate double-parse in handlePaste by inlining handleKeyValuePaste
2026-04-18 21:22:29 -07:00
Waleed
951fbd4ded fix(landing): render proper 404 for invalid /models and /integrations routes (#4232) 2026-04-18 21:13:48 -07:00
Waleed
f91c1b614a chore(docker): add packages/utils to app and realtime Dockerfiles (#4229)
* chore(docker): add packages/utils to app and realtime Dockerfiles

* chore(docker): copy packages/utils in realtime runner stage
2026-04-18 18:00:44 -07:00
Waleed
b5674d9ed4 improvement(codebase): centralize test mocks, extract @sim/utils, remove dead code (#4228)
* improvement(codebase): centralize test mocks, extract @sim/utils, remove dead code

* improvement(codebase): apply @sim/utils conventions to staging-introduced files
2026-04-18 14:39:03 -07:00
Theodore Li
c19187257e fix(ui): stop scrolling on leaving workflow sidebar for drag-drop (#4139)
* fix(ui): stop scrolling on leaving workflow sidebar for drag-drop

* Address comments, fix hover state

* address comments
2026-04-18 15:45:25 -04:00
Vikhyath Mondreti
c246f5c660 improvement(billing): route scope by subscription referenceId, sync plan from Stripe, transfer storage on org join, outbox service (#4219)
* fix(billing): route scope by subscription referenceId, sync plan from Stripe, transfer storage on org join

Route every billing decision (usage limits, credits, storage, rate
limit, threshold billing, webhooks, UI permissions) through the
subscription's `referenceId` instead of plan-name heuristics. Fixes
the production state where a `pro_6000` subscription attached to an
organization was treated as personal Pro by display/edit code while
execution correctly enforced the org cap.

Scope
- Add `isOrgScopedSubscription(sub, userId)` (pure) and
  `isSubscriptionOrgScoped(sub)` (async DB-backed) helpers. One is
  used wherever a user perspective is available; the other in webhook
  handlers that only have a subscription row.
- Replace plan-name scope checks in ~20 files: usage/limit readers,
  credits balance + purchase, threshold billing, storage limits +
  tracking, rate limiter, invoice + subscription webhooks, seat
  management, membership join/leave, `switch-plan` admin gate,
  admin credits/billing routes, copilot 402 handler, UI subscription
  settings + permissions + sidebar indicator, React Query types.

Plan sync
- Add `syncSubscriptionPlan(subscriptionId, currentPlan, planFromStripe)`
  called from `onSubscriptionComplete` and `onSubscriptionUpdate` so
  the DB `plan` column heals on every Stripe event. Pro->Team upgrades
  previously updated price, seats, and referenceId but left `plan`
  stale — this is what produced the `pro_6000`-on-org row.

Priority + grace period
- `getHighestPrioritySubscription` now prefers org over personal
  within each tier (Enterprise > Team > Pro, org > personal at each).
  A user with a `cancelAtPeriodEnd` personal Pro who joins a paid org
  routes pooled resources to the org through the grace window.
- `calculateSubscriptionOverage` personal-Pro branch reads user_stats
  directly (bypassing priority) and bills only `proPeriodCostSnapshot`
  when the user joined a paid org mid-cycle, so post-join org usage
  isn't double-charged on the personal Pro's final invoice.
  `resetUsageForSubscription` mirrors this: preserves
  `currentPeriodCost` / `currentPeriodCopilotCost` when
  `proPeriodCostSnapshot > 0` so the org's next cycle-close captures
  post-join usage correctly.

Uniform base-price formula
- `basePrice × (seats ?? 1)` everywhere: `getOrgUsageLimit`,
  `updateOrganizationUsageLimit`, `setUsageLimitForCredits`,
  `calculateSubscriptionOverage`, threshold billing,
  `syncSubscriptionUsageLimits`, `getOrganizationBillingData`.
  Admin dashboard math now agrees with enforcement math.

Storage transfer on join
- Invitation-accept flow moves `user_stats.storageUsedBytes` into
  `organization.storageUsedBytes` inside the same transaction when
  the org is paid.
- `syncSubscriptionUsageLimits` runs a bulk-backfill version so
  members who joined before this fix, or orgs that upgraded from
  free to paid after members joined, get pulled into the org pool
  on the next subscription event. Idempotent.

UX polish
- Copilot 402 handler differentiates personal-scoped ("increase your
  usage limit") from org-scoped ("ask an owner or admin to raise the
  limit") while keeping the `increase_limit` action code the parser
  already understands.
- Duplicate-subscription error on team upgrade names the existing
  plan via `getDisplayPlanName`.
- Invitation-accept invalidates subscription + organization React
  Query caches before redirect so settings doesn't flash the user's
  pre-join personal view.

Dead code removal
- Remove unused `calculateUserOverage`, and the following fields on
  `SubscriptionBillingData` / `getSimplifiedBillingSummary` that no
  consumer in the monorepo read: `basePrice`, `overageAmount`,
  `totalProjected`, `tierCredits`, `basePriceCredits`,
  `currentUsageCredits`, `overageAmountCredits`, `totalProjectedCredits`,
  `usageLimitCredits`, `currentCredits`, `limitCredits`,
  `lastPeriodCostCredits`, `lastPeriodCopilotCostCredits`,
  `copilotCostCredits`, and the `organizationData` subobject. Add
  `metadata: unknown` to match what the server returns.

Notes for the triggering customer
- The `pro_6000`-on-org row self-heals on the next Stripe event via
  `syncSubscriptionPlan`. For the one known customer, a direct
  UPDATE is sufficient:
  `UPDATE subscription SET plan='team_6000' WHERE id='aq2...' AND plan='pro_6000'`.

Made-with: Cursor

* fix tests

* address more comments

* progress

* harden further

* outbox service

* address comments

* address comment on check

* simplify

* cleanup code

* minor improvement
2026-04-18 10:46:14 -07:00
Waleed
28b4c4cc67 fix(blocks): resolve variable display in mothership resource preview (#4226)
* fix(blocks): resolve variable display in mothership resource preview

Variables block showed empty assignments in the embedded workflow preview
because currentWorkflowId was read from URL params, which don't contain
workflowId in the mothership route. Fall back to activeWorkflowId from
the workflow registry.

* fix(blocks): narrow currentWorkflowId to string to satisfy strict null checks
2026-04-18 10:26:46 -07:00
Vikhyath Mondreti
32541e79d4 chore(readme): update tech stack section (#4227)
* chore(readme): update tech stack section

* fix
2026-04-18 10:02:54 -07:00
Waleed
a01f80c6a3 feat(tables): column selection, keyboard shortcuts, drag reorder, and undo improvements (#4222)
* feat(tables): add column selection, missing keyboard shortcuts, and Sheets-aligned operations

Click column headers to select entire columns, shift-click to extend to
a column range. Delete, cut, and copy operations work on column
selections with full undo/redo support. Adds Home, End, Ctrl+Home,
Ctrl+End, PageUp, PageDown, Ctrl+Space, and all Shift variants.
Changes Ctrl+A to select all cells instead of checkbox rows. Column
header dropdown menu now opens on right-click instead of left-click.

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

* fix(tables): chevron opens dropdown, drag header to reorder columns

Split column header into label area (click to select, draggable for
reorder) and chevron button (click to open dropdown menu). Remove
the grip handle — dragging the header itself now reorders columns.

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

* fix(tables): full-column highlight during drag reorder

Replace the thin 2px line drop indicator with a full-column highlight
that spans the entire table height, matching Google Sheets behavior.
The insertion line is still shown at the drop edge for precision.

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

* fix(tables): handle drag reorder edge cases, dim source column

Suppress drop indicator when drag would result in no position change
(dragging onto self or adjacent no-op positions). Dim the source
column body cells during drag with a background overlay. Skip the
API call when the computed order is identical to the current order.

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

* feat(tables): add column reorder undo/redo, body drop targets, and escape cancel

Column drag-and-drop now supports dropping anywhere in a column (not just headers),
pressing Escape to cancel a drag, and full undo/redo integration for column reordering.

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

* fix(tables): merge partial updates in updateRow to prevent column data loss

When Mothership called updateRow directly (bypassing the PATCH API route),
it passed only the changed fields — which were written as the entire row,
wiping all other columns. Move the merge logic into updateRow itself so
all callers get correct partial-update semantics, and remove the now-redundant
pre-merge from both PATCH routes.

* test(tables): add updateRow partial merge tests

Covers the bug where partial updates wiped unmentioned columns — verifies
that fields not in the update payload are preserved, nulling a field works,
full-row updates are idempotent, and missing rows throw correctly.

* feat(tables): add delete-column undo/redo, rename metadata sync, and comprehensive row ID patching

- Delete column now captures column definition, cell data, order, and width for full undo/redo
- Column rename undo/redo now properly syncs columnWidths and columnOrder metadata
- patchRedoRowId/patchUndoRowId extended to handle all action types containing row IDs

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

* fix(tables): remove source column dimming during drag reorder

Only show the insertion line at the drop position, matching Google Sheets
behavior. Remove dragSourceBounds memo and isDragging prop.

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

* fix(tables): preserve selection on right-click, auto-resize on double-click, fix escape during drag

- Right-clicking within an existing selection now preserves it instead of
  resetting to a single cell, so context menu operations apply to the full range
- Double-clicking a column border auto-resizes the column to fit its content
- Escape during column drag now immediately clears refs before state update,
  preventing the dragend handler from executing the reorder

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

* fix(tables): add aria-hidden value and aria-label for column header accessibility

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

* fix(tables): tighten auto-resize padding to match Google Sheets

Reduce header padding from +48px to +36px (icon + cell padding) and cell
padding from +20px to +17px (cell padding + border) for a snug fit.

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

* fix(tables): clean drag ghost and clear selection on drag start

- Create a minimal custom drag image showing only the column name instead
  of the browser's default ghost that includes adjacent columns/checkboxes
- Clear any existing cell/column selection when starting a column drag to
  prevent stale highlights from persisting during reorder

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

* feat(tables): add Shift+Space row selection and Ctrl+D fill down

Shift+Space now selects the entire row (all columns) instead of toggling
a checkbox, matching Google Sheets behavior. Ctrl+D copies the top cell's
value down through the selected range with full undo/redo support.

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

* fix(tables): show toast on incompatible column type change

The server validates type compatibility and returns a clear error message
(e.g. "3 row(s) have incompatible values"), but the client was silently
swallowing it. Now surfaces the error via toast notification. Also moved
the undo push to onSuccess so a failed type change doesn't pollute the
undo stack.

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

* fix(tables): scroll-into-view for selection focus, Home/End origin, delete-column undo timing

- Scroll-into-view now tracks selectionFocus (not just anchor), so
  Shift+Arrow extending selection off-screen properly auto-scrolls
- Shift+Home/End now uses the current focus as origin (matching
  Shift+Arrow behavior) instead of always using anchor
- Delete column undo entry is now pushed in onSuccess, preventing
  a corrupted undo stack if the server rejects the deletion
- Dialog copy updated from "cannot be undone" to "You can undo this
  action" since undo/redo is supported

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

* fix: resolve duplicate declarations from rebase against staging

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

* fix file upload

* fix(tables): merge column widths on delete-column undo, try/finally for auto-resize

- Delete-column undo now reads current column widths via getColumnWidths
  callback and merges the restored column's width into the full map,
  preventing other columns' widths from being wiped
- Auto-resize measurement span is now wrapped in try/finally to ensure
  DOM cleanup if an exception occurs during measurement

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

* fix: revert accidental home.tsx change from rebase conflict resolution

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

* fix(tables): clear isColumnSelection on double-click and right-click, skip scroll for column select

- Clear isColumnSelection when double-clicking a cell to edit, preventing
  the column selection effect from fighting with the editing state
- Clear isColumnSelection when right-clicking outside the current
  selection, preventing stale column selection from re-expanding
- Skip scroll-into-view when isColumnSelection is true, preventing
  the viewport from jumping to the bottom row when clicking a column header

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

* fix(tables): remove inline font override in auto-resize, guard undefined columnOrder

- Remove `font:inherit` from measurement span inline style so Tailwind
  classes (font-medium, text-small) control font properties for accurate
  column width measurement
- Only include columnOrder in metadata update when defined, preventing
  handleColumnRename from clearing a persisted column order when
  columnOrderRef is null

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

* fix(tables): capture columnRequired in delete-column undo for full restoration

The delete-column undo action captured columnUnique but not columnRequired,
so undoing a delete on a required column would silently drop the constraint.
Now captures and restores both constraints.

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

* fix(tables): restore width independently of order on delete-column undo, batch fill-down

- Column width restoration in delete-column undo no longer requires
  previousOrder to be non-null — width is restored independently
- Ctrl+D fill-down now uses batchUpdateRef (single API call) instead
  of calling mutateRef per row in a loop

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

* fix(tables): multi-column delete, select-all cell model, cut flash, chevron alignment

- Multi-select delete: detect column selection range and delete all selected
  columns sequentially with individual undo entries
- Select all (header checkbox): use cell selection model instead of checkbox
  model for consistent highlighting
- Cut flash: batch cell clears into single mutation to prevent stale data
  flashing from multiple onSettled invalidations
- Chevron alignment: adjust right padding from pr-2 to pr-2.5

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

* fix(tables): restore column width locally on delete-column undo

Add onColumnWidthsChange callback to undo hook so restored column
widths update local component state, not just server metadata.

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

* fix(tables): prevent Ctrl+D bookmark dialog, batch Delete/Backspace mutations

- Move e.preventDefault() before early returns in Ctrl+D handler so
  the browser bookmark dialog is always suppressed
- Replace per-row mutateRef calls with single batchUpdateRef call in
  both Delete/Backspace handlers (checked rows and cell selection),
  consistent with cut and fill-down

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

* fix(tables): adjust column positions for multi-column delete undo

Capture original schema positions upfront and adjust each by the
count of previously-deleted columns with lower positions, so undo
restores columns at correct server-side positions in LIFO order.

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

* fix(tables): only multi-delete when clicked column is within selection

Check that the right-clicked column is within the selected column
range before using multi-column delete. If the click is outside the
selection, delete only the clicked column.

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

* fix(tables): prevent duplicate undo entry on column drag-drop

Clear dragColumnNameRef immediately in handleColumnDragEnd so the
second invocation (from dragend after drop already fired) is a no-op.

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

* fix(tables): clean up width on delete-column redo, suppress click during drag

- Redo path for delete-column now removes the column's width from
  metadata and local state, preventing stale width entries
- Add didDragRef to ColumnHeaderMenu to suppress the click event
  that fires after a drag operation, preventing selection flash

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

* fix(tables): remove unstable mutation object from useCallback deps

deleteTableMutation is not referentially stable — only .mutateAsync()
is. Including the mutation object causes unnecessary callback recreation
on every mutation state change.

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

* fix(tables): fix auto-resize header padding, deduplicate rename metadata logic

Increase header text measurement padding from 36px to 57px to account
for the chevron dropdown button (pl-0.5 + 9px icon + pr-2.5) that
always occupies layout space. Prevents header text truncation on
auto-resize.

Deduplicate column rename metadata logic by having columnRename.onSave
call handleColumnRename instead of reimplementing the same width/order
transfer and metadata persist.

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

* fix(tables): log error on cell data restoration failure during undo

Add onError handler to the batchUpdateRowsMutation inside
delete-column undo so failures are logged instead of silently
swallowed. The column schema restores first, and the cell data
restoration is a separate async call that the outer try/catch
cannot intercept.

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

* fix(tables): address audit findings across table, undo hook, and store

- Add missing bounds check in handleCopy (c >= cols.length) matching
  handleCut for defensive consistency
- Clear lastCheckboxRowRef in Ctrl+Space and Shift+Space to prevent
  stale shift-click checkbox range after keyboard selection
- Fix stale snapshot race in patchRedoRowId/patchUndoRowId by reading
  state inside the set() updater instead of via get() outside it
- Add metadata cleanup to create-column undo so column width is removed
  from both local state and server, symmetric with delete-column redo
- Remove stale width key from columnWidths on column delete instead of
  persisting orphaned entries
- Normalize undefined vs null in handleInlineSave change detection to
  prevent unnecessary mutations when oldValue is undefined
- Use ghost.parentNode?.removeChild instead of document.body.removeChild
  in drag ghost cleanup to prevent throw on component unmount

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

* fix(tables): reset didDragRef in handleDragEnd to prevent stale flag

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-18 01:30:32 -07:00
Vikhyath Mondreti
bc09865d81 v0.6.50: ppt/doc/pdf worker isolation, docs, chat, sidebar improvements 2026-04-17 22:11:10 -07:00
Vikhyath Mondreti
524f33cc9e fix(pdf): PDF previews by adding the missing preview endpoint and allowing same-origin blob URLs in iframe CSP (#4225)
* fix(pdf): PDF previews by adding the missing preview endpoint and allowing same-origin blob URLs in iframe CSP

* fixed

* add preview routes and tests

* follow nextjs route gen strat
2026-04-17 20:59:38 -07:00
Waleed
47519e34d9 fix(fireflies): support V2 webhook payload format for meetingId mapping (#4221)
* fix(fireflies): support V2 webhook payload format for meetingId mapping

Fireflies V2 webhooks use snake_case field names (meeting_id, event,
client_reference_id) instead of camelCase (meetingId, eventType,
clientReferenceId). The formatInput handler now auto-detects V1 vs V2
payloads and maps fields correctly, fixing empty meetingId on V2 webhooks.

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

* fix(fireflies): guard against NaN timestamp, use stricter V2 detection

Address PR review feedback:
- Use Number.isFinite guard to prevent NaN timestamp propagation
- Use AND instead of OR for V2 detection since both meeting_id and
  event are required fields in every V2 payload

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 19:43:28 -07:00
Vikhyath Mondreti
2f932054a7 fix(execution): run pptx/docx/pdf generation inside isolated-vm sandbox (#4217)
* fix(execution): run pptx/docx/pdf generation inside isolated-vm sandbox

Retires the legacy doc-worker.cjs / pptx-worker.cjs pipeline that ran user
DSL via node:vm + full require() in the same UID/PID namespace as the main
Next.js process. User code now runs inside the existing isolated-vm pool
(V8 isolate, no process / require / fs, no /proc/1/environ reachability).

Introduces a first-class SandboxTask abstraction under apps/sim/sandbox-tasks/
that mirrors apps/sim/background/ — one file per task, central typed
registry, kebab-case ids. Adding a new thing that runs in the isolate is
one file plus one registry entry.

Runtime additions in lib/execution/:
 - task-mode execution in isolated-vm-worker.cjs: load pre-built library
   bundles, run task bootstrap, run user code, run finalize, transfer
   Uint8Array result as base64 via IPC
 - named broker IPC bridge (generalizes the existing fetch bridge) with
   args size, result size, and per-execution call caps
 - cooperative AbortSignal support: cancel IPC disposes the isolate, pool
   slot is freed, pending broker-call timers are swept
 - compiled scripts + references explicitly released per execution
 - isolate.isDisposed used for cancellation detection (no error-string
   substring matching)

Library bundles (pptxgenjs, docx, pdf-lib) are built into isolate-safe
IIFE bundles by apps/sim/lib/execution/sandbox/bundles/build.ts and
committed; next.config.ts / trigger.config.ts / Dockerfile updated to
ship them instead of the deleted dist/*-worker.cjs artifacts.

Call sites migrated:
 - app/api/workspaces/[id]/pptx/preview/route.ts
 - app/api/files/serve/[...path]/route.ts (+ test mock)
 - lib/copilot/tools/server/files/{workspace-file,edit-content}.ts

All pass owner key user:<userId> for per-user pool fairness + distributed
lease accounting.

Made-with: Cursor

* improvement(sandbox): delegate timers to Node, add phase timings + saturation logs

Follow-ups on top of the isolated-vm migration (da14027b2):

Timer delegation (laverdet/isolated-vm#136 recommended pattern):
 - setTimeout / setInterval / clearTimeout / clearImmediate delegate to
   Node's real timer heap via ivm.Reference. Real delays are honored;
   clearTimeout actually cancels; ms is clamped to the script timeout
   so callbacks can't fire after the isolate is disposed.
 - Per-execution timer tracking + dispose-sweep in finally. Zero stale
   callbacks post-dispose.
 - unwrapPrimitive helper normalizes ivm.Reference-wrapped primitives
   (arguments: { reference: true } applies uniformly to all args).
 - _polyfills.ts shrinks from ~130 lines to the global->globalThis alias.
   Timers / TextEncoder / TextDecoder / console all install per-execution
   from the worker via ivm bridges.

AbortSignal race fix (pre-existing bug surfaced by the timer smoke):
 - Listener is registered after await tryAcquireDistributedLease. If the
   signal aborted during that ~200ms window (Redis down), AbortSignal
   doesn't fire listeners registered after the fact — the abort was
   silently missed. Now re-checks signal.aborted synchronously after
   addEventListener.

Observability:
 - executeTask returns IsolatedVMTaskTimings (setup, runtimeBootstrap,
   bundles, brokerInstall, taskBootstrap, harden, userCode, finalize,
   total) in every success + error path. run-task.ts logs these with
   workspaceId + queueMs so 'which tenant is slow' is queryable.
 - Pool saturation events now emit structured logger.warn with reason
   codes: queue_full_global, queue_full_owner, queue_wait_timeout,
   distributed_lease_limit. Matches the existing broker reject pattern.

Security policy:
 - New .cursor/rules/sim-sandbox.mdc codifies the hard rules for the
   worker process: no app credentials, all credentialed work goes
   through host-side brokers, every broker scopes by workspaceId.
   Pre-merge checklist for future changes to isolated-vm-worker.cjs.

Measured phase breakdown (local smoke, Redis down): pptx wall=~310ms
with bundles=~16ms, finalize=~83ms; docx ~290ms / 17ms / 70ms; pdf
~235ms / 17ms / 5ms. Bundle compilation is not the bottleneck —
library finalize is.

Made-with: Cursor

* fix(sandbox): thread AbortSignal into runSandboxTask at every call site

Three remaining callers of runSandboxTask were not threading a
cancellation signal, so a client disconnect mid-compile left the pool
slot occupied for the full 60s task timeout. Matching the pattern the
pptx-preview route already uses.

 - apps/sim/app/api/files/serve/[...path]/route.ts — GET forwards
   `request.signal` into handleLocalFile / handleCloudProxy, which
   forward into compileDocumentIfNeeded, which forwards into
   runSandboxTask.
 - apps/sim/lib/copilot/tools/server/files/workspace-file.ts — passes
   `context.abortSignal` (transport/user stop) into runSandboxTask.
 - apps/sim/lib/copilot/tools/server/files/edit-content.ts — same.

Smoke: simulated client disconnect at t=1000ms during a task that would
otherwise have waited 10s. The pool slot unwinds at t=1002ms with
AbortError; previously would have sat 60s until the task-level timeout.

Made-with: Cursor

* chore(build): raise node heap to 8GB for next build type-check

Next.js's type-check worker OOMs at the default 4GB heap on Node 23 for
this project's type graph size. Bumps the heap to 8GB only for the
`next build` invocation inside `bun run build`.

Docker builds are unaffected — `next.config.ts` sets
`typescript.ignoreBuildErrors: true` when DOCKER_BUILD=1, which skips
the type-check pass entirely. This only fixes local `bun run build`.

No functional code changes.

Made-with: Cursor

* fix lint

* refactor(copilot): dedup getDocumentFormatInfo across copilot file tools

The same extension -> { formatName, sourceMime, taskId } mapping was
duplicated in workspace-file.ts and edit-content.ts. Any future format
or task-id change had to happen in two places.

Exports getDocumentFormatInfo + DocumentFormatInfo from workspace-file.ts
(which already owned the PPTX/DOCX/PDF source MIME constants) and
imports it in edit-content.ts. Same source-of-truth pattern the file
already uses for inferContentType.

Made-with: Cursor

* fix(sandbox): propagate empty-message broker/fetch errors

Both bridges in the isolate used truthiness to detect host-side errors:

    if (response.error) throw new Error(response.error);   // broker
    if (result.error)   throw new Error(result.error);     // fetch

If a host handler ever threw `new Error('')`, err.message would be ''
(falsy), so { error: '' } was silently swallowed and the isolate saw
a successful null result. Existing call sites don't throw empty-message
errors, but the pattern was structurally unsafe.

Switch both to typeof check === 'string' and fall back to a default
message if the string is empty, so all host-reported errors propagate
into the isolate regardless of message content.

Made-with: Cursor
2026-04-17 19:07:46 -07:00
Vikhyath Mondreti
319e0db732 improvement(mothership): agent model dropdown validations, markers for recommended models (#4213)
* improvement(mothership): agent model dropdown validations, recommendation system

* mark a few more models:

* remove regex based checks'

* remove dead code

* remove inherited reseller flags

* fix note

* address bugbot comments

* code cleanup
2026-04-17 18:58:02 -07:00
Waleed
003e931546 improvement(terminal): resize output panel on any layout change via ResizeObserver (#4220)
Replaces MutationObserver on document.documentElement (watching CSS variable
changes) + window resize listener with a ResizeObserver on the terminal element
itself. The terminal now measures its own rendered width directly, so it responds
correctly to all layout changes — sidebar, workflow panel, and mothership resize —
without indirect CSS variable plumbing or cross-component coupling.
2026-04-17 18:23:51 -07:00
Waleed
948cdbcc3f fix(chat): prevent @-mention menu focus loss and stabilize render identity (#4218)
* fix(docs): preserve gif playback position in lightbox and clean up ui components

- Capture currentTime on click and seek lightbox video to match using useLayoutEffect
- Convert lightboxStartTime from useState to useRef (no independent render needed)
- Apply same fix to ActionVideo in action-media.tsx
- Remove dead AnimatedBlocks component (zero imports)
- Fix language-dropdown to derive currentLang during render instead of mirroring into state via effect
- Replace template literals with cn() in faq.tsx and video.tsx

* fix(chat): prevent @-mention menu focus loss and stabilize render identity

Radix DropdownMenu's FocusScope was restoring focus from the search input
to the content root whenever registered menu items mounted or unmounted
inside the content, interrupting typing after a keystroke or two.

- Keep the default tree always mounted under `hidden` instead of swapping
  subtrees when the filter activates.
- Render filtered results as plain <button role="menuitem"> so they do not
  participate in Radix's menu Collection.
- Add activeIndex state with ArrowUp/Down/Enter keyboard nav, mouse-hover
  sync, and scrollIntoView so the highlighted row stays visible and users
  can see what Enter will select.

While tracing the cascade that compounded the bug:

- Hoist `select` in useWorkflowMap / useWorkspacesQuery / useFolderMap to
  module scope so TanStack Query caches the select result across renders.
- Guard setSelectedContexts([]) with a functional updater that bails out
  when already empty, preventing a fresh [] literal from invalidating
  consumers that key on reference identity.
- Wrap WorkspaceHeader in React.memo so it bails out on parent renders
  once its (now-stable) props are unchanged.

Made-with: Cursor

* remove extraneous comments

* cleanup

* fix(chat): apply same setState bail-out to clearContexts for consistency

Matches the invariant we already established for the message effect:
calling setSelectedContexts([]) against an already-empty array emits a
fresh [] reference (Object.is bails out are not reference-level), which
cascades through consumers that key on selectedContexts identity.
clearContexts is part of the hook's public API so callers can't know
whether the list is empty — make it safe for them.

Made-with: Cursor
2026-04-17 17:38:37 -07:00
Theodore Li
5e716d74bc docs(assets): Add pics and videos for mothership (#4216)
* Add pics and videos for mothership

* Minimal edit
2026-04-17 16:31:34 -04:00
Waleed
e1018f1c72 improvement(utils): add shared utility functions and replace inline patterns (#4214)
* improvement(utils): add shared utility functions and replace inline patterns

Add sleep, toError, safeJsonParse, isNonNull helpers and invariant/assertNever
assertions. Replace all inline implementations across the codebase with these
shared utilities for consistency. Zero behavioral changes.

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

* fix(agiloft): remove import type from .server module to fix client bundle build

Turbopack resolves .server.ts modules even for type-only imports,
pulling dns/promises into client bundles. Define SecureFetchResponse
locally instead.

* fix(agiloft): revert to client-safe imports to fix build

The SSRF upgrade to input-validation.server introduced dns/promises
into client bundles via tools/registry.ts. Revert to the original
client-safe validateExternalUrl + fetch. The SSRF DNS-pinning upgrade
for agiloft directExecution should be done via API routes in a
separate PR.

* feat(agiloft): add API route for retrieve_attachment, matching established file patterns

Convert retrieve_attachment from directExecution to standard API route
pattern, consistent with Slack download and Google Drive download tools.

- Create /api/tools/agiloft/retrieve with DNS validation, auth lifecycle,
  and base64 file response matching the { file: { name, mimeType, data,
  size } } convention
- Update retrieve_attachment tool to use request/transformResponse
  instead of directExecution, removing the dependency on
  executeAgiloftRequest from the tool definition
- File output type: 'file' enables FileToolProcessor to store downloaded
  files in execution filesystem automatically

* shopify

* fix(agiloft): add optional flag to nullable lock record block outputs

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

* fix(agiloft): revert optional flag on block outputs — property only exists on tool outputs

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

* chore(utils): remove unused utilities (asserts, safeJsonParse, isNonNull)

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 13:29:42 -07:00
Waleed
351873ac04 improvement(sidebar): interleave folders and workflows by sort order in all resource pickers (#4215)
* improvement(sidebar): interleave folders and workflows by sort order in all resource pickers

- Merge folder/workflow submenus into a single Workflows tree sorted by sortOrder in both the @ plus-menu and add-resource dropdowns
- Widen both dropdowns from 240px to 320px and remove type labels from search results
- Fix isOpen/onSwitch regression: WorkflowFolderTreeItems now forwards node.isOpen so already-open tabs are switched to rather than duplicated
- Apply same interleaved sortOrder ordering to the collapsed sidebar's root-level folder+workflow list

* fix(add-resource-dropdown): align sort tiebreaker with compareByOrder, document empty-folder omission

Use id.localeCompare as the sort tiebreaker in buildWorkflowFolderTree to match the sidebar's
compareByOrder fallback (sortOrder → id) instead of name. Add a comment clarifying that empty
folders are intentionally omitted from the tree view.

* chore: remove extraneous inline comment
2026-04-17 13:01:44 -07:00
Waleed
dcf33021f4 v0.6.49: deploy sockets event, resolver, logs improvements, monday.com integration, atlassian triggers 2026-04-16 20:31:23 -07:00
Waleed
38864fac34 feat(monday): add full Monday.com integration (#4210)
* feat(monday): add full Monday.com integration with tools, block, triggers, and OAuth

Adds a comprehensive Monday.com integration:
- 13 tools: list/get boards, CRUD items, search, subitems, updates, groups, move, archive
- Block with operation dropdown, board/group selectors, OAuth credential, advanced mode
- 9 webhook triggers with auto-subscription lifecycle (create/delete via GraphQL API)
- OAuth config with 7 scopes (boards, updates, webhooks, me:read)
- Provider handler with challenge verification, formatInput, idempotency
- Docs, icon, selectors, and all registry wiring

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

* fix(monday): cast userId to string in deleteSubscription fallback

The DeleteSubscriptionContext type has userId as unknown, causing a
TypeScript error when passing it to getOAuthToken which expects string.

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

* fix(monday): escape string params in GraphQL, align deleteSubscription with established patterns

- Use JSON.stringify() for groupId in get_items.ts (matches create_item.ts
  and move_item_to_group.ts)
- Use JSON.stringify() for notificationUrl in webhook provider
- Remove non-standard getOAuthToken fallback in deleteSubscription to match
  Airtable/Webflow pattern (credential resolution only, warn and return on failure)

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

* fix(monday): sanitize columns JSON in search_items GraphQL query

Parse and re-stringify the columns param to ensure well-formed JSON
before interpolating into the GraphQL query, preventing injection
via malformed input.

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

* fix(monday): validate all numeric IDs and sanitize columns in GraphQL queries

- Add sanitizeNumericId() helper to tools/monday/utils.ts for consistent
  validation across all tool body builders
- Apply to all 13 instances of boardId, itemId, parentItemId interpolation
  across 11 tool files, preventing GraphQL injection via crafted IDs
- Wrap JSON.parse in search_items.ts with try-catch for user-friendly
  error on malformed column filter JSON

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

* fix(monday): deduplicate numeric ID validation, sanitize limit/page params

- Refactor sanitizeNumericId to delegate to validateMondayNumericId
  from input-validation.ts, eliminating duplicated regex logic
- Add sanitizeLimit helper for safe integer coercion with bounds
- Apply sanitizeLimit to limit/page params in list_boards, get_items,
  and search_items for consistent validation across all GraphQL params

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

* fix(monday): align list_boards limit description with code (max 500)

The param description said "max 100" but sanitizeLimit caps at 500,
which is what Monday.com's API supports for boards. Updated both the
tool description and docs to say "max 500".

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 20:27:44 -07:00
Waleed
2266bb384b feat(triggers): add Atlassian triggers for Jira, JSM, and Confluence (#4211)
* feat(triggers): add Atlassian triggers for Jira, JSM, and Confluence

- Jira: add 9 new triggers (sprint created/started/closed, project created, version released, comment updated/deleted, worklog updated/deleted)
- JSM: add 5 triggers from scratch (request created/updated/commented/resolved, generic webhook)
- Confluence: add 7 new triggers (comment updated, attachment updated, page/blog restored, space removed, page permissions updated, user created)
- Add JSM webhook provider handler with HMAC validation and changelog-based event matching
- Add Atlassian webhook identifier to idempotency service for native dedup
- Add extractIdempotencyId to Confluence handler
- Fix Jira generic webhook to pass through full payload for non-issue events
- Fix output schemas: add description (ADF), updateAuthor, resolution, components, fixVersions, worklog timestamps, note emailAddress as Jira Server only

* fix(triggers): replace any with Record<string, unknown> in confluence extract functions

* lint

* fix(triggers): use comment.id in JSM idempotency, fix confluence type cast

JSM extractIdempotencyId now prioritizes comment.id over issue.id for
comment_created events, matching Jira's documented webhook payload
structure. Also fixes type cast for confluence extract function calls.

* fix(triggers): correct comment.body type to json, fix TriggerOutput description type

- JSM webhook comment.body changed from string to json (ADF format)
- Widened TriggerOutput.description to accept TriggerOutput objects,
  removing unsafe `as unknown as string` casts for Jira description fields
2026-04-16 19:59:12 -07:00
Vikhyath Mondreti
a589c8e318 improvement(mothership): whitespace only deltas need to be preserved, update docs for theshold billing (#4212)
* fix(mothership): content block spaces trimmed

* update overage threshold docs
2026-04-16 19:22:01 -07:00
Vikhyath Mondreti
3d909d5416 fix(resolver): turn off resolver for opaque schema nodes, unrun paths (#4208)
* fix(resolver): turn off resolver for opaque schema nodes, unrun paths

* fix subflows to make them consistent

* fix tests
2026-04-16 18:33:28 -07:00
JaeHyung Jang
3e2a7a2eb1 fix(export): preserve unicode characters in workflow filenames (#4120)
Previously, Non-ASCII characters (like Korean) in workflow names were
replaced by dashes during export because of a restrictive regex.
This update uses a Unicode-aware regex to allow letters and numbers
from any language while still sanitizing unsafe filesystem characters.

fixes #4119

Signed-off-by: JaeHyung Jang <jaehyung.jang@navercorp.com>
2026-04-16 18:18:56 -07:00
Waleed
49a1495e15 improvement(logs): fix trigger badge wrapping, time range picker, status filters, and React anti-patterns (#4207)
* improvement(logs): fix trigger badge wrapping, time range picker, status filters, and React anti-patterns

* chore(logs): remove dev mock logs

* fix(logs): prevent DatePicker onOpenChange from reverting time range after Apply
2026-04-16 18:00:57 -07:00
Waleed
1d0e118eef fix(socket): sync deploy button state across collaborators (#4206)
* fix(socket): sync deploy button state across collaborators

Broadcast workflow-deployed events via socket so all connected users
invalidate their deployment query cache when any user deploys, undeploys,
activates a version, or triggers a deploy through chat/form endpoints.

* fix(socket): check response status on deployment notification

Log a warning when the socket server returns a non-2xx status for
deployment notifications, matching the pattern in lifecycle.ts.

* improvement(config): consolidate socket server URL into getSocketServerUrl/getSocketUrl

Replace all inline `env.SOCKET_SERVER_URL || 'http://localhost:3002'` and
`getEnv('NEXT_PUBLIC_SOCKET_URL') || 'http://localhost:3002'` with centralized
utility functions in urls.ts, matching the getBaseUrl() pattern.

* improvement(config): consolidate Ollama URL and CSP socket/Ollama hardcodes

Add getOllamaUrl() to urls.ts and replace inline env.OLLAMA_URL fallbacks
in the provider and API route. Update CSP to use getSocketUrl(),
getOllamaUrl(), and a local toWebSocketUrl() helper instead of hardcoded
localhost strings.

* lint

* fix(tests): add missing mocks for new URL utility exports

Update lifecycle, async execute, and chat manage test mocks to include
getSocketServerUrl, getOllamaUrl, and notifySocketDeploymentChanged.

* fix(csp): remove urls.ts import to fix next.config.ts build

CSP is loaded by next.config.ts which transpiles outside the @/ alias
context. Use local constants instead of importing from urls.ts.

* fix(queries): invalidate chat and form status on deployment change

Add chatStatus and formStatus to invalidateDeploymentQueries so all
deployment-related queries refresh when any user deploys or undeploys.
2026-04-16 17:46:53 -07:00
Waleed
c06361b142 improvement(tables): clean up duplicate types, unnecessary memos, and barrel imports (#4205)
* improvement(tables): clean up duplicate types, unnecessary memos, and barrel imports

* fix(tables): revert barrel import in client component to avoid bundling server-only deps
2026-04-16 16:47:51 -07:00
Waleed
8a50f1844c v0.6.48: import csv into tables, subflow fixes, CSP updates 2026-04-16 14:08:49 -07:00
Waleed
6fd1767c7e improvement(ui): remove React anti-patterns, fix CSP violations (#4203)
* improvement(ui): remove React anti-patterns, fix CSP violations

* fix(ui): restore useMemo on existingKeys — it is observed by useAvailableResources

* improvement(ui): add RefreshCw icon, update Bell SVG, active state styling for header actions

* minor UI improvements
2026-04-16 13:58:15 -07:00
Vikhyath Mondreti
6aa6346330 fix(executor): subflow edge keys mismatch (#4202)
* fix(executor): subflow edge keys mismatch'

* improve style
2026-04-16 13:41:11 -07:00
Vikhyath Mondreti
1708bbee35 feat(tables): import csv into existing tables (#4199)
* feat(tables): import csv into existing tables

* update types

* address comments

* address comment
2026-04-16 13:37:54 -07:00
Waleed
2dbc7fdddf v0.6.47: files focusing, documentation, opus 4.7 2026-04-16 12:57:12 -07:00
Waleed
e16c8e6f70 fix(ui): stop terminal auto-select from stealing copilot input focus (#4201) 2026-04-16 12:44:26 -07:00
Waleed
9f41736d50 fix(misc): remove duplicate docs page, update clopus 4.7 (#4200)
* fix(misc): remove duplicate docs page, update clopus 4.7

* fix(docs): consolidate duplicate docs and fix SDK API signatures

- Remove duplicate custom-tools page (custom-tools/index.mdx → tools/custom-tools.mdx is canonical)
- Remove comparison table from custom-tools per product preference
- Fix permissions inconsistency: delete now requires Admin across all docs
- Consolidate sdks/ into api-reference/ (sdks/ directory deleted)
- Fix Python SDK docs: correct param is `input`, not `input_data`
- Fix TypeScript SDK docs: correct signature is executeWorkflow(id, input, options) not options-object form
- Add FAQ sections to both SDK reference pages

* fix(docs): update SDKs card links from /sdks to /api-reference

* fix(docs): update /sdks references to /api-reference in llms.txt files
2026-04-16 12:33:25 -07:00
Waleed
147ac89672 feat(docs): fill documentation gaps across platform features (#4110)
* feat(docs): fill documentation gaps across platform features

* fix(docs): address PR review comments on chat OTP cookies and MCP env var placeholders

* fix(docs): replace smart quotes with straight quotes in JSX attributes

* update(docs): update mcp, custom tools, and variables docs

* Fix grammar

* mothership docs, tags, connectors, api, chat deploy, etc

* more info

* more

* feat(docs): auto-generate per-provider trigger documentation

Extends scripts/generate-docs.ts to produce one MDX page per trigger
provider (39 pages) in apps/docs/content/docs/en/triggers/. The 5
hand-written pages (index, start, schedule, webhook, rss) are never
touched.

Key additions to the generation script:
- resolveConstVariable() resolves module-level const spreads so
  providers like Vercel that build outputs from const variables (not
  just functions) are fully documented
- resolveTriggerBuilderFunction() extended to expand variable spreads
  (...varName) in addition to function-call spreads (...fn())
- groupTriggersByProvider() deduplicates v1/v2 trigger variants by
  name, keeping the highest-versioned one per provider
- writeIconMapping() adds bare-name aliases for versioned block types
  (github_v2 → github, fireflies_v2 → fireflies, etc.) so
  BlockInfoCard resolves icons for all 39 trigger providers
- extractTriggerConfigFields() filters readOnly display blocks (webhook
  URL displays, sample payloads, curl examples) from config tables

Each generated page includes: BlockInfoCard with correct icon/color,
trigger count, polling note where applicable, Configuration table, and
Output table for every trigger. No "Type:" lines.

* refactor(docs): align trigger docs structure with tools docs

- Use ### `trigger_id` headings (matching ### `tool_id` in tools docs)
- Wrap all trigger sections under a ## Triggers header
- Rename Configuration/Output to #### level (matching #### Input/Output)
- Use Parameter column header to match tools docs table style
- Map UI widget types to semantic types: short-input/long-input/dropdown
  → string, switch → boolean, slider → number, oauth-input → string

* refactor(docs): use human-readable names for trigger section headings

Trigger IDs are internal identifiers; users scan by name. Switch from
### `trigger_id` to ### Trigger Name for cleaner sidebar navigation
and better readability.

* fix(docs): resolve subBlock builder functions for all trigger Config sections

Extends generate-docs.ts to parse subBlock builder functions so all 15
providers previously missing Configuration sections now generate them.

Handles three patterns:
- `buildTriggerSubBlocks({extraFields: buildX(...)})` — extracts extra
  fields from the call site and resolves them from the provider's utils.ts
- `return [...]` — direct array return (Attio, Confluence, etc.)
- `blocks.push(...)` — imperative push pattern (Linear, Ashby)

Also resolves const-reference field IDs (SCREAMING_CASE) by searching
the webhook provider constants cache, fixing Gong's `gongJwtPublicKeyPem`
field which was previously unresolvable. Adds title-as-description fallback
for OAuth credential fields that have no explicit description.

* fix(docs): correctly destructure nested implicit-object trigger outputs

Fixes a parser bug where output fields with no top-level `type` key but
child fields each having their own `type`/`description` were incorrectly
parsed. The `type:` and `description:` regex matches were not
depth-aware, so values from nested children bled into the parent field.

Changes:
- Add `isAtDepthZero()` helper for brace-depth-aware regex matching
- Fix `parseFieldContent` to only match `type:` at brace depth 0
- Fix `extractDescription` to only match `description:` at brace depth 0
- Add implicit-object fallback: when no top-level `type` exists but child
  fields have their own types, treat as `object` with `properties`
- Regenerate all affected trigger docs (Cal.com payload, Linear data,
  Jira issue.fields, Ashby application, Greenhouse candidate, etc.)

* chore(docs): update static trigger and start page images

* feat(providers): add claude-opus-4-7 model with adaptive thinking support

* Add workflow version screenshots

* Add function block screenshots

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-16 11:51:49 -07:00
Theodore Li
4cdc941490 fix(ui): fix focusing bugs while editing files (#4197) 2026-04-16 14:21:10 -04:00
Waleed
387cc977fa v0.6.46: mothership queueing, web vitals 2026-04-16 00:12:50 -07:00
Waleed
0464a57601 fix(ui): posthog guard, dynamic import loading, compact variant, rebase cleanup (#4196)
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* fix(ui): posthog guard, dynamic import loading, compact variant, rebase cleanup

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
2026-04-15 23:52:57 -07:00
Emir Karabeg
23ccd4a50c improvement(landing): optimize core web vitals and accessibility (#4193)
* improvement(landing): optimize core web vitals and accessibility

Code-split AuthModal and DemoRequestModal via next/dynamic across 7 landing
components to move auth-client bundle (~150-250KB) out of the initial JS payload.
Replace useSession import in navbar with direct SessionContext read to avoid
pulling the entire better-auth client into the landing page bundle. Add immutable
cache header for content-hashed _next/static assets. Defer PostHog session
recording until user identification to avoid loading the recorder (~80KB) on
anonymous visits. Fix accessibility issues flagged by Lighthouse: add missing
aria-label on preview submit button, add inert to aria-hidden ReactFlow wrapper,
set decorative alt on logos inside labeled links, disambiguate duplicate footer
API links.

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

* fix(posthog): guard startSessionRecording against repeated calls on refetch

The effect fires on every session reload (e.g., subscription upgrade).
Calling startSessionRecording() while already recording fragments the
session in the analytics dashboard. Add sessionRecordingStarted() guard
so recording only starts once per page lifecycle.

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

* fix(config): remove redundant _next/static cache header

Next.js already sets Cache-Control: public, max-age=31536000, immutable
on _next/static assets natively and this cannot be overridden. The custom
rule was redundant on Vercel and conflicted with the extension-based rule
on self-hosted deployments due to last-match-wins ordering.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:56:16 -07:00
Theodore Li
ba6bc91681 fix(ui): fix attachment logic on queued mothership messages (#4191)
* fix(ui): fix attachment logic on queued mothership messages

* Add focus after hitting pencil button for queued message

* fix copilot layout
2026-04-15 21:42:48 -04:00
Vikhyath Mondreti
c0bc62c592 Merge pull request #4190 from simstudioai/staging
v0.6.46: mothership streaming fixes, brightdata integration
2026-04-15 17:28:28 -07:00
Vikhyath Mondreti
377712c9f3 fix(mothership): chat stream structuring + logs resource post fix (#4189)
* fix(mothership): chat streaming structure

* fix logs resource thinking bug"

* address comments

* address comments
2026-04-15 16:43:22 -07:00
Waleed
6dddc3f796 fix(brightdata): fix async Discover API, echo-back fields, and registry ordering (#4188)
* fix(brightdata): use params for echo-back fields in transformResponse

transformResponse receives params as its second argument. Use it to
return the original url, query, snapshotId, and searchEngine values
instead of hardcoding null or extracting from response data that may
not contain them.

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

* fix(brightdata): handle async Discover API with polling

The Bright Data Discover API is asynchronous — POST /discover returns
a task_id, and results must be polled via GET /discover?task_id=...
The previous implementation incorrectly treated it as synchronous,
always returning empty results.

Uses postProcess (matching Firecrawl crawl pattern) to poll every 3s
with a 120s timeout until status is "done".

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

* fix(brightdata): alphabetize block registry entry

Move box before brandfetch/brightdata to maintain alphabetical ordering.

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

* lint

* fix(brightdata): return error objects instead of throwing in postProcess

The executor wraps postProcess in try-catch and falls back to the
intermediate transformResponse result on error, which has success: true
with empty results. Throwing errors would silently return empty results.

Match Firecrawl's pattern: return { ...result, success: false, error }
instead of throwing. Also add taskId to BrightDataDiscoverResponse type
to eliminate unsafe casts.

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

* fix(brightdata): use platform execution timeout for Discover polling

Replace hardcoded 120s timeout with DEFAULT_EXECUTION_TIMEOUT_MS to
match Firecrawl and other async polling tools. Respects platform-
configured limits (300s free, 3000s paid).

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 16:20:39 -07:00
Siddharth Ganesan
010435c53b v0.6.45: superagent, csp, brightdata integration, gemini response format, logs performance improvements
fix(csp): add missing analytics domains, remove unsafe-eval, fix workspace CSP gap (#4179)
fix(landing): return 404 for invalid dynamic route slugs (#4182)
improvement(seo): optimize sitemaps, robots.txt, and core web vitals across sim and docs (#4170)
fix(gemini): support structured output with tools on Gemini 3 models (#4184)
feat(brightdata): add Bright Data integration with 8 tools (#4183)
fix(mothership): fix superagent credentials (#4185)
fix(logs): close sidebar when selected log disappears from filtered list; cleanup (#4186)
2026-04-15 13:20:27 -07:00
Waleed
cd8c5bd0b8 fix(logs): close sidebar when selected log disappears from filtered list + cleanup (#4186)
Derive sidebar open state from selection validity instead of using a
separate useEffect. Also removes unnecessary useMemo/useCallback in
non-memo'd components, replaces useEffect with render-time reset in
dashboard, fixes CSS tokens, and adds hierarchical query key factory.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 12:58:22 -07:00
Siddharth Ganesan
f0285adc38 fix(mothership): fix superagent credentials (#4185)
* Fix

* Fix ajv csp issue

* Lint
2026-04-15 12:52:02 -07:00
Waleed
a39dc158cf feat(brightdata): add Bright Data integration with 8 tools (#4183)
* feat(brightdata): add Bright Data integration with 8 tools

Add complete Bright Data integration supporting Web Unlocker, SERP API,
Discover API, and Web Scraper dataset operations. Includes scrape URL,
SERP search, discover, sync scrape, scrape dataset, snapshot status,
download snapshot, and cancel snapshot tools.

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

* fix(brightdata): address PR review feedback

- Fix truncated "Download Snapshot" description in integrations.json and docs
- Map engine-specific query params (num/count/numdoc, hl/setLang/lang/kl,
  gl/cc/lr) per search engine instead of using Google-specific params for all
- Attempt to parse snapshot_id from cancel/download response bodies instead
  of hardcoding null

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

* lint

* fix(agiloft): change bgColor to white; fix docs truncation

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

* fix(brightdata): avoid inner quotes in description to fix docs generation

The docs generator regex truncates at inner quotes. Reword the
download_snapshot description to avoid embedded double quotes.

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

* fix(brightdata): disable incompatible DuckDuckGo and Yandex URL params

DuckDuckGo kl expects region-language format (us-en) and Yandex lr
expects numeric region IDs (213), not plain two-letter codes. Disable
these URL-level params since Bright Data normalizes localization through
the body-level country param.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 12:47:02 -07:00
Waleed
05c1c5b1f6 fix(gemini): support structured output with tools on Gemini 3 models (#4184)
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* fix(gemini): support structured output with tools on Gemini 3 models

* fix(home): remove duplicate handleStopGeneration declaration

* refactor(gemini): use prefix-based Gemini 3 model detection

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
2026-04-15 12:21:39 -07:00
Emir Karabeg
5274efd8f9 improvement(seo): optimize sitemaps, robots.txt, and core web vitals across sim and docs (#4170)
* improvement(seo): optimize sitemaps and robots.txt across sim and docs

- Add missing pages to sim sitemap: blog author pages, academy catalog and course pages
- Fix 6x duplicate URL bug in docs sitemap by deduplicating with source.getLanguages()
- Convert docs sitemap from route handler to Next.js metadata convention with native hreflang
- Add x-default hreflang alternate for docs multi-language pages
- Remove changeFrequency and priority fields (Google ignores both)
- Fix inaccurate lastModified timestamps — derive from real content dates, omit when unknown
- Consolidate 20+ redundant per-bot robots rules into single wildcard entry
- Add /form/ and /credential-account/ to sim robots disallow list
- Reference image sitemap in sim robots.txt
- Remove deprecated host directive from sim robots
- Move disallow rules before allow in docs robots for crawler compatibility
- Extract hardcoded docs baseUrl to env variable with production fallback

* fix(seo): remove homepage new Date(), guard latestModelDate empty array

* improvement(seo): consolidate DOCS_BASE_URL, optimize core web vitals

Extract hardcoded https://docs.sim.ai into shared DOCS_BASE_URL constant
in lib/urls.ts and replace all 20+ instances across layouts, metadata,
structured data, LLM manifest, sitemap, and robots files. Remove
OneDollarStats analytics script and tighten CSP for improved core web vitals.

* fix: removed onedollarstats from bun lock

* fix(seo): guard per-provider Math.max, consolidate docs robots to single wildcard
2026-04-15 12:13:30 -07:00
Waleed
0b36c8bcb6 fix(landing): return 404 for invalid dynamic route slugs (#4182)
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* fix(landing): return 404 for invalid dynamic route slugs

Add `dynamicParams = false` to all landing page dynamic routes so
Next.js returns a proper 404 instead of a client-side exception for
slugs not in generateStaticParams.

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

* fix(home): remove duplicate handleStopGeneration declaration

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

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 11:48:39 -07:00
Waleed
842aa2c254 fix(csp): add missing analytics domains, remove unsafe-eval, fix workspace CSP gap (#4179) 2026-04-15 10:04:53 -07:00
Waleed
46ffc4904e v0.6.44: streamdown, mothership intelligence, excel extension 2026-04-14 22:13:57 -07:00
Waleed
ff71a07e8f improvement(ui): rename user-facing "execution" to "run" (#4176)
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* improvement(ui): rename user-facing "execution" to "run"

* fix(mothership): remove duplicate handleStopGeneration declaration

* chore: remove verbose comment in cancel route

* fix(ui): missed execution → run renames in search suggestions and error fallback

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
2026-04-14 21:49:20 -07:00
Waleed
22d4639f13 refactor(microsoft-excel): export GRAPH_ID_PATTERN and deduplicate validation (#4174)
* refactor(microsoft-excel): export GRAPH_ID_PATTERN and reuse across routes

Export the shared regex pattern from utils.ts and import it in files/route.ts
and drives/route.ts instead of duplicating the inline pattern. Also reorders
the TSDoc comment to sit above getItemBasePath where it belongs.

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

* lint

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 21:34:54 -07:00
Waleed
80095788fc feat(microsoft-excel): add SharePoint drive support for Excel integration (#4162)
* feat(microsoft-excel): add SharePoint drive support for Excel integration

* fix(microsoft-excel): address PR review comments

- Validate siteId/driveId format in drives route to prevent path traversal
- Use direct single-drive endpoint for fetchById instead of filtering full list
- Fix dependsOn on sheet/spreadsheet selectors so driveId flows into context
- Fix NextRequest type in drives route for build compatibility

* fix(microsoft-excel): validate driveId in files route

Add regex validation for driveId query param in the Microsoft OAuth
files route to prevent path traversal, matching the drives route.

* fix(microsoft-excel): unblock OneDrive users and validate driveId in sheets route

- Add credential to any[] arrays so OneDrive users (no drive selected)
  still pass the dependsOn gate while driveSelector remains in the
  dependency list for context flow to SharePoint users
- Add /^[\w-]+$/ validation for driveId in sheets API route

* fix(microsoft-excel): validate driveId in getItemBasePath utility

Add regex validation for driveId at the shared utility level to prevent
path traversal through the tool execution path, which bypasses the
API route validators.

* fix(microsoft-excel): use centralized input validation

Replace inline regex validation with platform validators from
@/lib/core/security/input-validation:
- validateSharePointSiteId for siteId in drives route
- validateAlphanumericId for driveId in drives, sheets, files routes
  and getItemBasePath utility

* lint

* improvement(microsoft-excel): add File Source dropdown to control SharePoint visibility

Replace always-visible optional SharePoint fields with a File Source
dropdown (OneDrive/SharePoint) that conditionally shows site and drive
selectors. OneDrive users see zero extra fields (default). SharePoint
users switch the dropdown and get the full cascade.

* fix(microsoft-excel): fix canonical param test failures

Make fileSource dropdown mode:'both' so it appears in basic and advanced
modes. Add condition to manualDriveId to match driveSelector's condition,
satisfying the canonical pair consistency test.

* fix(microsoft-excel): address PR review feedback for SharePoint drive support

- Clear stale driveId/siteId/spreadsheetId when fileSource changes by adding
  fileSource to dependsOn arrays for siteSelector, driveSelector, and
  spreadsheetId selectors
- Reorder manualDriveId before manualSpreadsheetId in advanced mode for
  logical top-down flow
- Validate spreadsheetId with validateMicrosoftGraphId in getItemBasePath()
  and sheets route to close injection vector (uses permissive validator that
  accepts ! chars in OneDrive item IDs)

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

* fix(microsoft-excel): use validateMicrosoftGraphId for driveId validation

SharePoint drive IDs use the format b!<base64-string> which contains !
characters rejected by validateAlphanumericId. Switch all driveId
validation to validateMicrosoftGraphId which blocks path traversal and
control characters while accepting valid Microsoft Graph identifiers.

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

* fix(microsoft-excel): use validatePathSegment with strict pattern for driveId/spreadsheetId

Replace validateMicrosoftGraphId with validatePathSegment using a custom
pattern ^[a-zA-Z0-9!_-]+$ for all URL-interpolated IDs. validatePathSegment
blocks /, \, path traversal, and null bytes before checking the pattern,
preventing URL-modifying characters like ?, #, & from altering the Graph
API endpoint. The pattern allows ! for SharePoint b!<base64> drive IDs.

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

* lint

* fix(microsoft-excel): reorder driveId before spreadsheetId in v1 block

Move driveId subBlock before manualSpreadsheetId in the legacy v1 block
to match the logical top-down flow (Drive ID → Spreadsheet ID), consistent
with the v2 block ordering.

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

* fix(microsoft-excel): clear manualDriveId when fileSource changes

Add dependsOn: ['fileSource'] to manualDriveId so its value is cleared
when switching from SharePoint back to OneDrive. Without this, the stale
driveId would still be serialized and forwarded to getItemBasePath,
routing through the SharePoint drive path instead of me/drive.

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

* refactor(microsoft-excel): use getItemBasePath in sheets route to remove duplication

Replace inline URL construction and validation logic with the shared
getItemBasePath utility, eliminating duplicated GRAPH_ID_PATTERN regex
and conditional URL building.

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

* lint

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 21:10:55 -07:00
Waleed
61b33e5978 fix(blocks): correct required field validation for Jira and Confluence blocks (#4172)
* fix(blocks): correct required field validation for Jira and Confluence blocks

Jira: summary is only required for create (not update), projectId is not required for update (API uses issueKey). Confluence: title and content are required for page creation, title is required for blog post creation — all enforced by backend validation.

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

* fix(blocks): remove projectId dependsOn gate for update fields, require content for blog post creation

Jira: Remove dependsOn projectId from shared write/update fields — projectId is not required for update so the gate would disable all update fields when no project is selected. Write-only fields (issueType, parentIssue, reporter) retain the gate since projectId is required for create.

Confluence V2: Add create_blogpost to content required condition — backend Zod schema enforces content for blog post creation.

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

* lint

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 20:44:47 -07:00
Siddharth Ganesan
29fbad2874 fix(mothership): fix intelligence regression (#4171) 2026-04-14 20:01:44 -07:00
Emir Karabeg
e281ca0dac fix(ui): align PlayOutline icon with filled Play shape (#4169)
The PlayOutline icon had a non-standard viewBox and mismatched path,
causing it to render at an inconsistent size and shape compared to the
filled Play icon and other action bar icons.
2026-04-14 19:38:00 -07:00
Emir Karabeg
cbf0a139ed fix(seo): correct canonical URLs, compress oversized images, add cache headers (#4168)
* fix(seo): correct canonical URLs, compress oversized images, add cache headers

- Replace all hardcoded https://sim.ai with https://www.sim.ai via SITE_URL constant
- Migrate models, integrations, and homepage metadata from getBaseUrl() to SITE_URL
- Compress 6 blog/landing images from 2.6MB to 300KB total
- Convert mothership cover from PNG to JPEG (1.1MB → 99KB)
- Add Cache-Control headers for static assets (1d max-age, 7d stale-while-revalidate)
- Add SEO regression test scanning all public pages for canonical URL violations

* fix(seo): replace hardcoded URLs with SITE_URL, broaden test detection

- Replace hardcoded https://www.sim.ai with SITE_URL in academy, changelog.xml, and whitelabeling
- Broaden getBaseUrl() detection in SEO test to match any variable name assignment
- Add ee/whitelabeling/metadata.ts to SEO test scan scope
2026-04-14 16:42:32 -07:00
Theodore Li
751eeaccd4 fix(ui): resource tab fixes, add search to workspace modal (#4166)
* fix(ui): fix resource switching logic, multi select delete

* Allow cmd+click on workspace menu

* Add search bar to workspace modal

* address greptile comments

* fix resource tab scroll
2026-04-14 19:06:51 -04:00
Emir Karabeg
1bf2d95813 improvement(ui): delegate streaming animation to Streamdown component (#4163)
* improvement(ui): delegate streaming animation to Streamdown component

Remove custom useStreamingText hook and useThrottledValue indirection
in favor of Streamdown's built-in streaming props. This eliminates the
manual character-by-character reveal logic (setInterval, easing, chase
factor) and lets the library handle animation natively, reducing
complexity and improving consistency across Mothership and chat.

* improvement(ui): inline passthrough wrapper, add hydration guard

- Inline EnhancedMarkdownRenderer which became a trivial passthrough
  after removing useThrottledValue
- Add hydration guard to MarkdownRenderer to prevent replaying the
  entrance animation when mounting mid-stream with existing content

* improvement: removed chat animation

* improvement(ui): remove hardcoded fade-in animations from special tags

Remove animate-stream-fade-in from OptionsDisplay, CredentialDisplay,
MothershipErrorDisplay, and UsageUpgradeDisplay. These components
re-render after streaming ends, causing a visible flash as the
opacity animation replays. PendingTagIndicator retains its animation
since it only renders during active streaming.

* fix(ui): use streaming mode for Streamdown during active streams

mode='static' disables Remend (auto-closing incomplete markdown),
incremental block splitting, and React Transitions. Switch to
streaming mode while isStreaming is true so partial markdown renders
correctly, without re-adding animation props.
2026-04-14 15:31:39 -07:00
Waleed
3a1b1a8032 v0.6.43: mothership billing idempotency, env var resolution fixes 2026-04-14 15:22:32 -07:00
Waleed
3d6660ba4d feat(jira): support raw ADF in description and environment fields (#4164)
* fix(security): resolve ReDoS vulnerability in function execute tag pattern

Simplified regex to eliminate overlapping quantifiers that caused exponential
backtracking on malformed input without closing delimiter.

* feat(jira): support raw ADF document objects in description and environment fields

Add toAdf() helper that passes through ADF objects as-is or wraps plain
text in a single-paragraph ADF doc. Update write and update routes to
use it, replacing inline ADF wrapping. Update Zod schema to accept
string or object for description. Fully backward compatible — plain
text still works, but callers can now pass rich ADF with expand nodes,
tables, code blocks, etc.

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

* fix(jira): handle partial ADF nodes and non-ADF objects in toAdf()

Wrap partial ADF nodes (type + content but not doc) in a doc envelope.
Fall back to JSON.stringify for non-ADF objects instead of String()
which produces [object Object].

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

* lint

* fix(jira): handle JSON-stringified ADF in toAdf() for variable resolution

The executor's formatValueForBlock() JSON.stringify's object values when
resolving <Block.output> references. This means an ADF object from an
upstream Agent block arrives at the route as a JSON string. toAdf() now
detects JSON strings containing valid ADF documents or nodes and parses
them back, ensuring rich formatting is preserved through the pipeline.

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

* lint changes

* fix(jira): update environment Zod schema to accept ADF objects

Match the description field schema change — environment also passes
through toAdf() so its Zod schema must accept objects too.

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

* updated lobkc

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 15:18:18 -07:00
Waleed
48e174b21f fix(google-drive): add auto export format and validate against Drive API docs (#4161)
* fix(google-drive): add auto export format and Azure storage debug logging

* chore: remove Azure storage debug logging

* fix(google-drive): use status-based fallback instead of string matching for export errors

* fix(google-drive): validate export formats against Drive API docs, remove fallback

* fix(google-drive): use value function for dropdown default

* fix(google-drive): add text/markdown to valid export formats for Google Docs

* fix(google-drive): correct ODS MIME type for Sheets export format
2026-04-14 15:09:36 -07:00
Vikhyath Mondreti
7529a75ac0 fix(triggers): env var resolution in provider configs (#4160)
* fix(triggers): env var resolution in provider configs

* throw on errored resolution
2026-04-14 14:30:26 -07:00
Theodore Li
6b2e83bf58 fix(billing): add idempotency to billing (#4157)
* fix(billing): add idempotency to billing

* Only release redis lock if billed
2026-04-14 17:15:54 -04:00
Waleed
fc07922536 v0.6.42: mothership nested file reads, search modal improvements 2026-04-14 13:07:50 -07:00
Siddharth Ganesan
367415f649 fix(mothership): tool path for nested folders (#4158) 2026-04-14 13:03:07 -07:00
Siddharth Ganesan
ff2e369c20 fix(mothership): fix workflow vfs reads (#4156)
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* fix build error

* improvement(mothership): new agent loop (#3920)

* feat(transport): replace shared chat transport with mothership-stream module

* improvement(contracts): regenerate contracts from go

* feat(tools): add tool catalog codegen from go tool contracts

* feat(tools): add tool-executor dispatch framework for sim side tool routing

* feat(orchestrator): rewrite tool dispatch with catalog-driven executor and simplified resume loop

* feat(orchestrator): checkpoint resume flow

* refactor(copilot): consolidate orchestrator into request/ layer

* refactor(mothership): reorganize lib/copilot into structured subdirectories

* refactor(mothership): canonical transcript layer, dead code cleanup, type consolidation

* refactor(mothership): rebase onto latest staging

* refactor(mothership): rename request continue to lifecycle

* feat(trace): add initial version of request traces

* improvement(stream): batch stream from redis

* fix(resume): fix the resume checkpoint

* fix(resume): fix resume client tool

* fix(subagents): subagent resume should join on existing subagent text block

* improvement(reconnect): harden reconnect logic

* fix(superagent): fix superagent integration tools

* improvement(stream): improve stream perf

* Rebase with origin dev

* fix(tests): fix failing test

* fix(build): fix type errors

* fix(build): fix build errors

* fix(build): fix type errors

* feat(mothership): add cli execution

* fix(mothership): fix function execute tests

* Force redeploy

* feat(motheship): add docx support

* feat(mothership): append

* Add deps

* improvement(mothership): docs

* File types

* Add client retry logic

* Fix stream reconnect

* Eager tool streaming

* Fix client side tools

* Security

* Fix shell var injection

* Remove auto injected tasks

* Fix 10mb tool response limit

* Fix trailing leak

* Remove dead tools

* file/folder tools

* Folder tools

* Hide function code inline

* Dont show internal tool result reads

* Fix spacing

* Auth vfs

* Empty folders should show in vfs

* Fix run workflow

* change to node runtime

* revert back to bun runtime

* Fix

* Appends

* Remove debug logs

* Patch

* Fix patch tool

* Temp

* Checkpoint

* File writes

* Fix

* Remove tool truncation limits

* Bad hook

* replace react markdown with streamdown

* Checkpoitn

* fix code block

* fix stream persistence

* temp

* Fix file tools

* tool joining

* cleanup subagent + streaming issues

* streamed text change

* Tool display intetns

* Fix dev

* Fix tests

* Fix dev

* Speed up dev ci

* Add req id

* Fix persistence

* Tool call names

* fix payload accesses

* Fix name

* fix snapshot crash bug

* fix

* Fix

* remove worker code

* Clickable resources

* Options ordering

* Folder vfs

* Restore and mass delete tools

* Fix

* lint

* Update request tracing and skills and handlers

* Fix editable

* fix type error

* Html code

* fix(chat): make inline code inherit parent font size in markdown headers

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

* improved autolayout

* durable stream for files

* one more fix

* POSSIBLE BREAKAGE: SCROLLING

* Fixes

* Fixes

* Lint fix

* fix(resource): fix resource view disappearing on ats (#4103)

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

* Fixes

* feat(mothership): add execution logs as a resource type

Adds `log` as a first-class mothership resource type so copilot can open
and display workflow execution logs as tabs alongside workflows, tables,
files, and knowledge bases.

- Add `log` to MothershipResourceType, all Zod enums, and VALID_RESOURCE_TYPES
- Register log in RESOURCE_REGISTRY (Library icon) and RESOURCE_INVALIDATORS
- Add EmbeddedLog and EmbeddedLogActions components in resource-content
- Export WorkflowOutputSection from log-details for reuse in EmbeddedLog
- Add log resolution branch in open_resource handler via new getLogById service
- Include log id in get_workflow_logs response and extract resources from output
- Exclude log from manual add-resource dropdown (enters via copilot tools only)
- Regenerate copilot contracts after adding log to open_resource Go enum

* Fix perf and message queueing

* Fix abort

* fix(ui): dont delete resource on clearing from context, set resource closed on new task (#4113)

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

* improvement(mothership): structure sim side typing

* address comments

* reactive text editor tweaks

* Fix file read and tool call name persistence bug

* Fix code stream + create file opening resource

* fix use chat race + headless trace issues

* Fix type issue

* Fix mothership block req lifecycle

* Fix build

* Move copy reqid

* Fix

* fix(ui): fix resource tag transition from home to task (#4132)

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

* Fix persistence

* Clean code, fix bugs

* Fix

* Fixes

---------

Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-14 12:11:53 -07:00
Theodore Li
64cdab24f7 fix(ui): handle long file paths and names in search modal (#4155)
* fix(ui): handle long file paths and names in search modal

* Handle long subfolder names

* fix memo
2026-04-14 13:04:08 -04:00
Waleed
3838b6e892 v0.6.41: webhooks fix, workers removal 2026-04-14 08:44:39 -07:00
Waleed
a51333aa2f fix(webhooks): non-polling webhook executions silently dropped after BullMQ removal (#4153) 2026-04-14 08:43:17 -07:00
Waleed
0ac05397eb v0.6.40: mothership tool loop, new skills, agiloft, STS, IAM integrations, jira forms endpoints 2026-04-13 22:26:19 -07:00
Waleed
8a8bc1b0e6 fix(posthog): set email and name on person profile at signup (#4152) 2026-04-13 22:19:09 -07:00
Waleed
48d5101151 fix(ci): replace dynamic secret access with explicit secret references (#4151)
* fix(ci): replace dynamic secret access with explicit secret references

Resolves CodeQL "Excessive Secrets Exposure" warning by replacing
secrets[matrix.ecr_repo_secret] with conditional expressions that
reference only the specific secrets needed.

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

* fix(ci): add explicit ECR_REALTIME guard and use env block for secret injection

- Prevent silent fallthrough to ECR_REALTIME for unrecognized secret keys
- Move build-amd64 secret resolution to env: block matching build-dev pattern

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 22:10:52 -07:00
Waleed
9c1b0bc15f improvement(ui): remove anti-patterns, fix follow-up auto-scroll, move CopyCodeButton to emcn (#4148)
* improvement(ui): restore smooth streaming animation, fix follow-up auto-scroll, move CopyCodeButton to emcn

* fix(ui): restore delayed animation, handle tilde fences, fix follow-up scroll root cause

* fix(ui): extract useStreamingReveal to followup, keep cleanup changes

* fix(ui): restore hydratedStreamingRef for reconnect path order-of-ops

* fix(ui): restore full hydratedStreamingRef effect for reconnect path

* fix(ui): use hover-hover prefix on CopyCodeButton callers to correctly override ghost variant

* fix(logs): remove destructive color from cancel execution menu item

* feat(logs): optimistic cancelling status on cancel execution

* feat(logs): allow cancellation of pending (paused) executions

* fix(hitl): cancel paused executions directly in DB

Paused HITL executions are idle in the DB — they don't poll Redis or
run in-process, so the existing cancel signals had no effect. The DB
status stayed 'pending', causing the optimistic 'cancelling' update to
revert on refetch.

- Add PauseResumeManager.cancelPausedExecution: atomically sets
  paused_executions.status and workflow_execution_logs.status to
  'cancelled' inside a FOR UPDATE transaction
- Guard enqueueOrStartResume against resuming a cancelled execution
- Include pausedCancelled in the cancel route success check

* upgrade turbo

* test(hitl): update cancel route tests for paused execution cancellation

- Mock PauseResumeManager.cancelPausedExecution to prevent DB calls
- Add pausedCancelled to all expected response objects
- Add test for HITL paused execution cancellation path
- Add missing auth/authz tests
- Switch to vi.hoisted pattern for all mocks

* fix(hitl): set endedAt when cancelling paused execution

Without endedAt, the logs API running filter (isNull(endedAt)) would
keep cancelled paused executions in the running view indefinitely.

* fix(hitl): emit execution:cancelled event to canvas when cancelling paused execution

Paused HITL executions have no active SSE stream, so the canvas never
received the cancellation event. Now writes execution:cancelled to the
event buffer and updates the stream meta so the canvas reconnect path
picks it up and shows 'Execution Cancelled'.

* fix(hitl): isolate cancelPausedExecution failure from successful cancellation

Wrap cancelPausedExecution in try/catch so a DB error does not mask
a prior successful Redis or in-process cancellation. Also move the
resource-collapse side effect in home.tsx to a useEffect to avoid the
stale closure on the resources array.

* fix(hitl): add .catch() to fire-and-forget event buffer calls in cancel route
2026-04-13 22:04:13 -07:00
Waleed
0e6ada4bdb fix(security): resolve ReDoS vulnerability in function execute tag pattern (#4149)
* fix(security): resolve ReDoS vulnerability in function execute tag pattern

Simplified regex to eliminate overlapping quantifiers that caused exponential
backtracking on malformed input without closing delimiter.

* fix(security): exclude trailing-dot refs and hoist tag pattern to module level

* fix(security): align tag pattern with codebase standard [^<>]+ pattern

Matches createReferencePattern() from reference-validation.ts used by the
core executor. Invalid refs handled gracefully by resolveBlockReference.

* refactor(security): use createReferencePattern() instead of inline regex
2026-04-13 20:34:25 -07:00
Waleed
85fda999b5 fix(block-card): webhook URL never hydrates due to namespaced subBlock ID (#4150)
getTrigger() namespaces condition-gated subBlock IDs (e.g. webhookUrlDisplay
→ webhookUrlDisplay_github_release_published). The block card's useMemo was
checking for an exact match on 'webhookUrlDisplay', which never matched.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 20:19:24 -07:00
Vikhyath Mondreti
a4da8beb20 improvement(docs): remove references to concurrency control (#4147) 2026-04-13 19:17:11 -07:00
Siddharth Ganesan
bd9dcf1ec0 fix(mothership): revert to deployment and set env var tools (#4141)
* fix build error

* improvement(mothership): new agent loop (#3920)

* feat(transport): replace shared chat transport with mothership-stream module

* improvement(contracts): regenerate contracts from go

* feat(tools): add tool catalog codegen from go tool contracts

* feat(tools): add tool-executor dispatch framework for sim side tool routing

* feat(orchestrator): rewrite tool dispatch with catalog-driven executor and simplified resume loop

* feat(orchestrator): checkpoint resume flow

* refactor(copilot): consolidate orchestrator into request/ layer

* refactor(mothership): reorganize lib/copilot into structured subdirectories

* refactor(mothership): canonical transcript layer, dead code cleanup, type consolidation

* refactor(mothership): rebase onto latest staging

* refactor(mothership): rename request continue to lifecycle

* feat(trace): add initial version of request traces

* improvement(stream): batch stream from redis

* fix(resume): fix the resume checkpoint

* fix(resume): fix resume client tool

* fix(subagents): subagent resume should join on existing subagent text block

* improvement(reconnect): harden reconnect logic

* fix(superagent): fix superagent integration tools

* improvement(stream): improve stream perf

* Rebase with origin dev

* fix(tests): fix failing test

* fix(build): fix type errors

* fix(build): fix build errors

* fix(build): fix type errors

* feat(mothership): add cli execution

* fix(mothership): fix function execute tests

* Force redeploy

* feat(motheship): add docx support

* feat(mothership): append

* Add deps

* improvement(mothership): docs

* File types

* Add client retry logic

* Fix stream reconnect

* Eager tool streaming

* Fix client side tools

* Security

* Fix shell var injection

* Remove auto injected tasks

* Fix 10mb tool response limit

* Fix trailing leak

* Remove dead tools

* file/folder tools

* Folder tools

* Hide function code inline

* Dont show internal tool result reads

* Fix spacing

* Auth vfs

* Empty folders should show in vfs

* Fix run workflow

* change to node runtime

* revert back to bun runtime

* Fix

* Appends

* Remove debug logs

* Patch

* Fix patch tool

* Temp

* Checkpoint

* File writes

* Fix

* Remove tool truncation limits

* Bad hook

* replace react markdown with streamdown

* Checkpoitn

* fix code block

* fix stream persistence

* temp

* Fix file tools

* tool joining

* cleanup subagent + streaming issues

* streamed text change

* Tool display intetns

* Fix dev

* Fix tests

* Fix dev

* Speed up dev ci

* Add req id

* Fix persistence

* Tool call names

* fix payload accesses

* Fix name

* fix snapshot crash bug

* fix

* Fix

* remove worker code

* Clickable resources

* Options ordering

* Folder vfs

* Restore and mass delete tools

* Fix

* lint

* Update request tracing and skills and handlers

* Fix editable

* fix type error

* Html code

* fix(chat): make inline code inherit parent font size in markdown headers

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

* improved autolayout

* durable stream for files

* one more fix

* POSSIBLE BREAKAGE: SCROLLING

* Fixes

* Fixes

* Lint fix

* fix(resource): fix resource view disappearing on ats (#4103)

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

* Fixes

* feat(mothership): add execution logs as a resource type

Adds `log` as a first-class mothership resource type so copilot can open
and display workflow execution logs as tabs alongside workflows, tables,
files, and knowledge bases.

- Add `log` to MothershipResourceType, all Zod enums, and VALID_RESOURCE_TYPES
- Register log in RESOURCE_REGISTRY (Library icon) and RESOURCE_INVALIDATORS
- Add EmbeddedLog and EmbeddedLogActions components in resource-content
- Export WorkflowOutputSection from log-details for reuse in EmbeddedLog
- Add log resolution branch in open_resource handler via new getLogById service
- Include log id in get_workflow_logs response and extract resources from output
- Exclude log from manual add-resource dropdown (enters via copilot tools only)
- Regenerate copilot contracts after adding log to open_resource Go enum

* Fix perf and message queueing

* Fix abort

* fix(ui): dont delete resource on clearing from context, set resource closed on new task (#4113)

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

* improvement(mothership): structure sim side typing

* address comments

* reactive text editor tweaks

* Fix file read and tool call name persistence bug

* Fix code stream + create file opening resource

* fix use chat race + headless trace issues

* Fix type issue

* Fix mothership block req lifecycle

* Fix build

* Move copy reqid

* Fix

* fix(ui): fix resource tag transition from home to task (#4132)

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

* Fix persistence

* Clean code, fix bugs

* Fix

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
2026-04-13 19:16:58 -07:00
Waleed
6ce299bb23 feat(jsm): add all Forms API endpoints for jira (#4142)
* feat(jsm): add all Forms API endpoints for two-step form workflow

* removed tyoes

* fix(jsm): handle 204 No Content on action endpoints and reject array answers

* fix(jsm): validate formIds is an array in copy_forms route and block

* fix(jsm): add formTemplateId validation and conditional required on formAnswers
2026-04-13 19:03:36 -07:00
Theodore Li
c75d7b9ddc fix(ui): fix home button not working until stream ends (#4145)
Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-13 21:55:55 -04:00
Vikhyath Mondreti
c71ae49da0 chore(copilot): streaming paths reviewer group (#4144)
* chore(copilot): streaming paths reviewer group

* narrow scope
2026-04-13 18:28:18 -07:00
Theodore Li
d6dc9f73cd fix(ui): fix flash between home and new chat (#4143)
Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-13 21:18:37 -04:00
Theodore Li
6587afb97e fix(ci): Increase build application memory (#4140)
Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-13 20:35:47 -04:00
Waleed
e23557fdfe feat(aws): add IAM and STS integrations (#4137)
* feat(aws): add IAM and STS integrations

* fix(sts): address PR review comments

- Fix CrowdStrike tags to include "security" (unintended removal)
- Standardize STS tool versions to '1.0.0' (matching IAM convention)
- Add range validation to durationSeconds in Zod schemas

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

* icon

* lint

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 16:53:15 -07:00
Siddharth Ganesan
0abcc6e813 improvement(mothership): restructured stream, tool structures, code typing, file write/patch/append tools, timing issues (#4090)
* fix build error

* improvement(mothership): new agent loop (#3920)

* feat(transport): replace shared chat transport with mothership-stream module

* improvement(contracts): regenerate contracts from go

* feat(tools): add tool catalog codegen from go tool contracts

* feat(tools): add tool-executor dispatch framework for sim side tool routing

* feat(orchestrator): rewrite tool dispatch with catalog-driven executor and simplified resume loop

* feat(orchestrator): checkpoint resume flow

* refactor(copilot): consolidate orchestrator into request/ layer

* refactor(mothership): reorganize lib/copilot into structured subdirectories

* refactor(mothership): canonical transcript layer, dead code cleanup, type consolidation

* refactor(mothership): rebase onto latest staging

* refactor(mothership): rename request continue to lifecycle

* feat(trace): add initial version of request traces

* improvement(stream): batch stream from redis

* fix(resume): fix the resume checkpoint

* fix(resume): fix resume client tool

* fix(subagents): subagent resume should join on existing subagent text block

* improvement(reconnect): harden reconnect logic

* fix(superagent): fix superagent integration tools

* improvement(stream): improve stream perf

* Rebase with origin dev

* fix(tests): fix failing test

* fix(build): fix type errors

* fix(build): fix build errors

* fix(build): fix type errors

* feat(mothership): add cli execution

* fix(mothership): fix function execute tests

* Force redeploy

* feat(motheship): add docx support

* feat(mothership): append

* Add deps

* improvement(mothership): docs

* File types

* Add client retry logic

* Fix stream reconnect

* Eager tool streaming

* Fix client side tools

* Security

* Fix shell var injection

* Remove auto injected tasks

* Fix 10mb tool response limit

* Fix trailing leak

* Remove dead tools

* file/folder tools

* Folder tools

* Hide function code inline

* Dont show internal tool result reads

* Fix spacing

* Auth vfs

* Empty folders should show in vfs

* Fix run workflow

* change to node runtime

* revert back to bun runtime

* Fix

* Appends

* Remove debug logs

* Patch

* Fix patch tool

* Temp

* Checkpoint

* File writes

* Fix

* Remove tool truncation limits

* Bad hook

* replace react markdown with streamdown

* Checkpoitn

* fix code block

* fix stream persistence

* temp

* Fix file tools

* tool joining

* cleanup subagent + streaming issues

* streamed text change

* Tool display intetns

* Fix dev

* Fix tests

* Fix dev

* Speed up dev ci

* Add req id

* Fix persistence

* Tool call names

* fix payload accesses

* Fix name

* fix snapshot crash bug

* fix

* Fix

* remove worker code

* Clickable resources

* Options ordering

* Folder vfs

* Restore and mass delete tools

* Fix

* lint

* Update request tracing and skills and handlers

* Fix editable

* fix type error

* Html code

* fix(chat): make inline code inherit parent font size in markdown headers

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

* improved autolayout

* durable stream for files

* one more fix

* POSSIBLE BREAKAGE: SCROLLING

* Fixes

* Fixes

* Lint fix

* fix(resource): fix resource view disappearing on ats (#4103)

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

* Fixes

* feat(mothership): add execution logs as a resource type

Adds `log` as a first-class mothership resource type so copilot can open
and display workflow execution logs as tabs alongside workflows, tables,
files, and knowledge bases.

- Add `log` to MothershipResourceType, all Zod enums, and VALID_RESOURCE_TYPES
- Register log in RESOURCE_REGISTRY (Library icon) and RESOURCE_INVALIDATORS
- Add EmbeddedLog and EmbeddedLogActions components in resource-content
- Export WorkflowOutputSection from log-details for reuse in EmbeddedLog
- Add log resolution branch in open_resource handler via new getLogById service
- Include log id in get_workflow_logs response and extract resources from output
- Exclude log from manual add-resource dropdown (enters via copilot tools only)
- Regenerate copilot contracts after adding log to open_resource Go enum

* Fix perf and message queueing

* Fix abort

* fix(ui): dont delete resource on clearing from context, set resource closed on new task (#4113)

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

* improvement(mothership): structure sim side typing

* address comments

* reactive text editor tweaks

* Fix file read and tool call name persistence bug

* Fix code stream + create file opening resource

* fix use chat race + headless trace issues

* Fix type issue

* Fix mothership block req lifecycle

* Fix build

* Move copy reqid

* Fix

* fix(ui): fix resource tag transition from home to task (#4132)

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

* Fix persistence

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
2026-04-13 16:46:35 -07:00
Theodore Li
d238052fe8 feat(ui): show folder path in search modal (#4138)
* feat(ui): show folder path in search modal

* Switch truncate folder over workspace name

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-13 19:41:21 -04:00
Theodore Li
c0db9de07b fix(ui): Focus first text input by default (#4134)
* Auto-focus input boxes for modals and copilot

* Fix focus in emcn modal

* Fix integrations manager focus

* Change modal tabs to auto focus on first text input

* Auto-focus mothership task chats

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-13 19:40:19 -04:00
Waleed
7491d70a67 feat(workspaces): add workspace logo upload (#4136)
* feat(workspaces): add workspace logo upload

* feat(workspaces): add workspace logo upload

* fix(workspaces): validate logoUrl accepts only paths or HTTPS URLs

* fix(workspaces): add admin authorization, audit log, and posthog event for workspace logo uploads

* lint

* fix: add WebP support and use refs pattern in useProfilePictureUpload

- Add image/webp to ACCEPTED_IMAGE_TYPES in useProfilePictureUpload
- Add image/webp to file input accept attributes in whitelabeling settings
- Refactor useProfilePictureUpload to use refs for onUpload, onError, and
  currentImage callbacks, matching the established codebase pattern

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

* fix: restore cloudwatch/cloudformation files from staging

These files were accidentally regressed during rebase conflict resolution,
reverting changes from #4027. Restoring to staging versions.

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

* fix: add workspace_logo_uploaded to PostHogEventMap

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

* fix: separate workspaceId ref sync to prevent overwrite on re-render

Split the ref sync useEffect so workspaceIdRef only updates when the
workspaceId prop changes, not when onUpload/onError callbacks get new
references. Prevents setTargetWorkspaceId from being overwritten by
a re-render before the file upload completes.

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

* fix: use Pick type for workspace dropdown in knowledge header

The shared Workspace type requires ownerId and other fields that aren't
available from the workspaces API response mapping. Use a Pick type to
accurately represent the subset of fields actually constructed.

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

* refactor: replace raw fetch with useWorkspacesQuery in knowledge header

Remove useState + useEffect + fetch anti-pattern for loading workspaces.
Use useWorkspacesQuery from React Query with inline filter for write/admin
permissions. Eliminates ~30 lines of manual state management, any casts,
and the Pick type workaround.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 15:54:21 -07:00
Waleed
4375f9921a fix(atlassian): unify error message extraction across all routes (#4135)
* fix(atlassian): unify error message extraction across all Jira, JSM, and Confluence routes

Add parseAtlassianErrorMessage() to jira/utils.ts as single source of truth for
parsing all 5 Atlassian error formats. Update 51 proxy routes (18 JSM, 5 Jira,
28 Confluence) to use it instead of hardcoded generic errors. Remove dead
errorExtractor field from 95 Atlassian tool files — the compat loop in
extractErrorMessage() already handles all formats without it. Consolidate
duplicate parseJsmErrorMessage into a re-export from the shared utility.

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

* fix: address PR review comments from Bugbot

- Remove debug logger.info for formAnswers in JSM request route
- Restore user-friendly spaceId error message in Confluence create-page route
- Restore details field in Jira write and update route error responses

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

* refactor: remove re-exports from jsm/utils and import directly from source

Remove re-exports of getJiraCloudId, parseAtlassianErrorMessage, and
parseJsmErrorMessage from jsm/utils.ts. Update all 21 JSM routes to
import directly from @/tools/jira/utils per CLAUDE.md import rules.

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

* regen docs

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 15:03:20 -07:00
Waleed
fb4fb9e869 feat(agiloft): add Agiloft CLM integration with token-based auth (#4133)
* feat(agiloft): add Agiloft CLM integration with token-based auth

Add 12 tools (CRUD, search, select, saved search, attachments, lock),
block, icon, docs, and internal API route for file attachments.
Uses EWLogin/EWLogout for short-lived Bearer tokens — credentials
are never embedded in API request URLs.

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

* fix(agiloft): address PR review feedback

- Add HTTPS enforcement guard to agiloftLogin to prevent plaintext credential transit
- Add null guard on data.output in attach_file transformResponse
- Change empty AgiloftSavedSearchParams interface to type alias

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

* fix(agiloft): add SSRF protection via DNS validation on instanceUrl

Validates user-supplied instanceUrl against private/reserved IP ranges
using validateUrlWithDNS before making any outbound requests. Uses dynamic
import to avoid bundling Node.js dns module in client-side code.

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

* fix(agiloft): fix SSRF protection to avoid client bundle breakage

Replace dynamic import of input-validation.server (which Turbopack traces
into the client bundle) with client-safe validateExternalUrl in utils.ts.
Add full DNS-level SSRF validation via validateUrlWithDNS in the attach
API route (server-only file). This matches the Okta pattern for
directExecution tools and the textract pattern for API routes.

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

* fix(agiloft): use DELETE method for EWRemoveAttachment endpoint

The remove_attachment tool was incorrectly using GET instead of DELETE
for the Agiloft EWRemoveAttachment endpoint, which would cause removals
to fail at runtime.

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

* fix(agiloft): correct HTTP methods and parameter names per Agiloft API docs

- EWRemoveAttachment uses GET, not DELETE (revert incorrect change)
- EWRetrieve uses `filePosition` parameter, not `position`
- EWAttach uses PUT, not POST

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:36:50 -07:00
Waleed
5ab85c6930 feat(workspaces): add recency-based workspace switching and redirect (#4131)
* feat(workspaces): add recency-based workspace switching and redirect

* fix(workspaces): skip prune when workspace list is empty on mount
2026-04-13 14:10:29 -07:00
Waleed
eba48e815f feat(logs): add cancel execution to log row context menu (#4130)
* feat(logs): add cancel execution to log row context menu

* lint

* fix(logs): check success response and use targeted cache invalidation
2026-04-13 12:05:37 -07:00
Waleed
cd7e413607 chore(skills): add code quality review skills and cleanup command (#4129)
* chore(skills): add code quality review skills and cleanup command

* chore(skills): fix emcn design review with verified codebase patterns
2026-04-13 11:33:12 -07:00
Waleed
cfe55914c9 fix(navbar): eliminate auth button flash using useSyncExternalStore (#4127)
* fix(navbar): eliminate auth button flash using useSyncExternalStore

* fix(navbar): add inert and fix aria-hidden on auth button containers
2026-04-13 11:05:27 -07:00
2632 changed files with 321407 additions and 70513 deletions

View File

@@ -0,0 +1,25 @@
---
name: cleanup
description: Run all code quality skills in sequence — effects, memo, callbacks, state, React Query, and emcn design review
---
# Cleanup
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Steps
Run each of these skills in order on the specified scope, passing through the scope and fix arguments. After each skill completes, move to the next. Do not skip any.
1. `/you-might-not-need-an-effect $ARGUMENTS`
2. `/you-might-not-need-a-memo $ARGUMENTS`
3. `/you-might-not-need-a-callback $ARGUMENTS`
4. `/you-might-not-need-state $ARGUMENTS`
5. `/react-query-best-practices $ARGUMENTS`
6. `/emcn-design-review $ARGUMENTS`
After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.

View File

@@ -0,0 +1,335 @@
---
name: emcn-design-review
description: Review UI code for alignment with the emcn design system — components, tokens, patterns, and conventions
---
# EMCN Design Review
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA (class-variance-authority) variants and CSS variable design tokens. All UI must use emcn components and tokens — never raw HTML elements or hardcoded colors.
## Steps
1. Read the emcn barrel export at `apps/sim/components/emcn/components/index.ts` to know what's available
2. Read `apps/sim/app/_styles/globals.css` for the full set of CSS variable tokens
3. Analyze the specified scope against every rule below
4. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
---
## Imports
- Import components from `@/components/emcn`, never from subpaths
- Import icons from `@/components/emcn/icons` or `lucide-react`
- Import `cn` from `@/lib/core/utils/cn` for conditional class merging
- Import app-specific wrappers (Select, VerifiedBadge) from `@/components/ui`
```tsx
// Good
import { Button, Modal, Badge } from '@/components/emcn'
// Bad
import { Button } from '@/components/emcn/components/button/button'
```
---
## Design Tokens (CSS Variables)
Never use raw color values. Always use CSS variable tokens via Tailwind arbitrary values: `text-[var(--text-primary)]`, not `text-gray-500` or `#333`. The CSS variable pattern is canonical (1,700+ uses) — do not use Tailwind semantic classes like `text-muted-foreground`.
### Text hierarchy
| Token | Use |
|-------|-----|
| `text-[var(--text-primary)]` | Main content text |
| `text-[var(--text-secondary)]` | Secondary/supporting text |
| `text-[var(--text-tertiary)]` | Tertiary text |
| `text-[var(--text-muted)]` | Disabled, placeholder text |
| `text-[var(--text-icon)]` | Icon tinting |
| `text-[var(--text-inverse)]` | Text on dark backgrounds |
| `text-[var(--text-error)]` | Error/warning messages |
### Surfaces (elevation)
| Token | Use |
|-------|-----|
| `bg-[var(--bg)]` | Page background |
| `bg-[var(--surface-2)]` through `bg-[var(--surface-7)]` | Increasing elevation |
| `bg-[var(--surface-hover)]` | Hover state backgrounds |
| `bg-[var(--surface-active)]` | Active/selected backgrounds |
### Borders
| Token | Use |
|-------|-----|
| `border-[var(--border)]` | Default borders |
| `border-[var(--border-1)]` | Stronger borders (inputs, cards) |
| `border-[var(--border-muted)]` | Subtle dividers |
### Status
| Token | Use |
|-------|-----|
| `--success` | Success states |
| `--error` | Error states |
| `--caution` | Warning states |
### Brand
| Token | Use |
|-------|-----|
| `--brand-secondary` | Brand color |
| `--brand-accent` | Accent/CTA color |
### Shadows
Use shadow tokens, never raw box-shadow values:
- `shadow-subtle`, `shadow-medium`, `shadow-overlay`
- `shadow-kbd`, `shadow-card`
### Z-Index
Use z-index tokens for layering:
- `z-[var(--z-dropdown)]` (100), `z-[var(--z-modal)]` (200), `z-[var(--z-popover)]` (300), `z-[var(--z-tooltip)]` (400), `z-[var(--z-toast)]` (500)
---
## Component Usage Rules
### Buttons
Available variants: `default`, `primary`, `destructive`, `ghost`, `outline`, `active`, `secondary`, `tertiary`, `subtle`, `ghost-secondary`, `3d`
| Action type | Variant | Frequency |
|-------------|---------|-----------|
| Toolbar, icon-only, utility actions | `ghost` | Most common (28%) |
| Primary action (create, save, submit) | `primary` | Very common (24%) |
| Cancel, close, secondary action | `default` | Common |
| Delete, remove, destructive action | `destructive` | Targeted use only |
| Active/selected state | `active` | Targeted use only |
| Toggle, mode switch | `outline` | Moderate |
Sizes: `sm` (compact, 32% of buttons) or `md` (default, used when no size specified). Never create custom button styles — use an existing variant.
Buttons without an explicit variant prop get `default` styling. This is acceptable for cancel/secondary actions.
### Modals (Dialogs)
Use `Modal` + subcomponents. Never build custom dialog overlays.
```tsx
<Modal open={open} onOpenChange={setOpen}>
<ModalContent size="sm">
<ModalHeader>Title</ModalHeader>
<ModalBody>Content</ModalBody>
<ModalFooter>
<Button variant="default" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary" onClick={handleSubmit}>Save</Button>
</ModalFooter>
</ModalContent>
</Modal>
```
Modal sizes by frequency: `sm` (440px, most common — confirmations and simple dialogs), `md` (500px, forms), `lg` (600px, content-heavy), `xl` (800px, rare), `full` (1200px, rare).
Footer buttons: Cancel on left (`variant="default"`), primary action on right. This pattern is followed 100% across the codebase.
### Delete/Remove Confirmations
Always use Modal with `size="sm"`. The established pattern:
```tsx
<Modal open={open} onOpenChange={setOpen}>
<ModalContent size="sm">
<ModalHeader>Delete {itemType}</ModalHeader>
<ModalBody>
<p>Description of consequences</p>
<p className="text-[var(--text-error)]">Warning about irreversibility</p>
</ModalBody>
<ModalFooter>
<Button variant="default" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="destructive" onClick={handleDelete} disabled={isDeleting}>
Delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
```
Rules:
- Title: "Delete {ItemType}" or "Remove {ItemType}" (use "Remove" for membership/association changes)
- Include consequence description
- Use `text-[var(--text-error)]` for warning text when the action is irreversible
- `variant="destructive"` for the action button (100% compliance)
- `variant="default"` for cancel (100% compliance)
- Cancel left, destructive right (100% compliance)
- For high-risk deletes (workspaces), require typing the name to confirm
- Include recovery info if soft-delete: "You can restore it from Recently Deleted in Settings"
### Toast Notifications
Use the imperative `toast` API from `@/components/emcn`. Never build custom notification UI.
```tsx
import { toast } from '@/components/emcn'
toast.success('Item saved')
toast.error('Something went wrong')
toast.success('Deleted', { action: { label: 'Undo', onClick: handleUndo } })
```
Variants: `default`, `success`, `error`. Auto-dismiss after 5s. Supports optional action buttons with callbacks.
### Badges
Use semantic color variants for status:
| Status | Variant | Usage |
|--------|---------|-------|
| Error, failed, disconnected | `red` | Most common (15 uses) |
| Metadata, roles, auth types, scopes | `gray-secondary` | Very common (12 uses) |
| Type annotations (TS types, field types) | `type` | Very common (12 uses) |
| Success, active, enabled, running | `green` | Common (7 uses) |
| Neutral, default, unknown | `gray` | Common (6 uses) |
| Outline, parameters, public | `outline` | Moderate (6 uses) |
| Warning, processing | `amber` | Moderate (5 uses) |
| Paused, warning | `orange` | Occasional |
| Info, queued | `blue` | Occasional |
| Data types (arrays) | `purple` | Occasional |
| Generic with border | `default` | Occasional |
Use `dot` prop for status indicators (19 instances in codebase). `icon` prop is available but rarely used.
### Tooltips
Use `Tooltip` from emcn with namespace pattern:
```tsx
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant="ghost">{icon}</Button>
</Tooltip.Trigger>
<Tooltip.Content>Helpful text</Tooltip.Content>
</Tooltip.Root>
```
Use tooltips for icon-only buttons and truncated text. Don't tooltip self-explanatory elements.
### Popovers
Use for filters, option menus, and nested navigation:
```tsx
<Popover open={open} onOpenChange={setOpen} size="sm">
<PopoverTrigger asChild>
<Button variant="ghost">Trigger</Button>
</PopoverTrigger>
<PopoverContent side="bottom" align="end" minWidth={160}>
<PopoverSection>Section Title</PopoverSection>
<PopoverItem active={isActive} onClick={handleClick}>
Item Label
</PopoverItem>
<PopoverDivider />
</PopoverContent>
</Popover>
```
### Dropdown Menus
Use for context menus and action menus:
```tsx
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<MoreHorizontal className="h-[14px] w-[14px]" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={handleEdit}>Edit</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleDelete} className="text-[var(--text-error)]">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
```
Destructive items go last, after a separator, in error color.
### Forms
Use `FormField` wrapper for labeled inputs:
```tsx
<FormField label="Name" htmlFor="name" error={errors.name} optional>
<Input id="name" value={name} onChange={e => setName(e.target.value)} />
</FormField>
```
Rules:
- Use `Input` from emcn, never raw `<input>` (exception: hidden file inputs)
- Use `Textarea` from emcn, never raw `<textarea>`
- Use `FormField` for label + input + error layout
- Mark optional fields with `optional` prop
- Show errors inline below the input
- Use `Combobox` for searchable selects
- Use `TagInput` for multi-value inputs
### Loading States
Use `Skeleton` for content placeholders:
```tsx
<Skeleton className="h-5 w-[200px] rounded-md" />
```
Rules:
- Mirror the actual UI structure with skeletons
- Match exact dimensions of the final content
- Use `rounded-md` to match component radius
- Stack multiple skeletons for lists
### Icons
Standard sizing — `h-[14px] w-[14px]` is the dominant pattern (400+ uses):
```tsx
<Icon className="h-[14px] w-[14px] text-[var(--text-icon)]" />
```
Size scale by frequency:
1. `h-[14px] w-[14px]` — default for inline icons (most common)
2. `h-[16px] w-[16px]` — slightly larger inline icons
3. `h-3 w-3` (12px) — compact/tight spaces
4. `h-4 w-4` (16px) — Tailwind equivalent, also common
5. `h-3.5 w-3.5` (14px) — Tailwind equivalent of 14px
6. `h-5 w-5` (20px) — larger icons, section headers
Use `text-[var(--text-icon)]` for icon color (113+ uses in codebase).
---
## Styling Rules
1. **Use `cn()` for conditional classes**: `cn('base', condition && 'conditional')` — never template literal concatenation like `` `base ${condition ? 'active' : ''}` ``
2. **Inline styles**: Avoid. Exception: dynamic values that can't be expressed as Tailwind classes (e.g., `style={{ width: dynamicVar }}` or CSS variable references). Never use inline styles for colors or static values.
3. **Never hardcode colors**: Use CSS variable tokens. Never `text-gray-500`, `bg-red-100`, `#fff`, or `rgb()`. Always `text-[var(--text-*)]`, `bg-[var(--surface-*)]`, etc.
4. **Never use Tailwind semantic color classes**: Use `text-[var(--text-muted)]` not `text-muted-foreground`. The CSS variable pattern is canonical.
5. **Never use global styles**: Keep all styling local to components
6. **Hover states**: Use `hover-hover:` pseudo-class for hover-capable devices
7. **Transitions**: Use `transition-colors` for color changes, `transition-colors duration-100` for fast hover
8. **Border radius**: `rounded-lg` (large cards), `rounded-md` (medium), `rounded-sm` (small), `rounded-xs` (tiny)
9. **Typography**: Use semantic sizes — `text-small` (13px), `text-caption` (12px), `text-xs` (11px), `text-micro` (10px)
10. **Font weight**: Use `font-medium` for emphasis, avoid `font-bold` unless for headings
11. **Spacing**: Use Tailwind gap/padding utilities. Common patterns: `gap-2`, `gap-3`, `px-4 py-2.5`
---
## Anti-patterns to flag
- Raw HTML `<button>` instead of Button component (exception: inside Radix primitives)
- Raw HTML `<input>` instead of Input component (exception: hidden file inputs, read-only checkboxes in markdown)
- Hardcoded Tailwind default colors (`text-gray-*`, `bg-red-*`, `text-blue-*`)
- Hex values in className (`bg-[#fff]`, `text-[#333]`)
- Tailwind semantic classes (`text-muted-foreground`) instead of CSS variables (`text-[var(--text-muted)]`)
- Custom modal/dialog implementations instead of `Modal`
- Custom toast/notification implementations instead of `toast`
- Inline styles for colors or static values (dynamic values are acceptable)
- Template literal className concatenation instead of `cn()`
- Wrong button variant for the action type
- Missing loading/skeleton states
- Missing error states on forms
- Importing from emcn subpaths instead of barrel export
- Using arbitrary z-index (`z-50`, `z-[9999]`) instead of z-index tokens
- Custom shadows instead of shadow tokens
- Icon sizes that don't follow the established scale (default to `h-[14px] w-[14px]`)

View File

@@ -0,0 +1,54 @@
---
name: react-query-best-practices
description: Audit React Query usage for best practices — key factories, staleTime, mutations, and server state ownership
---
# React Query Best Practices
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/hooks/queries/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query (TanStack Query) as the single source of truth for all server state. All query hooks live in `hooks/queries/`. Zustand is used only for client-only UI state. Server data must never be duplicated into useState or Zustand outside of mutation callbacks that coordinate cross-store state.
## References
Read these before analyzing:
1. https://tkdodo.eu/blog/practical-react-query — foundational defaults, custom hooks, avoiding local state copies
2. https://tkdodo.eu/blog/effective-react-query-keys — key factory pattern, hierarchical keys, fuzzy invalidation
3. https://tkdodo.eu/blog/react-query-as-a-state-manager — React Query IS your server state manager
## Rules to enforce
### Query key factories
- Every file in `hooks/queries/` must have a hierarchical key factory with an `all` root key
- Keys must include intermediate plural keys (`lists`, `details`) for prefix invalidation
- Key factories are colocated with their query hooks, not in a global keys file
### Query hooks
- Every `queryFn` must forward `signal` for request cancellation
- Every query must have an explicit `staleTime` (default 0 is almost never correct)
- `keepPreviousData` / `placeholderData` only on variable-key queries (where params change), never on static keys
- Use `enabled` to prevent queries from running without required params
### Mutations
- Use `onSettled` (not `onSuccess`) for cache reconciliation — it fires on both success and error
- For optimistic updates: save previous data in `onMutate`, roll back in `onError`
- Use targeted invalidation (`entityKeys.lists()`) not broad (`entityKeys.all`) when possible
- Don't include mutation objects in `useCallback` deps — `.mutate()` is stable
### Server state ownership
- Never copy query data into useState. Use query data directly in components.
- Never copy query data into Zustand stores (exception: mutation callbacks that coordinate cross-store state like temp ID replacement)
- The query cache is not a local state manager — `setQueryData` is for optimistic updates only
- Forms are the one deliberate exception: copy server data into local form state with `staleTime: Infinity`
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope against the rules listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,51 @@
---
name: you-might-not-need-a-callback
description: Analyze and fix useCallback anti-patterns in your code
---
# You Might Not Need a Callback
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://react.dev/reference/react/useCallback — official docs on when useCallback is actually needed
## When useCallback IS needed
- Passing a callback to a child wrapped in `React.memo` (to preserve referential equality)
- The callback is a dependency of another hook (`useEffect`, `useMemo`)
- The callback is used in a custom hook that documents referential stability requirements
## Anti-patterns to detect
1. **useCallback on functions not passed as props or deps**: If the function is only called within the same component and isn't in any dependency array, useCallback adds overhead for no benefit. Just declare the function normally.
2. **useCallback with exhaustive deps that change every render**: If the dependency array includes values that change on every render, useCallback recalculates every time. The memoization is wasted. Either stabilize the deps (use refs) or remove the useCallback.
3. **useCallback on event handlers passed to native elements**: `<button onClick={handleClick}>` — native elements don't benefit from stable references. Only child components wrapped in React.memo do.
4. **useCallback wrapping a function that creates new objects/arrays**: If the callback returns `{ ...newObj }` or `[...newArr]`, memoizing the callback doesn't prevent the child from re-rendering due to new return values. The memoization is at the wrong level.
5. **useCallback with an empty dep array when deps are needed**: Stale closures — the callback captures outdated values. Either add proper deps or use refs for values that shouldn't trigger re-creation.
6. **Pairing useCallback with React.memo unnecessarily**: If the child component is cheap to render, neither useCallback nor React.memo adds value. Only optimize when you've measured a performance problem.
7. **useCallback in custom hooks that don't need stable references**: Not every hook return needs to be memoized. Only stabilize callbacks when consumers depend on referential equality.
## Codebase-specific notes
This codebase uses a ref pattern for stable callbacks in hooks:
```tsx
const idRef = useRef(id)
useEffect(() => { idRef.current = id }, [id])
const fetchData = useCallback(async () => {
// use idRef.current instead of id
}, []) // empty deps because refs are used
```
This pattern is correct — don't flag it as an anti-pattern.
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,33 @@
---
name: you-might-not-need-a-memo
description: Analyze and fix useMemo/React.memo anti-patterns in your code
---
# You Might Not Need a Memo
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://overreacted.io/before-you-memo/ — two techniques to avoid memo entirely
## Anti-patterns to detect
1. **Wrapping a slow component in React.memo when state can be moved down**: If a component re-renders because of state it doesn't use, move that state into a smaller child component instead of memoizing. The slow component stops re-rendering without memo.
2. **Wrapping in React.memo when children can be lifted up**: If a parent owns state that changes frequently, extract the stateful part and pass the expensive subtree as `children`. Children passed as props don't re-render when the parent's state changes.
3. **useMemo on cheap computations**: Filtering or mapping a small array, string concatenation, simple arithmetic — these don't need memoization. Only memoize when you've measured a performance problem.
4. **useMemo with constantly-changing deps**: If the dependency array changes on every render, useMemo does nothing — it recalculates every time. Fix the deps or remove the memo.
5. **useMemo to create objects/arrays passed as props**: Instead of memoizing to prevent child re-renders, consider whether the child even needs referential stability. If the child doesn't use React.memo or pass it to a dep array, the memo is wasted.
6. **React.memo on components that always receive new props**: If the parent always passes new objects, arrays, or callbacks, React.memo's shallow comparison always fails. Fix the parent instead of memoizing the child.
7. **useMemo for derived state**: If you're computing a value from props or state, just compute it inline during render. React renders are fast. `const fullName = first + ' ' + last` doesn't need useMemo.
## Steps
1. Read the reference above to understand the two core techniques (move state down, lift content up)
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,38 @@
---
name: you-might-not-need-state
description: Analyze and fix unnecessary useState, derived state, and server-state-in-local-state anti-patterns
---
# You Might Not Need State
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query for all server state and Zustand for client-only global state. useState should only be used for ephemeral UI concerns (open/closed, hover, local form input). Server data should never be copied into useState or Zustand — React Query is the single source of truth.
## References
Read these before analyzing:
1. https://react.dev/learn/choosing-the-state-structure — 5 principles for structuring state
2. https://tkdodo.eu/blog/dont-over-use-state — never store derived/computed values in state
3. https://tkdodo.eu/blog/putting-props-to-use-state — never mirror props into state via useEffect
## Anti-patterns to detect
1. **Derived state stored in useState**: If a value can be computed from props, other state, or query data, compute it inline during render instead of storing it in state.
2. **Server state copied into useState**: Never `useState` + `useEffect` to sync React Query data into local state. Use query data directly. The only exception is forms where users edit server data.
3. **Props mirrored into state**: Never `useState(prop)` + `useEffect(() => setState(prop))`. Use the prop directly, or use a key to reset component state.
4. **Chained useEffect state updates**: Never chain Effects that set state to trigger other Effects. Calculate all derived values in the event handler or inline during render.
5. **Storing objects when an ID suffices**: Store `selectedId` not a copy of the selected object. Derive the object: `items.find(i => i.id === selectedId)`.
6. **State that duplicates Zustand or React Query**: If the data already lives in a store or query cache, don't create a parallel useState.
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,25 @@
---
description: Run all code quality skills in sequence — effects, memo, callbacks, state, React Query, and emcn design review
argument-hint: [scope] [fix=true|false]
---
# Cleanup
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Steps
Run each of these skills in order on the specified scope, passing through the scope and fix arguments. After each skill completes, move to the next. Do not skip any.
1. `/you-might-not-need-an-effect $ARGUMENTS`
2. `/you-might-not-need-a-memo $ARGUMENTS`
3. `/you-might-not-need-a-callback $ARGUMENTS`
4. `/you-might-not-need-state $ARGUMENTS`
5. `/react-query-best-practices $ARGUMENTS`
6. `/emcn-design-review $ARGUMENTS`
After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.

View File

@@ -0,0 +1,79 @@
---
description: Review UI code for alignment with the emcn design system — components, tokens, patterns, and conventions
argument-hint: [scope] [fix=true|false]
---
# EMCN Design Review
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA variants and CSS variable design tokens. All UI must use emcn components and tokens.
## Steps
1. Read the emcn barrel export at `apps/sim/components/emcn/components/index.ts` to know what's available
2. Read `apps/sim/app/_styles/globals.css` for CSS variable tokens
3. Analyze the specified scope against every rule below
4. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
---
## Imports
- Import from `@/components/emcn` barrel, never subpaths
- Icons from `@/components/emcn/icons` or `lucide-react`
- Use `cn` from `@/lib/core/utils/cn` for conditional classes
## Design Tokens
Use CSS variable pattern (`text-[var(--text-primary)]`), never Tailwind semantics (`text-muted-foreground`) or hardcoded colors (`text-gray-500`, `#333`).
**Text**: `--text-primary`, `--text-secondary`, `--text-tertiary`, `--text-muted`, `--text-icon`, `--text-inverse`, `--text-error`
**Surfaces**: `--bg`, `--surface-2` through `--surface-7`, `--surface-hover`, `--surface-active`
**Borders**: `--border`, `--border-1`, `--border-muted`
**Z-Index**: `--z-dropdown` (100), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-toast` (500)
**Shadows**: `shadow-subtle`, `shadow-medium`, `shadow-overlay`, `shadow-card`
## Buttons
| Action | Variant |
|--------|---------|
| Toolbar, icon-only | `ghost` (most common, 28%) |
| Create, save, submit | `primary` (24%) |
| Cancel, close | `default` |
| Delete, remove | `destructive` |
| Selected state | `active` |
| Toggle | `outline` |
## Delete/Remove Confirmations
Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` action button, `variant="default"` cancel. Cancel left, action right (100% compliance). Use `text-[var(--text-error)]` for irreversible warnings.
## Toast
`toast.success()`, `toast.error()`, `toast()` from `@/components/emcn`. Never custom notification UI.
## Badges
`red`=error/failed, `gray-secondary`=metadata/roles, `type`=type annotations, `green`=success/active, `gray`=neutral, `amber`=processing, `orange`=paused, `blue`=info. Use `dot` prop for status indicators.
## Icons
Default: `h-[14px] w-[14px]` (400+ uses). Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px.
## Anti-patterns to flag
- Raw `<button>`/`<input>` instead of emcn components
- Hardcoded colors (`text-gray-*`, `#hex`, `rgb()`)
- Tailwind semantics (`text-muted-foreground`) instead of CSS variables
- Template literal className instead of `cn()`
- Inline styles for colors/static values (dynamic values OK)
- Importing from emcn subpaths instead of barrel
- Arbitrary z-index instead of tokens
- Wrong button variant for action type

View File

@@ -0,0 +1,54 @@
---
description: Audit React Query usage for best practices — key factories, staleTime, mutations, and server state ownership
argument-hint: [scope] [fix=true|false]
---
# React Query Best Practices
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/hooks/queries/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query (TanStack Query) as the single source of truth for all server state. All query hooks live in `hooks/queries/`. Zustand is used only for client-only UI state. Server data must never be duplicated into useState or Zustand outside of mutation callbacks that coordinate cross-store state.
## References
Read these before analyzing:
1. https://tkdodo.eu/blog/practical-react-query — foundational defaults, custom hooks, avoiding local state copies
2. https://tkdodo.eu/blog/effective-react-query-keys — key factory pattern, hierarchical keys, fuzzy invalidation
3. https://tkdodo.eu/blog/react-query-as-a-state-manager — React Query IS your server state manager
## Rules to enforce
### Query key factories
- Every file in `hooks/queries/` must have a hierarchical key factory with an `all` root key
- Keys must include intermediate plural keys (`lists`, `details`) for prefix invalidation
- Key factories are colocated with their query hooks, not in a global keys file
### Query hooks
- Every `queryFn` must forward `signal` for request cancellation
- Every query must have an explicit `staleTime` (default 0 is almost never correct)
- `keepPreviousData` / `placeholderData` only on variable-key queries (where params change), never on static keys
- Use `enabled` to prevent queries from running without required params
### Mutations
- Use `onSettled` (not `onSuccess`) for cache reconciliation — it fires on both success and error
- For optimistic updates: save previous data in `onMutate`, roll back in `onError`
- Use targeted invalidation (`entityKeys.lists()`) not broad (`entityKeys.all`) when possible
- Don't include mutation objects in `useCallback` deps — `.mutate()` is stable
### Server state ownership
- Never copy query data into useState. Use query data directly in components.
- Never copy query data into Zustand stores (exception: mutation callbacks that coordinate cross-store state like temp ID replacement)
- The query cache is not a local state manager — `setQueryData` is for optimistic updates only
- Forms are the one deliberate exception: copy server data into local form state with `staleTime: Infinity`
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope against the rules listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,52 @@
---
description: Analyze and fix useCallback anti-patterns in your code
argument-hint: [scope] [fix=true|false]
---
# You Might Not Need a Callback
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://react.dev/reference/react/useCallback — official docs on when useCallback is actually needed
## The one rule that matters
`useCallback` is only useful when **something observes the reference**. Ask: does anything care if this function gets a new identity on re-render?
Observers that care about reference stability:
- A `useEffect` that lists the function in its deps array
- A `useMemo` that lists the function in its deps array
- Another `useCallback` that lists the function in its deps array
- A child component wrapped in `React.memo` that receives the function as a prop
If none of those apply — if the function is only called inline, or passed to a non-memoized child, or assigned to a native element event — the reference is unobserved and `useCallback` adds overhead with zero benefit.
## Anti-patterns to detect
1. **No observer tracks the reference**: The function is only called inline in the same component, or passed to a non-memoized child, or used as a native element handler (`<button onClick={fn}>`). Nothing re-runs or bails out based on reference identity. Remove `useCallback`.
2. **useCallback with deps that change every render**: If a dep is a plain object/array created inline, or state that changes on every interaction, memoization buys nothing — the function gets a new identity anyway.
3. **useCallback on handlers passed only to native elements**: `<button onClick={fn}>` — React never does reference equality on native element props. No benefit.
4. **useCallback wrapping functions that return new objects/arrays**: Stable function identity, unstable return value — memoization is at the wrong level. Use `useMemo` on the return value instead, or restructure.
5. **useCallback with empty deps when deps are needed**: Stale closure — reads initial values forever. This is a correctness bug, not just a performance issue.
6. **Pairing useCallback + React.memo on trivially cheap renders**: If the child renders in < 1ms and re-renders rarely, the memo infrastructure costs more than it saves.
## Patterns that ARE correct — do not flag
- `useCallback` whose result is in a `useEffect` dep array — prevents the effect from re-running on every render
- `useCallback` whose result is in a `useMemo` dep array — prevents the memo from recomputing on every render
- `useCallback` whose result is a dep of another `useCallback` — stabilises a callback chain
- `useCallback` passed to a `React.memo`-wrapped child — the whole point of the pattern
- This codebase's ref pattern: `useRef` + callback with empty deps that reads the ref inside — correct, do not flag
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,33 @@
---
description: Analyze and fix useMemo/React.memo anti-patterns in your code
argument-hint: [scope] [fix=true|false]
---
# You Might Not Need a Memo
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://overreacted.io/before-you-memo/ — two techniques to avoid memo entirely
## Anti-patterns to detect
1. **State can be moved down instead of memoizing**: Move state into a smaller child so the slow component stops re-rendering without memo.
2. **Children can be lifted up**: Extract stateful part, pass expensive subtree as `children` — children as props don't re-render when parent state changes.
3. **useMemo on cheap computations**: Small array filters, string concat, arithmetic don't need memoization.
4. **useMemo with constantly-changing deps**: Deps change every render = useMemo does nothing.
5. **useMemo to stabilize props for non-memoized children**: If the child isn't wrapped in React.memo, stable references don't matter.
6. **React.memo on components that always receive new props**: Fix the parent instead.
7. **useMemo for derived state**: Just compute inline during render.
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,38 @@
---
description: Analyze and fix unnecessary useState, derived state, and server-state-in-local-state anti-patterns
argument-hint: [scope] [fix=true|false]
---
# You Might Not Need State
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query for all server state and Zustand for client-only global state. useState should only be used for ephemeral UI concerns (open/closed, hover, local form input). Server data should never be copied into useState or Zustand — React Query is the single source of truth.
## References
Read these before analyzing:
1. https://react.dev/learn/choosing-the-state-structure — 5 principles for structuring state
2. https://tkdodo.eu/blog/dont-over-use-state — never store derived/computed values in state
3. https://tkdodo.eu/blog/putting-props-to-use-state — never mirror props into state via useEffect
## Anti-patterns to detect
1. **Derived state stored in useState**: If a value can be computed from props, other state, or query data, compute it inline during render instead of storing it in state.
2. **Server state copied into useState**: Never `useState` + `useEffect` to sync React Query data into local state. Use query data directly. The only exception is forms where users edit server data.
3. **Props mirrored into state**: Never `useState(prop)` + `useEffect(() => setState(prop))`. Use the prop directly, or use a key to reset component state.
4. **Chained useEffect state updates**: Never chain Effects that set state to trigger other Effects. Calculate all derived values in the event handler or inline during render.
5. **Storing objects when an ID suffices**: Store `selectedId` not a copy of the selected object. Derive the object: `items.find(i => i.id === selectedId)`.
6. **State that duplicates Zustand or React Query**: If the data already lives in a store or query cache, don't create a parallel useState.
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -1,7 +1,10 @@
# Global Standards
## Logging
Import `createLogger` from `sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`.
Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`. Inside API routes wrapped with `withRouteHandler`, loggers automatically include the request ID.
## API Route Handlers
All API route handlers must be wrapped with `withRouteHandler` from `@/lib/core/utils/with-route-handler`. Never export a bare `async function GET/POST/...` — always use `export const METHOD = withRouteHandler(...)`.
## Comments
Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
@@ -10,7 +13,7 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
Never update global styles. Keep all styling local to components.
## ID Generation
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@sim/utils/id`:
- `generateId()` — UUID v4, use by default
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers
@@ -24,11 +27,32 @@ import { v4 as uuidv4 } from 'uuid'
const id = crypto.randomUUID()
// ✓ Good
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
import { generateId, generateShortId } from '@sim/utils/id'
const uuid = generateId()
const shortId = generateShortId()
const tiny = generateShortId(8)
```
## Common Utilities
Use shared helpers from `@sim/utils` instead of writing inline implementations:
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
```typescript
// ✗ Bad
await new Promise(resolve => setTimeout(resolve, 1000))
const msg = error instanceof Error ? error.message : String(error)
const err = error instanceof Error ? error : new Error(String(error))
// ✓ Good
import { sleep } from '@sim/utils/helpers'
import { toError } from '@sim/utils/errors'
await sleep(1000)
const msg = toError(error).message
const err = toError(error)
```
## Package Manager
Use `bun` and `bunx`, not `npm` and `npx`.

View File

@@ -13,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`
These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior:
- `@sim/db``databaseMock`
- `@sim/db/schema``schemaMock`
- `drizzle-orm``drizzleOrmMock`
- `@sim/logger``loggerMock`
- `@/lib/auth``authMock`
- `@/lib/auth/hybrid``hybridAuthMock` (with default session-delegating behavior)
- `@/lib/core/utils/request``requestUtilsMock`
- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store`
- `@/blocks/registry`
- `@trigger.dev/sdk`
@@ -102,10 +106,6 @@ vi.mock('@/lib/workspaces/utils', () => ({
}))
```
### NEVER use `mockAuth()`, `mockConsoleLogger()`, or `setupCommonApiMocks()` from `@sim/testing`
These helpers internally use `vi.doMock()` which is slow. Use direct `vi.hoisted()` + `vi.mock()` instead.
### Mock heavy transitive dependencies
If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them:
@@ -135,83 +135,129 @@ await new Promise(r => setTimeout(r, 1))
vi.useFakeTimers()
```
## Mock Pattern Reference
## Centralized Mocks (prefer over local declarations)
`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides.
| Module mocked | Import | Factory form |
|---|---|---|
| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` |
| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` |
| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` |
| `@sim/audit` | `auditMock`, `auditMockFns` | `vi.mock('@sim/audit', () => auditMock)` |
| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` |
| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` |
| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` |
| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` |
| `@/lib/core/config/feature-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock)` |
| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` |
| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` |
| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` |
| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` |
| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` |
| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` |
| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` |
| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` |
| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` |
| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` |
| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` |
| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` |
### Auth mocking (API routes)
```typescript
const { mockGetSession } = vi.hoisted(() => ({
mockGetSession: vi.fn(),
}))
import { authMock, authMockFns } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('@/lib/auth', () => ({
auth: { api: { getSession: vi.fn() } },
getSession: mockGetSession,
}))
vi.mock('@/lib/auth', () => authMock)
// In tests:
mockGetSession.mockResolvedValue({ user: { id: 'user-1', email: 'test@example.com' } })
mockGetSession.mockResolvedValue(null) // unauthenticated
import { GET } from '@/app/api/my-route/route'
beforeEach(() => {
vi.clearAllMocks()
authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
})
```
Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`).
### Hybrid auth mocking
```typescript
const { mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({
mockCheckSessionOrInternalAuth: vi.fn(),
}))
import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing'
vi.mock('@/lib/auth/hybrid', () => ({
checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth,
}))
vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)
// In tests:
mockCheckSessionOrInternalAuth.mockResolvedValue({
hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({
success: true, userId: 'user-1', authType: 'session',
})
```
### Database chain mocking
```typescript
const { mockSelect, mockFrom, mockWhere } = vi.hoisted(() => ({
mockSelect: vi.fn(),
mockFrom: vi.fn(),
mockWhere: vi.fn(),
}))
Use the centralized `dbChainMock` + `dbChainMockFns` helpers — no `vi.hoisted()` or chain-wiring boilerplate needed.
vi.mock('@sim/db', () => ({
db: { select: mockSelect },
}))
```typescript
import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing'
vi.mock('@sim/db', () => dbChainMock)
// Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} }))
beforeEach(() => {
mockSelect.mockReturnValue({ from: mockFrom })
mockFrom.mockReturnValue({ where: mockWhere })
mockWhere.mockResolvedValue([{ id: '1', name: 'test' }])
vi.clearAllMocks()
resetDbChainMock() // only needed if tests use permanent (non-`Once`) overrides
})
it('reads a row', async () => {
dbChainMockFns.limit.mockResolvedValueOnce([{ id: '1', name: 'test' }])
// exercise code that hits db.select().from().where().limit()
expect(dbChainMockFns.where).toHaveBeenCalled()
})
```
**Default chains supported:**
- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()/for()`
- `insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing()`
- `update() → set() → where() → limit()/orderBy()/returning()/for()`
- `delete() → where() → limit()/orderBy()/returning()/for()`
- `db.execute()` resolves `[]`
- `db.transaction(cb)` calls cb with `dbChainMock.db`
`.for('update')` (Postgres row-level locking) is supported on `where`
builders. It returns a thenable with `.limit` / `.orderBy` / `.returning` /
`.groupBy` attached, so both `await .where().for('update')` (terminal) and
`await .where().for('update').limit(1)` (chained) work. Override the terminal
result with `dbChainMockFns.for.mockResolvedValueOnce([...])`; for the chained
form, mock the downstream terminal (e.g. `dbChainMockFns.limit.mockResolvedValueOnce([...])`).
All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns.<terminal>.mockResolvedValueOnce(...)`.
Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent). Tests using only `...Once` variants don't need it.
## @sim/testing Package
Always prefer over local test data.
| Category | Utilities |
|----------|-----------|
| **Mocks** | `loggerMock`, `databaseMock`, `drizzleOrmMock`, `setupGlobalFetchMock()` |
| **Module mocks** | See "Centralized Mocks" table above |
| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` |
| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` |
| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` |
| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` |
| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` |
| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` |
| **Requests** | `createMockRequest()`, `createEnvMock()` |
| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` |
## Rules Summary
1. `@vitest-environment node` unless DOM is required
2. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports
3. `vi.mock()` calls before importing mocked modules
4. `@sim/testing` utilities over local mocks
2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate
3. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports
4. `vi.mock()` calls before importing mocked modules
5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach`
6. No `vi.importActual()` — mock everything explicitly
7. No `mockAuth()`, `mockConsoleLogger()`, `setupCommonApiMocks()` — use direct mocks
8. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them
9. Use absolute imports in test files
10. Avoid real timers — use 1ms delays or `vi.useFakeTimers()`
7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them
8. Use absolute imports in test files
9. Avoid real timers — use 1ms delays or `vi.useFakeTimers()`

View File

@@ -0,0 +1,20 @@
# Cleanup
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Steps
Run each of these skills in order on the specified scope, passing through the scope and fix arguments. After each skill completes, move to the next. Do not skip any.
1. `/you-might-not-need-an-effect $ARGUMENTS`
2. `/you-might-not-need-a-memo $ARGUMENTS`
3. `/you-might-not-need-a-callback $ARGUMENTS`
4. `/you-might-not-need-state $ARGUMENTS`
5. `/react-query-best-practices $ARGUMENTS`
6. `/emcn-design-review $ARGUMENTS`
After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.

View File

@@ -0,0 +1,74 @@
# EMCN Design Review
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA variants and CSS variable design tokens. All UI must use emcn components and tokens.
## Steps
1. Read the emcn barrel export at `apps/sim/components/emcn/components/index.ts` to know what's available
2. Read `apps/sim/app/_styles/globals.css` for CSS variable tokens
3. Analyze the specified scope against every rule below
4. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
---
## Imports
- Import from `@/components/emcn` barrel, never subpaths
- Icons from `@/components/emcn/icons` or `lucide-react`
- Use `cn` from `@/lib/core/utils/cn` for conditional classes
## Design Tokens
Use CSS variable pattern (`text-[var(--text-primary)]`), never Tailwind semantics (`text-muted-foreground`) or hardcoded colors (`text-gray-500`, `#333`).
**Text**: `--text-primary`, `--text-secondary`, `--text-tertiary`, `--text-muted`, `--text-icon`, `--text-inverse`, `--text-error`
**Surfaces**: `--bg`, `--surface-2` through `--surface-7`, `--surface-hover`, `--surface-active`
**Borders**: `--border`, `--border-1`, `--border-muted`
**Z-Index**: `--z-dropdown` (100), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-toast` (500)
**Shadows**: `shadow-subtle`, `shadow-medium`, `shadow-overlay`, `shadow-card`
## Buttons
| Action | Variant |
|--------|---------|
| Toolbar, icon-only | `ghost` (most common, 28%) |
| Create, save, submit | `primary` (24%) |
| Cancel, close | `default` |
| Delete, remove | `destructive` |
| Selected state | `active` |
| Toggle | `outline` |
## Delete/Remove Confirmations
Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` action button, `variant="default"` cancel. Cancel left, action right (100% compliance). Use `text-[var(--text-error)]` for irreversible warnings.
## Toast
`toast.success()`, `toast.error()`, `toast()` from `@/components/emcn`. Never custom notification UI.
## Badges
`red`=error/failed, `gray-secondary`=metadata/roles, `type`=type annotations, `green`=success/active, `gray`=neutral, `amber`=processing, `orange`=paused, `blue`=info. Use `dot` prop for status indicators.
## Icons
Default: `h-[14px] w-[14px]` (400+ uses). Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px.
## Anti-patterns to flag
- Raw `<button>`/`<input>` instead of emcn components
- Hardcoded colors (`text-gray-*`, `#hex`, `rgb()`)
- Tailwind semantics (`text-muted-foreground`) instead of CSS variables
- Template literal className instead of `cn()`
- Inline styles for colors/static values (dynamic values OK)
- Importing from emcn subpaths instead of barrel
- Arbitrary z-index instead of tokens
- Wrong button variant for action type

View File

@@ -0,0 +1,49 @@
# React Query Best Practices
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/hooks/queries/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query (TanStack Query) as the single source of truth for all server state. All query hooks live in `hooks/queries/`. Zustand is used only for client-only UI state. Server data must never be duplicated into useState or Zustand outside of mutation callbacks that coordinate cross-store state.
## References
Read these before analyzing:
1. https://tkdodo.eu/blog/practical-react-query — foundational defaults, custom hooks, avoiding local state copies
2. https://tkdodo.eu/blog/effective-react-query-keys — key factory pattern, hierarchical keys, fuzzy invalidation
3. https://tkdodo.eu/blog/react-query-as-a-state-manager — React Query IS your server state manager
## Rules to enforce
### Query key factories
- Every file in `hooks/queries/` must have a hierarchical key factory with an `all` root key
- Keys must include intermediate plural keys (`lists`, `details`) for prefix invalidation
- Key factories are colocated with their query hooks, not in a global keys file
### Query hooks
- Every `queryFn` must forward `signal` for request cancellation
- Every query must have an explicit `staleTime` (default 0 is almost never correct)
- `keepPreviousData` / `placeholderData` only on variable-key queries (where params change), never on static keys
- Use `enabled` to prevent queries from running without required params
### Mutations
- Use `onSettled` (not `onSuccess`) for cache reconciliation — it fires on both success and error
- For optimistic updates: save previous data in `onMutate`, roll back in `onError`
- Use targeted invalidation (`entityKeys.lists()`) not broad (`entityKeys.all`) when possible
- Don't include mutation objects in `useCallback` deps — `.mutate()` is stable
### Server state ownership
- Never copy query data into useState. Use query data directly in components.
- Never copy query data into Zustand stores (exception: mutation callbacks that coordinate cross-store state like temp ID replacement)
- The query cache is not a local state manager — `setQueryData` is for optimistic updates only
- Forms are the one deliberate exception: copy server data into local form state with `staleTime: Infinity`
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope against the rules listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,30 @@
# You Might Not Need a Callback
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://react.dev/reference/react/useCallback — official docs on when useCallback is actually needed
## Anti-patterns to detect
1. **useCallback on functions not passed as props or deps**: No benefit if only called within the same component.
2. **useCallback with deps that change every render**: Memoization is wasted.
3. **useCallback on handlers passed to native elements**: `<button onClick={fn}>` doesn't benefit from stable references.
4. **useCallback wrapping functions that return new objects/arrays**: Memoization at the wrong level.
5. **useCallback with empty deps when deps are needed**: Stale closures.
6. **Pairing useCallback + React.memo unnecessarily**: Only optimize when you've measured a problem.
7. **useCallback in hooks that don't need stable references**: Not every hook return needs memoization.
Note: This codebase uses a ref pattern for stable callbacks (`useRef` + empty deps). That pattern is correct — don't flag it.
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,28 @@
# You Might Not Need a Memo
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://overreacted.io/before-you-memo/ — two techniques to avoid memo entirely
## Anti-patterns to detect
1. **State can be moved down instead of memoizing**: Move state into a smaller child so the slow component stops re-rendering without memo.
2. **Children can be lifted up**: Extract stateful part, pass expensive subtree as `children` — children as props don't re-render when parent state changes.
3. **useMemo on cheap computations**: Small array filters, string concat, arithmetic don't need memoization.
4. **useMemo with constantly-changing deps**: Deps change every render = useMemo does nothing.
5. **useMemo to stabilize props for non-memoized children**: If the child isn't wrapped in React.memo, stable references don't matter.
6. **React.memo on components that always receive new props**: Fix the parent instead.
7. **useMemo for derived state**: Just compute inline during render.
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -0,0 +1,33 @@
# You Might Not Need State
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query for all server state and Zustand for client-only global state. useState should only be used for ephemeral UI concerns (open/closed, hover, local form input). Server data should never be copied into useState or Zustand — React Query is the single source of truth.
## References
Read these before analyzing:
1. https://react.dev/learn/choosing-the-state-structure — 5 principles for structuring state
2. https://tkdodo.eu/blog/dont-over-use-state — never store derived/computed values in state
3. https://tkdodo.eu/blog/putting-props-to-use-state — never mirror props into state via useEffect
## Anti-patterns to detect
1. **Derived state stored in useState**: If a value can be computed from props, other state, or query data, compute it inline during render instead of storing it in state.
2. **Server state copied into useState**: Never `useState` + `useEffect` to sync React Query data into local state. Use query data directly. The only exception is forms where users edit server data.
3. **Props mirrored into state**: Never `useState(prop)` + `useEffect(() => setState(prop))`. Use the prop directly, or use a key to reset component state.
4. **Chained useEffect state updates**: Never chain Effects that set state to trigger other Effects. Calculate all derived values in the event handler or inline during render.
5. **Storing objects when an ID suffices**: Store `selectedId` not a copy of the selected object. Derive the object: `items.find(i => i.id === selectedId)`.
6. **State that duplicates Zustand or React Query**: If the data already lives in a store or query cache, don't create a parallel useState.
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.

View File

@@ -17,7 +17,7 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
Never update global styles. Keep all styling local to components.
## ID Generation
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@sim/utils/id`:
- `generateId()` — UUID v4, use by default
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers
@@ -31,11 +31,32 @@ import { v4 as uuidv4 } from 'uuid'
const id = crypto.randomUUID()
// ✓ Good
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
import { generateId, generateShortId } from '@sim/utils/id'
const uuid = generateId()
const shortId = generateShortId()
const tiny = generateShortId(8)
```
## Common Utilities
Use shared helpers from `@sim/utils` instead of writing inline implementations:
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
```typescript
// ✗ Bad
await new Promise(resolve => setTimeout(resolve, 1000))
const msg = error instanceof Error ? error.message : String(error)
const err = error instanceof Error ? error : new Error(String(error))
// ✓ Good
import { sleep } from '@sim/utils/helpers'
import { toError } from '@sim/utils/errors'
await sleep(1000)
const msg = toError(error).message
const err = toError(error)
```
## Package Manager
Use `bun` and `bunx`, not `npm` and `npx`.

View File

@@ -0,0 +1,85 @@
---
description: Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code.
globs: ["apps/sim/lib/execution/isolated-vm-worker.cjs", "apps/sim/lib/execution/isolated-vm.ts", "apps/sim/lib/execution/sandbox/**", "apps/sim/sandbox-tasks/**"]
---
# Sim Sandbox — Worker Security Policy
The isolated-vm worker child process at
`apps/sim/lib/execution/isolated-vm-worker.cjs` runs untrusted user code inside
V8 isolates. The process itself is a trust boundary. Everything in this rule is
about what must **never** live in that process.
## Hard rules
1. **No app credentials in the worker process**. The worker must not hold, load,
or receive via IPC: database URLs, Redis URLs, AWS keys, Stripe keys,
session-signing keys, encryption keys, OAuth client secrets, internal API
secrets, or any LLM / email / search provider API keys. If you catch yourself
`require`'ing `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/storage-service`,
or anything that imports `env` directly inside the worker, stop and use a
host-side broker instead.
2. **Host-side brokers own all credentialed work**. The worker can only access
resources through `ivm.Reference` / `ivm.Callback` bridges back to the host
process. Today the only broker is `workspaceFileBroker`
(`apps/sim/lib/execution/sandbox/brokers/workspace-file.ts`); adding a new
one requires co-reviewing this file.
3. **Host-side brokers must scope every resource access to a single tenant**.
The `SandboxBrokerContext` always carries `workspaceId`. Any new broker that
accesses storage, DB, or an external API must use `ctx.workspaceId` to scope
the lookup — never accept a raw path, key, or URL from isolate code without
validation.
4. **Nothing that runs in the isolate is trusted, even if we wrote it**. The
task `bootstrap` and `finalize` strings in `apps/sim/sandbox-tasks/` execute
inside the isolate. They must treat `globalThis` as adversarial — no pulling
values from it that might have been mutated by user code. The hardening
script in `executeTask` undefines dangerous globals before user code runs.
## Why
A V8 JIT bug (Chrome ships these roughly monthly) gives an attacker a native
code primitive inside the process that owns whatever that process can reach.
If the worker only holds `isolated-vm` + a single narrow workspace-file broker,
a V8 escape leaks one tenant's files. If the worker holds a Stripe key or a DB
connection, a V8 escape leaks the service.
The original `doc-worker.cjs` vulnerability (CVE-class, 225 production secrets
leaked via `/proc/1/environ`) was the forcing function for this architecture.
Keep the blast radius small.
## Checklist for changes to `isolated-vm-worker.cjs`
Before landing any change that adds a new `require(...)` or `process.send(...)`
payload or `ivm.Reference` wrapper in the worker:
- [ ] Does it load a credential, key, connection string, or secret? If yes,
move it host-side and expose as a broker.
- [ ] Does it import from `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/*`,
`@/lib/core/config/env`, or any module that reads `process.env` of the
main app? If yes, same — move host-side.
- [ ] Does it expose a resource that's workspace-scoped without taking a
`workspaceId`? If yes, re-scope.
- [ ] Did you update the broker limits (`IVM_MAX_BROKER_ARGS_JSON_CHARS`,
`IVM_MAX_BROKER_RESULT_JSON_CHARS`, `IVM_MAX_BROKERS_PER_EXECUTION`) if
the new broker can emit large payloads or fire frequently?
## What the worker *may* hold
- `isolated-vm` module
- Node built-ins: `node:fs` (only for reading the checked-in bundle `.cjs`
files) and `node:path`
- The three prebuilt library bundles under
`apps/sim/lib/execution/sandbox/bundles/*.cjs`
- IPC message handlers for `execute`, `cancel`, `fetchResponse`,
`brokerResponse`
The worker deliberately has **no host-side logger**. All errors and
diagnostics flow through IPC back to the host, which has `@sim/logger`. Do
not add `createLogger` or console-based logging to the worker — it would
require pulling the main app's config / env, which is exactly what this
rule is preventing.
Anything else is suspect.

View File

@@ -3,6 +3,7 @@ description: Testing patterns with Vitest and @sim/testing
globs: ["apps/sim/**/*.test.ts", "apps/sim/**/*.test.tsx"]
---
# Testing Patterns
Use Vitest. Test files: `feature.ts` → `feature.test.ts`
@@ -12,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`
These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior:
- `@sim/db` → `databaseMock`
- `@sim/db/schema` → `schemaMock`
- `drizzle-orm` → `drizzleOrmMock`
- `@sim/logger` → `loggerMock`
- `@/lib/auth` → `authMock`
- `@/lib/auth/hybrid` → `hybridAuthMock` (with default session-delegating behavior)
- `@/lib/core/utils/request` → `requestUtilsMock`
- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store`
- `@/blocks/registry`
- `@trigger.dev/sdk`
@@ -101,10 +106,6 @@ vi.mock('@/lib/workspaces/utils', () => ({
}))
```
### NEVER use `mockAuth()`, `mockConsoleLogger()`, or `setupCommonApiMocks()` from `@sim/testing`
These helpers internally use `vi.doMock()` which is slow. Use direct `vi.hoisted()` + `vi.mock()` instead.
### Mock heavy transitive dependencies
If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them:
@@ -134,38 +135,61 @@ await new Promise(r => setTimeout(r, 1))
vi.useFakeTimers()
```
## Mock Pattern Reference
## Centralized Mocks (prefer over local declarations)
`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` — no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides.
| Module mocked | Import | Factory form |
|---|---|---|
| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` |
| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` |
| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` |
| `@sim/audit` | `auditMock`, `auditMockFns` | `vi.mock('@sim/audit', () => auditMock)` |
| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` |
| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` |
| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` |
| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` |
| `@/lib/core/config/feature-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock)` |
| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` |
| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` |
| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` |
| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` |
| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` |
| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` |
| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` |
| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` |
| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` |
| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` |
| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` |
| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` |
### Auth mocking (API routes)
```typescript
const { mockGetSession } = vi.hoisted(() => ({
mockGetSession: vi.fn(),
}))
import { authMock, authMockFns } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('@/lib/auth', () => ({
auth: { api: { getSession: vi.fn() } },
getSession: mockGetSession,
}))
vi.mock('@/lib/auth', () => authMock)
// In tests:
mockGetSession.mockResolvedValue({ user: { id: 'user-1', email: 'test@example.com' } })
mockGetSession.mockResolvedValue(null) // unauthenticated
import { GET } from '@/app/api/my-route/route'
beforeEach(() => {
vi.clearAllMocks()
authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
})
```
Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`).
### Hybrid auth mocking
```typescript
const { mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({
mockCheckSessionOrInternalAuth: vi.fn(),
}))
import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing'
vi.mock('@/lib/auth/hybrid', () => ({
checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth,
}))
vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)
// In tests:
mockCheckSessionOrInternalAuth.mockResolvedValue({
hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({
success: true, userId: 'user-1', authType: 'session',
})
```
@@ -196,21 +220,23 @@ Always prefer over local test data.
| Category | Utilities |
|----------|-----------|
| **Mocks** | `loggerMock`, `databaseMock`, `drizzleOrmMock`, `setupGlobalFetchMock()` |
| **Module mocks** | See "Centralized Mocks" table above |
| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` |
| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` |
| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` |
| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` |
| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` |
| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` |
| **Requests** | `createMockRequest()`, `createEnvMock()` |
| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` |
## Rules Summary
1. `@vitest-environment node` unless DOM is required
2. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports
3. `vi.mock()` calls before importing mocked modules
4. `@sim/testing` utilities over local mocks
2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate
3. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports
4. `vi.mock()` calls before importing mocked modules
5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach`
6. No `vi.importActual()` — mock everything explicitly
7. No `mockAuth()`, `mockConsoleLogger()`, `setupCommonApiMocks()` — use direct mocks
8. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them
9. Use absolute imports in test files
10. Avoid real timers — use 1ms delays or `vi.useFakeTimers()`
7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them
8. Use absolute imports in test files
9. Avoid real timers — use 1ms delays or `vi.useFakeTimers()`

View File

@@ -1,4 +1,4 @@
FROM oven/bun:1.3.11-alpine
FROM oven/bun:1.3.13-alpine
# Install necessary packages for development
RUN apk add --no-cache \

View File

@@ -71,7 +71,7 @@ fi
# Set up environment variables if .env doesn't exist for the sim app
if [ ! -f "apps/sim/.env" ]; then
echo "📄 Creating .env file from template..."
echo "📄 Creating apps/sim/.env from template..."
if [ -f "apps/sim/.env.example" ]; then
cp apps/sim/.env.example apps/sim/.env
else
@@ -79,6 +79,18 @@ if [ ! -f "apps/sim/.env" ]; then
fi
fi
# Set up env for the realtime server (must match the shared values in apps/sim/.env)
if [ ! -f "apps/realtime/.env" ] && [ -f "apps/realtime/.env.example" ]; then
echo "📄 Creating apps/realtime/.env from template..."
cp apps/realtime/.env.example apps/realtime/.env
fi
# Set up packages/db/.env for drizzle-kit and migration scripts
if [ ! -f "packages/db/.env" ] && [ -f "packages/db/.env.example" ]; then
echo "📄 Creating packages/db/.env from template..."
cp packages/db/.env.example packages/db/.env
fi
# Generate schema and run database migrations
echo "🗃️ Running database schema generation and migrations..."
echo "Generating schema..."

28
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,28 @@
# Copilot/Mothership chat streaming entrypoints and replay surfaces.
/apps/sim/app/api/copilot/chat/ @simstudioai/mothership
/apps/sim/app/api/copilot/confirm/ @simstudioai/mothership
/apps/sim/app/api/copilot/chats/ @simstudioai/mothership
/apps/sim/app/api/mothership/chat/ @simstudioai/mothership
/apps/sim/app/api/mothership/chats/ @simstudioai/mothership
/apps/sim/app/api/mothership/execute/ @simstudioai/mothership
/apps/sim/app/api/v1/copilot/chat/ @simstudioai/mothership
# Server-side stream orchestration, persistence, and protocol.
/apps/sim/lib/copilot/chat/ @simstudioai/mothership
/apps/sim/lib/copilot/async-runs/ @simstudioai/mothership
/apps/sim/lib/copilot/request/ @simstudioai/mothership
/apps/sim/lib/copilot/generated/ @simstudioai/mothership
/apps/sim/lib/copilot/constants.ts @simstudioai/mothership
/apps/sim/lib/core/utils/sse.ts @simstudioai/mothership
# Stream-time tool execution, confirmations, resource persistence, and handlers.
/apps/sim/lib/copilot/tool-executor/ @simstudioai/mothership
/apps/sim/lib/copilot/tools/ @simstudioai/mothership
/apps/sim/lib/copilot/persistence/ @simstudioai/mothership
/apps/sim/lib/copilot/resources/ @simstudioai/mothership
# Client-side stream consumption, hydration, and reconnect.
/apps/sim/app/workspace/*/home/hooks/index.ts @simstudioai/mothership
/apps/sim/app/workspace/*/home/hooks/use-chat.ts @simstudioai/mothership
/apps/sim/app/workspace/*/home/hooks/use-file-preview-sessions.ts @simstudioai/mothership
/apps/sim/hooks/queries/tasks.ts @simstudioai/mothership

View File

@@ -2,8 +2,15 @@
Thank you for your interest in contributing to Sim! Our goal is to provide developers with a powerful, user-friendly platform for building, testing, and optimizing agentic workflows. We welcome contributions in all forms—from bug fixes and design improvements to brand-new features.
> **Project Overview:**
> Sim is a monorepo using Turborepo, containing the main application (`apps/sim/`), documentation (`apps/docs/`), and shared packages (`packages/`). The main application is built with Next.js (app router), ReactFlow, Zustand, Shadcn, and Tailwind CSS. Please ensure your contributions follow our best practices for clarity, maintainability, and consistency.
> **Project Overview:**
> Sim is a Turborepo monorepo with two deployable apps and a set of shared packages:
>
> - `apps/sim/` — the main Next.js application (App Router, ReactFlow, Zustand, Shadcn, Tailwind CSS).
> - `apps/realtime/` — a small Bun + Socket.IO server that powers the collaborative canvas. Shares DB and Better Auth secrets with `apps/sim` via `@sim/*` packages.
> - `apps/docs/` — Fumadocs-based documentation site.
> - `packages/` — shared workspace packages (`@sim/db`, `@sim/auth`, `@sim/audit`, `@sim/workflow-types`, `@sim/workflow-persistence`, `@sim/workflow-authz`, `@sim/realtime-protocol`, `@sim/security`, `@sim/logger`, `@sim/utils`, `@sim/testing`, `@sim/tsconfig`).
>
> Strict one-way dependency flow: `apps/* → packages/*`. Packages never import from apps. Please ensure your contributions follow this and our best practices for clarity, maintainability, and consistency.
---
@@ -24,14 +31,17 @@ Thank you for your interest in contributing to Sim! Our goal is to provide devel
We strive to keep our workflow as simple as possible. To contribute:
1. **Fork the Repository**
1. **Fork the Repository**
Click the **Fork** button on GitHub to create your own copy of the project.
2. **Clone Your Fork**
```bash
git clone https://github.com/<your-username>/sim.git
cd sim
```
3. **Create a Feature Branch**
3. **Create a Feature Branch**
Create a new branch with a descriptive name:
```bash
@@ -40,21 +50,23 @@ We strive to keep our workflow as simple as possible. To contribute:
Use a clear naming convention to indicate the type of work (e.g., `feat/`, `fix/`, `docs/`).
4. **Make Your Changes**
4. **Make Your Changes**
Ensure your changes are small, focused, and adhere to our coding guidelines.
5. **Commit Your Changes**
5. **Commit Your Changes**
Write clear, descriptive commit messages that follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) specification. This allows us to maintain a coherent project history and generate changelogs automatically. For example:
- `feat(api): add new endpoint for user authentication`
- `fix(ui): resolve button alignment issue`
- `docs: update contribution guidelines`
6. **Push Your Branch**
```bash
git push origin feat/your-feature-name
```
7. **Create a Pull Request**
7. **Create a Pull Request**
Open a pull request against the `staging` branch on GitHub. Please provide a clear description of the changes and reference any relevant issues (e.g., `fixes #123`).
---
@@ -65,7 +77,7 @@ If you discover a bug or have a feature request, please open an issue in our Git
- Provide a clear, descriptive title.
- Include as many details as possible (steps to reproduce, screenshots, etc.).
- **Tag Your Issue Appropriately:**
- **Tag Your Issue Appropriately:**
Use the following labels to help us categorize your issue:
- **active:** Actively working on it right now.
- **bug:** Something isn't working.
@@ -82,12 +94,11 @@ If you discover a bug or have a feature request, please open an issue in our Git
Before creating a pull request:
- **Ensure Your Branch Is Up-to-Date:**
- **Ensure Your Branch Is Up-to-Date:**
Rebase your branch onto the latest `staging` branch to prevent merge conflicts.
- **Follow the Guidelines:**
- **Follow the Guidelines:**
Make sure your changes are well-tested, follow our coding standards, and include relevant documentation if necessary.
- **Reference Issues:**
- **Reference Issues:**
If your PR addresses an existing issue, include `refs #<issue-number>` or `fixes #<issue-number>` in your PR description.
Our maintainers will review your pull request and provide feedback. We aim to make the review process as smooth and timely as possible.
@@ -166,27 +177,27 @@ To use local models with Sim:
1. Install Ollama and pull models:
```bash
# Install Ollama (if not already installed)
curl -fsSL https://ollama.ai/install.sh | sh
```bash
# Install Ollama (if not already installed)
curl -fsSL https://ollama.ai/install.sh | sh
# Pull a model (e.g., gemma3:4b)
ollama pull gemma3:4b
```
# Pull a model (e.g., gemma3:4b)
ollama pull gemma3:4b
```
2. Start Sim with local model support:
```bash
# With NVIDIA GPU support
docker compose --profile local-gpu -f docker-compose.ollama.yml up -d
```bash
# With NVIDIA GPU support
docker compose --profile local-gpu -f docker-compose.ollama.yml up -d
# Without GPU (CPU only)
docker compose --profile local-cpu -f docker-compose.ollama.yml up -d
# Without GPU (CPU only)
docker compose --profile local-cpu -f docker-compose.ollama.yml up -d
# If hosting on a server, update the environment variables in the docker-compose.prod.yml file
# to include the server's public IP then start again (OLLAMA_URL to i.e. http://1.1.1.1:11434)
docker compose -f docker-compose.prod.yml up -d
```
# If hosting on a server, update the environment variables in the docker-compose.prod.yml file
# to include the server's public IP then start again (OLLAMA_URL to i.e. http://1.1.1.1:11434)
docker compose -f docker-compose.prod.yml up -d
```
### Option 3: Using VS Code / Cursor Dev Containers
@@ -201,61 +212,104 @@ Dev Containers provide a consistent and easy-to-use development environment:
2. **Setup Steps:**
- Clone the repository:
```bash
git clone https://github.com/<your-username>/sim.git
cd sim
```
- Open the project in VS Code/Cursor
- When prompted, click "Reopen in Container" (or press F1 and select "Remote-Containers: Reopen in Container")
- Wait for the container to build and initialize
- Open the project in VS Code/Cursor.
- When prompted, click "Reopen in Container" (or press F1 and select "Remote-Containers: Reopen in Container").
- Wait for the container to build and initialize.
3. **Start Developing:**
- Run `bun run dev:full` in the terminal or use the `sim-start` alias
- This starts both the main application and the realtime socket server
- All dependencies and configurations are automatically set up
- Your changes will be automatically hot-reloaded
- Run `bun run dev:full` in the terminal or use the `sim-start` alias.
- This starts both the main application and the realtime socket server.
- All dependencies and configurations are automatically set up.
- Your changes will be automatically hot-reloaded.
4. **GitHub Codespaces:**
- This setup also works with GitHub Codespaces if you prefer development in the browser
- Just click "Code" → "Codespaces" → "Create codespace on staging"
- This setup also works with GitHub Codespaces if you prefer development in the browser.
- Just click "Code" → "Codespaces" → "Create codespace on staging".
### Option 4: Manual Setup
If you prefer not to use Docker or Dev Containers:
If you prefer not to use Docker or Dev Containers. **All commands run from the repository root unless explicitly noted.**
1. **Clone and Install:**
1. **Clone the Repository:**
```bash
git clone https://github.com/<your-username>/sim.git
cd sim
bun install
```
2. **Set Up Environment:**
Bun workspaces handle dependency resolution for all apps and packages from the root `bun install`.
- Navigate to the app directory:
```bash
cd apps/sim
```
- Copy `.env.example` to `.env`
- Configure required variables (DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL)
2. **Set Up Environment Files:**
3. **Set Up Database:**
We use **per-app `.env` files** (the Turborepo-canonical pattern), not a single root `.env`. Three files are needed for local dev:
```bash
bunx drizzle-kit push
# Main app — large, app-specific (OAuth secrets, LLM keys, Stripe, etc.)
cp apps/sim/.env.example apps/sim/.env
# Realtime server — small, only the values shared with the main app
cp apps/realtime/.env.example apps/realtime/.env
# DB tooling (drizzle-kit, db:migrate)
cp packages/db/.env.example packages/db/.env
```
4. **Run the Development Server:**
At minimum, each `.env` needs `DATABASE_URL`. `apps/sim/.env` and `apps/realtime/.env` additionally need matching values for `BETTER_AUTH_URL`, `BETTER_AUTH_SECRET`, `INTERNAL_API_SECRET`, and `NEXT_PUBLIC_APP_URL`. `apps/sim/.env` also needs `ENCRYPTION_KEY` and `API_ENCRYPTION_KEY`. Generate any 32-char secrets with `openssl rand -hex 32`.
The same `BETTER_AUTH_SECRET`, `INTERNAL_API_SECRET`, and `DATABASE_URL` must appear in both `apps/sim/.env` and `apps/realtime/.env` so the two services share auth and DB. After editing `apps/sim/.env`, you can mirror the shared subset into the realtime env in one shot:
```bash
grep -E '^(DATABASE_URL|BETTER_AUTH_URL|BETTER_AUTH_SECRET|INTERNAL_API_SECRET|NEXT_PUBLIC_APP_URL|REDIS_URL)=' apps/sim/.env > apps/realtime/.env
grep -E '^DATABASE_URL=' apps/sim/.env > packages/db/.env
```
3. **Run Database Migrations:**
Migrations live in `packages/db/migrations/`. Run them via the dedicated workspace script:
```bash
cd packages/db && bun run db:migrate && cd ../..
```
For ad-hoc schema iteration during development you can also use `bun run db:push` from `packages/db`, but `db:migrate` is the canonical command for both local and CI/CD setups.
4. **Run the Development Servers:**
```bash
bun run dev:full
```
This command starts both the main application and the realtime socket server required for full functionality.
This launches both apps with coloured prefixes:
- `[App]` — Next.js on `http://localhost:3000`
- `[Realtime]` — Socket.IO on `http://localhost:3002`
Or run them separately:
```bash
bun run dev # Next.js app only
bun run dev:sockets # realtime server only
```
5. **Make Your Changes and Test Locally.**
Before opening a PR, run the same checks CI runs:
```bash
bun run type-check # TypeScript across every workspace
bun run lint:check # Biome lint across every workspace
bun run test # Vitest across every workspace
```
### Email Template Development
When working on email templates, you can preview them using a local email preview server:
@@ -263,18 +317,19 @@ When working on email templates, you can preview them using a local email previe
1. **Run the Email Preview Server:**
```bash
bun run email:dev
cd apps/sim && bun run email:dev
```
2. **Access the Preview:**
- Open `http://localhost:3000` in your browser
- You'll see a list of all email templates
- Click on any template to view and test it with various parameters
- Open `http://localhost:3000` in your browser.
- You'll see a list of all email templates.
- Click on any template to view and test it with various parameters.
3. **Templates Location:**
- Email templates are located in `sim/app/emails/`
- After making changes to templates, they will automatically update in the preview
- Email templates live in `apps/sim/components/emails/`.
- Changes hot-reload automatically in the preview.
---
@@ -282,28 +337,41 @@ When working on email templates, you can preview them using a local email previe
Sim is built in a modular fashion where blocks and tools extend the platform's functionality. To maintain consistency and quality, please follow the guidelines below when adding a new block or tool.
> **Use the skill guides for step-by-step recipes.** The repository ships opinionated, end-to-end guides under `.agents/skills/` that cover the exact file layout, conventions, registry wiring, and gotchas for each kind of contribution. Read the relevant SKILL.md before you start writing code:
>
> | Adding… | Read |
> | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
> | A new integration end-to-end (tools + block + icon + optional triggers + all registrations) | [`.agents/skills/add-integration/SKILL.md`](../.agents/skills/add-integration/SKILL.md) |
> | Just a block (or aligning an existing block with its tools) | [`.agents/skills/add-block/SKILL.md`](../.agents/skills/add-block/SKILL.md) |
> | Just tool configs for a service | [`.agents/skills/add-tools/SKILL.md`](../.agents/skills/add-tools/SKILL.md) |
> | A webhook trigger for a service | [`.agents/skills/add-trigger/SKILL.md`](../.agents/skills/add-trigger/SKILL.md) |
> | A knowledge-base connector (sync docs from an external source) | [`.agents/skills/add-connector/SKILL.md`](../.agents/skills/add-connector/SKILL.md) |
>
> The shorter overview below is a high-level reference; the SKILL.md files are the authoritative source of truth and stay in sync with the codebase.
### Where to Add Your Code
- **Blocks:** Create your new block file under the `/apps/sim/blocks/blocks` directory. The name of the file should match the provider name (e.g., `pinecone.ts`).
- **Tools:** Create a new directory under `/apps/sim/tools` with the same name as the provider (e.g., `/apps/sim/tools/pinecone`).
- **Blocks:** Create your new block file under the `apps/sim/blocks/blocks/` directory. The name of the file should match the provider name (e.g., `pinecone.ts`).
- **Tools:** Create a new directory under `apps/sim/tools/` with the same name as the provider (e.g., `apps/sim/tools/pinecone`).
In addition, you will need to update the registries:
- **Block Registry:** Update the blocks index (`/apps/sim/blocks/index.ts`) to include your new block.
- **Tool Registry:** Update the tools registry (`/apps/sim/tools/index.ts`) to add your new tool.
- **Block Registry:** Add your block to `apps/sim/blocks/registry.ts`. (`apps/sim/blocks/index.ts` re-exports lookups from the registry; you do not need to edit it.)
- **Tool Registry:** Add your tool to `apps/sim/tools/index.ts`.
### How to Create a New Block
1. **Create a New File:**
Create a file for your block named after the provider (e.g., `pinecone.ts`) in the `/apps/sim/blocks/blocks` directory.
1. **Create a New File:**
Create a file for your block named after the provider (e.g., `pinecone.ts`) in the `apps/sim/blocks/blocks/` directory.
2. **Create a New Icon:**
Create a new icon for your block in the `/apps/sim/components/icons.tsx` file. The icon should follow the same naming convention as the block (e.g., `PineconeIcon`).
Create a new icon for your block in `apps/sim/components/icons.tsx`. The icon should follow the same naming convention as the block (e.g., `PineconeIcon`).
3. **Define the Block Configuration:**
3. **Define the Block Configuration:**
Your block should export a constant of type `BlockConfig`. For example:
```typescript:/apps/sim/blocks/blocks/pinecone.ts
```typescript
// apps/sim/blocks/blocks/pinecone.ts
import { PineconeIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { PineconeResponse } from '@/tools/pinecone/types'
@@ -321,7 +389,7 @@ In addition, you will need to update the registries:
{
id: 'operation',
title: 'Operation',
type: 'dropdown'
type: 'dropdown',
required: true,
options: [
{ label: 'Generate Embeddings', id: 'generate' },
@@ -332,7 +400,7 @@ In addition, you will need to update the registries:
{
id: 'apiKey',
title: 'API Key',
type: 'short-input'
type: 'short-input',
placeholder: 'Your Pinecone API key',
password: true,
required: true,
@@ -370,10 +438,11 @@ In addition, you will need to update the registries:
}
```
4. **Register Your Block:**
Add your block to the blocks registry (`/apps/sim/blocks/registry.ts`):
4. **Register Your Block:**
Add your block to the blocks registry (`apps/sim/blocks/registry.ts`):
```typescript:/apps/sim/blocks/registry.ts
```typescript
// apps/sim/blocks/registry.ts
import { PineconeBlock } from '@/blocks/blocks/pinecone'
// Registry of all available blocks
@@ -385,24 +454,25 @@ In addition, you will need to update the registries:
The block will be automatically available to the application through the registry.
5. **Test Your Block:**
5. **Test Your Block:**
Ensure that the block displays correctly in the UI and that its functionality works as expected.
### How to Create a New Tool
1. **Create a New Directory:**
Create a directory under `/apps/sim/tools` with the same name as the provider (e.g., `/apps/sim/tools/pinecone`).
1. **Create a New Directory:**
Create a directory under `apps/sim/tools/` with the same name as the provider (e.g., `apps/sim/tools/pinecone`).
2. **Create Tool Files:**
2. **Create Tool Files:**
Create separate files for each tool functionality with descriptive names (e.g., `fetch.ts`, `generate_embeddings.ts`, `search_text.ts`) in your tool directory.
3. **Create a Types File:**
3. **Create a Types File:**
Create a `types.ts` file in your tool directory to define and export all types related to your tools.
4. **Create an Index File:**
4. **Create an Index File:**
Create an `index.ts` file in your tool directory that imports and exports all tools:
```typescript:/apps/sim/tools/pinecone/index.ts
```typescript
// apps/sim/tools/pinecone/index.ts
import { fetchTool } from './fetch'
import { generateEmbeddingsTool } from './generate_embeddings'
import { searchTextTool } from './search_text'
@@ -410,10 +480,11 @@ In addition, you will need to update the registries:
export { fetchTool, generateEmbeddingsTool, searchTextTool }
```
5. **Define the Tool Configuration:**
5. **Define the Tool Configuration:**
Your tool should export a constant with a naming convention of `{toolName}Tool`. The tool ID should follow the format `{provider}_{tool_name}`. For example:
```typescript:/apps/sim/tools/pinecone/fetch.ts
```typescript
// apps/sim/tools/pinecone/fetch.ts
import { ToolConfig, ToolResponse } from '@/tools/types'
import { PineconeParams, PineconeResponse } from '@/tools/pinecone/types'
@@ -449,11 +520,12 @@ In addition, you will need to update the registries:
}
```
6. **Register Your Tool:**
Update the tools registry in `/apps/sim/tools/index.ts` to include your new tool:
6. **Register Your Tool:**
Update the tools registry in `apps/sim/tools/index.ts` to include your new tool:
```typescript:/apps/sim/tools/index.ts
import { fetchTool, generateEmbeddingsTool, searchTextTool } from '/@tools/pinecone'
```typescript
// apps/sim/tools/index.ts
import { fetchTool, generateEmbeddingsTool, searchTextTool } from '@/tools/pinecone'
// ... other imports
export const tools: Record<string, ToolConfig> = {
@@ -464,13 +536,14 @@ In addition, you will need to update the registries:
}
```
7. **Test Your Tool:**
7. **Test Your Tool:**
Ensure that your tool functions correctly by making test requests and verifying the responses.
8. **Generate Documentation:**
Run the documentation generator to create docs for your new tool:
8. **Generate Documentation:**
Run the documentation generator (from `apps/sim`) to create docs for your new tool:
```bash
./scripts/generate-docs.sh
cd apps/sim && bun run generate-docs
```
### Naming Conventions
@@ -480,7 +553,7 @@ Maintaining consistent naming across the codebase is critical for auto-generatio
- **Block Files:** Name should match the provider (e.g., `pinecone.ts`)
- **Block Export:** Should be named `{Provider}Block` (e.g., `PineconeBlock`)
- **Icons:** Should be named `{Provider}Icon` (e.g., `PineconeIcon`)
- **Tool Directories:** Should match the provider name (e.g., `/tools/pinecone/`)
- **Tool Directories:** Should match the provider name (e.g., `tools/pinecone/`)
- **Tool Files:** Should be named after their function (e.g., `fetch.ts`, `search_text.ts`)
- **Tool Exports:** Should be named `{toolName}Tool` (e.g., `fetchTool`)
- **Tool IDs:** Should follow the format `{provider}_{tool_name}` (e.g., `pinecone_fetch`)
@@ -489,12 +562,12 @@ Maintaining consistent naming across the codebase is critical for auto-generatio
Sim implements a sophisticated parameter visibility system that controls how parameters are exposed to users and LLMs in agent workflows. Each parameter can have one of four visibility levels:
| Visibility | User Sees | LLM Sees | How It Gets Set |
|-------------|-----------|----------|--------------------------------|
| `user-only` | ✅ Yes | ❌ No | User provides in UI |
| `user-or-llm` | ✅ Yes | ✅ Yes | User provides OR LLM generates |
| `llm-only` | ❌ No | ✅ Yes | LLM generates only |
| `hidden` | ❌ No | ❌ No | Application injects at runtime |
| Visibility | User Sees | LLM Sees | How It Gets Set |
| ------------- | --------- | -------- | ------------------------------ |
| `user-only` | ✅ Yes | ❌ No | User provides in UI |
| `user-or-llm` | ✅ Yes | ✅ Yes | User provides OR LLM generates |
| `llm-only` | ❌ No | ✅ Yes | LLM generates only |
| `hidden` | ❌ No | ❌ No | Application injects at runtime |
#### Visibility Guidelines

View File

@@ -16,6 +16,7 @@ permissions:
jobs:
test-build:
name: Test and Build
if: github.ref != 'refs/heads/dev' || github.event_name == 'pull_request'
uses: ./.github/workflows/test-build.yml
secrets: inherit
@@ -45,11 +46,72 @@ jobs:
echo " Not a release commit"
fi
# Build AMD64 images and push to ECR immediately (+ GHCR for main)
# Dev: build all 3 images for ECR only (no GHCR, no ARM64)
build-dev:
name: Build Dev ECR
needs: [detect-version]
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
runs-on: blacksmith-8vcpu-ubuntu-2404
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
include:
- dockerfile: ./docker/app.Dockerfile
ecr_repo_secret: ECR_APP
- dockerfile: ./docker/db.Dockerfile
ecr_repo_secret: ECR_MIGRATIONS
- dockerfile: ./docker/realtime.Dockerfile
ecr_repo_secret: ECR_REALTIME
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.DEV_AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.DEV_AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: useblacksmith/setup-docker-builder@v1
- name: Resolve ECR repo name
id: ecr-repo
run: echo "name=$ECR_REPO" >> $GITHUB_OUTPUT
env:
ECR_REPO: ${{ matrix.ecr_repo_secret == 'ECR_APP' && secrets.ECR_APP || matrix.ecr_repo_secret == 'ECR_MIGRATIONS' && secrets.ECR_MIGRATIONS || matrix.ecr_repo_secret == 'ECR_REALTIME' && secrets.ECR_REALTIME || '' }}
- name: Build and push
uses: useblacksmith/build-push-action@v2
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: linux/amd64
push: true
tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:dev
provenance: false
sbom: false
# Main/staging: build AMD64 images and push to ECR + GHCR
build-amd64:
name: Build AMD64
needs: [detect-version]
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/dev')
needs: [test-build, detect-version]
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
runs-on: blacksmith-8vcpu-ubuntu-2404
permissions:
contents: read
@@ -75,8 +137,8 @@ jobs:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || github.ref == 'refs/heads/dev' && secrets.DEV_AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || github.ref == 'refs/heads/dev' && secrets.DEV_AWS_REGION || secrets.STAGING_AWS_REGION }}
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
@@ -99,33 +161,33 @@ jobs:
- name: Set up Docker Buildx
uses: useblacksmith/setup-docker-builder@v1
- name: Resolve ECR repo name
id: ecr-repo
run: echo "name=$ECR_REPO" >> $GITHUB_OUTPUT
env:
ECR_REPO: ${{ matrix.ecr_repo_secret == 'ECR_APP' && secrets.ECR_APP || matrix.ecr_repo_secret == 'ECR_MIGRATIONS' && secrets.ECR_MIGRATIONS || matrix.ecr_repo_secret == 'ECR_REALTIME' && secrets.ECR_REALTIME || '' }}
- name: Generate tags
id: meta
run: |
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
ECR_REPO="${{ secrets[matrix.ecr_repo_secret] }}"
ECR_REPO="${{ steps.ecr-repo.outputs.name }}"
GHCR_IMAGE="${{ matrix.ghcr_image }}"
# ECR tags (always build for ECR)
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
ECR_TAG="latest"
elif [ "${{ github.ref }}" = "refs/heads/dev" ]; then
ECR_TAG="dev"
else
ECR_TAG="staging"
fi
ECR_IMAGE="${ECR_REGISTRY}/${ECR_REPO}:${ECR_TAG}"
# Build tags list
TAGS="${ECR_IMAGE}"
# Add GHCR tags only for main branch
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
GHCR_AMD64="${GHCR_IMAGE}:latest-amd64"
GHCR_SHA="${GHCR_IMAGE}:${{ github.sha }}-amd64"
TAGS="${TAGS},$GHCR_AMD64,$GHCR_SHA"
# Add version tag if this is a release commit
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
VERSION="${{ needs.detect-version.outputs.version }}"
GHCR_VERSION="${GHCR_IMAGE}:${VERSION}-amd64"
@@ -256,6 +318,14 @@ jobs:
docker manifest push "${IMAGE_BASE}:${VERSION}"
fi
# Run database migrations for dev
migrate-dev:
name: Migrate Dev DB
needs: [build-dev]
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
uses: ./.github/workflows/migrations.yml
secrets: inherit
# Check if docs changed
check-docs-changes:
name: Check Docs Changes

View File

@@ -20,7 +20,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13
- name: Setup Node
uses: actions/setup-node@v4

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13
- name: Cache Bun dependencies
uses: actions/cache@v4
@@ -122,7 +122,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13
- name: Cache Bun dependencies
uses: actions/cache@v4

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13
- name: Cache Bun dependencies
uses: actions/cache@v4
@@ -38,5 +38,5 @@ jobs:
- name: Apply migrations
working-directory: ./packages/db
env:
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || github.ref == 'refs/heads/dev' && secrets.DEV_DATABASE_URL || secrets.STAGING_DATABASE_URL }}
run: bunx drizzle-kit migrate --config=./drizzle.config.ts

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13
- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13
- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4

View File

@@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13
- name: Setup Node
uses: actions/setup-node@v4
@@ -103,9 +103,18 @@ jobs:
- name: Lint code
run: bun run lint:check
- name: Enforce monorepo boundaries
run: bun run check:boundaries
- name: Verify realtime prune graph
run: bun run check:realtime-prune
- name: Type-check realtime server
run: bunx turbo run type-check --filter=@sim/realtime
- name: Run tests with coverage
env:
NODE_OPTIONS: '--no-warnings'
NODE_OPTIONS: '--no-warnings --max-old-space-size=8192'
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
@@ -127,7 +136,7 @@ jobs:
- name: Build application
env:
NODE_OPTIONS: '--no-warnings'
NODE_OPTIONS: '--no-warnings --max-old-space-size=8192'
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
STRIPE_SECRET_KEY: 'dummy_key_for_ci_only'

View File

@@ -7,7 +7,7 @@ You are a professional software engineer. All code must follow best practices: a
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
- **Styling**: Never update global styles. Keep all styling local to components
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id`
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
## Architecture
@@ -20,19 +20,42 @@ You are a professional software engineer. All code must follow best practices: a
### Root Structure
```
apps/sim/
├── app/ # Next.js app router (pages, API routes)
├── blocks/ # Block definitions and registry
├── components/ # Shared UI (emcn/, ui/)
├── executor/ # Workflow execution engine
├── hooks/ # Shared hooks (queries/, selectors/)
├── lib/ # App-wide utilities
├── providers/ # LLM provider integrations
├── stores/ # Zustand stores
├── tools/ # Tool definitions
└── triggers/ # Trigger definitions
apps/
├── sim/ # Next.js app (UI + API routes + workflow editor)
│ ├── app/ # Next.js app router (pages, API routes)
│ ├── blocks/ # Block definitions and registry
│ ├── components/ # Shared UI (emcn/, ui/)
│ ├── executor/ # Workflow execution engine
│ ├── hooks/ # Shared hooks (queries/, selectors/)
│ ├── lib/ # App-wide utilities
│ ├── providers/ # LLM provider integrations
├── stores/ # Zustand stores
│ ├── tools/ # Tool definitions
│ └── triggers/ # Trigger definitions
└── realtime/ # Bun Socket.IO server (collaborative canvas)
└── src/ # auth, config, database, handlers, middleware,
# rooms, routes, internal/webhook-cleanup.ts
packages/
├── audit/ # @sim/audit — recordAudit + AuditAction + AuditResourceType
├── auth/ # @sim/auth — @sim/auth/verify (shared Better Auth verifier)
├── db/ # @sim/db — drizzle schema + client
├── logger/ # @sim/logger
├── realtime-protocol/ # @sim/realtime-protocol — socket operation constants + zod schemas
├── security/ # @sim/security — safeCompare
├── tsconfig/ # shared tsconfig presets
├── utils/ # @sim/utils
├── workflow-authz/ # @sim/workflow-authz — authorizeWorkflowByWorkspacePermission
├── workflow-persistence/ # @sim/workflow-persistence — raw load/save + subflow helpers
└── workflow-types/ # @sim/workflow-types — pure BlockState/Loop/Parallel/... types
```
### Package boundaries
- `apps/* → packages/*` only. Packages never import from `apps/*`.
- Each package has explicit subpath `exports` maps; no barrels that accidentally pull in heavy halves.
- `apps/realtime` intentionally avoids Next.js, React, the block/tool registry, provider SDKs, and the executor. CI enforces this via `scripts/check-monorepo-boundaries.ts` and `scripts/check-realtime-prune-graph.ts`.
- Auth is shared across services via the Better Auth "Shared Database Session" pattern: both apps read the same `BETTER_AUTH_SECRET` and point at the same DB via `@sim/db`.
### Naming Conventions
- Components: PascalCase (`WorkflowList`)
- Hooks: `use` prefix (`useWorkflowOperations`)

View File

@@ -4,10 +4,12 @@ You are a professional software engineer. All code must follow best practices: a
## Global Standards
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`. Inside API routes wrapped with `withRouteHandler`, loggers automatically include the request ID — no manual `withMetadata({ requestId })` needed
- **API Route Handlers**: All API route handlers (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) must be wrapped with `withRouteHandler` from `@/lib/core/utils/with-route-handler`. This provides request ID tracking, automatic error logging for 4xx/5xx responses, and unhandled error catching. See "API Route Pattern" section below
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
- **Styling**: Never update global styles. Keep all styling local to components
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id`
- **Common Utilities**: Use shared helpers from `@sim/utils` instead of inline implementations. `sleep(ms)` from `@sim/utils/helpers` for delays, `toError(e)` from `@sim/utils/errors` to normalize caught values.
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
## Architecture
@@ -92,6 +94,41 @@ export function Component({ requiredProp, optionalProp = false }: ComponentProps
Extract when: 50+ lines, used in 2+ files, or has own state/logic. Keep inline when: < 10 lines, single use, purely presentational.
## API Route Pattern
Every API route handler must be wrapped with `withRouteHandler`. This sets up `AsyncLocalStorage`-based request context so all loggers in the request lifecycle automatically include the request ID.
```typescript
import { createLogger } from '@sim/logger'
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('MyAPI')
// Simple route
export const GET = withRouteHandler(async (request: NextRequest) => {
logger.info('Handling request') // automatically includes {requestId=...}
return NextResponse.json({ ok: true })
})
// Route with params
export const DELETE = withRouteHandler(async (
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) => {
const { id } = await params
return NextResponse.json({ deleted: id })
})
// Composing with other middleware (withRouteHandler wraps the outermost layer)
export const POST = withRouteHandler(withAdminAuth(async (request) => {
return NextResponse.json({ ok: true })
}))
```
Never export a bare `async function GET/POST/...` — always use `export const METHOD = withRouteHandler(...)`.
## Hooks
```typescript

View File

@@ -74,10 +74,6 @@ docker compose -f docker-compose.prod.yml up -d
Open [http://localhost:3000](http://localhost:3000)
#### Background worker note
The Docker Compose stack starts a dedicated worker container by default. If `REDIS_URL` is not configured, the worker will start, log that it is idle, and do no queue processing. This is expected. Queue-backed API, webhook, and schedule execution requires Redis; installs without Redis continue to use the inline execution path.
Sim also supports local models via [Ollama](https://ollama.ai) and [vLLM](https://docs.vllm.ai/) — see the [Docker self-hosting docs](https://docs.sim.ai/self-hosting/docker) for setup details.
### Self-hosted: Manual Setup
@@ -123,12 +119,10 @@ cd packages/db && bun run db:migrate
5. Start development servers:
```bash
bun run dev:full # Starts Next.js app, realtime socket server, and the BullMQ worker
bun run dev:full # Starts Next.js app and realtime socket server
```
If `REDIS_URL` is not configured, the worker will remain idle and execution continues inline.
Or run separately: `bun run dev` (Next.js), `cd apps/sim && bun run dev:sockets` (realtime), and `cd apps/sim && bun run worker` (BullMQ worker).
Or run separately: `bun run dev` (Next.js) and `cd apps/sim && bun run dev:sockets` (realtime).
## Copilot API Keys
@@ -148,13 +142,15 @@ See the [environment variables reference](https://docs.sim.ai/self-hosting/envir
- **Database**: PostgreSQL with [Drizzle ORM](https://orm.drizzle.team)
- **Authentication**: [Better Auth](https://better-auth.com)
- **UI**: [Shadcn](https://ui.shadcn.com/), [Tailwind CSS](https://tailwindcss.com)
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/)
- **Streaming Markdown**: [Streamdown](https://github.com/vercel/streamdown)
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/), [TanStack Query](https://tanstack.com/query)
- **Flow Editor**: [ReactFlow](https://reactflow.dev/)
- **Docs**: [Fumadocs](https://fumadocs.vercel.app/)
- **Monorepo**: [Turborepo](https://turborepo.org/)
- **Realtime**: [Socket.io](https://socket.io/)
- **Background Jobs**: [Trigger.dev](https://trigger.dev/)
- **Remote Code Execution**: [E2B](https://www.e2b.dev/)
- **Isolated Code Execution**: [isolated-vm](https://github.com/laverdet/isolated-vm)
## Contributing

View File

@@ -17,9 +17,10 @@ import { ResponseSection } from '@/components/ui/response-section'
import { i18n } from '@/lib/i18n'
import { getApiSpecContent, openapi } from '@/lib/openapi'
import { type PageData, source } from '@/lib/source'
import { DOCS_BASE_URL } from '@/lib/urls'
const SUPPORTED_LANGUAGES: Set<string> = new Set(i18n.languages)
const BASE_URL = 'https://docs.sim.ai'
const BASE_URL = DOCS_BASE_URL
const OG_LOCALE_MAP: Record<string, string> = {
en: 'en_US',

View File

@@ -3,7 +3,6 @@ import { defineI18nUI } from 'fumadocs-ui/i18n'
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
import { RootProvider } from 'fumadocs-ui/provider/next'
import { Geist_Mono, Inter } from 'next/font/google'
import Script from 'next/script'
import {
SidebarFolder,
SidebarItem,
@@ -13,6 +12,7 @@ import { Navbar } from '@/components/navbar/navbar'
import { SimLogoFull } from '@/components/ui/sim-logo'
import { i18n } from '@/lib/i18n'
import { source } from '@/lib/source'
import { DOCS_BASE_URL } from '@/lib/urls'
import '../global.css'
const inter = Inter({
@@ -67,14 +67,14 @@ export default async function Layout({ children, params }: LayoutProps) {
name: 'Sim Documentation',
description:
'Documentation for Sim — the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
url: 'https://docs.sim.ai',
url: DOCS_BASE_URL,
publisher: {
'@type': 'Organization',
name: 'Sim',
url: 'https://sim.ai',
logo: {
'@type': 'ImageObject',
url: 'https://docs.sim.ai/static/logo.png',
url: `${DOCS_BASE_URL}/static/logo.png`,
},
},
inLanguage: lang,
@@ -82,7 +82,7 @@ export default async function Layout({ children, params }: LayoutProps) {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: 'https://docs.sim.ai/api/search?q={search_term_string}',
urlTemplate: `${DOCS_BASE_URL}/api/search?q={search_term_string}`,
},
'query-input': 'required name=search_term_string',
},
@@ -101,7 +101,6 @@ export default async function Layout({ children, params }: LayoutProps) {
/>
</head>
<body className='flex min-h-screen flex-col font-sans'>
<Script src='https://assets.onedollarstats.com/stonks.js' strategy='lazyOnload' />
<RootProvider i18n={provider(lang)}>
<Navbar />
<DocsLayout

View File

@@ -1,4 +1,5 @@
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
import { DocsPage } from 'fumadocs-ui/page'
import Link from 'next/link'
export const metadata = {
title: 'Page Not Found',
@@ -7,17 +8,21 @@ export const metadata = {
export default function NotFound() {
return (
<DocsPage>
<DocsBody>
<div className='flex min-h-[60vh] flex-col items-center justify-center text-center'>
<h1 className='mb-4 bg-gradient-to-b from-[#47d991] to-[#33c482] bg-clip-text font-bold text-8xl text-transparent'>
404
</h1>
<h2 className='mb-2 font-semibold text-2xl text-foreground'>Page Not Found</h2>
<p className='text-muted-foreground'>
The page you're looking for doesn't exist or has been moved.
</p>
</div>
</DocsBody>
<div className='flex min-h-[70vh] flex-col items-center justify-center gap-4 text-center'>
<h1 className='bg-gradient-to-b from-[#47d991] to-[#33c482] bg-clip-text font-bold text-8xl text-transparent'>
404
</h1>
<h2 className='font-semibold text-2xl text-foreground'>Page Not Found</h2>
<p className='text-muted-foreground'>
The page you're looking for doesn't exist or has been moved.
</p>
<Link
href='/'
className='ml-1 flex items-center rounded-[8px] bg-[#33c482] px-2.5 py-1.5 text-[13px] text-white transition-colors duration-200 hover:bg-[#2DAC72]'
>
Go home
</Link>
</div>
</DocsPage>
)
}

View File

@@ -1,5 +1,6 @@
import type { ReactNode } from 'react'
import type { Viewport } from 'next'
import { DOCS_BASE_URL } from '@/lib/urls'
export default function RootLayout({ children }: { children: ReactNode }) {
return children
@@ -12,7 +13,7 @@ export const viewport: Viewport = {
}
export const metadata = {
metadataBase: new URL('https://docs.sim.ai'),
metadataBase: new URL(DOCS_BASE_URL),
title: {
default: 'Sim Documentation — The AI Workspace for Teams',
template: '%s | Sim Docs',
@@ -61,14 +62,14 @@ export const metadata = {
type: 'website',
locale: 'en_US',
alternateLocale: ['es_ES', 'fr_FR', 'de_DE', 'ja_JP', 'zh_CN'],
url: 'https://docs.sim.ai',
url: DOCS_BASE_URL,
siteName: 'Sim Documentation',
title: 'Sim Documentation — The AI Workspace for Teams',
description:
'Documentation for Sim — the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
images: [
{
url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation',
url: `${DOCS_BASE_URL}/api/og?title=Sim%20Documentation`,
width: 1200,
height: 630,
alt: 'Sim Documentation',
@@ -82,7 +83,7 @@ export const metadata = {
'Documentation for Sim — the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
creator: '@simdotai',
site: '@simdotai',
images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation'],
images: [`${DOCS_BASE_URL}/api/og?title=Sim%20Documentation`],
},
robots: {
index: true,
@@ -96,15 +97,15 @@ export const metadata = {
},
},
alternates: {
canonical: 'https://docs.sim.ai',
canonical: DOCS_BASE_URL,
languages: {
'x-default': 'https://docs.sim.ai',
en: 'https://docs.sim.ai',
es: 'https://docs.sim.ai/es',
fr: 'https://docs.sim.ai/fr',
de: 'https://docs.sim.ai/de',
ja: 'https://docs.sim.ai/ja',
zh: 'https://docs.sim.ai/zh',
'x-default': DOCS_BASE_URL,
en: DOCS_BASE_URL,
es: `${DOCS_BASE_URL}/es`,
fr: `${DOCS_BASE_URL}/fr`,
de: `${DOCS_BASE_URL}/de`,
ja: `${DOCS_BASE_URL}/ja`,
zh: `${DOCS_BASE_URL}/zh`,
},
},
}

View File

@@ -1,9 +1,10 @@
import { source } from '@/lib/source'
import { DOCS_BASE_URL } from '@/lib/urls'
export const revalidate = false
export async function GET() {
const baseUrl = 'https://docs.sim.ai'
const baseUrl = DOCS_BASE_URL
try {
const pages = source.getPages().filter((page) => {
@@ -61,7 +62,7 @@ ${Object.entries(sections)
- Full documentation content: ${baseUrl}/llms-full.txt
- Individual page content: ${baseUrl}/llms.mdx/[page-path]
- API documentation: ${baseUrl}/sdks/
- API documentation: ${baseUrl}/api-reference/
- Tool integrations: ${baseUrl}/tools/
## Statistics

View File

@@ -1,70 +1,18 @@
import { DOCS_BASE_URL } from '@/lib/urls'
export const revalidate = false
export async function GET() {
const baseUrl = 'https://docs.sim.ai'
const baseUrl = DOCS_BASE_URL
const robotsTxt = `# Robots.txt for Sim Documentation
User-agent: *
Allow: /
# Search engine crawlers
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: Slurp
Allow: /
User-agent: DuckDuckBot
Allow: /
User-agent: Baiduspider
Allow: /
User-agent: YandexBot
Allow: /
# AI and LLM crawlers - explicitly allowed for documentation indexing
User-agent: GPTBot
Allow: /
User-agent: ChatGPT-User
Allow: /
User-agent: CCBot
Allow: /
User-agent: anthropic-ai
Allow: /
User-agent: Claude-Web
Allow: /
User-agent: Applebot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Diffbot
Allow: /
User-agent: FacebookBot
Allow: /
User-agent: cohere-ai
Allow: /
# Disallow admin and internal paths (if any exist)
Disallow: /.next/
Disallow: /api/internal/
Disallow: /_next/static/
Disallow: /admin/
# Allow but don't prioritize these
Allow: /
Allow: /api/search
Allow: /llms.txt
Allow: /llms-full.txt
@@ -73,23 +21,12 @@ Allow: /llms.mdx/
# Sitemaps
Sitemap: ${baseUrl}/sitemap.xml
# Crawl delay for aggressive bots (optional)
# Crawl-delay: 1
# Additional resources for AI indexing
# See https://github.com/AnswerDotAI/llms-txt for more info
# LLM-friendly content:
# Manifest: ${baseUrl}/llms.txt
# Full content: ${baseUrl}/llms-full.txt
# Individual pages: ${baseUrl}/llms.mdx/[page-path]
# Multi-language documentation available at:
# ${baseUrl}/en - English
# ${baseUrl}/es - Español
# ${baseUrl}/fr - Français
# ${baseUrl}/de - Deutsch
# ${baseUrl}/ja - 日本語
# ${baseUrl}/zh - 简体中文`
# Individual pages: ${baseUrl}/llms.mdx/[page-path]`
return new Response(robotsTxt, {
headers: {

42
apps/docs/app/sitemap.ts Normal file
View File

@@ -0,0 +1,42 @@
import type { MetadataRoute } from 'next'
import { i18n } from '@/lib/i18n'
import { source } from '@/lib/source'
import { DOCS_BASE_URL } from '@/lib/urls'
export const revalidate = 3600
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = DOCS_BASE_URL
const languages = source.getLanguages()
const pagesBySlug = new Map<string, Map<string, string>>()
for (const { language, pages } of languages) {
for (const page of pages) {
const key = page.slugs.join('/')
if (!pagesBySlug.has(key)) {
pagesBySlug.set(key, new Map())
}
pagesBySlug.get(key)!.set(language, `${baseUrl}${page.url}`)
}
}
const entries: MetadataRoute.Sitemap = []
for (const [, localeMap] of pagesBySlug) {
const defaultUrl = localeMap.get(i18n.defaultLanguage)
if (!defaultUrl) continue
const langAlternates: Record<string, string> = {}
for (const [lang, url] of localeMap) {
langAlternates[lang] = url
}
langAlternates['x-default'] = defaultUrl
entries.push({
url: defaultUrl,
alternates: { languages: langAlternates },
})
}
return entries
}

View File

@@ -1,62 +0,0 @@
import { i18n } from '@/lib/i18n'
import { source } from '@/lib/source'
export const revalidate = 3600
export async function GET() {
const baseUrl = 'https://docs.sim.ai'
const allPages = source.getPages()
const getPriority = (url: string): string => {
if (url === '/introduction' || url === '/') return '1.0'
if (url === '/getting-started') return '0.9'
if (url.match(/^\/[^/]+$/)) return '0.8'
if (url.includes('/sdks/') || url.includes('/tools/')) return '0.7'
return '0.6'
}
const urls = allPages
.flatMap((page) => {
const urlWithoutLang = page.url.replace(/^\/[a-z]{2}\//, '/')
return i18n.languages.map((lang) => {
const url =
lang === i18n.defaultLanguage
? `${baseUrl}${urlWithoutLang}`
: `${baseUrl}/${lang}${urlWithoutLang}`
return ` <url>
<loc>${url}</loc>
<priority>${getPriority(urlWithoutLang)}</priority>
${i18n.languages.length > 1 ? generateAlternateLinks(baseUrl, urlWithoutLang) : ''}
</url>`
})
})
.join('\n')
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
${urls}
</urlset>`
return new Response(sitemap, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
},
})
}
function generateAlternateLinks(baseUrl: string, urlWithoutLang: string): string {
const langLinks = i18n.languages
.map((lang) => {
const url =
lang === i18n.defaultLanguage
? `${baseUrl}${urlWithoutLang}`
: `${baseUrl}/${lang}${urlWithoutLang}`
return ` <xhtml:link rel="alternate" hreflang="${lang}" href="${url}" />`
})
.join('\n')
return `${langLinks}\n <xhtml:link rel="alternate" hreflang="x-default" href="${baseUrl}${urlWithoutLang}" />`
}

View File

@@ -28,6 +28,36 @@ export function AgentMailIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function AgentPhoneIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 150 150' xmlns='http://www.w3.org/2000/svg'>
<path
fill='#23AF58'
stroke='#007F3F'
strokeWidth='0.15'
strokeMiterlimit='10'
d='m139.6 53.3c-1.4-2.3-4.9-3.3-7.6-4.8-2.7-1.3-4.2-2.4-5.7-3.6-1.9-1-2.5-2.7-3.3-3.2s-2.7-1.4-4.5 1.3c-2 2.7-4.5 6.6-6.6 11.1-2.3 5.4-6.3 14.9-6.3 18.9 0.5 4.9 3.1 4.6 6.1 7.2 2.5 2.1 2.8 5.8 1.5 12.5-1.3 6.6-4 12.8-7.8 19.2-3.3 5.1-5.8 8.7-10 9.1-5.3 0.5-12.5-3.1-16.8-5.6-1-0.6-2.5-0.9-3.8-0.2-1.3 0.5-2.2 1.6-3.2 3.3-1.5 2.5-4.6 7.7-5.8 12.2-0.5 3 0 6.4 2.9 9 1.4 1.2 2.8 2.5 4.4 3.4 5 2.8 9.6 4.5 16.5 4.9 5.3 0.2 9.3-1 13.4-3.1 2.4-1.3 6.6-4.2 9.6-7.3l1.1-1.2c2.8-3.1 8.8-10 11.6-14.5 2.3-3.5 4.8-7.4 6.9-12.3 2.9-6.7 4.4-14 5-17.9 1.2-7 2.4-17.5 3.4-31.1 0.1-4.3-0.3-6.1-1-7.3zm-4.5 6.7c-0.5 9.5-1.9 23.3-3.1 30.1-0.9 4.5-2.4 9.6-3.8 13.4-1.1 2.6-3.1 7-5.6 10.8-3.4 5.3-8.4 11.6-12 15.8-6.4 6.6-10.2 9.6-14.2 10.8-2.2 0.9-3.8 1.2-7 1.2-3.4-0.1-8-0.7-11.3-2.2-3-1.2-7-4-6.9-6.8 0.4-3.2 3.3-9.6 5.2-11.9 0.2-0.3 0.5-0.3 0.7-0.2 2.5 1.1 6 3.2 9.6 4.5 2.4 0.9 4.8 1.4 7.3 1.4 3.9 0 6.7-1.2 9.5-3.2 5.6-4.6 9-10.8 12.1-17.5 2-4.3 4.1-11.6 4.4-18.3 0.1-4.9-1.1-8.9-4.5-12.2-1.1-0.7-3-2.1-3-2.8 0-4.2 3.9-13 8.9-22.9 0.2-0.7 0.5-1 1.1-0.7 1.1 0.6 3 1.4 4.6 2.4 2.1 1 5.4 2.4 7.1 3.9 0.9 0.4 1 3 0.9 4.4z'
/>
<path
fill='#23AF58'
d='m104.7 27.8c-1.3-1.5-3.3-1.3-6.2-1.5l-1.9 0.2-7-0.2-31.5 0.2 1.5-9.3c2-1.1 5.1-3.5 5.8-6.3 1-2.8 0.2-5.9-2-7.4-2.3-1.9-5.8-2.4-9.3-0.8-1.6 1-4.7 3.4-5.4 6.9-0.8 4.1 2.4 6.7 4.7 7.9l-1.5 9.1-17.2 0.9c-12.3 1.1-16.3 1.2-20.6 4.3-2 1.3-3 4.5-3.4 9.8-0.6 11.3-0.7 18.7-0.6 28.3 0.4 11.2 0 36.6 3 39.8l-1.2 0.3c-3.8 0.6-4 6.2-0.5 6.6l15.5-1 69.7-7.6c2.5-0.4 4.3-0.9 4.6-4.3l3.7-71.5c0-1.9 0.2-3.6-0.2-4.4zm-49.6-17.3c0.3-2.2 2.4-3 3.3-2.8 0.7 0.4 1 1.8 0 2.8-1.5 2-3.3 1.7-3.3 0zm40 90.2c-4 1-5.5 1.5-11.5 2.4-7.7 1-19.7 2.1-31.2 3.4l-33.8 2.9c-0.7 0.2-1-0.4-1-1-0.6-6.5-1.2-20.5-1.5-39.5l0.3-23.3c0.6-7.5 0.7-8.7 4.6-9.7 5.1-0.9 7.4-1.4 14.9-1.8l19.5-0.5 41.1-0.5c1.4 0 1.9 0.4 1.9 1.5l-3.3 66.1z'
/>
<path
fill='#23AF58'
d='m38.9 52.4c-1.8 0-4 1.1-4.5 3.3-1 3.9 1 7.6 4.5 7.7 3.8 0 5-3.8 4.7-6.3-0.2-2-2-4.7-4.7-4.7z'
/>
<path
fill='#23AF58'
d='m73.5 53.9c-1.8 0-4.3 1.5-4.4 4.5-0.1 3.2 2 5.3 4.3 5.3 2.5 0 4.2-1.7 4.2-4.8 0-3.2-1.7-4.8-4.1-5z'
/>
<path
fill='#23AF58'
d='m72.1 77.1c-2.7 3.4-7.2 7.4-14.7 8.3-7.3 0.3-13.9-2.9-20-8.5-3.5-3.4-8 0-6.2 2.7 1.7 2.5 6.4 6.6 10.4 8.8 3.5 2 7.3 3.3 13.8 3.5 4.7 0 9.2-0.8 12.7-2.4 2.9-1.1 5-2.8 6-3.8 2.3-2.1 3.8-4.1 3.5-7.3-0.9-2.5-3.6-2.8-5.5-1.3z'
/>
</svg>
)
}
export function CrowdStrikeIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 768 500' fill='none' xmlns='http://www.w3.org/2000/svg'>
@@ -2087,6 +2117,21 @@ export function BrandfetchIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function BrightDataIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='54 93 22 52' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M62 95.21c.19 2.16 1.85 3.24 2.82 4.74.25.38.48.11.67-.16.21-.31.6-1.21 1.15-1.28-.35 1.38-.04 3.15.16 4.45.49 3.05-1.22 5.64-4.07 6.18-3.38.65-6.22-2.21-5.6-5.62.23-1.24 1.37-2.5.77-3.7-.85-1.7.54-.52.79-.22 1.04 1.2 1.21.09 1.45-.55.24-.63.31-1.31.47-1.97.19-.77.55-1.4 1.39-1.87z'
fill='currentColor'
/>
<path
d='M66.70 123.37c0 3.69.04 7.38-.03 11.07-.02 1.04.31 1.48 1.32 1.49.29 0 .59.12.88.13.93.01 1.18.47 1.16 1.37-.05 2.19 0 2.19-2.24 2.19-3.48 0-6.96-.04-10.44.03-1.09.02-1.47-.33-1.3-1.36.02-.12.02-.26 0-.38-.28-1.39.39-1.96 1.7-1.9 1.36.06 1.76-.51 1.74-1.88-.09-5.17-.08-10.35 0-15.53.02-1.22-.32-1.87-1.52-2.17-.57-.14-1.47-.11-1.57-.85-.15-1.04-.05-2.11.01-3.17.02-.34.44-.35.73-.39 2.81-.39 5.63-.77 8.44-1.18.92-.14 1.15.2 1.14 1.09-.04 3.8-.02 7.62-.02 11.44z'
fill='currentColor'
/>
</svg>
)
}
export function BrowserUseIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -3587,6 +3632,29 @@ export function OpenRouterIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function MondayIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
viewBox='0 -50 256 256'
xmlns='http://www.w3.org/2000/svg'
preserveAspectRatio='xMidYMid'
>
<g>
<path
d='M31.8458633,153.488694 C20.3244423,153.513586 9.68073708,147.337265 3.98575204,137.321731 C-1.62714067,127.367831 -1.29055839,115.129325 4.86093879,105.498969 L62.2342919,15.4033556 C68.2125882,5.54538256 79.032489,-0.333585033 90.5563073,0.0146553508 C102.071737,0.290611552 112.546041,6.74705604 117.96667,16.9106216 C123.315033,27.0238906 122.646488,39.1914174 116.240607,48.6847625 L58.9037201,138.780375 C52.9943022,147.988884 42.7873202,153.537154 31.8458633,153.488694 L31.8458633,153.488694 Z'
fill='#F62B54'
/>
<path
d='M130.25575,153.488484 C118.683837,153.488484 108.035731,147.301291 102.444261,137.358197 C96.8438154,127.431292 97.1804475,115.223704 103.319447,105.620522 L160.583402,15.7315506 C166.47539,5.73210989 177.327374,-0.284878136 188.929728,0.0146553508 C200.598885,0.269918151 211.174058,6.7973526 216.522421,17.0078646 C221.834319,27.2183766 221.056375,39.4588356 214.456008,48.9278699 L157.204209,138.816842 C151.313487,147.985468 141.153618,153.5168 130.25575,153.488484 Z'
fill='#FFCC00'
/>
<ellipse fill='#00CA72' cx='226.465527' cy='125.324379' rx='29.5375538' ry='28.9176274' />
</g>
</svg>
)
}
export function MongoDBIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
@@ -4625,6 +4693,78 @@ export function DynamoDBIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function IAMIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 80 80' xmlns='http://www.w3.org/2000/svg'>
<defs>
<linearGradient x1='0%' y1='100%' x2='100%' y2='0%' id='iamGradient'>
<stop stopColor='#BD0816' offset='0%' />
<stop stopColor='#FF5252' offset='100%' />
</linearGradient>
</defs>
<rect fill='url(#iamGradient)' width='80' height='80' />
<path
d='M14,59 L66,59 L66,21 L14,21 L14,59 Z M68,20 L68,60 C68,60.552 67.553,61 67,61 L13,61 C12.447,61 12,60.552 12,60 L12,20 C12,19.448 12.447,19 13,19 L67,19 C67.553,19 68,19.448 68,20 L68,20 Z M44,48 L59,48 L59,46 L44,46 L44,48 Z M57,42 L62,42 L62,40 L57,40 L57,42 Z M44,42 L52,42 L52,40 L44,40 L44,42 Z M29,46 C29,45.449 28.552,45 28,45 C27.448,45 27,45.449 27,46 C27,46.551 27.448,47 28,47 C28.552,47 29,46.551 29,46 L29,46 Z M31,46 C31,47.302 30.161,48.401 29,48.816 L29,51 L27,51 L27,48.815 C25.839,48.401 25,47.302 25,46 C25,44.346 26.346,43 28,43 C29.654,43 31,44.346 31,46 L31,46 Z M19,53.993 L36.994,54 L36.996,50 L33,50 L33,48 L36.996,48 L36.998,45 L33,45 L33,43 L36.999,43 L37,40.007 L19.006,40 L19,53.993 Z M22,38.001 L34,38.006 L34,31 C34.001,28.697 31.197,26.677 28,26.675 L27.996,26.675 C24.804,26.675 22.004,28.696 22.002,31 L22,38.001 Z M17,54.992 L17.006,39 C17.006,38.734 17.111,38.48 17.299,38.292 C17.486,38.105 17.741,38 18.006,38 L20,38.001 L20.002,31 C20.004,27.512 23.59,24.675 27.996,24.675 L28,24.675 C32.412,24.677 36.001,27.515 36,31 L36,38.007 L38,38.008 C38.553,38.008 39,38.456 39,39.008 L38.994,55 C38.994,55.266 38.889,55.52 38.701,55.708 C38.514,55.895 38.259,56 37.994,56 L18,55.992 C17.447,55.992 17,55.544 17,54.992 L17,54.992 Z M60,36 L62,36 L62,34 L60,34 L60,36 Z M44,36 L55,36 L55,34 L44,34 L44,36 Z'
fill='#FFFFFF'
/>
</svg>
)
}
export function IdentityCenterIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 80 80' xmlns='http://www.w3.org/2000/svg'>
<defs>
<linearGradient x1='0%' y1='100%' x2='100%' y2='0%' id='identityCenterGradient'>
<stop stopColor='#BD0816' offset='0%' />
<stop stopColor='#FF5252' offset='100%' />
</linearGradient>
</defs>
<rect fill='url(#identityCenterGradient)' width='80' height='80' />
<path
d='M46.694,46.8194562 C47.376,46.1374562 47.376,45.0294562 46.694,44.3474562 C46.353,44.0074562 45.906,43.8374562 45.459,43.8374562 C45.01,43.8374562 44.563,44.0074562 44.222,44.3474562 C43.542,45.0284562 43.542,46.1384562 44.222,46.8194562 C44.905,47.5014562 46.013,47.4994562 46.694,46.8194562 M47.718,47.1374562 L51.703,51.1204562 L50.996,51.8274562 L49.868,50.6994562 L48.793,51.7754562 L48.086,51.0684562 L49.161,49.9924562 L47.011,47.8444562 C46.545,48.1654562 46.003,48.3294562 45.458,48.3294562 C44.755,48.3294562 44.051,48.0624562 43.515,47.5264562 C42.445,46.4554562 42.445,44.7124562 43.515,43.6404562 C44.586,42.5714562 46.329,42.5694562 47.401,43.6404562 C48.351,44.5904562 48.455,46.0674562 47.718,47.1374562 M53,44.1014562 C53,46.1684562 51.505,47.0934562 50.023,47.0934562 L50.023,46.0934562 C50.487,46.0934562 52,45.9494562 52,44.1014562 C52,43.0044562 51.353,42.3894562 49.905,42.1084562 C49.68,42.0654562 49.514,41.8754562 49.501,41.6484562 C49.446,40.7444562 48.987,40.1124562 48.384,40.1124562 C48.084,40.1124562 47.854,40.2424562 47.616,40.5464562 C47.506,40.6884562 47.324,40.7594562 47.147,40.7324562 C46.968,40.7054562 46.818,40.5844562 46.755,40.4144562 C46.577,39.9434562 46.211,39.4334562 45.723,38.9774562 C45.231,38.5094562 43.883,37.5074562 41.972,38.2734562 C40.885,38.7054562 40.034,39.9494562 40.034,41.1074562 C40.034,41.2354562 40.043,41.3624562 40.058,41.4884562 C40.061,41.5094562 40.062,41.5304562 40.062,41.5514562 C40.062,41.7994562 39.882,42.0064562 39.645,42.0464562 C38.886,42.2394562 38,42.7454562 38,44.0554562 L38.005,44.2104562 C38.069,45.3254562 39.252,45.9954562 40.358,45.9984562 L41,45.9984562 L41,46.9984562 L40.357,46.9984562 C38.536,46.9944562 37.095,45.8194562 37.006,44.2644562 C37.003,44.1944562 37,44.1244562 37,44.0554562 C37,42.6944562 37.752,41.6484562 39.035,41.1884562 C39.034,41.1614562 39.034,41.1344562 39.034,41.1074562 C39.034,39.5434562 40.138,37.9254562 41.602,37.3434562 C43.298,36.6654562 45.095,37.0034562 46.409,38.2494562 C46.706,38.5274562 47.076,38.9264562 47.372,39.4134562 C47.673,39.2124562 48.008,39.1124562 48.384,39.1124562 C49.257,39.1124562 50.231,39.7714562 50.458,41.2074562 C52.145,41.6324562 53,42.6054562 53,44.1014562 M27,53 L27,27 L53,27 L53,34 L51,34 L51,29 L29,29 L29,51 L51,51 L51,46 L53,46 L53,53 Z'
fill='#FFFFFF'
/>
</svg>
)
}
export function STSIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 80 80' xmlns='http://www.w3.org/2000/svg'>
<defs>
<linearGradient x1='0%' y1='100%' x2='100%' y2='0%' id='stsGradient'>
<stop stopColor='#BD0816' offset='0%' />
<stop stopColor='#FF5252' offset='100%' />
</linearGradient>
</defs>
<rect fill='url(#stsGradient)' width='80' height='80' />
<path
d='M14,59 L66,59 L66,21 L14,21 L14,59 Z M68,20 L68,60 C68,60.552 67.553,61 67,61 L13,61 C12.447,61 12,60.552 12,60 L12,20 C12,19.448 12.447,19 13,19 L67,19 C67.553,19 68,19.448 68,20 L68,20 Z M44,48 L59,48 L59,46 L44,46 L44,48 Z M57,42 L62,42 L62,40 L57,40 L57,42 Z M44,42 L52,42 L52,40 L44,40 L44,42 Z M29,46 C29,45.449 28.552,45 28,45 C27.448,45 27,45.449 27,46 C27,46.551 27.448,47 28,47 C28.552,47 29,46.551 29,46 L29,46 Z M31,46 C31,47.302 30.161,48.401 29,48.816 L29,51 L27,51 L27,48.815 C25.839,48.401 25,47.302 25,46 C25,44.346 26.346,43 28,43 C29.654,43 31,44.346 31,46 L31,46 Z M19,53.993 L36.994,54 L36.996,50 L33,50 L33,48 L36.996,48 L36.998,45 L33,45 L33,43 L36.999,43 L37,40.007 L19.006,40 L19,53.993 Z M22,38.001 L34,38.006 L34,31 C34.001,28.697 31.197,26.677 28,26.675 L27.996,26.675 C24.804,26.675 22.004,28.696 22.002,31 L22,38.001 Z M17,54.992 L17.006,39 C17.006,38.734 17.111,38.48 17.299,38.292 C17.486,38.105 17.741,38 18.006,38 L20,38.001 L20.002,31 C20.004,27.512 23.59,24.675 27.996,24.675 L28,24.675 C32.412,24.677 36.001,27.515 36,31 L36,38.007 L38,38.008 C38.553,38.008 39,38.456 39,39.008 L38.994,55 C38.994,55.266 38.889,55.52 38.701,55.708 C38.514,55.895 38.259,56 37.994,56 L18,55.992 C17.447,55.992 17,55.544 17,54.992 L17,54.992 Z M60,36 L62,36 L62,34 L60,34 L60,36 Z M44,36 L55,36 L55,34 L44,34 L44,36 Z'
fill='#FFFFFF'
/>
</svg>
)
}
export function SESIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 80 80' xmlns='http://www.w3.org/2000/svg'>
<defs>
<linearGradient x1='0%' y1='100%' x2='100%' y2='0%' id='sesGradient'>
<stop stopColor='#BD0816' offset='0%' />
<stop stopColor='#FF5252' offset='100%' />
</linearGradient>
</defs>
<rect fill='url(#sesGradient)' width='80' height='80' />
<path
d='M57,60.999875 C57,59.373846 55.626,57.9998214 54,57.9998214 C52.374,57.9998214 51,59.373846 51,60.999875 C51,62.625904 52.374,63.9999286 54,63.9999286 C55.626,63.9999286 57,62.625904 57,60.999875 L57,60.999875 Z M40,59.9998571 C38.374,59.9998571 37,61.3738817 37,62.9999107 C37,64.6259397 38.374,65.9999643 40,65.9999643 C41.626,65.9999643 43,64.6259397 43,62.9999107 C43,61.3738817 41.626,59.9998571 40,59.9998571 L40,59.9998571 Z M26,57.9998214 C24.374,57.9998214 23,59.373846 23,60.999875 C23,62.625904 24.374,63.9999286 26,63.9999286 C27.626,63.9999286 29,62.625904 29,60.999875 C29,59.373846 27.626,57.9998214 26,57.9998214 L26,57.9998214 Z M28.605,42.9995536 L51.395,42.9995536 L43.739,36.1104305 L40.649,38.7584778 C40.463,38.9194807 40.23,38.9994821 39.999,38.9994821 C39.768,38.9994821 39.535,38.9194807 39.349,38.7584778 L36.26,36.1104305 L28.605,42.9995536 Z M27,28.1732888 L27,41.7545313 L34.729,34.7984071 L27,28.1732888 Z M51.297,26.9992678 L28.703,26.9992678 L39.999,36.6824408 L51.297,26.9992678 Z M53,41.7545313 L53,28.1732888 L45.271,34.7974071 L53,41.7545313 Z M59,60.999875 C59,63.7099234 56.71,65.9999643 54,65.9999643 C51.29,65.9999643 49,63.7099234 49,60.999875 C49,58.6308327 50.75,56.5837961 53,56.1057876 L53,52.9997321 L41,52.9997321 L41,58.1058233 C43.25,58.5838319 45,60.6308684 45,62.9999107 C45,65.7099591 42.71,68 40,68 C37.29,68 35,65.7099591 35,62.9999107 C35,60.6308684 36.75,58.5838319 39,58.1058233 L39,52.9997321 L27,52.9997321 L27,56.1057876 C29.25,56.5837961 31,58.6308327 31,60.999875 C31,63.7099234 28.71,65.9999643 26,65.9999643 C23.29,65.9999643 21,63.7099234 21,60.999875 C21,58.6308327 22.75,56.5837961 25,56.1057876 L25,51.9997143 C25,51.4477044 25.447,50.9996964 26,50.9996964 L39,50.9996964 L39,44.9995893 L26,44.9995893 C25.447,44.9995893 25,44.5515813 25,43.9995714 L25,25.99925 C25,25.4472401 25.447,24.9992321 26,24.9992321 L54,24.9992321 C54.553,24.9992321 55,25.4472401 55,25.99925 L55,43.9995714 C55,44.5515813 54.553,44.9995893 54,44.9995893 L41,44.9995893 L41,50.9996964 L54,50.9996964 C54.553,50.9996964 55,51.4477044 55,51.9997143 L55,56.1057876 C57.25,56.5837961 59,58.6308327 59,60.999875 L59,60.999875 Z M68,39.9995 C68,45.9066055 66.177,51.5597064 62.727,56.3447919 L61.104,55.174771 C64.307,50.7316916 66,45.4845979 66,39.9995 C66,25.664244 54.337,14.0000357 40.001,14.0000357 C25.664,14.0000357 14,25.664244 14,39.9995 C14,45.4845979 15.693,50.7316916 18.896,55.174771 L17.273,56.3447919 C13.823,51.5597064 12,45.9066055 12,39.9995 C12,24.5612243 24.561,12 39.999,12 C55.438,12 68,24.5612243 68,39.9995 L68,39.9995 Z'
fill='#FFFFFF'
/>
</svg>
)
}
export function SecretsManagerIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 80 80' xmlns='http://www.w3.org/2000/svg'>
@@ -4835,6 +4975,17 @@ export function WordpressIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function AgiloftIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 47.3 47.2' xmlns='http://www.w3.org/2000/svg'>
<path d='M47.3,21.4H0v-4.3l4.3-4.2h43V21.4z' fill='#263A5C' />
<path d='M47.3,8.6H8.6L17.2,0h30.1V8.6z' fill='#001028' />
<path d='M0,25.7h47.3V30L43,34.4H0V25.7z' fill='#4A6587' />
<path d='M0,38.7h38.8l-8.6,8.5H0V38.7z' fill='#6D8DAF' />
</svg>
)
}
export function AhrefsIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1065 1300'>

View File

@@ -1,3 +1,5 @@
import { DOCS_BASE_URL } from '@/lib/urls'
interface StructuredDataProps {
title: string
description: string
@@ -15,7 +17,7 @@ export function StructuredData({
dateModified,
breadcrumb,
}: StructuredDataProps) {
const baseUrl = 'https://docs.sim.ai'
const baseUrl = DOCS_BASE_URL
const articleStructuredData = {
'@context': 'https://schema.org',

View File

@@ -1,6 +1,6 @@
'use client'
import { useState } from 'react'
import { useRef, useState } from 'react'
import { cn, getAssetUrl } from '@/lib/utils'
import { Lightbox } from './lightbox'
@@ -50,11 +50,14 @@ export function ActionImage({ src, alt, enableLightbox = true }: ActionImageProp
}
export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProps) {
const videoRef = useRef<HTMLVideoElement>(null)
const startTimeRef = useRef(0)
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
const resolvedSrc = getAssetUrl(src)
const handleClick = () => {
if (enableLightbox) {
startTimeRef.current = videoRef.current?.currentTime ?? 0
setIsLightboxOpen(true)
}
}
@@ -62,6 +65,7 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
return (
<>
<video
ref={videoRef}
src={resolvedSrc}
autoPlay
loop
@@ -80,6 +84,7 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
src={src}
alt={alt}
type='video'
startTime={startTimeRef.current}
/>
)}
</>

View File

@@ -1,195 +0,0 @@
import { memo } from 'react'
const RX = '2.59574'
interface BlockRect {
opacity: number
width: string
height: string
fill: string
x?: string
y?: string
transform?: string
}
const RECTS = {
topRight: [
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '33.7252', fill: '#2ABBF8' },
{ opacity: 0.6, x: '0', y: '0', width: '85.3433', height: '16.8626', fill: '#2ABBF8' },
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
{ opacity: 0.6, x: '34.2403', y: '0', width: '34.2403', height: '33.7252', fill: '#2ABBF8' },
{ opacity: 1, x: '34.2403', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
{
opacity: 1,
x: '51.6188',
y: '16.8626',
width: '16.8626',
height: '16.8626',
fill: '#2ABBF8',
},
{ opacity: 1, x: '68.4812', y: '0', width: '54.6502', height: '16.8626', fill: '#00F701' },
{ opacity: 0.6, x: '106.268', y: '0', width: '34.2403', height: '33.7252', fill: '#00F701' },
{ opacity: 0.6, x: '106.268', y: '0', width: '51.103', height: '16.8626', fill: '#00F701' },
{
opacity: 1,
x: '123.6484',
y: '16.8626',
width: '16.8626',
height: '16.8626',
fill: '#00F701',
},
{ opacity: 0.6, x: '157.371', y: '0', width: '34.2403', height: '16.8626', fill: '#FFCC02' },
{ opacity: 1, x: '157.371', y: '0', width: '16.8626', height: '16.8626', fill: '#FFCC02' },
{ opacity: 0.6, x: '208.993', y: '0', width: '68.4805', height: '16.8626', fill: '#FA4EDF' },
{ opacity: 0.6, x: '209.137', y: '0', width: '16.8626', height: '33.7252', fill: '#FA4EDF' },
{ opacity: 0.6, x: '243.233', y: '0', width: '34.2403', height: '33.7252', fill: '#FA4EDF' },
{ opacity: 1, x: '243.233', y: '0', width: '16.8626', height: '16.8626', fill: '#FA4EDF' },
{ opacity: 0.6, x: '260.096', y: '0', width: '34.04', height: '16.8626', fill: '#FA4EDF' },
{
opacity: 1,
x: '260.611',
y: '16.8626',
width: '16.8626',
height: '16.8626',
fill: '#FA4EDF',
},
],
bottomLeft: [
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '33.7252', fill: '#2ABBF8' },
{ opacity: 0.6, x: '0', y: '0', width: '85.3433', height: '16.8626', fill: '#2ABBF8' },
{ opacity: 1, x: '0', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
{ opacity: 0.6, x: '34.2403', y: '0', width: '34.2403', height: '33.7252', fill: '#2ABBF8' },
{ opacity: 1, x: '34.2403', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' },
{
opacity: 1,
x: '51.6188',
y: '16.8626',
width: '16.8626',
height: '16.8626',
fill: '#2ABBF8',
},
{ opacity: 1, x: '68.4812', y: '0', width: '54.6502', height: '16.8626', fill: '#00F701' },
{ opacity: 0.6, x: '106.268', y: '0', width: '34.2403', height: '33.7252', fill: '#00F701' },
{ opacity: 0.6, x: '106.268', y: '0', width: '51.103', height: '16.8626', fill: '#00F701' },
{
opacity: 1,
x: '123.6484',
y: '16.8626',
width: '16.8626',
height: '16.8626',
fill: '#00F701',
},
],
bottomRight: [
{
opacity: 0.6,
width: '16.8626',
height: '33.726',
fill: '#FA4EDF',
transform: 'matrix(0 1 1 0 0 0)',
},
{
opacity: 0.6,
width: '34.241',
height: '16.8626',
fill: '#FA4EDF',
transform: 'matrix(0 1 1 0 16.891 0)',
},
{
opacity: 0.6,
width: '16.8626',
height: '68.482',
fill: '#FA4EDF',
transform: 'matrix(-1 0 0 1 33.739 16.888)',
},
{
opacity: 0.6,
width: '16.8626',
height: '33.726',
fill: '#FA4EDF',
transform: 'matrix(0 1 1 0 0 33.776)',
},
{
opacity: 1,
width: '16.8626',
height: '16.8626',
fill: '#FA4EDF',
transform: 'matrix(-1 0 0 1 33.739 34.272)',
},
{
opacity: 0.6,
width: '16.8626',
height: '34.24',
fill: '#2ABBF8',
transform: 'matrix(-1 0 0 1 33.787 68)',
},
{
opacity: 0.4,
width: '16.8626',
height: '16.8626',
fill: '#1A8FCC',
transform: 'matrix(-1 0 0 1 33.787 85)',
},
],
} as const satisfies Record<string, readonly BlockRect[]>
const GLOBAL_OPACITY = 0.55
const BlockGroup = memo(function BlockGroup({
width,
height,
viewBox,
rects,
}: {
width: number
height: number
viewBox: string
rects: readonly BlockRect[]
}) {
return (
<svg
width={width}
height={height}
viewBox={viewBox}
fill='none'
xmlns='http://www.w3.org/2000/svg'
className='h-auto w-full'
style={{ opacity: GLOBAL_OPACITY }}
>
{rects.map((r, i) => (
<rect
key={i}
x={r.x}
y={r.y}
width={r.width}
height={r.height}
rx={RX}
fill={r.fill}
transform={r.transform}
opacity={r.opacity}
/>
))}
</svg>
)
})
export function AnimatedBlocks() {
return (
<div
className='pointer-events-none fixed inset-0 z-0 hidden overflow-hidden lg:block'
aria-hidden='true'
>
<div className='absolute top-[93px] right-0 w-[calc(140px+10.76vw)] max-w-[295px]'>
<BlockGroup width={295} height={34} viewBox='0 0 295 34' rects={RECTS.topRight} />
</div>
<div className='-left-24 absolute bottom-0 w-[calc(140px+10.76vw)] max-w-[295px] rotate-180'>
<BlockGroup width={295} height={34} viewBox='0 0 295 34' rects={RECTS.bottomLeft} />
</div>
<div className='-bottom-2 absolute right-0 w-[calc(16px+1.25vw)] max-w-[34px]'>
<BlockGroup width={34} height={102} viewBox='0 0 34 102' rects={RECTS.bottomRight} />
</div>
</div>
)
}

View File

@@ -2,6 +2,7 @@
import { useState } from 'react'
import { ChevronRight } from 'lucide-react'
import { cn } from '@/lib/utils'
interface FAQItem {
question: string
@@ -31,9 +32,10 @@ function FAQItemRow({
className='flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-left font-[470] text-[0.875rem] text-[rgba(0,0,0,0.8)] transition-colors hover:bg-[rgba(0,0,0,0.02)] dark:text-[rgba(255,255,255,0.85)] dark:hover:bg-[rgba(255,255,255,0.03)]'
>
<ChevronRight
className={`h-3.5 w-3.5 shrink-0 text-[rgba(0,0,0,0.3)] transition-transform duration-200 dark:text-[rgba(255,255,255,0.3)] ${
isOpen ? 'rotate-90' : ''
}`}
className={cn(
'h-3.5 w-3.5 shrink-0 text-[rgba(0,0,0,0.3)] transition-transform duration-200 dark:text-[rgba(255,255,255,0.3)]',
isOpen && 'rotate-90'
)}
/>
{item.question}
</button>
@@ -81,11 +83,10 @@ export function FAQ({ items, title = 'Common Questions' }: FAQProps) {
{items.map((item, index) => (
<div
key={index}
className={
index !== items.length - 1
? 'border-[rgba(0,0,0,0.08)] border-b dark:border-[rgba(255,255,255,0.08)]'
: ''
}
className={cn(
index !== items.length - 1 &&
'border-[rgba(0,0,0,0.08)] border-b dark:border-[rgba(255,255,255,0.08)]'
)}
>
<FAQItemRow
item={item}

View File

@@ -6,6 +6,8 @@ import type { ComponentType, SVGProps } from 'react'
import {
A2AIcon,
AgentMailIcon,
AgentPhoneIcon,
AgiloftIcon,
AhrefsIcon,
AirtableIcon,
AirweaveIcon,
@@ -22,6 +24,7 @@ import {
BoxCompanyIcon,
BrainIcon,
BrandfetchIcon,
BrightDataIcon,
BrowserUseIcon,
CalComIcon,
CalendlyIcon,
@@ -88,6 +91,8 @@ import {
HubspotIcon,
HuggingFaceIcon,
HunterIOIcon,
IAMIcon,
IdentityCenterIcon,
ImageIcon,
IncidentioIcon,
InfisicalIcon,
@@ -116,6 +121,7 @@ import {
MicrosoftSharepointIcon,
MicrosoftTeamsIcon,
MistralIcon,
MondayIcon,
MongoDBIcon,
MySQLIcon,
Neo4jIcon,
@@ -148,6 +154,7 @@ import {
RootlyIcon,
S3Icon,
SalesforceIcon,
SESIcon,
SearchIcon,
SecretsManagerIcon,
SendgridIcon,
@@ -162,6 +169,7 @@ import {
SmtpIcon,
SQSIcon,
SshIcon,
STSIcon,
STTIcon,
StagehandIcon,
StripeIcon,
@@ -197,6 +205,8 @@ type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
a2a: A2AIcon,
agentmail: AgentMailIcon,
agentphone: AgentPhoneIcon,
agiloft: AgiloftIcon,
ahrefs: AhrefsIcon,
airtable: AirtableIcon,
airweave: AirweaveIcon,
@@ -211,6 +221,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
attio: AttioIcon,
box: BoxCompanyIcon,
brandfetch: BrandfetchIcon,
brightdata: BrightDataIcon,
browser_use: BrowserUseIcon,
calcom: CalComIcon,
calendly: CalendlyIcon,
@@ -220,8 +231,10 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
cloudflare: CloudflareIcon,
cloudformation: CloudFormationIcon,
cloudwatch: CloudWatchIcon,
confluence: ConfluenceIcon,
confluence_v2: ConfluenceIcon,
crowdstrike: CrowdStrikeIcon,
cursor: CursorIcon,
cursor_v2: CursorIcon,
dagster: DagsterIcon,
databricks: DatabricksIcon,
@@ -239,19 +252,25 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
enrich: EnrichSoIcon,
evernote: EvernoteIcon,
exa: ExaAIIcon,
extend: ExtendIcon,
extend_v2: ExtendIcon,
fathom: FathomIcon,
file: DocumentIcon,
file_v3: DocumentIcon,
firecrawl: FirecrawlIcon,
fireflies: FirefliesIcon,
fireflies_v2: FirefliesIcon,
gamma: GammaIcon,
github: GithubIcon,
github_v2: GithubIcon,
gitlab: GitLabIcon,
gmail: GmailIcon,
gmail_v2: GmailIcon,
gong: GongIcon,
google_ads: GoogleAdsIcon,
google_bigquery: GoogleBigQueryIcon,
google_books: GoogleBooksIcon,
google_calendar: GoogleCalendarIcon,
google_calendar_v2: GoogleCalendarIcon,
google_contacts: GoogleContactsIcon,
google_docs: GoogleDocsIcon,
@@ -262,7 +281,9 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_meet: GoogleMeetIcon,
google_pagespeed: GooglePagespeedIcon,
google_search: GoogleIcon,
google_sheets: GoogleSheetsIcon,
google_sheets_v2: GoogleSheetsIcon,
google_slides: GoogleSlidesIcon,
google_slides_v2: GoogleSlidesIcon,
google_tasks: GoogleTasksIcon,
google_translate: GoogleTranslateIcon,
@@ -276,20 +297,25 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
hubspot: HubspotIcon,
huggingface: HuggingFaceIcon,
hunter: HunterIOIcon,
iam: IAMIcon,
identity_center: IdentityCenterIcon,
image_generator: ImageIcon,
imap: MailServerIcon,
incidentio: IncidentioIcon,
infisical: InfisicalIcon,
intercom: IntercomIcon,
intercom_v2: IntercomIcon,
jina: JinaAIIcon,
jira: JiraIcon,
jira_service_management: JiraServiceManagementIcon,
kalshi: KalshiIcon,
kalshi_v2: KalshiIcon,
ketch: KetchIcon,
knowledge: PackageSearchIcon,
langsmith: LangsmithIcon,
launchdarkly: LaunchDarklyIcon,
lemlist: LemlistIcon,
linear: LinearIcon,
linear_v2: LinearIcon,
linkedin: LinkedInIcon,
linkup: LinkupIcon,
@@ -301,13 +327,17 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
memory: BrainIcon,
microsoft_ad: AzureIcon,
microsoft_dataverse: MicrosoftDataverseIcon,
microsoft_excel: MicrosoftExcelIcon,
microsoft_excel_v2: MicrosoftExcelIcon,
microsoft_planner: MicrosoftPlannerIcon,
microsoft_teams: MicrosoftTeamsIcon,
mistral_parse: MistralIcon,
mistral_parse_v3: MistralIcon,
monday: MondayIcon,
mongodb: MongoDBIcon,
mysql: MySQLIcon,
neo4j: Neo4jIcon,
notion: NotionIcon,
notion_v2: NotionIcon,
obsidian: ObsidianIcon,
okta: OktaIcon,
@@ -324,12 +354,14 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
postgresql: PostgresIcon,
posthog: PosthogIcon,
profound: ProfoundIcon,
pulse: PulseIcon,
pulse_v2: PulseIcon,
qdrant: QdrantIcon,
quiver: QuiverIcon,
rds: RDSIcon,
reddit: RedditIcon,
redis: RedisIcon,
reducto: ReductoIcon,
reducto_v2: ReductoIcon,
resend: ResendIcon,
revenuecat: RevenueCatIcon,
@@ -343,6 +375,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
sentry: SentryIcon,
serper: SerperIcon,
servicenow: ServiceNowIcon,
ses: SESIcon,
sftp: SftpIcon,
sharepoint: MicrosoftSharepointIcon,
shopify: ShopifyIcon,
@@ -354,11 +387,14 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
ssh: SshIcon,
stagehand: StagehandIcon,
stripe: StripeIcon,
sts: STSIcon,
stt: STTIcon,
stt_v2: STTIcon,
supabase: SupabaseIcon,
tailscale: TailscaleIcon,
tavily: TavilyIcon,
telegram: TelegramIcon,
textract: TextractIcon,
textract_v2: TextractIcon,
tinybird: TinybirdIcon,
translate: TranslateIcon,
@@ -369,7 +405,9 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
typeform: TypeformIcon,
upstash: UpstashIcon,
vercel: VercelIcon,
video_generator: VideoIcon,
video_generator_v2: VideoIcon,
vision: EyeIcon,
vision_v2: EyeIcon,
wealthbox: WealthboxIcon,
webflow: WebflowIcon,

View File

@@ -1,6 +1,5 @@
'use client'
import { useEffect, useState } from 'react'
import { Check } from 'lucide-react'
import { useParams, usePathname, useRouter } from 'next/navigation'
import {
@@ -25,24 +24,9 @@ export function LanguageDropdown() {
const params = useParams()
const router = useRouter()
const [currentLang, setCurrentLang] = useState(() => {
const langFromParams = params?.lang as string
return langFromParams && Object.keys(languages).includes(langFromParams) ? langFromParams : 'en'
})
useEffect(() => {
const langFromParams = params?.lang as string
if (langFromParams && Object.keys(languages).includes(langFromParams)) {
if (langFromParams !== currentLang) {
setCurrentLang(langFromParams)
}
} else {
if (currentLang !== 'en') {
setCurrentLang('en')
}
}
}, [params])
const langFromParams = params?.lang as string
const currentLang =
langFromParams && Object.keys(languages).includes(langFromParams) ? langFromParams : 'en'
const handleLanguageChange = (locale: string) => {
if (locale === currentLang) return

View File

@@ -1,6 +1,6 @@
'use client'
import { useEffect, useRef } from 'react'
import { useEffect, useLayoutEffect, useRef } from 'react'
import { getAssetUrl } from '@/lib/utils'
interface LightboxProps {
@@ -9,10 +9,12 @@ interface LightboxProps {
src: string
alt: string
type: 'image' | 'video'
startTime?: number
}
export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
export function Lightbox({ isOpen, onClose, src, alt, type, startTime }: LightboxProps) {
const overlayRef = useRef<HTMLDivElement>(null)
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -40,6 +42,12 @@ export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
}
}, [isOpen, onClose])
useLayoutEffect(() => {
if (isOpen && type === 'video' && videoRef.current && startTime != null && startTime > 0) {
videoRef.current.currentTime = startTime
}
}, [isOpen, startTime, type])
if (!isOpen) return null
return (
@@ -61,6 +69,7 @@ export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
/>
) : (
<video
ref={videoRef}
src={getAssetUrl(src)}
autoPlay
loop

View File

@@ -1,7 +1,7 @@
'use client'
import { useState } from 'react'
import { getAssetUrl } from '@/lib/utils'
import { useRef, useState } from 'react'
import { cn, getAssetUrl } from '@/lib/utils'
import { Lightbox } from './lightbox'
interface VideoProps {
@@ -12,6 +12,8 @@ interface VideoProps {
muted?: boolean
playsInline?: boolean
enableLightbox?: boolean
width?: number
height?: number
}
export function Video({
@@ -22,11 +24,16 @@ export function Video({
muted = true,
playsInline = true,
enableLightbox = true,
width,
height,
}: VideoProps) {
const videoRef = useRef<HTMLVideoElement>(null)
const startTimeRef = useRef(0)
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
const handleVideoClick = () => {
if (enableLightbox) {
startTimeRef.current = videoRef.current?.currentTime ?? 0
setIsLightboxOpen(true)
}
}
@@ -34,11 +41,17 @@ export function Video({
return (
<>
<video
ref={videoRef}
autoPlay={autoPlay}
loop={loop}
muted={muted}
playsInline={playsInline}
className={`${className} ${enableLightbox ? 'cursor-pointer transition-opacity hover:opacity-95' : ''}`}
width={width}
height={height}
className={cn(
className,
enableLightbox && 'cursor-pointer transition-opacity hover:opacity-95'
)}
src={getAssetUrl(src)}
onClick={handleVideoClick}
/>
@@ -50,6 +63,7 @@ export function Video({
src={src}
alt={`Video: ${src}`}
type='video'
startTime={startTimeRef.current}
/>
)}
</>

View File

@@ -51,7 +51,7 @@ Willkommen bei Sim, einem visuellen Workflow-Builder für KI-Anwendungen. Erstel
<Card title="MCP-Integration" href="/mcp">
Externe Dienste mit dem Model Context Protocol verbinden
</Card>
<Card title="SDKs" href="/sdks">
<Card title="SDKs" href="/api-reference">
Sim in Ihre Anwendungen integrieren
</Card>
</Cards>

View File

@@ -65,14 +65,14 @@ Execute a workflow with optional input data.
```python
result = client.execute_workflow(
"workflow-id",
input_data={"message": "Hello, world!"},
input={"message": "Hello, world!"},
timeout=30.0 # 30 seconds
)
```
**Parameters:**
- `workflow_id` (str): The ID of the workflow to execute
- `input_data` (dict, optional): Input data to pass to the workflow
- `input` (dict, optional): Input data to pass to the workflow
- `timeout` (float, optional): Timeout in seconds (default: 30.0)
- `stream` (bool, optional): Enable streaming responses (default: False)
- `selected_outputs` (list[str], optional): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`)
@@ -144,7 +144,7 @@ Execute a workflow with automatic retry on rate limit errors using exponential b
```python
result = client.execute_with_retry(
"workflow-id",
input_data={"message": "Hello"},
input={"message": "Hello"},
timeout=30.0,
max_retries=3, # Maximum number of retries
initial_delay=1.0, # Initial delay in seconds
@@ -155,7 +155,7 @@ result = client.execute_with_retry(
**Parameters:**
- `workflow_id` (str): The ID of the workflow to execute
- `input_data` (dict, optional): Input data to pass to the workflow
- `input` (dict, optional): Input data to pass to the workflow
- `timeout` (float, optional): Timeout in seconds
- `stream` (bool, optional): Enable streaming responses
- `selected_outputs` (list, optional): Block outputs to stream
@@ -359,7 +359,7 @@ def run_workflow():
# Execute the workflow
result = client.execute_workflow(
"my-workflow-id",
input_data={
input={
"message": "Process this data",
"user_id": "12345"
}
@@ -488,7 +488,7 @@ def execute_async():
# Start async execution
result = client.execute_workflow(
"workflow-id",
input_data={"data": "large dataset"},
input={"data": "large dataset"},
async_execution=True # Execute asynchronously
)
@@ -533,7 +533,7 @@ def execute_with_retry_handling():
# Automatically retries on rate limit
result = client.execute_with_retry(
"workflow-id",
input_data={"message": "Process this"},
input={"message": "Process this"},
max_retries=5,
initial_delay=1.0,
max_delay=60.0,
@@ -615,7 +615,7 @@ def execute_with_streaming():
# Enable streaming for specific block outputs
result = client.execute_workflow(
"workflow-id",
input_data={"message": "Count to five"},
input={"message": "Count to five"},
stream=True,
selected_outputs=["agent1.content"] # Use blockName.attribute format
)
@@ -758,4 +758,15 @@ Configure the client using environment variables:
## License
Apache-2.0
Apache-2.0
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "Do I need to deploy a workflow before I can execute it via the SDK?", answer: "Yes. Workflows must be deployed before they can be executed through the SDK. You can use the validate_workflow() method to check whether a workflow is deployed and ready. If it returns False, deploy the workflow from the Sim UI first and create or select an API key during deployment." },
{ question: "What is the difference between sync and async execution?", answer: "Sync execution (the default) blocks until the workflow completes and returns the full result. Async execution (async_execution=True) returns immediately with a task ID that you can poll using get_job_status(). Use async mode for long-running workflows to avoid request timeouts. Async job statuses include queued, processing, completed, failed, and cancelled." },
{ question: "How does the SDK handle rate limiting?", answer: "The SDK provides built-in rate limiting support through the execute_with_retry() method. It uses exponential backoff (1s, 2s, 4s, 8s...) with 25% jitter to avoid thundering herd problems. If the API returns a retry-after header, that value is used instead. You can configure max_retries, initial_delay, max_delay, and backoff_multiplier. Use get_rate_limit_info() to check your current rate limit status." },
{ question: "Can I use the Python SDK as a context manager?", answer: "Yes. The SimStudioClient supports Python's context manager protocol. Use it with the 'with' statement to automatically close the underlying HTTP session when you are done, which is especially useful for scripts that create and discard client instances." },
{ question: "How do I handle different types of errors from the SDK?", answer: "The SDK raises SimStudioError with a code property for API-specific errors. Common error codes are UNAUTHORIZED (invalid API key), TIMEOUT (request timed out), RATE_LIMIT_EXCEEDED (too many requests), USAGE_LIMIT_EXCEEDED (billing limit reached), and EXECUTION_ERROR (workflow failed). Use the error code to implement targeted error handling and recovery logic." },
{ question: "How do I monitor my API usage and remaining quota?", answer: "Use the get_usage_limits() method to check your current usage. It returns sync and async rate limit details (limit, remaining, reset time, whether you are currently limited), plus your current period cost, usage limit, and plan tier. This lets you monitor consumption and alert before hitting limits." },
]} />

View File

@@ -78,16 +78,15 @@ new SimStudioClient(config: SimStudioConfig)
Execute a workflow with optional input data.
```typescript
const result = await client.executeWorkflow('workflow-id', {
input: { message: 'Hello, world!' },
const result = await client.executeWorkflow('workflow-id', { message: 'Hello, world!' }, {
timeout: 30000 // 30 seconds
});
```
**Parameters:**
- `workflowId` (string): The ID of the workflow to execute
- `input` (any, optional): Input data to pass to the workflow
- `options` (ExecutionOptions, optional):
- `input` (any): Input data to pass to the workflow
- `timeout` (number): Timeout in milliseconds (default: 30000)
- `stream` (boolean): Enable streaming responses (default: false)
- `selectedOutputs` (string[]): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`)
@@ -158,8 +157,7 @@ if (status.status === 'completed') {
Execute a workflow with automatic retry on rate limit errors using exponential backoff.
```typescript
const result = await client.executeWithRetry('workflow-id', {
input: { message: 'Hello' },
const result = await client.executeWithRetry('workflow-id', { message: 'Hello' }, {
timeout: 30000
}, {
maxRetries: 3, // Maximum number of retries
@@ -171,6 +169,7 @@ const result = await client.executeWithRetry('workflow-id', {
**Parameters:**
- `workflowId` (string): The ID of the workflow to execute
- `input` (any, optional): Input data to pass to the workflow
- `options` (ExecutionOptions, optional): Same as `executeWorkflow()`
- `retryOptions` (RetryOptions, optional):
- `maxRetries` (number): Maximum number of retries (default: 3)
@@ -389,10 +388,8 @@ async function runWorkflow() {
// Execute the workflow
const result = await client.executeWorkflow('my-workflow-id', {
input: {
message: 'Process this data',
userId: '12345'
}
});
if (result.success) {
@@ -508,8 +505,7 @@ app.post('/execute-workflow', async (req, res) => {
try {
const { workflowId, input } = req.body;
const result = await client.executeWorkflow(workflowId, {
input,
const result = await client.executeWorkflow(workflowId, input, {
timeout: 60000
});
@@ -555,8 +551,7 @@ export default async function handler(
try {
const { workflowId, input } = req.body;
const result = await client.executeWorkflow(workflowId, {
input,
const result = await client.executeWorkflow(workflowId, input, {
timeout: 30000
});
@@ -586,9 +581,7 @@ const client = new SimStudioClient({
async function executeClientSideWorkflow() {
try {
const result = await client.executeWorkflow('workflow-id', {
input: {
userInput: 'Hello from browser'
}
});
console.log('Workflow result:', result);
@@ -642,10 +635,8 @@ Alternatively, you can manually provide files using the URL format:
// Include files under the field name from your API trigger's input format
const result = await client.executeWorkflow('workflow-id', {
input: {
documents: files, // Must match your workflow's "files" field name
instructions: 'Analyze these documents'
}
});
console.log('Result:', result);
@@ -669,10 +660,8 @@ Alternatively, you can manually provide files using the URL format:
// Include files under the field name from your API trigger's input format
const result = await client.executeWorkflow('workflow-id', {
input: {
documents: [file], // Must match your workflow's "files" field name
query: 'Summarize this document'
}
});
```
</Tab>
@@ -712,8 +701,7 @@ export function useWorkflow(): UseWorkflowResult {
setResult(null);
try {
const workflowResult = await client.executeWorkflow(workflowId, {
input,
const workflowResult = await client.executeWorkflow(workflowId, input, {
timeout: 30000
});
setResult(workflowResult);
@@ -774,8 +762,7 @@ const client = new SimStudioClient({
async function executeAsync() {
try {
// Start async execution
const result = await client.executeWorkflow('workflow-id', {
input: { data: 'large dataset' },
const result = await client.executeWorkflow('workflow-id', { data: 'large dataset' }, {
async: true // Execute asynchronously
});
@@ -823,9 +810,7 @@ const client = new SimStudioClient({
async function executeWithRetryHandling() {
try {
// Automatically retries on rate limit
const result = await client.executeWithRetry('workflow-id', {
input: { message: 'Process this' }
}, {
const result = await client.executeWithRetry('workflow-id', { message: 'Process this' }, {}, {
maxRetries: 5,
initialDelay: 1000,
maxDelay: 60000,
@@ -908,8 +893,7 @@ const client = new SimStudioClient({
async function executeWithStreaming() {
try {
// Enable streaming for specific block outputs
const result = await client.executeWorkflow('workflow-id', {
input: { message: 'Count to five' },
const result = await client.executeWorkflow('workflow-id', { message: 'Count to five' }, {
stream: true,
selectedOutputs: ['agent1.content'] // Use blockName.attribute format
});
@@ -1033,3 +1017,14 @@ function StreamingWorkflow() {
## License
Apache-2.0
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "Do I need to deploy a workflow before I can execute it via the SDK?", answer: "Yes. Workflows must be deployed before they can be executed through the SDK. You can use the validateWorkflow() method to check whether a workflow is deployed and ready. If it returns false, deploy the workflow from the Sim UI first and create or select an API key during deployment." },
{ question: "What is the difference between sync and async execution?", answer: "Sync execution (the default) blocks until the workflow completes and returns the full result. Async execution returns immediately with a task ID that you can poll using getJobStatus(). Use async mode for long-running workflows to avoid request timeouts. Async job statuses include queued, processing, completed, failed, and cancelled." },
{ question: "How does streaming work with the SDK?", answer: "Enable streaming by setting stream: true and specifying selectedOutputs with block names and attributes in blockName.attribute format (e.g., ['agent1.content']). The response uses Server-Sent Events (SSE) format, sending incremental chunks as the workflow executes. Each chunk includes the blockId and the text content. A final done event includes the execution metadata." },
{ question: "How does the SDK handle rate limiting?", answer: "The SDK provides built-in rate limiting support through the executeWithRetry() method. It uses exponential backoff (1s, 2s, 4s, 8s...) with 25% jitter to avoid thundering herd problems. If the API returns a retry-after header, that value is used instead. You can configure maxRetries, initialDelay, maxDelay, and backoffMultiplier. Use getRateLimitInfo() to check your current rate limit status." },
{ question: "Is it safe to use the SDK in browser-side code?", answer: "You can use the SDK in the browser, but you should not expose your API key in client-side code. In production, use a backend proxy server to handle SDK calls, or use a public API key with limited permissions. The SDK works with both Node.js and browser environments, but sensitive keys should stay server-side." },
{ question: "How do I send files to a workflow through the SDK?", answer: "File objects are automatically detected and converted to base64 format. Include them in the input object under the field name that matches your workflow's API trigger input format. In the browser, pass File objects directly from file inputs. In Node.js, create File objects from buffers. You can also provide files as URL references with type, data, name, and mime fields." },
]} />

View File

@@ -2,10 +2,12 @@
title: Function
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
The Function block executes custom JavaScript or TypeScript code in your workflows. Transform data, perform calculations, or implement custom logic.
The Function block executes custom JavaScript, TypeScript, or Python code in your workflows. Transform data, perform calculations, or implement custom logic.
<div className="flex justify-center">
<Image
@@ -41,6 +43,8 @@ Input → Function (Validate & Sanitize) → API (Save to Database)
### Example: Loyalty Score Calculator
<Tabs items={['JavaScript', 'Python']}>
<Tab value="JavaScript">
```javascript title="loyalty-calculator.js"
// Process customer data and calculate loyalty score
const { purchaseHistory, accountAge, supportTickets } = <agent>;
@@ -64,6 +68,120 @@ return {
metrics: { spendScore, frequencyScore, supportScore }
};
```
</Tab>
<Tab value="Python">
```python title="loyalty-calculator.py"
import json
# Reference outputs from other blocks using angle bracket syntax
data = json.loads('<agent>')
purchase_history = data["purchaseHistory"]
account_age = data["accountAge"]
support_tickets = data["supportTickets"]
# Calculate metrics
total_spent = sum(p["amount"] for p in purchase_history)
purchase_frequency = len(purchase_history) / (account_age / 365)
ticket_ratio = support_tickets["resolved"] / support_tickets["total"]
# Calculate loyalty score (0-100)
spend_score = min(total_spent / 1000 * 30, 30)
frequency_score = min(purchase_frequency * 20, 40)
support_score = ticket_ratio * 30
loyalty_score = round(spend_score + frequency_score + support_score)
tier = "Platinum" if loyalty_score >= 80 else "Gold" if loyalty_score >= 60 else "Silver"
result = {
"customer": data["name"],
"loyaltyScore": loyalty_score,
"loyaltyTier": tier,
"metrics": {
"spendScore": spend_score,
"frequencyScore": frequency_score,
"supportScore": support_score
}
}
print(json.dumps(result))
```
</Tab>
</Tabs>
## Python Support
The Function block supports Python as an alternative to JavaScript. Python code runs in a secure [E2B](https://e2b.dev) cloud sandbox.
<div className="flex justify-center">
<Image
src="/static/blocks/function-python.png"
alt="Function block with Python selected"
width={400}
height={500}
className="my-6"
/>
</div>
### Enabling Python
Select **Python** from the language dropdown in the Function block. Python execution requires E2B to be enabled on your Sim instance.
<Callout type="warn">
If you don't see Python as an option in the language dropdown, E2B is not enabled. This only applies to self-hosted instances — E2B is enabled by default on sim.ai.
</Callout>
<Callout type="info">
Python code always runs in the E2B sandbox, even for simple scripts without imports. This ensures a secure, isolated execution environment.
</Callout>
### Returning Results
In Python, print your result as JSON to stdout. The Function block captures stdout and makes it available via `<function.result>`:
```python title="example.py"
import json
data = {"status": "processed", "count": 42}
print(json.dumps(data))
```
### Available Libraries
The E2B sandbox includes the Python standard library (`json`, `re`, `datetime`, `math`, `os`, `collections`, etc.) and common packages like `matplotlib` for visualization. Charts generated with matplotlib are captured as images automatically.
<Callout type="info">
The exact set of pre-installed packages depends on the E2B sandbox configuration. If a package you need isn't available, consider calling an external API from your code instead.
</Callout>
### Matplotlib Charts
When your Python code generates matplotlib figures, they are automatically captured and returned as base64-encoded PNG images in the output:
```python title="chart.py"
import matplotlib.pyplot as plt
import json
data = json.loads('<api.data>')
plt.figure(figsize=(10, 6))
plt.bar(data["labels"], data["values"])
plt.title("Monthly Revenue")
plt.xlabel("Month")
plt.ylabel("Revenue ($)")
plt.savefig("chart.png")
plt.show()
```
{/* TODO: Screenshot of Python code execution output in the logs panel */}
### JavaScript vs. Python
| | JavaScript | Python |
|--|-----------|--------|
| **Execution** | Local VM (fast) or E2B sandbox (with imports) | Always E2B sandbox |
| **Returning results** | `return { ... }` | `print(json.dumps({ ... }))` |
| **HTTP requests** | `fetch()` built-in | `requests` or `httpx` |
| **Best for** | Quick transforms, JSON manipulation | Data science, charting, complex math |
## Best Practices

View File

@@ -1,225 +1,71 @@
---
title: Copilot
description: Your per-workflow AI assistant for building and editing workflows.
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Image } from '@/components/ui/image'
import { MessageCircle, Hammer, ListChecks, Zap, Globe, Paperclip, History, RotateCcw, Brain } from 'lucide-react'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
Copilot is your in-editor assistant that helps you build and edit workflows. It can:
Copilot is the AI assistant built into every workflow editor. It is scoped to the workflow you have open — it reads the current structure, makes changes directly, and saves checkpoints so you can revert if needed.
- **Explain**: Answer questions about Sim and your current workflow
- **Guide**: Suggest edits and best practices
- **Build**: Add blocks, wire connections, and configure settings
- **Debug**: Analyze execution issues and optimize performance
For workspace-wide tasks (managing multiple workflows, running research, working with tables, scheduling jobs), use [Mothership](/mothership).
<Callout type="info">
Copilot is a Sim-managed service. For self-hosted deployments:
1. Go to [sim.ai](https://sim.ai) → Settings → Copilot and generate a Copilot API key
2. Set `COPILOT_API_KEY` in your self-hosted environment
Copilot is a Sim-managed service. For self-hosted deployments, go to [sim.ai](https://sim.ai) → Settings → Copilot, generate a Copilot API key, then set `COPILOT_API_KEY` in your self-hosted environment.
</Callout>
## Modes
<Video src="copilot/copilot.mp4" width={700} height={450} />
Switch between modes using the mode selector at the bottom of the input area.
## What Copilot Can Do
<Cards>
<Card
title={
<span className="inline-flex items-center gap-2">
<MessageCircle className="h-4 w-4 text-muted-foreground" />
Ask
</span>
}
>
<div className="m-0 text-sm">
Q&A mode for explanations, guidance, and suggestions without making changes to your workflow.
</div>
</Card>
<Card
title={
<span className="inline-flex items-center gap-2">
<Hammer className="h-4 w-4 text-muted-foreground" />
Build
</span>
}
>
<div className="m-0 text-sm">
Workflow building mode. Copilot can add blocks, wire connections, edit configurations, and debug issues.
</div>
</Card>
<Card
title={
<span className="inline-flex items-center gap-2">
<ListChecks className="h-4 w-4 text-muted-foreground" />
Plan
</span>
}
>
<div className="m-0 text-sm">
Creates a step-by-step implementation plan for your workflow without making any changes. Helps you think through the approach before building.
</div>
</Card>
</Cards>
Copilot can read and modify the workflow you are currently editing:
## Models
- Add, configure, and connect blocks
- Edit existing block configurations
- Delete blocks and connections
- Debug failures by reading execution logs
- Answer questions about the workflow or how Sim works
Select your preferred AI model using the model selector at the bottom right of the input area.
## Chat History
**Available Models:**
- Claude 4.6 Opus (default), 4.5 Opus, Sonnet, Haiku
- GPT 5.2 Codex, Pro
- Gemini 3 Pro
Choose based on your needs: faster models for simple tasks, more capable models for complex workflows.
## Context Menu (@)
Use the `@` symbol to reference resources and give Copilot more context:
| Reference | Description |
|-----------|-------------|
| **Chats** | Previous copilot conversations |
| **Workflows** | Any workflow in your workspace |
| **Workflow Blocks** | Blocks in the current workflow |
| **Blocks** | Block types and templates |
| **Knowledge** | Uploaded documents and knowledge bases |
| **Docs** | Sim documentation |
| **Templates** | Workflow templates |
| **Logs** | Execution logs and results |
Type `@` in the input field to open the context menu, then search or browse to find what you need.
## Slash Commands (/)
Use slash commands for quick actions:
| Command | Description |
|---------|-------------|
| `/fast` | Fast mode execution |
| `/research` | Research and exploration mode |
| `/actions` | Execute agent actions |
**Web Commands:**
| Command | Description |
|---------|-------------|
| `/search` | Search the web |
| `/read` | Read a specific URL |
| `/scrape` | Scrape web page content |
| `/crawl` | Crawl multiple pages |
Type `/` in the input field to see available commands.
## Chat Management
### Starting a New Chat
Click the **+** button in the Copilot header to start a fresh conversation.
### Chat History
Click **History** to view previous conversations grouped by date. You can:
- Click a chat to resume it
- Delete chats you no longer need
### Editing Messages
Hover over any of your messages and click **Edit** to modify and resend it. This is useful for refining your prompts.
### Message Queue
If you send a message while Copilot is still responding, it gets queued. You can:
- View queued messages in the expandable queue panel
- Send a queued message immediately (aborts current response)
- Remove messages from the queue
Click **History** (clock icon) in the Copilot header to see past conversations for this workflow. Click any chat to resume it, or click **+** to start a new one.
## File Attachments
Click the attachment icon to upload files with your message. Supported file types include:
- Images (preview thumbnails shown)
- PDFs
- Text files, JSON, XML
- Other document formats
Click the attachment icon in the input to upload files alongside your message. Copilot can read images, PDFs, and text-based files as context.
Files are displayed as clickable thumbnails that open in a new tab.
## Checkpoints
## Checkpoints & Changes
When Copilot modifies a workflow, it saves a checkpoint of the previous state.
When Copilot makes changes to your workflow, it saves checkpoints so you can revert if needed.
To revert: hover over a Copilot message and click the checkpoints icon, then click **Revert** on the state you want to restore. Reverting cannot be undone.
### Viewing Checkpoints
## Thinking
Hover over a Copilot message and click the checkpoints icon to see saved workflow states for that message.
For complex requests, Copilot may show its reasoning in an expandable thinking block before responding. The block shows how long the thinking took and collapses after the response is complete.
### Reverting Changes
## Usage
Click **Revert** on any checkpoint to restore your workflow to that state. A confirmation dialog will warn that this action cannot be undone.
Copilot usage is billed per token and counts toward your plan's credit usage. If you reach your limit, enable on-demand billing from Settings → Subscription.
### Accepting Changes
When Copilot proposes changes, you can:
- **Accept**: Apply the proposed changes (`Mod+Shift+Enter`)
- **Reject**: Dismiss the changes and keep your current workflow
## Thinking Blocks
For complex requests, Copilot may show its reasoning process in expandable thinking blocks:
- Blocks auto-expand while Copilot is thinking
- Click to manually expand/collapse
- Shows duration of the thinking process
- Helps you understand how Copilot arrived at its solution
## Options Selection
When Copilot presents multiple options, you can select using:
| Control | Action |
|---------|--------|
| **1-9** | Select option by number |
| **Arrow Up/Down** | Navigate between options |
| **Enter** | Select highlighted option |
Selected options are highlighted; unselected options appear struck through.
## Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `@` | Open context menu |
| `/` | Open slash commands |
| `Arrow Up/Down` | Navigate menu items |
| `Enter` | Select menu item |
| `Esc` | Close menus |
| `Mod+Shift+Enter` | Accept Copilot changes |
## Usage Limits
Copilot usage is billed per token from the underlying LLM and counts toward your plan's credit usage. If you reach your usage limit, enable on-demand billing from Settings → Subscription to continue using Copilot beyond your plan's included credits.
<Callout type="info">
See the [Cost Calculation page](/execution/costs) for billing and plan details.
</Callout>
## Copilot MCP
You can use Copilot as an MCP server in your favorite editor or AI client. This lets you build, test, deploy, and manage Sim workflows directly from tools like Cursor, Claude Code, Claude Desktop, and VS Code.
You can use Copilot as an MCP server to build, test, and manage Sim workflows from external editors — Cursor, Claude Code, Claude Desktop, and VS Code.
### Generating a Copilot API Key
To connect to the Copilot MCP server, you need a **Copilot API key**:
1. Go to [sim.ai](https://sim.ai) and sign in
2. Navigate to **Settings** → **Copilot**
3. Click **Generate API Key**
4. Copy the key — it is only shown once
The key will look like `sk-sim-copilot-...`. You will use this in the configuration below.
The key will look like `sk-sim-copilot-...`.
### Cursor
Add the following to your `.cursor/mcp.json` (project-level) or global Cursor MCP settings:
Add to `.cursor/mcp.json`:
```json
{
@@ -234,12 +80,8 @@ Add the following to your `.cursor/mcp.json` (project-level) or global Cursor MC
}
```
Replace `YOUR_COPILOT_API_KEY` with the key you generated above.
### Claude Code
Run the following command to add the Copilot MCP server:
```bash
claude mcp add sim-copilot \
--transport http \
@@ -247,11 +89,9 @@ claude mcp add sim-copilot \
--header "X-API-Key: YOUR_COPILOT_API_KEY"
```
Replace `YOUR_COPILOT_API_KEY` with your key.
### Claude Desktop
Claude Desktop requires [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) to connect to HTTP-based MCP servers. Add the following to your Claude Desktop config file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
Claude Desktop requires [`mcp-remote`](https://www.npmjs.com/package/mcp-remote). Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
@@ -270,11 +110,9 @@ Claude Desktop requires [`mcp-remote`](https://www.npmjs.com/package/mcp-remote)
}
```
Replace `YOUR_COPILOT_API_KEY` with your key.
### VS Code
Add the following to your VS Code `settings.json` or workspace `.vscode/settings.json`:
Add to `settings.json` or `.vscode/settings.json`:
```json
{
@@ -292,21 +130,14 @@ Add the following to your VS Code `settings.json` or workspace `.vscode/settings
}
```
Replace `YOUR_COPILOT_API_KEY` with your key.
<Callout type="info">
For self-hosted deployments, replace `https://www.sim.ai` with your self-hosted Sim URL.
</Callout>
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "What is the difference between Ask, Build, and Plan mode?", answer: "Copilot has three modes. Ask mode is a read-only Q&A mode for explanations, guidance, and suggestions without making any changes to your workflow. Build mode allows Copilot to actively modify your workflow by adding blocks, wiring connections, editing configurations, and debugging issues. Plan mode creates a step-by-step implementation plan for your request without making any changes, so you can review the approach before committing. Use Ask when you want to learn or explore ideas, Plan when you want to see a proposed approach first, and Build when you want Copilot to make changes directly." },
{ question: "Does Copilot have access to my full workflow when answering questions?", answer: "Copilot has access to the workflow you are currently editing as context. You can also use the @ context menu to reference other workflows, previous chats, execution logs, knowledge bases, documentation, and templates to give Copilot additional context for your request." },
{ question: "How do I use Copilot from an external editor like Cursor or VS Code?", answer: "You can use Copilot as an MCP server from external editors. First, generate a Copilot API key from Settings > Copilot on sim.ai. Then add the MCP server configuration to your editor using the endpoint https://www.sim.ai/api/mcp/copilot with your API key in the X-API-Key header. Configuration examples are available for Cursor, Claude Code, Claude Desktop, and VS Code." },
{ question: "Can I revert changes that Copilot made to my workflow?", answer: "Yes. When Copilot makes changes in Build mode, it saves checkpoints of your workflow state. You can hover over a Copilot message and click the checkpoints icon to see saved states, then click Revert on any checkpoint to restore your workflow. Note that reverting cannot be undone, so review the checkpoint before confirming." },
{ question: "How does Copilot billing work?", answer: "Copilot usage is billed per token from the underlying LLM and counts toward your plan's credit usage. More capable models like Claude Opus cost more per token than lighter models like Haiku. If you reach your usage limit, you can enable on-demand billing from Settings > Subscription to continue using Copilot." },
{ question: "What do the slash commands like /research and /search do?", answer: "Slash commands trigger specialized behaviors. /fast enables fast mode execution, /research activates a research and exploration mode, and /actions executes agent actions. Web commands like /search, /read, /scrape, and /crawl let Copilot interact with the web to search for information, read URLs, scrape page content, or crawl multiple pages to gather context for your request." },
{ question: "How do I set up Copilot for a self-hosted deployment?", answer: "For self-hosted deployments, go to sim.ai > Settings > Copilot and generate a Copilot API key. Then set the COPILOT_API_KEY environment variable in your self-hosted environment. Copilot is a Sim-managed service, so the self-hosted instance communicates with Sim's servers to process requests." },
{ question: "How is Copilot different from Mothership?", answer: "Copilot is scoped to the workflow you have open — it reads and edits that workflow's blocks and connections. Mothership has access to your entire workspace and can build workflows, manage tables, run research, schedule jobs, and take actions across integrations." },
{ question: "Can Copilot access other workflows or workspace data?", answer: "Copilot is scoped to the current workflow. For tasks that span multiple workflows or require workspace-level context, use Mothership." },
{ question: "Can I revert changes Copilot made?", answer: "Yes. Copilot saves a checkpoint before each change. Hover over the message and click the checkpoints icon to see saved states, then click Revert to restore one. Reverting cannot be undone." },
{ question: "How does Copilot billing work?", answer: "Copilot usage is billed per token and counts toward your plan's credit usage. If you reach your limit, enable on-demand billing from Settings → Subscription." },
{ question: "How do I set up Copilot for a self-hosted deployment?", answer: "Go to sim.ai → Settings → Copilot and generate a Copilot API key. Set the COPILOT_API_KEY environment variable in your self-hosted environment. Copilot runs on Sim's infrastructure regardless of where you host the application." },
]} />

View File

@@ -1,203 +1,121 @@
---
title: Credentials
description: Manage secrets, API keys, and OAuth connections for your workflows
title: Secrets
description: Manage API keys and environment variables for your workflows
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Image } from '@/components/ui/image'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { FAQ } from '@/components/ui/faq'
Credentials provide a secure way to manage API keys, tokens, and third-party service connections across your workflows. Instead of hardcoding sensitive values into your workflow, you store them as credentials and reference them at runtime.
Secrets are key-value pairs that store sensitive data like API keys, tokens, and passwords. Instead of hardcoding values into your workflows, you store them as secrets and reference them by name at runtime.
Sim supports two categories of credentials: **secrets** for static values like API keys, and **OAuth accounts** for authenticated service connections like Google or Slack.
## Managing Secrets
## Getting Started
To manage credentials, open your workspace **Settings** and navigate to the **Secrets** tab.
To manage secrets, open your workspace **Settings** and navigate to the **Secrets** tab.
<Image
src="/static/credentials/settings-secrets.png"
alt="Settings modal showing the Secrets tab with a list of saved credentials"
src="/static/secrets/secrets-list.png"
alt="Secrets tab showing Workspace and Personal sections with inline key-value rows"
width={700}
height={200}
height={500}
/>
From here you can search, create, and delete both secrets and OAuth connections.
Secrets are organized into two sections:
## Secrets
- **Workspace** — shared with all members of your workspace
- **Personal** — private to you
Secrets are key-value pairs that store sensitive data like API keys, tokens, and passwords. Each secret has a **key** (used to reference it in workflows) and a **value** (the actual secret).
### Adding a Secret
### Creating a Secret
Type a key name (e.g. `OPENAI_API_KEY`) into the **Key** column and its value into the **Value** column in the last empty row. A new empty row appears automatically as you type. Existing values are masked by default.
<Image
src="/static/credentials/create-secret.png"
alt="Create Secret dialog with fields for key, value, description, and scope toggle"
width={500}
height={400}
/>
When you're done, click **Save** to persist all changes.
<Steps>
<Step>
Click **+ Add** and select **Secret** as the type
</Step>
<Step>
Enter a **Key** name (letters, numbers, and underscores only, e.g. `OPENAI_API_KEY`)
</Step>
<Step>
Enter the **Value**
</Step>
<Step>
Optionally add a **Description** to help your team understand what the secret is for
</Step>
<Step>
Choose the **Scope** — Workspace or Personal
</Step>
<Step>
Click **Create**
</Step>
</Steps>
<Callout type="info">
Keys must use only letters, numbers, and underscores — no spaces or special characters.
</Callout>
### Using Secrets in Workflows
### Bulk Import
To reference a secret in any input field, type `{{` to open the dropdown. It will show your available secrets grouped by scope.
You can populate multiple secrets at once by pasting `.env`-style content into any key or value field. The parser supports standard `KEY=VALUE` pairs, `export KEY=VALUE`, quoted values, and inline comments.
### Editing and Deleting
Click directly into any key or value cell to edit it. To delete a secret, click the trash icon on its row and save.
## Using Secrets in Workflows
To reference a secret in any input field, type `{{` to open the variable dropdown. Your available secrets are listed grouped by scope (workspace, then personal).
<Image
src="/static/credentials/secret-dropdown.png"
alt="Typing {{ in a code block opens a dropdown showing available workspace secrets"
alt="Typing {{ in an input opens a dropdown showing available secrets"
width={400}
height={250}
/>
Select the secret you want to use. The reference will appear highlighted in blue, indicating it will be resolved at runtime.
Select the secret you want to use. The reference appears highlighted in blue and is resolved to its actual value at runtime.
<Image
src="/static/credentials/secret-resolved.png"
alt="A resolved secret reference shown in blue text as {{OPENAI_API_KEY}}"
alt="A resolved secret reference shown as {{OPENAI_API_KEY}}"
width={400}
height={200}
/>
<Callout type="warn">
Secret values are never exposed in the workflow editor or logs. They are only resolved during execution.
Secret values are never exposed in the workflow editor or execution logs — they are only resolved during execution.
</Callout>
### Bulk Import
## Secret Details
You can import multiple secrets at once by pasting `.env`-style content:
1. Click **+ Add**, then switch to **Bulk** mode
2. Paste your environment variables in `KEY=VALUE` format
3. Choose the scope for all imported secrets
4. Click **Create**
The parser supports standard `KEY=VALUE` pairs, quoted values, comments (`#`), and blank lines.
## OAuth Accounts
OAuth accounts are authenticated connections to third-party services like Google, Slack, GitHub, and more. Sim handles the OAuth flow, token storage, and automatic refresh.
You can connect **multiple accounts per provider** — for example, two separate Gmail accounts for different workflows.
### Connecting an OAuth Account
Click **Details** on any secret row to open its detail view.
<Image
src="/static/credentials/create-oauth.png"
alt="Create Secret dialog with OAuth Account type selected, showing display name and provider dropdown"
width={500}
src="/static/secrets/secret-details.png"
alt="Secret details view showing Display Name, Description, and Members sections"
width={700}
height={400}
/>
<Steps>
<Step>
Click **+ Add** and select **OAuth Account** as the type
</Step>
<Step>
Enter a **Display name** to identify this connection (e.g. "Work Gmail" or "Marketing Slack")
</Step>
<Step>
Optionally add a **Description**
</Step>
<Step>
Select the **Account** provider from the dropdown
</Step>
<Step>
Click **Connect** and complete the authorization flow
</Step>
</Steps>
From here you can:
### Using OAuth Accounts in Workflows
- Edit the **Display Name** and **Description**
- Manage **Members** — invite teammates by email and assign them an **Admin** or **Member** role
Blocks that require authentication (e.g. Gmail, Slack, Google Sheets) display a credential selector dropdown. Select the OAuth account you want the block to use.
<Image
src="/static/credentials/oauth-selector.png"
alt="Gmail block showing the account selector dropdown with a connected account and option to connect another"
width={500}
height={350}
/>
You can also connect additional accounts directly from the block by selecting **Connect another account** at the bottom of the dropdown.
<Callout type="info">
If a block requires an OAuth connection and none is selected, the workflow will fail at that step.
</Callout>
Click **Save** to apply changes, or **Back** to return to the list.
## Workspace vs. Personal
Credentials can be scoped to your **workspace** (shared with your team) or kept **personal** (private to you).
| | Workspace | Personal |
|---|---|---|
| **Visibility** | All workspace members | Only you |
| **Use in workflows** | Any member can use | Only you can use |
| **Best for** | Production workflows, shared services | Testing, personal API keys |
| **Who can edit** | Workspace admins | Only you |
| **Auto-shared** | Yes — all members get access on creation | No — only you have access |
<Callout type="info">
When a workspace and personal secret share the same key name, the **workspace secret takes precedence**.
When a workspace secret and a personal secret share the same key name, the **workspace secret takes precedence**.
</Callout>
### Resolution Order
When a workflow runs, Sim resolves secrets in this order:
When a workflow runs, secrets resolve in this order:
1. **Workspace secrets** are checked first
2. **Personal secrets** are used as a fallback — from the user who triggered the run (manual) or the workflow owner (automated runs via API, webhook, or schedule)
## Access Control
Each credential has role-based access control:
- **Admin** — can view, edit, delete, and manage who has access
- **Member** — can use the credential in workflows (read-only)
When you create a workspace secret, all current workspace members are automatically granted access. Personal secrets are only accessible to you by default.
### Sharing a Credential
To share a credential with specific team members:
1. Click **Details** on the credential
2. Invite members by email
3. Assign them an **Admin** or **Member** role
## Best Practices
- **Use workspace credentials for production** so workflows work regardless of who triggers them
- **Use personal credentials for development** to keep your test keys separate
- **Use workspace secrets for production** so workflows work regardless of who triggers them
- **Use personal secrets for development** to keep test keys separate
- **Name keys descriptively** — `STRIPE_SECRET_KEY` over `KEY1`
- **Connect multiple OAuth accounts** when you need different permissions or identities per workflow
- **Never hardcode secrets** in workflow input fields — always use `{{KEY}}` references
<FAQ items={[
{ question: "Are my secrets encrypted at rest?", answer: "Yes. Secret values and OAuth tokens are encrypted before being stored in the database. The platform uses server-side encryption so that raw secret values are never persisted in plaintext. Secret values are also never exposed in the workflow editor, logs, or API responses." },
{ question: "What happens if both a workspace secret and a personal secret have the same key name?", answer: "The workspace secret takes precedence. During execution, the resolver checks workspace secrets first and uses personal secrets only as a fallback. This ensures that production workflows use the shared, team-managed value." },
{ question: "Are my secrets encrypted at rest?", answer: "Yes. Secret values are encrypted before being stored in the database using server-side encryption, so raw values are never persisted in plaintext. They are also never exposed in the workflow editor, logs, or API responses." },
{ question: "What happens if both a workspace secret and a personal secret have the same key name?", answer: "The workspace secret takes precedence. During execution, the resolver checks workspace secrets first and uses personal secrets only as a fallback. This ensures production workflows use the shared, team-managed value." },
{ question: "Who determines which personal secret is used for automated runs?", answer: "For manual runs, the personal secrets of the user who clicked Run are used as fallback. For automated runs triggered by API, webhook, or schedule, the personal secrets of the workflow owner are used instead." },
{ question: "Does Sim handle OAuth token refresh automatically?", answer: "Yes. When an OAuth token is used during execution, the platform checks whether the access token has expired and automatically refreshes it using the stored refresh token before making the API call. You do not need to handle token refresh manually." },
{ question: "Can I connect multiple OAuth accounts for the same provider?", answer: "Yes. You can connect multiple accounts per provider (for example, two separate Gmail accounts). Each block that requires OAuth lets you select which specific account to use from the credential dropdown. This is useful when different workflows or blocks need different permissions or identities." },
{ question: "What happens if I delete a credential that is used in a workflow?", answer: "If a block references a deleted credential, the workflow will fail at that block during execution because the credential cannot be resolved. Make sure to update any blocks that reference a credential before deleting it." },
{ question: "Can I import secrets from a .env file?", answer: "Yes. The bulk import feature lets you paste .env-style content in KEY=VALUE format. The parser supports quoted values, comments (lines starting with #), and blank lines. All imported secrets are created with the scope you choose (workspace or personal)." },
{ question: "Can I import secrets from a .env file?", answer: "Yes. Paste .env-style content (KEY=VALUE format) into any key or value field and the secrets will be auto-populated. The parser supports export KEY=VALUE, quoted values, and inline comments." },
{ question: "What happens if I delete a secret that is used in a workflow?", answer: "The workflow will fail at any block that references the deleted secret during execution because the value cannot be resolved. Update any references before deleting a secret." },
]} />

View File

@@ -1,5 +1,5 @@
{
"title": "Credentials",
"pages": ["index", "google-service-account"],
"title": "Secrets",
"pages": ["index"],
"defaultOpen": false
}

View File

@@ -0,0 +1,216 @@
---
title: Access Control
description: Restrict which models, blocks, and platform features each group of users can access
---
import { Callout } from 'fumadocs-ui/components/callout'
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
Access Control lets workspace admins define permission groups that restrict what each set of workspace members can do — which AI model providers they can use, which workflow blocks they can place, and which platform features are visible to them. Permission groups are scoped to a single workspace: a user can be in different groups (or no group) in different workspaces. Restrictions are enforced both in the workflow executor and in Mothership, based on the workflow's workspace.
---
## How it works
Access control is built around **permission groups**. Each group belongs to a specific workspace and has a name, an optional description, and a configuration that defines what its members can and cannot do. A user can belong to at most one permission group **per workspace**, but can belong to different groups in different workspaces.
When a user runs a workflow or uses Mothership, Sim reads their group's configuration and applies it:
- **In the executor:** If a workflow uses a disallowed block type or model provider, execution halts immediately with an error. This applies to both manual runs and scheduled or API-triggered deployments.
- **In Mothership:** Disallowed blocks are filtered out of the block list so they cannot be added to a workflow. Disallowed tool types (MCP, custom tools, skills) are skipped if Mothership attempts to use them.
---
## Setup
### 1. Open Access Control settings
Go to **Settings → Enterprise → Access Control** in the workspace you want to manage. Each workspace has its own set of permission groups.
<Image src="/static/enterprise/access-control-groups.png" alt="Access Control settings showing a list of permission groups: Contractors, Sales, Engineering, and Marketing, each with Details and Delete actions" width={900} height={500} />
### 2. Create a permission group
Click **+ Create** and enter a name (required) and optional description. You can also enable **Auto-add new members** — when active, any new member who joins this workspace is automatically added to this group. Only one group per workspace can have this setting enabled at a time.
### 3. Configure permissions
Click **Details** on a group, then open **Configure Permissions**. There are three tabs.
#### Model Providers
Controls which AI model providers members of this group can use.
<Image src="/static/enterprise/access-control-model-providers.png" alt="Model Providers tab showing a grid of AI providers including Ollama, vLLM, OpenAI, Anthropic, Google, Azure OpenAI, and others with checkboxes to allow or restrict access" width={900} height={500} /> The list shows all providers available in Sim.
- **All checked (default):** All providers are allowed.
- **Subset checked:** Only the selected providers are allowed. Any workflow block or agent using a provider not on the list will fail at execution time.
#### Blocks
Controls which workflow blocks members can place and execute.
<Image src="/static/enterprise/access-control-blocks.png" alt="Blocks tab showing Core Blocks (Agent, API, Condition, Function, Knowledge, etc.) and Tools (integrations like 1Password, A2A, Ahrefs, Airtable, and more) with checkboxes to allow or restrict each" width={900} height={500} /> Blocks are split into two sections: **Core Blocks** (Agent, API, Condition, Function, etc.) and **Tools** (all integration blocks).
- **All checked (default):** All blocks are allowed.
- **Subset checked:** Only the selected blocks are allowed. Workflows that already contain a disallowed block will fail when run — they are not automatically modified.
<Callout type="info">
The `start_trigger` block (the entry point of every workflow) is always allowed and cannot be restricted.
</Callout>
#### Platform
Controls visibility of platform features and modules.
<Image src="/static/enterprise/access-control-platform.png" alt="Platform tab showing feature toggles grouped by category: Sidebar (Knowledge Base, Tables, Templates), Workflow Panel (Copilot), Settings Tabs, Tools, Deploy Tabs, Features, Logs, and Collaboration" width={900} height={500} /> Each checkbox maps to a specific feature; checking it hides or disables that feature for group members.
**Sidebar**
| Feature | Effect when checked |
|---------|-------------------|
| Knowledge Base | Hides the Knowledge Base section from the sidebar |
| Tables | Hides the Tables section from the sidebar |
| Templates | Hides the Templates section from the sidebar |
**Workflow Panel**
| Feature | Effect when checked |
|---------|-------------------|
| Copilot | Hides the Copilot panel inside the workflow editor |
**Settings Tabs**
| Feature | Effect when checked |
|---------|-------------------|
| Integrations | Hides the Integrations tab in Settings |
| Secrets | Hides the Secrets tab in Settings |
| API Keys | Hides the Sim Keys tab in Settings |
| Files | Hides the Files tab in Settings |
**Tools**
| Feature | Effect when checked |
|---------|-------------------|
| MCP Tools | Disables the use of MCP tools in workflows and agents |
| Custom Tools | Disables the use of custom tools in workflows and agents |
| Skills | Disables the use of Sim Skills in workflows and agents |
**Deploy Tabs**
| Feature | Effect when checked |
|---------|-------------------|
| API | Hides the API deployment tab |
| MCP | Hides the MCP deployment tab |
| A2A | Hides the A2A deployment tab |
| Chat | Hides the Chat deployment tab |
| Template | Hides the Template deployment tab |
**Features**
| Feature | Effect when checked |
|---------|-------------------|
| Sim Mailer | Hides the Sim Mailer (Inbox) feature |
| Public API | Disables public API access for deployed workflows |
**Logs**
| Feature | Effect when checked |
|---------|-------------------|
| Trace Spans | Hides trace span details in execution logs |
**Collaboration**
| Feature | Effect when checked |
|---------|-------------------|
| Invitations | Disables the ability to invite new members to the workspace |
### 4. Add members
Open the group's **Details** view and add members by searching for users by name or email. Only users who already have workspace-level access can be added. A user can only belong to one group per workspace — adding a user to a new group within the same workspace removes them from their current group for that workspace.
---
## Enforcement
### Workflow execution
Restrictions are enforced at the point of execution, not at save time. If a group's configuration changes after a workflow is built:
- **Block restrictions:** Any workflow run that reaches a disallowed block halts immediately with an error. The workflow is not modified — only execution is blocked.
- **Model provider restrictions:** Any block or agent that uses a disallowed provider halts immediately with an error.
- **Tool restrictions (MCP, custom tools, skills):** Agents that use a disallowed tool type halt immediately with an error.
This applies regardless of how the workflow is triggered — manually, via API, via schedule, or via webhook.
### Mothership
When a user opens Mothership, their permission group is read before any block or tool suggestions are made:
- Blocks not in the allowed list are filtered out of the block picker entirely — they do not appear as options.
- If Mothership generates a workflow step that would use a disallowed tool (MCP, custom, or skills), that step is skipped and the reason is noted.
---
## User membership rules
- A user can belong to **at most one** permission group **per workspace**, but may be in different groups across different workspaces.
- Moving a user to a new group within a workspace automatically removes them from their previous group in that workspace.
- Users not assigned to any group in a workspace have no restrictions applied in that workspace (all blocks, providers, and features are available to them there).
- If **Auto-add new members** is enabled on a group, new members of that workspace are automatically placed in the group. Only one group per workspace can have this setting active.
---
<FAQ items={[
{
question: "Who can create and manage permission groups?",
answer: "Any workspace admin on an Enterprise-entitled workspace can create, edit, and delete permission groups for that workspace. The workspace's billed account must be on the Enterprise plan."
},
{
question: "What happens to a workflow that was built before a block was restricted?",
answer: "The workflow is not modified — it still exists and can be edited. However, any run that reaches a disallowed block will halt immediately with an error. The block must be removed or the user's group configuration must be updated before the workflow can run successfully."
},
{
question: "Can a user be in multiple permission groups?",
answer: "A user can belong to at most one permission group per workspace, but can belong to different groups in different workspaces. Adding a user to a new group within the same workspace automatically removes them from their previous group in that workspace."
},
{
question: "What does a user see if they have no permission group assigned in a workspace?",
answer: "Users with no group in a given workspace have no restrictions in that workspace. All blocks, model providers, and platform features are fully available to them there. Restrictions only apply in the specific workspaces where they are assigned to a group."
},
{
question: "Does Mothership respect the same restrictions as the executor?",
answer: "Yes. Mothership reads the user's permission group for the active workspace before suggesting blocks or tools. Disallowed blocks are filtered out of the block picker, and disallowed tool types are skipped during workflow generation."
},
{
question: "Can I restrict access to specific workflows or workspaces?",
answer: "Access Control operates at the feature and block level within a workspace. To restrict who can access the workspace itself, use workspace invitations and permissions. To apply different restrictions to different workflows, put them in different workspaces with distinct permission groups."
},
{
question: "What is Auto-add new members?",
answer: "When a group has Auto-add new members enabled, any new member who joins the workspace is automatically added to that group. Only one group per workspace can have this setting enabled at a time."
}
]} />
---
## Self-hosted setup
Self-hosted deployments use environment variables instead of the billing/plan check.
### Environment variables
```bash
ACCESS_CONTROL_ENABLED=true
NEXT_PUBLIC_ACCESS_CONTROL_ENABLED=true
```
You can also set a server-level block allowlist using the `ALLOWED_INTEGRATIONS` environment variable. This is applied as an additional constraint on top of any permission group configuration — a block must be allowed by both the environment allowlist and the user's group to be usable.
```bash
# Only these block types are available across the entire instance
ALLOWED_INTEGRATIONS=slack,gmail,agent,function,condition
```
Once enabled, permission groups are managed through **Settings → Enterprise → Access Control** the same way as Sim Cloud.

View File

@@ -0,0 +1,143 @@
---
title: Audit Logs
description: Track every action taken across your organization's workspaces
---
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
Audit logs give your organization a tamper-evident record of every significant action taken across workspaces — who did what, when, and on which resource. Use them for security reviews, compliance investigations, and incident response.
---
## Viewing audit logs
### In the UI
Go to **Settings → Enterprise → Audit Logs** in your workspace. Logs are displayed in a table with the following columns:
<Image src="/static/enterprise/audit-logs.png" alt="Audit Logs settings showing a table of events with columns for Timestamp, Event, Description, and Actor, along with search and filter controls" width={900} height={500} />
| Column | Description |
|--------|-------------|
| **Timestamp** | When the action occurred. |
| **Event** | The action taken, e.g. `workflow.created`. |
| **Description** | A human-readable summary of the action. |
| **Actor** | The email address of the user who performed the action. |
Use the search bar, event type filter, and date range selector to narrow results.
### Via API
Audit logs are also accessible through the Sim API for integration with external SIEM or log management tools.
```http
GET /api/v1/audit-logs
Authorization: Bearer <api-key>
```
**Query parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `action` | string | Filter by event type (e.g. `workflow.created`) |
| `resourceType` | string | Filter by resource type (e.g. `workflow`) |
| `resourceId` | string | Filter by a specific resource ID |
| `workspaceId` | string | Filter by workspace |
| `actorId` | string | Filter by user ID (must be an org member) |
| `startDate` | string | ISO 8601 date — return logs on or after this date |
| `endDate` | string | ISO 8601 date — return logs on or before this date |
| `includeDeparted` | boolean | Include logs from members who have since left the organization (default `false`) |
| `limit` | number | Results per page (1100, default 50) |
| `cursor` | string | Opaque cursor for fetching the next page |
**Example response:**
```json
{
"data": [
{
"id": "abc123",
"action": "workflow.created",
"resourceType": "workflow",
"resourceId": "wf_xyz",
"resourceName": "Customer Onboarding",
"description": "Created workflow \"Customer Onboarding\"",
"actorId": "usr_abc",
"actorName": "Alice Smith",
"actorEmail": "alice@company.com",
"workspaceId": "ws_def",
"metadata": {},
"createdAt": "2026-04-20T21:16:00.000Z"
}
],
"nextCursor": "eyJpZCI6ImFiYzEyMyJ9"
}
```
Paginate by passing the `nextCursor` value as the `cursor` parameter in the next request. When `nextCursor` is absent, you have reached the last page.
The API accepts both personal and workspace-scoped API keys. Rate limits apply — the response includes `X-RateLimit-*` headers with your current limit and remaining quota.
---
## Event types
Audit log events follow a `resource.action` naming pattern. The table below lists the main categories.
| Category | Example events |
|----------|---------------|
| **Workflows** | `workflow.created`, `workflow.deleted`, `workflow.deployed`, `workflow.locked` |
| **Workspaces** | `workspace.created`, `workspace.updated`, `workspace.deleted` |
| **Members** | `member.invited`, `member.removed`, `member.role_changed` |
| **Permission groups** | `permission_group.created`, `permission_group.updated`, `permission_group.deleted` |
| **Environments** | `environment.updated`, `environment.deleted` |
| **Knowledge bases** | `knowledge_base.created`, `knowledge_base.deleted`, `connector.synced` |
| **Tables** | `table.created`, `table.updated`, `table.deleted` |
| **API keys** | `api_key.created`, `api_key.revoked` |
| **Credentials** | `credential.created`, `credential.deleted`, `oauth.disconnected` |
| **Organization** | `organization.updated`, `org_member.added`, `org_member.role_changed` |
---
<FAQ items={[
{
question: "Who can view audit logs?",
answer: "Organization owners and admins can view audit logs. On Sim Cloud, you must be on the Enterprise plan."
},
{
question: "Are audit logs tamper-proof?",
answer: "Audit log entries are append-only and cannot be modified or deleted through the Sim interface or API. They represent a reliable record of actions taken in your organization."
},
{
question: "Can I export audit logs?",
answer: "Yes. Use the API to export logs programmatically. Paginate through all records using the cursor parameter and store them in your own data warehouse or SIEM."
},
{
question: "Are logs scoped to a single workspace or the whole organization?",
answer: "Audit logs are scoped to your organization and include activity across all workspaces within it. You can filter by workspaceId to narrow results to a specific workspace."
},
{
question: "What information is included in each log entry?",
answer: "Each entry includes the event type, a description, the actor's name and email, the affected resource, the workspace, and a timestamp. IP addresses and user agents are not exposed through the API."
},
{
question: "Can I filter logs by a specific user?",
answer: "Yes. Pass the actorId query parameter to filter logs by a specific user. The actor must be a current or former member of your organization."
}
]} />
---
## Self-hosted setup
Self-hosted deployments use environment variables instead of the billing/plan check.
### Environment variables
```bash
AUDIT_LOGS_ENABLED=true
NEXT_PUBLIC_AUDIT_LOGS_ENABLED=true
```
Once enabled, audit logs are viewable in **Settings → Enterprise → Audit Logs** and accessible via the API.

View File

@@ -0,0 +1,114 @@
---
title: Data Retention
description: Control how long execution logs, deleted resources, and copilot data are kept before permanent deletion
---
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
Data Retention lets organization owners and admins on Enterprise plans configure how long three categories of data are kept before they are permanently deleted. The configuration applies to every workspace in the organization.
---
## Setup
Go to **Settings → Enterprise → Data Retention** in your workspace.
<Image src="/static/enterprise/data-retention.png" alt="Data Retention settings showing three dropdowns — Log retention, Soft deletion cleanup, and Task cleanup — each set to Forever" width={900} height={500} />
You will see three independent settings, each with the same set of options: **1 day, 3 days, 7 days, 14 days, 30 days, 60 days, 90 days, 180 days, 1 year, 5 years,** or **Forever**.
Setting a period to **Forever** means that category of data is never automatically deleted.
---
## Settings
### Log retention
Controls how long **workflow execution logs** are kept.
When the retention period expires, execution log records are permanently deleted, along with any files associated with those executions stored in cloud storage.
### Soft deletion cleanup
Controls how long **soft-deleted resources** remain recoverable before permanent removal.
When you delete a workflow, folder, knowledge base, table, or file, it is initially soft-deleted and can be recovered from Recently Deleted. Once the soft deletion cleanup period expires, those resources are permanently removed and cannot be recovered.
Resources covered:
- Workflows
- Workflow folders
- Knowledge bases
- Tables
- Files
- MCP server configurations
- Agent memory
### Task cleanup
Controls how long **Mothership data** is kept, including:
- Copilot chats and run history
- Run checkpoints and async tool calls
- Inbox tasks (Sim Mailer)
Each setting is independent. You can configure a short log retention period alongside a long soft deletion cleanup period, or any combination that fits your compliance requirements.
---
## Organization-wide configuration
Retention is configured at the **organization level**. A single configuration applies to every workspace in the organization — there are no per-workspace overrides.
---
## Defaults
By default, all three settings are unconfigured — no data is automatically deleted in any category until you configure it. Setting a period to **Forever** has the same effect as leaving it unconfigured, but makes the intent explicit and allows you to change it later without saving from scratch.
---
<FAQ items={[
{
question: "Who can configure data retention settings?",
answer: "Only organization owners and admins can configure data retention settings. On Sim Cloud, the organization must be on an Enterprise plan."
},
{
question: "Is deletion immediate once the retention period expires?",
answer: "No. Deletion runs on a scheduled cleanup job. Data is deleted when the job next runs after the retention period has elapsed — not at the exact moment it expires."
},
{
question: "Can deleted data be recovered after the soft deletion cleanup period?",
answer: "No. Once the soft deletion cleanup period expires and the cleanup job runs, resources are permanently deleted and cannot be recovered."
},
{
question: "Does the retention period apply to all workspaces in my organization?",
answer: "Yes. Retention is configured once per organization and applies to every workspace in the organization."
},
{
question: "What happens if I shorten the retention period?",
answer: "The next cleanup job will delete any data that is older than the new, shorter period — including data that would have been kept under the previous setting. Shortening the period is irreversible for data that falls outside the new window."
},
{
question: "What is the minimum retention period?",
answer: "1 day (24 hours)."
},
{
question: "What is the maximum retention period?",
answer: "5 years."
}
]} />
---
## Self-hosted setup
### Environment variables
```bash
NEXT_PUBLIC_DATA_RETENTION_ENABLED=true
```
Once enabled, data retention settings are configurable through **Settings → Enterprise → Data Retention** the same way as Sim Cloud.

View File

@@ -3,7 +3,6 @@ title: Enterprise
description: Enterprise features for business organizations
---
import { Callout } from 'fumadocs-ui/components/callout'
import { FAQ } from '@/components/ui/faq'
Sim Enterprise provides advanced features for organizations with enhanced security, compliance, and management requirements.
@@ -12,7 +11,7 @@ Sim Enterprise provides advanced features for organizations with enhanced securi
## Access Control
Define permission groups to control what features and integrations team members can use.
Define permission groups on a workspace to control what features and integrations its members can use. Permission groups are scoped to a single workspace — a user can belong to different groups (or no group) in different workspaces.
### Features
@@ -22,104 +21,64 @@ Define permission groups to control what features and integrations team members
### Setup
1. Navigate to **Settings** → **Access Control** in your workspace
1. Navigate to **Settings** → **Access Control** in the workspace you want to manage
2. Create a permission group with your desired restrictions
3. Add team members to the permission group
3. Add workspace members to the permission group
<Callout type="info">
Users not assigned to any permission group have full access. Permission restrictions are enforced at both UI and execution time.
</Callout>
Any workspace admin on an Enterprise-entitled workspace can manage permission groups. Users not assigned to any group have full access. Restrictions are enforced at both UI and execution time, based on the workflow's workspace.
See the [Access Control guide](/docs/enterprise/access-control) for full details.
---
## Single Sign-On (SSO)
Enterprise authentication with SAML 2.0 and OIDC support for centralized identity management.
Enterprise authentication with SAML 2.0 and OIDC support. Works with Okta, Azure AD (Entra ID), Google Workspace, ADFS, and any standard OIDC or SAML 2.0 provider.
### Supported Providers
- Okta
- Azure AD / Entra ID
- Google Workspace
- OneLogin
- Any SAML 2.0 or OIDC provider
### Setup
1. Navigate to **Settings** → **SSO** in your workspace
2. Choose your identity provider
3. Configure the connection using your IdP's metadata
4. Enable SSO for your organization
<Callout type="info">
Once SSO is enabled, team members authenticate through your identity provider instead of email/password.
</Callout>
See the [SSO setup guide](/docs/enterprise/sso) for step-by-step instructions and provider-specific configuration.
---
## Self-Hosted Configuration
## Whitelabeling
For self-hosted deployments, enterprise features can be enabled via environment variables without requiring billing.
Replace Sim's default branding — logos, product name, and favicons — with your own. See the [whitelabeling guide](/docs/enterprise/whitelabeling).
### Environment Variables
---
## Audit Logs
Track configuration and security-relevant actions across your organization for compliance and monitoring. See the [audit logs guide](/docs/enterprise/audit-logs).
---
## Data Retention
Configure how long execution logs, soft-deleted resources, and Mothership data are kept before permanent deletion. See the [data retention guide](/docs/enterprise/data-retention).
---
<FAQ items={[
{ question: "Who can manage Enterprise features?", answer: "Workspace admins on an Enterprise-entitled workspace. Access Control, SSO, whitelabeling, audit logs, and data retention are all configured per workspace under Settings → Enterprise." },
{ question: "Which SSO providers are supported?", answer: "Sim supports SAML 2.0 and OIDC, which works with virtually any enterprise identity provider including Okta, Azure AD (Entra ID), Google Workspace, ADFS, and OneLogin." },
{ question: "How do access control permission groups work?", answer: "Permission groups are created per workspace and let you restrict which AI providers, workflow blocks, and platform features are available to specific members of that workspace. Each user can belong to at most one group per workspace. Users not assigned to any group have full access. Restrictions are enforced at both the UI level and at execution time based on the workflow's workspace." },
]} />
---
## Self-hosted setup
Self-hosted deployments enable enterprise features via environment variables instead of billing.
| Variable | Description |
|----------|-------------|
| `ORGANIZATIONS_ENABLED`, `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` | Enable team/organization management |
| `ACCESS_CONTROL_ENABLED`, `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` | Permission groups for access restrictions |
| `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | Single Sign-On with SAML/OIDC |
| `CREDENTIAL_SETS_ENABLED`, `NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | Polling Groups for email triggers |
| `INBOX_ENABLED`, `NEXT_PUBLIC_INBOX_ENABLED` | Sim Mailer inbox for outbound email |
| `WHITELABELING_ENABLED`, `NEXT_PUBLIC_WHITELABELING_ENABLED` | Custom branding and white-labeling |
| `AUDIT_LOGS_ENABLED`, `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | Audit logging for compliance and monitoring |
| `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Globally disable workspace/organization invitations |
| `ORGANIZATIONS_ENABLED`, `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` | Team and organization management |
| `ACCESS_CONTROL_ENABLED`, `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` | Permission groups |
| `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | SAML and OIDC sign-in |
| `WHITELABELING_ENABLED`, `NEXT_PUBLIC_WHITELABELING_ENABLED` | Custom branding |
| `AUDIT_LOGS_ENABLED`, `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | Audit logging |
| `NEXT_PUBLIC_DATA_RETENTION_ENABLED` | Data retention configuration |
| `CREDENTIAL_SETS_ENABLED`, `NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | Polling groups for email triggers |
| `INBOX_ENABLED`, `NEXT_PUBLIC_INBOX_ENABLED` | Sim Mailer inbox |
| `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Disable invitations; manage membership via Admin API |
### Organization Management
When billing is disabled, use the Admin API to manage organizations:
```bash
# Create an organization
curl -X POST https://your-instance/api/v1/admin/organizations \
-H "x-admin-key: YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "My Organization", "ownerId": "user-id-here"}'
# Add a member
curl -X POST https://your-instance/api/v1/admin/organizations/{orgId}/members \
-H "x-admin-key: YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"userId": "user-id-here", "role": "admin"}'
```
### Workspace Members
When invitations are disabled, use the Admin API to manage workspace memberships directly:
```bash
# Add a user to a workspace
curl -X POST https://your-instance/api/v1/admin/workspaces/{workspaceId}/members \
-H "x-admin-key: YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"userId": "user-id-here", "permissions": "write"}'
# Remove a user from a workspace
curl -X DELETE "https://your-instance/api/v1/admin/workspaces/{workspaceId}/members?userId=user-id-here" \
-H "x-admin-key: YOUR_ADMIN_API_KEY"
```
### Notes
- Enabling `ACCESS_CONTROL_ENABLED` automatically enables organizations, as access control requires organization membership.
- When `DISABLE_INVITATIONS` is set, users cannot send invitations. Use the Admin API to manage workspace and organization memberships instead.
<FAQ items={[
{ question: "What are the minimum requirements to self-host Sim?", answer: "The Docker Compose production setup includes the Sim application (8 GB memory limit), a realtime collaboration server (1 GB memory limit), and a PostgreSQL database with pgvector. A machine with at least 16 GB of RAM and 4 CPU cores is recommended. You will also need Docker and Docker Compose installed." },
{ question: "Can I run Sim completely offline with local AI models?", answer: "Yes. Sim supports Ollama and VLLM for running local AI models. A separate Docker Compose configuration (docker-compose.ollama.yml) is available for deploying with Ollama. This lets you run workflows without any external API calls, keeping all data on your infrastructure." },
{ question: "How does data privacy work with self-hosted deployments?", answer: "When self-hosted, all data stays on your infrastructure. Workflow definitions, execution logs, credentials, and user data are stored in your PostgreSQL database. If you use local AI models through Ollama or VLLM, no data leaves your network. When using external AI providers, only the data sent in prompts goes to those providers." },
{ question: "Do I need a paid license to self-host Sim?", answer: "The core Sim platform is open source under Apache 2.0 and can be self-hosted for free. Enterprise features like SSO (SAML/OIDC), access control with permission groups, and organization management require an Enterprise subscription for production use. These features can be enabled via environment variables for development and evaluation without a license." },
{ question: "Which SSO providers are supported?", answer: "Sim supports SAML 2.0 and OIDC protocols, which means it works with virtually any enterprise identity provider including Okta, Azure AD (Entra ID), Google Workspace, and OneLogin. Configuration is done through Settings in the workspace UI." },
{ question: "How do I manage users when invitations are disabled?", answer: "Use the Admin API with your admin API key. You can create organizations, add members to organizations with specific roles, add users to workspaces with defined permissions, and remove users. All management is done through REST API calls authenticated with the x-admin-key header." },
{ question: "Can I scale Sim horizontally for high availability?", answer: "The Docker Compose setup is designed for single-node deployments. For production scaling, you can deploy on Kubernetes with multiple application replicas behind a load balancer. The database can be scaled independently using managed PostgreSQL services. Redis can be configured for session and cache management across multiple instances." },
{ question: "How do access control permission groups work?", answer: "Permission groups let you restrict which AI providers, workflow blocks, and platform features are available to specific team members. Users not assigned to any group have full access. Restrictions are enforced at both the UI level (hiding restricted options) and at execution time (blocking unauthorized operations). Enabling access control automatically enables organization management." },
]} />
Once enabled, each feature is configured through the same Settings UI as Sim Cloud. When invitations are disabled, use the Admin API (`x-admin-key` header) to manage organization and workspace membership.

View File

@@ -0,0 +1,5 @@
{
"title": "Enterprise",
"pages": ["index", "sso", "access-control", "whitelabeling", "audit-logs", "data-retention"],
"defaultOpen": false
}

View File

@@ -0,0 +1,326 @@
---
title: Single Sign-On (SSO)
description: Configure SAML 2.0 or OIDC-based single sign-on for your organization
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
Single Sign-On lets your team sign in to Sim through your company's identity provider instead of managing separate passwords. Sim supports both OIDC and SAML 2.0.
---
## Setup
### 1. Open SSO settings
Go to **Settings → Enterprise → Single Sign-On** in your workspace.
### 2. Choose a protocol
| Protocol | Use when |
|----------|----------|
| **OIDC** | Your IdP supports OpenID Connect — Okta, Microsoft Entra ID, Auth0, Google Workspace |
| **SAML 2.0** | Your IdP is SAML-only — ADFS, Shibboleth, or older enterprise IdPs |
### 3. Fill in the form
<Image src="/static/enterprise/sso-form.png" alt="Single Sign-On configuration form showing Provider Type (OIDC), Provider ID, Issuer URL, Domain, Client ID, Client Secret, Scopes, and Callback URL fields" width={900} height={500} />
**Fields required for both protocols:**
| Field | What to enter |
|-------|--------------|
| **Provider ID** | A short slug identifying this connection, e.g. `okta` or `azure-ad`. Letters, numbers, and dashes only. |
| **Issuer URL** | The identity provider's issuer URL. Must be HTTPS. |
| **Domain** | Your organization's email domain, e.g. `company.com`. Users with this domain will be routed through SSO at sign-in. |
**OIDC additional fields:**
| Field | What to enter |
|-------|--------------|
| **Client ID** | The application client ID from your IdP. |
| **Client Secret** | The client secret from your IdP. |
| **Scopes** | Comma-separated OIDC scopes. Default: `openid,profile,email`. |
<Callout type="info">
For OIDC, Sim automatically fetches endpoints (`authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri`) from your issuer's `/.well-known/openid-configuration` discovery document. You only need to provide the issuer URL.
</Callout>
**SAML additional fields:**
| Field | What to enter |
|-------|--------------|
| **Entry Point URL** | The IdP's SSO service URL where Sim sends authentication requests. |
| **Identity Provider Certificate** | The Base-64 encoded X.509 certificate from your IdP for verifying assertions. |
### 4. Copy the Callback URL
The **Callback URL** shown in the form is the endpoint your identity provider must redirect users back to after authentication. Copy it and register it in your IdP before saving.
**OIDC providers** (Okta, Microsoft Entra ID, Google Workspace, Auth0):
```
https://sim.ai/api/auth/sso/callback/{provider-id}
```
**SAML providers** (ADFS, Shibboleth):
```
https://sim.ai/api/auth/sso/saml2/callback/{provider-id}
```
### 5. Save and test
Click **Save**. To test, sign out and use the **Sign in with SSO** button on the login page. Enter an email address at your configured domain — Sim will redirect you to your identity provider.
---
## Provider Guides
<Tabs items={['Okta', 'Microsoft Entra ID', 'Google Workspace', 'ADFS']}>
<Tab value="Okta">
### Okta (OIDC)
**In Okta** ([official docs](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_oidc.htm)):
1. Go to **Applications → Create App Integration**
2. Select **OIDC - OpenID Connect**, then **Web Application**
3. Set the **Sign-in redirect URI** to your Sim callback URL:
```
https://sim.ai/api/auth/sso/callback/okta
```
4. Under **Assignments**, grant access to the relevant users or groups
5. Copy the **Client ID** and **Client Secret** from the app's **General** tab
6. Your Okta domain is the hostname of your admin console, e.g. `dev-1234567.okta.com`
**In Sim:**
| Field | Value |
|-------|-------|
| Provider Type | OIDC |
| Provider ID | `okta` |
| Issuer URL | `https://dev-1234567.okta.com/oauth2/default` |
| Domain | `company.com` |
| Client ID | From Okta app |
| Client Secret | From Okta app |
The issuer URL uses Okta's default authorization server, which is pre-configured on every Okta org. If you created a custom authorization server, replace `default` with your server name.
</Tab>
<Tab value="Microsoft Entra ID">
### Microsoft Entra ID (OIDC)
**In Azure** ([official docs](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)):
1. Go to **Microsoft Entra ID → App registrations → New registration**
2. Under **Redirect URI**, select **Web** and enter your Sim callback URL:
```
https://sim.ai/api/auth/sso/callback/azure-ad
```
3. After registration, go to **Certificates & secrets → New client secret** and copy the value immediately — it won't be shown again
4. Go to **Overview** and copy the **Application (client) ID** and **Directory (tenant) ID**
**In Sim:**
| Field | Value |
|-------|-------|
| Provider Type | OIDC |
| Provider ID | `azure-ad` |
| Issuer URL | `https://login.microsoftonline.com/{tenant-id}/v2.0` |
| Domain | `company.com` |
| Client ID | Application (client) ID |
| Client Secret | Secret value |
</Tab>
<Tab value="Google Workspace">
### Google Workspace (OIDC)
**In Google Cloud Console** ([official docs](https://developers.google.com/identity/openid-connect/openid-connect)):
1. Go to **APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID**
2. Set the application type to **Web application**
3. Add your Sim callback URL to **Authorized redirect URIs**:
```
https://sim.ai/api/auth/sso/callback/google-workspace
```
4. Copy the **Client ID** and **Client Secret**
**In Sim:**
| Field | Value |
|-------|-------|
| Provider Type | OIDC |
| Provider ID | `google-workspace` |
| Issuer URL | `https://accounts.google.com` |
| Domain | `company.com` |
| Client ID | From Google Cloud Console |
| Client Secret | From Google Cloud Console |
<Callout type="info">
To restrict sign-in to your Google Workspace domain, configure the OAuth consent screen and ensure your app is set to **Internal** (Workspace users only) under **User type**. Setting the app to Internal limits access to users within your Google Workspace organization.
</Callout>
</Tab>
<Tab value="ADFS">
### ADFS (SAML 2.0)
**In ADFS** ([official docs](https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/operations/create-a-relying-party-trust)):
1. Open **AD FS Management → Relying Party Trusts → Add Relying Party Trust**
2. Choose **Claims aware**, then **Enter data about the relying party manually**
3. Set the **Relying party identifier** (Entity ID) to your Sim base URL:
```
https://sim.ai
```
4. Add an endpoint: **SAML Assertion Consumer Service** (HTTP POST) with the URL:
```
https://sim.ai/api/auth/sso/saml2/callback/adfs
```
5. Export the **Token-signing certificate** from **Certificates**: right-click → **View Certificate → Details → Copy to File**, choose **Base-64 encoded X.509 (.CER)**. The `.cer` file is PEM-encoded — rename it to `.pem` before pasting its contents into Sim.
6. Note the **ADFS Federation Service endpoint URL** (e.g. `https://adfs.company.com/adfs/ls`)
**In Sim:**
| Field | Value |
|-------|-------|
| Provider Type | SAML |
| Provider ID | `adfs` |
| Issuer URL | `https://sim.ai` |
| Domain | `company.com` |
| Entry Point URL | `https://adfs.company.com/adfs/ls` |
| Certificate | Contents of the `.pem` file |
<Callout type="info">
For ADFS, the **Issuer URL** field is the SP entity ID — the identifier ADFS uses to identify Sim as a relying party. It must match the **Relying party identifier** you registered in ADFS.
</Callout>
</Tab>
</Tabs>
---
## How sign-in works after setup
Once SSO is configured, users with your domain (`company.com`) can sign in through your identity provider:
1. User goes to `sim.ai` and clicks **Sign in with SSO**
2. They enter their work email (e.g. `alice@company.com`)
3. Sim redirects them to your identity provider
4. After authenticating, they are returned to Sim and added to your organization automatically
5. They land in the workspace
Users who sign in via SSO for the first time are automatically provisioned and added to your organization — no manual invite required.
<Callout type="info">
Password-based login remains available. Forcing all organization members to use SSO exclusively is not yet supported.
</Callout>
---
<FAQ items={[
{
question: "Which SSO providers are supported?",
answer: "Any identity provider that supports OIDC or SAML 2.0. This includes Okta, Microsoft Entra ID (Azure AD), Google Workspace, Auth0, OneLogin, JumpCloud, Ping Identity, ADFS, Shibboleth, and more."
},
{
question: "What is the Domain field used for?",
answer: "The domain (e.g. company.com) is how Sim routes users to the right identity provider. When a user enters their email on the SSO sign-in page, Sim matches their email domain to a registered SSO provider and redirects them there."
},
{
question: "Do I need to provide OIDC endpoints manually?",
answer: "No. For OIDC providers, Sim automatically fetches the authorization, token, and JWKS endpoints from the discovery document at {issuer}/.well-known/openid-configuration. You only need to provide the issuer URL."
},
{
question: "What happens when a user signs in with SSO for the first time?",
answer: "Sim creates an account for them automatically and adds them to your organization. No manual invite is needed. They are assigned the member role by default."
},
{
question: "Can I still use email/password login after enabling SSO?",
answer: "Yes. Enabling SSO does not disable password-based login. Users can still sign in with their email and password if they have one. Forced SSO (requiring all users on the domain to use SSO) is not yet supported."
},
{
question: "Who can configure SSO on Sim Cloud?",
answer: "Organization owners and admins can configure SSO. You must be on the Enterprise plan."
},
{
question: "What is the Callback URL?",
answer: "The Callback URL (also called Redirect URI or ACS URL) is the endpoint in Sim that receives the authentication response from your identity provider. For OIDC providers it follows the format: https://sim.ai/api/auth/sso/callback/{provider-id}. For SAML providers it is: https://sim.ai/api/auth/sso/saml2/callback/{provider-id}. You must register this URL in your identity provider before SSO will work."
},
{
question: "How do I update or replace an existing SSO configuration?",
answer: "Open Settings → Enterprise → Single Sign-On and click Edit. Update the fields and save. The existing provider configuration is replaced."
}
]} />
---
## Self-hosted setup
Self-hosted deployments use environment variables instead of the billing/plan check.
### Environment variables
```bash
# Required
SSO_ENABLED=true
NEXT_PUBLIC_SSO_ENABLED=true
# Required if you want users auto-added to your organization on first SSO sign-in
ORGANIZATIONS_ENABLED=true
NEXT_PUBLIC_ORGANIZATIONS_ENABLED=true
```
You can register providers through the **Settings UI** (same as cloud) or by running the registration script directly against your database.
### Script-based registration
Use this when you need to register an SSO provider without going through the UI — for example, during initial deployment or CI/CD automation.
```bash
# OIDC example (Okta)
SSO_ENABLED=true \
NEXT_PUBLIC_APP_URL=https://your-instance.com \
SSO_PROVIDER_TYPE=oidc \
SSO_PROVIDER_ID=okta \
SSO_ISSUER=https://dev-1234567.okta.com/oauth2/default \
SSO_DOMAIN=company.com \
SSO_USER_EMAIL=admin@company.com \
SSO_OIDC_CLIENT_ID=your-client-id \
SSO_OIDC_CLIENT_SECRET=your-client-secret \
bun run packages/db/scripts/register-sso-provider.ts
```
```bash
# SAML example (ADFS)
SSO_ENABLED=true \
NEXT_PUBLIC_APP_URL=https://your-instance.com \
SSO_PROVIDER_TYPE=saml \
SSO_PROVIDER_ID=adfs \
SSO_ISSUER=https://your-instance.com \
SSO_DOMAIN=company.com \
SSO_USER_EMAIL=admin@company.com \
SSO_SAML_ENTRY_POINT=https://adfs.company.com/adfs/ls \
SSO_SAML_CERT="-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----" \
bun run packages/db/scripts/register-sso-provider.ts
```
The script outputs the callback URL to configure in your IdP once it completes.
To remove a provider:
```bash
SSO_USER_EMAIL=admin@company.com \
bun run packages/db/scripts/deregister-sso-provider.ts
```

View File

@@ -0,0 +1,103 @@
---
title: Whitelabeling
description: Replace Sim branding with your own logo, colors, and links
---
import { FAQ } from '@/components/ui/faq'
import { Image } from '@/components/ui/image'
Whitelabeling lets you replace Sim's default branding — logo, colors, and support links — with your own. Members of your organization see your brand instead of Sim's throughout the workspace.
---
## Setup
### 1. Open Whitelabeling settings
Go to **Settings → Enterprise → Whitelabeling** in your workspace.
<Image src="/static/enterprise/whitelabeling.png" alt="Whitelabeling settings showing brand identity fields (Logo, Wordmark, Brand name), color pickers for primary and accent colors, and link fields for support email and documentation URL" width={900} height={500} />
### 2. Configure brand identity
| Field | Description |
|-------|-------------|
| **Logo** | Shown in the collapsed sidebar. Square image (PNG, JPEG, SVG, or WebP). Max 5 MB. |
| **Wordmark** | Shown in the expanded sidebar. Wide image (PNG, JPEG, SVG, or WebP). Max 5 MB. |
| **Brand name** | Replaces "Sim" in the sidebar and select UI elements. Max 64 characters. |
### 3. Configure colors
All colors must be valid hex values (e.g. `#701ffc`).
| Field | Description |
|-------|-------------|
| **Primary color** | Main accent color used for buttons and active states. |
| **Primary hover color** | Color shown when hovering over primary elements. |
| **Accent color** | Secondary accent for highlights and secondary interactive elements. |
| **Accent hover color** | Color shown when hovering over accent elements. |
### 4. Configure links
Replace Sim's default support and legal links with your own.
| Field | Description |
|-------|-------------|
| **Support email** | Shown in help prompts. Must be a valid email address. |
| **Documentation URL** | Link to your internal documentation. Must be a valid URL. |
| **Terms of service URL** | Link to your terms page. Must be a valid URL. |
| **Privacy policy URL** | Link to your privacy page. Must be a valid URL. |
### 5. Save
Click **Save changes**. The new branding is applied immediately for all members of your organization.
---
## What gets replaced
Whitelabeling replaces the following visual elements:
- **Sidebar logo and wordmark** — your uploaded images replace the Sim logo
- **Brand name** — appears in the sidebar and select UI labels
- **Primary and accent colors** — applied to buttons, active states, and highlights
- **Support and legal links** — help prompts and footer links point to your URLs
Whitelabeling applies only to members of your organization. Public-facing pages (login, marketing) are not affected.
---
<FAQ items={[
{
question: "Who can configure whitelabeling?",
answer: "Organization owners and admins can configure whitelabeling. On Sim Cloud, you must be on the Enterprise plan."
},
{
question: "What image formats are supported?",
answer: "PNG, JPEG, SVG, and WebP. Maximum file size is 5 MB for both the logo and wordmark."
},
{
question: "What is the difference between the logo and the wordmark?",
answer: "The logo is a square image shown in the collapsed sidebar. The wordmark is a wide image shown in the expanded sidebar alongside member names and navigation items."
},
{
question: "Do members outside my organization see the custom branding?",
answer: "No. Custom branding is scoped to your organization. Members see your branding when signed in to your organization's workspace."
}
]} />
---
## Self-hosted setup
Self-hosted deployments use environment variables instead of the billing/plan check.
### Environment variables
```bash
WHITELABELING_ENABLED=true
NEXT_PUBLIC_WHITELABELING_ENABLED=true
```
Once enabled, configure branding through **Settings → Enterprise → Whitelabeling** the same way.

View File

@@ -0,0 +1,343 @@
---
title: API Deployment
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
Deploy your workflow as a REST API endpoint that any application can call directly. Supports synchronous, streaming, and asynchronous execution modes.
## Deploying a Workflow
Open your workflow and click **Deploy**. The **General** tab opens first and shows you the current deployment state:
<Image src="/static/api-deployment/api-versions.png" alt="General tab of the Workflow Deployment modal showing a live workflow preview, a Versions table with v2 (live) and v1, and Undeploy / Update buttons" width={800} height={500} />
The **General** tab contains:
- **Live Workflow** — a read-only minimap of the workflow snapshot that is currently deployed
- **Versions** — a table of every deployment you've published, showing version number, who deployed it, and when
- **Deploy / Update / Undeploy** — action buttons at the bottom right
Click **Deploy** to publish your workflow for the first time, or **Update** to push a new snapshot after making changes. The green dot next to a version indicates it is the currently live version.
Once deployed, your workflow is available at:
```
POST https://sim.ai/api/workflows/{workflow-id}/execute
```
<Callout type="info">
API executions always run against the active deployment snapshot. After changing your workflow on the canvas, click **Update** to publish a new version.
</Callout>
### Keeping Track of Changes
When you modify the workflow canvas after deploying, an **Update deployment** badge appears at the bottom of the screen as a reminder that your live version is out of date:
<Image src="/static/api-deployment/api-update-button.png" alt="Canvas toolbar showing the Update and Run buttons with an Update deployment tooltip" width={400} height={200} />
You can click the **Update** button directly from the canvas toolbar — you don't need to open the Deploy modal every time.
## Version Control
Every time you deploy or update, a new version is recorded in the Versions table. You can manage past versions using the context menu (⋮) next to any row:
<Image src="/static/api-deployment/api-versions-menu.png" alt="Versions table showing v2 (live) and v1 with a context menu open offering Rename, Add description, Promote to live, and Load deployment options" width={800} height={400} />
| Action | Description |
|--------|-------------|
| **Rename** | Give the version a human-readable name (e.g., "Added memory") |
| **Add description** | Attach a note describing what changed in this version |
| **Promote to live** | Make this older version the active one without re-deploying |
| **Load deployment** | Load the workflow snapshot from this version back onto your canvas |
**Promote to live** is useful for rolling back — if a new deployment has an issue, promote the previous version to restore the last known-good state instantly.
## Making API Calls
Switch to the **API** tab in the Deploy modal to see ready-to-use code for all three execution modes:
<Image src="/static/api-deployment/api-tab.png" alt="API tab showing cURL, Python, JavaScript, and TypeScript language options, with Run workflow, Run workflow (stream response), and Run workflow (async) code sections" width={800} height={500} />
The language selector at the top lets you switch between **cURL**, **Python**, **JavaScript**, and **TypeScript**. Each mode — synchronous, streaming, and async — has its own code block that you can copy directly. The code is pre-filled with your workflow ID and a masked version of your API key.
At the bottom of the tab, two buttons give you quick access to key settings:
- **Edit API Info** — set a description and choose between API key auth or public access
- **Generate API Key** — create a new API key scoped to your workspace
## Authentication
By default, API endpoints require an API key passed in the `x-api-key` header. Generate keys in **Settings → Sim Keys** or via the **Generate API Key** button in the API tab.
```bash
curl -X POST https://sim.ai/api/workflows/{workflow-id}/execute \
-H "Content-Type: application/json" \
-H "x-api-key: $SIM_API_KEY" \
-d '{ "input": "Hello" }'
```
### API Info and Public Access
Click **Edit API Info** to add a description and change the access mode:
<Image src="/static/api-deployment/api-info.png" alt="Edit API Info modal with a Description textarea and an Access section toggling between API Key and Public modes" width={800} height={400} />
| Access Mode | Description |
|-------------|-------------|
| **API Key** (default) | Requires a valid API key in the `x-api-key` header |
| **Public** | No authentication required — anyone with the URL can call the endpoint |
The **Description** field documents what the workflow API does. This is useful for teams, or when exposing the workflow to tools and services that surface API metadata.
<Callout type="warn">
Public endpoints can be called by anyone with the URL. Only use this for workflows that don't expose sensitive data or perform sensitive actions.
</Callout>
## Execution Modes
### Synchronous
The default mode. Send a request and wait for the complete response:
<Tabs items={['cURL', 'Python', 'TypeScript']}>
<Tab value="cURL">
```bash
curl -X POST https://sim.ai/api/workflows/{workflow-id}/execute \
-H "Content-Type: application/json" \
-H "x-api-key: $SIM_API_KEY" \
-d '{ "input": "Summarize this article" }'
```
</Tab>
<Tab value="Python">
```python
import requests, os
response = requests.post(
"https://sim.ai/api/workflows/{workflow-id}/execute",
headers={
"Content-Type": "application/json",
"x-api-key": os.environ["SIM_API_KEY"]
},
json={"input": "Summarize this article"}
)
print(response.json())
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://sim.ai/api/workflows/{workflow-id}/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.SIM_API_KEY!
},
body: JSON.stringify({ input: 'Summarize this article' })
});
console.log(await response.json());
```
</Tab>
</Tabs>
### Streaming
Stream the response token-by-token as it is generated. Add `"stream": true` to your request body and specify which block output fields to stream using `selectedOutputs`.
Use the **Select outputs** dropdown in the API tab to choose which fields to stream:
<Image src="/static/api-deployment/api-select-outputs.png" alt="Select outputs dropdown open showing Agent 1 block with selectable output fields: content, model, tokens, toolCalls, providerTiming, cost" width={800} height={400} />
The dropdown groups available outputs by block. The most common choice is `content` from an Agent block, which streams the generated text. You can select fields from multiple blocks simultaneously.
The `selectedOutputs` values in the request body follow the format `blockName.field` (e.g., `agent_1.content`).
<Tabs items={['cURL', 'Python', 'TypeScript']}>
<Tab value="cURL">
```bash
curl -X POST https://sim.ai/api/workflows/{workflow-id}/execute \
-H "Content-Type: application/json" \
-H "x-api-key: $SIM_API_KEY" \
-d '{
"input": "Write a long essay",
"stream": true,
"selectedOutputs": ["agent_1.content"]
}'
```
</Tab>
<Tab value="Python">
```python
import requests, os
response = requests.post(
"https://sim.ai/api/workflows/{workflow-id}/execute",
headers={
"Content-Type": "application/json",
"x-api-key": os.environ["SIM_API_KEY"]
},
json={
"input": "Write a long essay",
"stream": True,
"selectedOutputs": ["agent_1.content"]
},
stream=True
)
for line in response.iter_lines():
if line:
print(line.decode())
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://sim.ai/api/workflows/{workflow-id}/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.SIM_API_KEY!
},
body: JSON.stringify({
input: 'Write a long essay',
stream: true,
selectedOutputs: ['agent_1.content']
})
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}
```
</Tab>
</Tabs>
### Asynchronous
For long-running workflows, async mode returns a job ID immediately so you don't need to hold the connection open. Add the `X-Execution-Mode: async` header to your request. The API returns HTTP 202 with a job ID and status URL. Poll the status URL until the job completes.
<Tabs items={['Start Job', 'Check Status']}>
<Tab value="Start Job">
```bash
curl -X POST https://sim.ai/api/workflows/{workflow-id}/execute \
-H "Content-Type: application/json" \
-H "x-api-key: $SIM_API_KEY" \
-H "X-Execution-Mode: async" \
-d '{ "input": "Process this large dataset" }'
```
**Response** (HTTP 202):
```json
{
"success": true,
"async": true,
"jobId": "run_abc123",
"executionId": "exec_xyz",
"message": "Workflow execution queued",
"statusUrl": "https://sim.ai/api/jobs/run_abc123"
}
```
</Tab>
<Tab value="Check Status">
```bash
curl https://sim.ai/api/jobs/{jobId} \
-H "x-api-key: $SIM_API_KEY"
```
**While processing:**
```json
{
"success": true,
"taskId": "run_abc123",
"status": "processing",
"metadata": {
"createdAt": "2025-09-10T12:00:00.000Z",
"startedAt": "2025-09-10T12:00:01.000Z"
},
"estimatedDuration": 300000
}
```
**When completed:**
```json
{
"success": true,
"taskId": "run_abc123",
"status": "completed",
"metadata": {
"createdAt": "2025-09-10T12:00:00.000Z",
"startedAt": "2025-09-10T12:00:01.000Z",
"completedAt": "2025-09-10T12:00:05.000Z",
"duration": 4000
},
"output": { "result": "..." }
}
```
</Tab>
</Tabs>
#### Job Status Values
| Status | Description |
|--------|-------------|
| `queued` | Job is waiting to be picked up |
| `processing` | Workflow is actively executing |
| `completed` | Finished successfully — `output` field contains the result |
| `failed` | Execution failed — `error` field contains the message |
Poll the `statusUrl` from the initial response until the status is `completed` or `failed`.
#### Execution Time Limits
| Plan | Sync Limit | Async Limit |
|------|-----------|-------------|
| **Community** | 5 minutes | 90 minutes |
| **Pro / Max / Team / Enterprise** | 50 minutes | 90 minutes |
If a job exceeds its time limit it is automatically marked as `failed`.
#### Job Retention
Completed and failed job results are retained for **24 hours**. After that, the status endpoint returns `404`. Retrieve and store results on your end if you need them longer.
#### Capacity Limits
If the execution queue is full, the API returns `503`:
```json
{
"error": "Service temporarily at capacity",
"retryAfterSeconds": 10
}
```
<Callout type="info">
Async mode always runs against the deployed version. It does not support draft state, block overrides, or partial execution options like `runFromBlock` or `stopAfterBlockId`.
</Callout>
## API Key Management
Generate and manage API keys in **Settings → Sim Keys**:
- **Create** new keys for different applications or environments
- **Revoke** keys that are no longer needed
- Keys are scoped to your workspace
## Rate Limits
API calls are subject to rate limits based on your plan. Rate limit details are returned in response headers (`X-RateLimit-*`) and in the response body. Use async mode for high-volume or long-running workloads.
For detailed rate limit information and the logs/webhooks API, see [External API](/execution/api).
<FAQ items={[
{ question: "What is the difference between the General tab and the API tab?", answer: "The General tab manages your deployment lifecycle — deploying, updating, rolling back, and viewing version history. The API tab gives you ready-to-use code samples and lets you configure the endpoint's description and access mode." },
{ question: "Can I deploy the same workflow as both an API and a chat?", answer: "Yes. A workflow can be simultaneously deployed as an API, chat, MCP tool, and more. Each deployment type runs against the same active snapshot." },
{ question: "How do I choose between sync, streaming, and async?", answer: "Use sync for quick workflows that finish in seconds. Use streaming when you want to show progressive output to users as it's generated. Use async for long-running workflows where holding a connection open isn't practical." },
{ question: "How do I select multiple outputs for streaming?", answer: "Open the Select outputs dropdown in the API tab and check each output field you want to stream. You can choose fields from multiple blocks. The selected fields are reflected as an array in the selectedOutputs request body parameter." },
{ question: "How does Promote to live work?", answer: "Promote to live sets an older version as the active deployment without creating a new version. Subsequent API calls immediately run against the promoted snapshot. This is the fastest way to roll back to a previous state." },
{ question: "How long are async job results available?", answer: "Completed and failed job results are retained for 24 hours. After that, the status endpoint returns 404. Retrieve and store results on your end if you need them longer." },
{ question: "What happens if my API key is compromised?", answer: "Revoke the key immediately in Settings → Sim Keys and generate a new one. Revoked keys stop working instantly." },
]} />

View File

@@ -6,7 +6,7 @@ import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Video } from '@/components/ui/video'
Sim provides a comprehensive external API for querying workflow execution logs and setting up webhooks for real-time notifications when workflows complete.
Sim provides a comprehensive external API for querying workflow run logs and setting up webhooks for real-time notifications when workflows complete.
## Authentication
@@ -21,7 +21,7 @@ You can generate API keys from the Sim platform and navigate to **Settings**, th
## Logs API
All API responses include information about your workflow execution limits and usage:
All API responses include information about your workflow run limits and usage:
```json
"limits": {
@@ -48,11 +48,11 @@ All API responses include information about your workflow execution limits and u
}
```
**Note:** Rate limits use a token bucket algorithm. `remaining` can exceed `requestsPerMinute` up to `maxBurst` when you haven't used your full allowance recently, allowing for burst traffic. The rate limits in the response body are for workflow executions. The rate limits for calling this API endpoint are in the response headers (`X-RateLimit-*`).
**Note:** Rate limits use a token bucket algorithm. `remaining` can exceed `requestsPerMinute` up to `maxBurst` when you haven't used your full allowance recently, allowing for burst traffic. The rate limits in the response body are for workflow runs. The rate limits for calling this API endpoint are in the response headers (`X-RateLimit-*`).
### Query Logs
Query workflow execution logs with extensive filtering options.
Query workflow run logs with extensive filtering options.
<Tabs items={['Request', 'Response']}>
<Tab value="Request">
@@ -70,11 +70,11 @@ Query workflow execution logs with extensive filtering options.
- `level` - Filter by level: `info`, `error`
- `startDate` - ISO timestamp for date range start
- `endDate` - ISO timestamp for date range end
- `executionId` - Exact execution ID match
- `minDurationMs` - Minimum execution duration in milliseconds
- `maxDurationMs` - Maximum execution duration in milliseconds
- `minCost` - Minimum execution cost
- `maxCost` - Maximum execution cost
- `executionId` - Exact run ID match
- `minDurationMs` - Minimum run duration in milliseconds
- `maxDurationMs` - Maximum run duration in milliseconds
- `minCost` - Minimum run cost
- `maxCost` - Maximum run cost
- `model` - Filter by AI model used
**Pagination:**
@@ -213,9 +213,9 @@ Retrieve detailed information about a specific log entry.
</Tab>
</Tabs>
### Get Execution Details
### Get Run Details
Retrieve execution details including the workflow state snapshot.
Retrieve run details including the workflow state snapshot.
<Tabs items={['Request', 'Response']}>
<Tab value="Request">
@@ -248,7 +248,7 @@ Retrieve execution details including the workflow state snapshot.
## Notifications
Get real-time notifications when workflow executions complete via webhook, email, or Slack. Notifications are configured at the workspace level from the Logs page.
Get real-time notifications when workflow runs complete via webhook, email, or Slack. Notifications are configured at the workspace level from the Logs page.
### Configuration
@@ -256,7 +256,7 @@ Configure notifications from the Logs page by clicking the menu button and selec
**Notification Channels:**
- **Webhook**: Send HTTP POST requests to your endpoint
- **Email**: Receive email notifications with execution details
- **Email**: Receive email notifications with run details
- **Slack**: Post messages to a Slack channel
**Workflow Selection:**
@@ -269,38 +269,38 @@ Configure notifications from the Logs page by clicking the menu button and selec
**Optional Data:**
- `includeFinalOutput`: Include the workflow's final output
- `includeTraceSpans`: Include detailed execution trace spans
- `includeTraceSpans`: Include detailed trace spans
- `includeRateLimits`: Include rate limit information (sync/async limits and remaining)
- `includeUsageData`: Include billing period usage and limits
### Alert Rules
Instead of receiving notifications for every execution, configure alert rules to be notified only when issues are detected:
Instead of receiving notifications for every run, configure alert rules to be notified only when issues are detected:
**Consecutive Failures**
- Alert after X consecutive failed executions (e.g., 3 failures in a row)
- Resets when an execution succeeds
- Alert after X consecutive failed runs (e.g., 3 failures in a row)
- Resets when a run succeeds
**Failure Rate**
- Alert when failure rate exceeds X% over the last Y hours
- Requires minimum 5 executions in the window
- Requires minimum 5 runs in the window
- Only triggers after the full time window has elapsed
**Latency Threshold**
- Alert when any execution takes longer than X seconds
- Alert when any run takes longer than X seconds
- Useful for catching slow or hanging workflows
**Latency Spike**
- Alert when execution is X% slower than the average
- Alert when a run is X% slower than the average
- Compares against the average duration over the configured time window
- Requires minimum 5 executions to establish baseline
- Requires minimum 5 runs to establish baseline
**Cost Threshold**
- Alert when a single execution costs more than $X
- Alert when a single run costs more than $X
- Useful for catching expensive LLM calls
**No Activity**
- Alert when no executions occur within X hours
- Alert when no runs occur within X hours
- Useful for monitoring scheduled workflows that should run regularly
**Error Count**
@@ -317,7 +317,7 @@ For webhooks, additional options are available:
### Payload Structure
When a workflow execution completes, Sim sends the following payload (via webhook POST, email, or Slack):
When a workflow run completes, Sim sends the following payload (via webhook POST, email, or Slack):
```json
{
@@ -456,7 +456,7 @@ Failed webhook deliveries are retried with exponential backoff and jitter:
- Deliveries timeout after 30 seconds
<Callout type="info">
Webhook deliveries are processed asynchronously and don't affect workflow execution performance.
Webhook deliveries are processed asynchronously and don't affect workflow run performance.
</Callout>
## Best Practices
@@ -596,11 +596,11 @@ app.listen(3000, () => {
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "How do I trigger async execution via the API?", answer: "Set the X-Execution-Mode header to 'async' on your POST request to /api/workflows/{id}/execute. The API returns a 202 response with a jobId, executionId, and a statusUrl you can poll to check when the job completes. Async mode does not support draft state, workflow overrides, or selective output options." },
{ question: "How do I trigger an async run via the API?", answer: "Set the X-Execution-Mode header to 'async' on your POST request to /api/workflows/{id}/execute. The API returns a 202 response with a jobId, executionId, and a statusUrl you can poll to check when the job completes. Async mode does not support draft state, workflow overrides, or selective output options." },
{ question: "What authentication methods does the API support?", answer: "The API supports two authentication methods: API keys passed in the x-api-key header, and session-based authentication for logged-in users. API keys can be generated from Settings > Sim Keys in the platform. Workflows with public API access enabled can also be called without authentication." },
{ question: "How does the webhook retry policy work?", answer: "Failed webhook deliveries are retried up to 5 times with exponential backoff: 5 seconds, 15 seconds, 1 minute, 3 minutes, and 10 minutes, plus up to 10% jitter. Only HTTP 5xx and 429 responses trigger retries. Each delivery times out after 30 seconds." },
{ question: "What rate limits apply to the Logs API?", answer: "Rate limits use a token bucket algorithm. Free plans get 30 requests/minute with 60 burst capacity, Pro gets 100/200, Team gets 200/400, and Enterprise gets 500/1000. These are separate from workflow execution rate limits, which are shown in the response body." },
{ question: "What rate limits apply to the Logs API?", answer: "Rate limits use a token bucket algorithm. Free plans get 30 requests/minute with 60 burst capacity, Pro gets 100/200, Team gets 200/400, and Enterprise gets 500/1000. These are separate from workflow run rate limits, which are shown in the response body." },
{ question: "How do I verify that a webhook is from Sim?", answer: "Configure a webhook secret when setting up notifications. Sim signs each delivery with HMAC-SHA256 using the format 't={timestamp},v1={signature}' in the sim-signature header. Compute the HMAC of '{timestamp}.{body}' with your secret and compare it to the signature value." },
{ question: "What alert rules are available for notifications?", answer: "You can configure alerts for consecutive failures, failure rate thresholds, latency thresholds, latency spikes (percentage above average), cost thresholds, no-activity periods, and error counts within a time window. All alert types include a 1-hour cooldown to prevent notification spam." },
{ question: "Can I filter which executions trigger notifications?", answer: "Yes. You can filter notifications by specific workflows (or select all), log level (info or error), and trigger type (api, webhook, schedule, manual, chat). You can also choose whether to include final output, trace spans, rate limits, and usage data in the notification payload." },
{ question: "Can I filter which runs trigger notifications?", answer: "Yes. You can filter notifications by specific workflows (or select all), log level (info or error), and trigger type (api, webhook, schedule, manual, chat). You can also choose whether to include final output, trace spans, rate limits, and usage data in the notification payload." },
]} />

View File

@@ -6,7 +6,7 @@ import { Callout } from 'fumadocs-ui/components/callout'
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Image } from '@/components/ui/image'
Understanding how workflows execute in Sim is key to building efficient and reliable automations. The execution engine automatically handles dependencies, concurrency, and data flow to ensure your workflows run smoothly and predictably.
Understanding how workflows run in Sim is key to building efficient and reliable automations. The execution engine automatically handles dependencies, concurrency, and data flow to ensure your workflows run smoothly and predictably.
## How Workflows Execute
@@ -14,7 +14,7 @@ Sim's execution engine processes workflows intelligently by analyzing dependenci
### Concurrent Execution by Default
Multiple blocks run concurrently when they don't depend on each other. This parallel execution dramatically improves performance without requiring manual configuration.
Multiple blocks run concurrently when they don't depend on each other. This dramatically improves performance without requiring manual configuration.
<Image
src="/static/execution/concurrency.png"
@@ -49,7 +49,7 @@ Workflows can branch in multiple directions using routing blocks. The execution
height={500}
/>
This workflow demonstrates how execution can follow different paths based on conditions or AI decisions, with each path executing independently.
This workflow demonstrates how a run can follow different paths based on conditions or AI decisions, with each path running independently.
## Block Types
@@ -57,7 +57,7 @@ Sim provides different types of blocks that serve specific purposes in your work
<Cards>
<Card title="Triggers" href="/triggers">
**Starter blocks** initiate workflows and **Webhook blocks** respond to external events. Every workflow needs a trigger to begin execution.
**Starter blocks** initiate workflows and **Webhook blocks** respond to external events. Every workflow needs a trigger to begin a run.
</Card>
<Card title="Processing Blocks" href="/blocks">
@@ -73,37 +73,37 @@ Sim provides different types of blocks that serve specific purposes in your work
</Card>
</Cards>
All blocks execute automatically based on their dependencies - you don't need to manually manage execution order or timing.
All blocks run automatically based on their dependencies - you don't need to manually manage run order or timing.
## Execution Monitoring
## Run Monitoring
When workflows run, Sim provides real-time visibility into the execution process:
When workflows run, Sim provides real-time visibility into the process:
- **Live Block States**: See which blocks are currently executing, completed, or failed
- **Execution Logs**: Detailed logs appear in real-time showing inputs, outputs, and any errors
- **Performance Metrics**: Track execution time and costs for each block
- **Path Visualization**: Understand which execution paths were taken through your workflow
- **Live Block States**: See which blocks are currently running, completed, or failed
- **Run Logs**: Detailed logs appear in real-time showing inputs, outputs, and any errors
- **Performance Metrics**: Track run time and costs for each block
- **Path Visualization**: Understand which paths were taken through your workflow
<Callout type="info">
All execution details are captured and available for review even after workflows complete, helping with debugging and optimization.
All run details are captured and available for review even after workflows complete, helping with debugging and optimization.
</Callout>
## Key Execution Principles
## Key Principles
Understanding these core principles will help you build better workflows:
1. **Dependency-Based Execution**: Blocks only run when all their dependencies have completed
2. **Automatic Parallelization**: Independent blocks run concurrently without configuration
3. **Smart Data Flow**: Outputs flow automatically to connected blocks
4. **Error Handling**: Failed blocks stop their execution path but don't affect independent paths
5. **Response Blocks as Exit Points**: When a Response block executes, the entire workflow stops and the API response is sent immediately. Multiple Response blocks can exist on different branches — the first one to execute wins
6. **State Persistence**: All block outputs and execution details are preserved for debugging
7. **Cycle Protection**: Workflows that call other workflows (via Workflow blocks, MCP tools, or API blocks) are tracked with a call chain. If the chain exceeds 25 hops, execution is stopped to prevent infinite loops
4. **Error Handling**: Failed blocks stop their run path but don't affect independent paths
5. **Response Blocks as Exit Points**: When a Response block runs, the entire workflow stops and the API response is sent immediately. Multiple Response blocks can exist on different branches — the first one to run wins
6. **State Persistence**: All block outputs and run details are preserved for debugging
7. **Cycle Protection**: Workflows that call other workflows (via Workflow blocks, MCP tools, or API blocks) are tracked with a call chain. If the chain exceeds 25 hops, the run is stopped to prevent infinite loops
## Next Steps
Now that you understand execution basics, explore:
- **[Block Types](/blocks)** - Learn about specific block capabilities
- **[Logging](/execution/logging)** - Monitor workflow executions and debug issues
- **[Logging](/execution/logging)** - Monitor workflow runs and debug issues
- **[Cost Calculation](/execution/costs)** - Understand and optimize workflow costs
- **[Triggers](/triggers)** - Set up different ways to run your workflows

View File

@@ -0,0 +1,184 @@
---
title: Chat Deployment
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
Deploy your workflow as a conversational chat interface that users can interact with via a shareable link or embedded widget. Chat supports multi-turn conversations, file uploads, and voice input.
<Image src="/static/chat/chat-live.png" alt="A deployed chat interface showing a conversation with Friendly Assistant" width={800} height={500} />
Every chat message triggers a fresh workflow execution, with the full conversation history passed in as context. Responses stream back to the user in real time.
<Callout type="info">
Chat executions run against your workflow's active deployment snapshot. Publish a new deployment after making canvas changes so the chat uses the updated version.
</Callout>
## Creating a Chat
Open your workflow, click **Deploy**, and select the **Chat** tab. You'll see the chat configuration panel:
<Image src="/static/chat/chat-deploy-config.png" alt="Chat deployment configuration panel showing URL, Title, Output, Access control, and Welcome message fields" width={800} height={500} />
Configure the following fields, then click **Launch Chat**:
| Field | Description |
|-------|-------------|
| **URL** | Slug that forms the public URL, e.g. `https://www.sim.ai/chat/your-slug`. Lowercase letters, numbers, and hyphens only. Must be unique across all workspaces. |
| **Title** | Display name shown in the chat header. |
| **Output** | Output fields from your workflow blocks returned as the chat response. At least one must be selected. |
| **Welcome Message** | Greeting shown before the user sends their first message. Defaults to `"Hi there! How can I help you today?"`. |
| **Access Control** | Controls who can access the chat. See [Access Control](#access-control) below. |
### Output Selection
<Image src="/static/chat/chat-deploy-output.png" alt="Output dropdown showing Agent 1 block with selectable fields: content, model, tokens, toolCalls, providerTiming, cost" width={800} height={400} />
The output dropdown groups available fields by block. For an Agent block, you can choose from `content`, `model`, `tokens`, `toolCalls`, `providerTiming`, and `cost`. In most cases, selecting `content` from the final Agent block is all you need — it streams the agent's text response directly to the user.
## Access Control
<Image src="/static/chat/chat-deploy-access-email.png" alt="Access control section with Email tab selected, showing an Allowed emails field with @sim.ai domain added" width={800} height={300} />
| Mode | Description |
|------|-------------|
| **Public** | Anyone with the link can chat — no authentication required |
| **Password** | Users must enter a password before they can start chatting |
| **Email** | Only specific email addresses or domains can access. Users verify with a 6-digit OTP sent to their email |
| **SSO** | OIDC-based single sign-on (enterprise only) |
**Email access:** Add individual addresses (`user@example.com`) or entire domains (`@example.com`) to the **Allowed emails** field. Users receive a one-time 6-digit OTP to their inbox — once verified, they can chat for the duration of their session.
**Password access:** A password field appears when this mode is selected. Share the password with users directly; they enter it before the conversation begins.
**SSO:** Uses OIDC to authenticate users through your identity provider. Available on enterprise plans.
## Sharing
### Direct Link
```
https://www.sim.ai/chat/your-slug
```
### Iframe
```html
<iframe
src="https://www.sim.ai/chat/your-slug"
width="100%"
height="600"
frameborder="0"
title="Chat"
></iframe>
```
## API Submission
You can also send messages to a chat programmatically. Responses are streamed using server-sent events (SSE).
<Tabs items={['cURL', 'TypeScript']}>
<Tab value="cURL">
```bash
curl -X POST https://www.sim.ai/api/chat/your-slug \
-H "Content-Type: application/json" \
-d '{
"input": "Hello, I need help with my order",
"conversationId": "optional-conversation-id"
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://www.sim.ai/api/chat/your-slug', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
input: 'Hello, I need help with my order',
conversationId: 'optional-conversation-id'
})
});
// Response is an SSE stream
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader!.read();
if (done) break;
console.log(decoder.decode(value));
}
```
</Tab>
</Tabs>
### With File Uploads
```bash
curl -X POST https://www.sim.ai/api/chat/your-slug \
-H "Content-Type: application/json" \
-d '{
"input": "What does this document say?",
"files": [{
"name": "report.pdf",
"type": "application/pdf",
"size": 1048576,
"data": "data:application/pdf;base64,..."
}]
}'
```
### Protected Chats
For password-protected chats, include the password in the request body:
```bash
curl -X POST https://www.sim.ai/api/chat/your-slug \
-H "Content-Type: application/json" \
-d '{ "password": "secret", "input": "Hello" }'
```
For email-protected chats, authenticate with OTP first:
```bash
# Step 1: Request OTP — sends a 6-digit code to the email address
curl -X POST https://www.sim.ai/api/chat/your-slug/otp \
-H "Content-Type: application/json" \
-d '{ "email": "allowed@example.com" }'
# Step 2: Verify OTP — save the Set-Cookie header for subsequent requests
curl -X PUT https://www.sim.ai/api/chat/your-slug/otp \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{ "email": "allowed@example.com", "otp": "123456" }'
# Step 3: Send messages using the auth cookie from Step 2
curl -X POST https://www.sim.ai/api/chat/your-slug \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{ "input": "Hello" }'
```
## Troubleshooting
**Chat returns 403** — The deployment is inactive. Open the Deploy modal and re-deploy the workflow.
**"At least one output block is required"** — No output field is selected in the Output dropdown. Open the Deploy modal, go to the Chat tab, and select at least one output from a block.
**OTP email not arriving** — Confirm the email address is on the allowed list and check spam folders. OTP codes expire after 15 minutes and can be resent after a 30-second cooldown.
**Chat not loading in iframe** — Check your site's Content Security Policy allows iframes from `sim.ai`.
**Responses not updating after workflow changes** — Chat uses the active deployment snapshot. Publish a new deployment from the Deploy modal to pick up your latest changes.
<FAQ items={[
{ question: "How is chat different from API deployment?", answer: "API deployment exposes your workflow as a REST endpoint for programmatic use. Chat wraps the workflow in a hosted conversational UI with streaming, file uploads, voice input, and access control — no application code required to use it." },
{ question: "Which output field should I select?", answer: "For workflows built around Agent blocks, select the content field from the final Agent block — this streams the agent's text response to the user. You can select multiple fields if your workflow produces structured output you want to expose." },
{ question: "How does conversation history work?", answer: "Each message triggers a new workflow execution. The full conversation history — all prior user messages and assistant responses — is passed as context so your workflow can maintain continuity across turns." },
{ question: "How does email OTP authentication work?", answer: "When a user opens an email-protected chat, they enter their email address. If it matches the allowed list, Sim sends a 6-digit OTP to that address. The user enters the code, and a session cookie is set for the duration of their visit." },
{ question: "Is there a message length limit?", answer: "There is no hard limit on message length. Very long messages may impact response time depending on your workflow's model context window." },
{ question: "Can I use chat with any workflow?", answer: "Yes, any workflow can be deployed as a chat. The chat sends the user's message as the workflow input and streams the selected block outputs back as the response." },
]} />

View File

@@ -6,7 +6,7 @@ import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
Sim automatically calculates costs for all workflow executions, providing transparent pricing based on AI model usage and execution charges. Understanding these costs helps you optimize workflows and manage your budget effectively.
Sim automatically calculates costs for all workflow runs, providing transparent pricing based on AI model usage and run charges. Understanding these costs helps you optimize workflows and manage your budget effectively.
## Credits
@@ -16,18 +16,18 @@ All plan limits, usage meters, and billing thresholds are displayed in credits t
## How Costs Are Calculated
Every workflow execution includes two cost components:
Every workflow run includes two cost components:
**Base Execution Charge**: 1 credit ($0.005) per execution
**Base Run Charge**: 1 credit ($0.005) per run
**AI Model Usage**: Variable cost based on token consumption
```javascript
modelCost = (inputTokens × inputPrice + outputTokens × outputPrice) / 1,000,000
totalCredits = baseExecutionCharge + modelCost × 200
totalCredits = baseRunCharge + modelCost × 200
```
<Callout type="info">
AI model prices are per million tokens. The calculation divides by 1,000,000 to get the actual cost. Workflows without AI blocks only incur the base execution charge.
AI model prices are per million tokens. The calculation divides by 1,000,000 to get the actual cost. Workflows without AI blocks only incur the base run charge.
</Callout>
## Model Breakdown in Logs
@@ -48,7 +48,7 @@ The model breakdown shows:
- **Token Usage**: Input and output token counts for each model
- **Cost Breakdown**: Individual costs per model and operation
- **Model Distribution**: Which models were used and how many times
- **Total Cost**: Aggregate cost for the entire workflow execution
- **Total Cost**: Aggregate cost for the entire workflow run
## Pricing Options
@@ -308,6 +308,17 @@ By default, your usage is capped at the credits included in your plan. To allow
## Plan Limits
### Workspaces
| Plan | Personal Workspaces | Shared (Organization) Workspaces |
|------|---------------------|----------------------------------|
| **Free** | 1 | — |
| **Pro** | Up to 3 | — |
| **Max** | Up to 10 | — |
| **Team / Enterprise** | Unlimited | Unlimited |
Team and Enterprise plans unlock shared workspaces that belong to your organization. Members invited to a shared workspace automatically join the organization and count toward your seat total. When a Team or Enterprise subscription is cancelled or downgraded, existing shared workspaces remain accessible to current members but new invites are disabled until the organization is upgraded again.
### Rate Limits
| Plan | Sync (req/min) | Async (req/min) |
@@ -319,17 +330,6 @@ By default, your usage is capped at the credits included in your plan. To allow
Max (individual) shares the same rate limits as team plans. Team plans (Pro or Max for Teams) use the Max-tier rate limits.
### Concurrent Execution Limits
| Plan | Concurrent Executions |
|------|----------------------|
| **Free** | 5 |
| **Pro** | 50 |
| **Max / Team** | 200 |
| **Enterprise** | 200 (customizable) |
Concurrent execution limits control how many workflow executions can run simultaneously within a workspace. When the limit is reached, new executions are queued and admitted as running executions complete. Manual runs from the editor are not subject to these limits.
### File Storage
| Plan | Storage |
@@ -341,18 +341,18 @@ Concurrent execution limits control how many workflow executions can run simulta
Team plans (Pro or Max for Teams) use 500 GB.
### Execution Time Limits
### Run Time Limits
| Plan | Sync | Async |
|------|------|-------|
| **Free** | 5 minutes | 90 minutes |
| **Pro / Max / Team / Enterprise** | 50 minutes | 90 minutes |
**Sync executions** run immediately and return results directly. These are triggered via the API with `async: false` (default) or through the UI.
**Async executions** (triggered via API with `async: true`, webhooks, or schedules) run in the background.
**Sync runs** complete immediately and return results directly. These are triggered via the API with `async: false` (default) or through the UI.
**Async runs** (triggered via API with `async: true`, webhooks, or schedules) run in the background.
<Callout type="info">
If a workflow exceeds its time limit, it will be terminated and marked as failed with a timeout error. Design long-running workflows to use async execution or break them into smaller workflows.
If a workflow exceeds its time limit, it will be terminated and marked as failed with a timeout error. Design long-running workflows to use async runs or break them into smaller workflows.
</Callout>
## Billing Model
@@ -378,12 +378,12 @@ Sim uses a **base subscription + overage** billing model:
### Threshold Billing
When on-demand is enabled and unbilled overage reaches $50, Sim automatically bills the full unbilled amount.
When on-demand is enabled and unbilled overage reaches $100, Sim automatically bills the full unbilled amount.
**Example:**
- Day 10: $70 overage → Bill $70 immediately
- Day 15: Additional $35 usage ($105 total) → Already billed, no action
- Day 20: Another $50 usage ($155 total, $85 unbilled) → Bill $85 immediately
- Day 10: $120 overage → Bill $120 immediately
- Day 15: Additional $60 usage ($180 total) → Already billed, no action
- Day 20: Another $80 usage ($260 total, $140 unbilled) → Bill $140 immediately
This spreads large overage charges throughout the month instead of one large bill at period end.
@@ -452,6 +452,21 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
- `limit` is derived from individual limits (Free/Pro/Max) or pooled organization limits (Team/Enterprise)
- `plan` is the highest-priority active plan associated with your user
## Purchasing Additional Credits
Pro and Team plan users can buy additional credits at any time in **Settings → Subscription → Credit Balance**:
- **Range**: $10 to $1,000 per purchase
- **Conversion**: 1 credit = $0.005 (a $10 purchase adds 2,000 credits)
- **Availability**: Credits are added immediately after payment
- **Expiration**: Purchased credits do not expire
- **Refunds**: Purchases are non-refundable
- **Team plans**: Only organization owners and admins can purchase credits. Purchased credits are added to the team's shared pool.
<Callout type="info">
Enterprise users should contact support for credit adjustments.
</Callout>
## Cost Optimization Strategies
- **Model Selection**: Choose models based on task complexity. Simple tasks can use GPT-4.1-nano while complex reasoning might need o1 or Claude Opus.
@@ -463,18 +478,18 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
## Next Steps
- Review your current usage in [Settings → Subscription](https://sim.ai/settings/subscription)
- Learn about [Logging](/execution/logging) to track execution details
- Learn about [Logging](/execution/logging) to track run details
- Explore the [External API](/execution/api) for programmatic cost monitoring
- Check out [workflow optimization techniques](/blocks) to reduce costs
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "How much does a single workflow execution cost?", answer: "Every execution incurs a base charge of 1 credit ($0.005). On top of that, any AI model usage is billed based on token consumption. Workflows that do not use AI blocks only pay the base execution charge." },
{ question: "How much does a single workflow run cost?", answer: "Every run incurs a base charge of 1 credit ($0.005). On top of that, any AI model usage is billed based on token consumption. Workflows that do not use AI blocks only pay the base run charge." },
{ question: "What is the credit-to-dollar conversion rate?", answer: "1 credit equals $0.005. All plan limits, usage meters, and billing thresholds in the Sim UI are displayed in credits." },
{ question: "Do unused daily refresh credits carry over?", answer: "No. Daily refresh credits reset every 24 hours and do not accumulate. If you do not use them within the day, they are lost." },
{ question: "What happens when I exceed my plan's credit limit?", answer: "By default, your usage is capped at your plan's included credits and executions will stop. If you enable on-demand billing or manually raise your usage limit in Settings, you can continue running workflows and pay for the overage at the end of the billing period." },
{ question: "What happens when I exceed my plan's credit limit?", answer: "By default, your usage is capped at your plan's included credits and runs will stop. If you enable on-demand billing or manually raise your usage limit in Settings, you can continue running workflows and pay for the overage at the end of the billing period." },
{ question: "How does the 1.1x hosted model multiplier work?", answer: "When you use Sim's hosted API keys (instead of bringing your own), a 1.1x multiplier is applied to the base model pricing for Agent blocks. This covers infrastructure and API management costs. You can avoid this multiplier by using your own API keys via the BYOK feature." },
{ question: "Are there any free options for AI models?", answer: "Yes. If you run local models through Ollama or VLLM, there are no API costs for those model calls. You still pay the base execution charge of 1 credit per execution." },
{ question: "When does threshold billing trigger?", answer: "When on-demand billing is enabled and your unbilled overage reaches $50, Sim automatically bills the full unbilled amount. This spreads large charges throughout the month instead of accumulating one large bill at period end." },
{ question: "Are there any free options for AI models?", answer: "Yes. If you run local models through Ollama or VLLM, there are no API costs for those model calls. You still pay the base run charge of 1 credit per run." },
{ question: "When does threshold billing trigger?", answer: "When on-demand billing is enabled and your unbilled overage reaches $100, Sim automatically bills the full unbilled amount. This spreads large charges throughout the month instead of accumulating one large bill at period end." },
]} />

View File

@@ -156,7 +156,7 @@ Use `url` for direct downloads or `base64` for inline processing.
- **Dropbox** - Dropbox file operations
<Callout type="info">
Files are automatically available to downstream blocks. The execution engine handles all file transfer and format conversion.
Files are automatically available to downstream blocks. The engine handles all file transfer and format conversion.
</Callout>
## Best Practices
@@ -165,15 +165,15 @@ Use `url` for direct downloads or `base64` for inline processing.
2. **Check file types** - Ensure the file type matches what the receiving block expects. The Vision block needs images, the File block handles documents.
3. **Consider file size** - Large files increase execution time. For very large files, consider using storage blocks (S3, Supabase) for intermediate storage.
3. **Consider file size** - Large files increase run time. For very large files, consider using storage blocks (S3, Supabase) for intermediate storage.
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "What is the maximum file size for uploads?", answer: "The maximum file size for files processed during workflow execution is 20 MB. Files exceeding this limit will be rejected with an error indicating the actual file size. For larger files, use storage blocks like S3 or Supabase for intermediate storage." },
{ question: "What is the maximum file size for uploads?", answer: "The maximum file size for files processed during a workflow run is 20 MB. Files exceeding this limit will be rejected with an error indicating the actual file size. For larger files, use storage blocks like S3 or Supabase for intermediate storage." },
{ question: "What file input formats are supported via the API?", answer: "When triggering a workflow via API, you can send files as base64-encoded data (using a data URI with the format 'data:{mime};base64,{data}') or as a URL pointing to a publicly accessible file. In both cases, include the file name and MIME type in the request." },
{ question: "How are files passed between blocks internally?", answer: "Files are represented as standardized UserFile objects with name, url, base64, type, and size properties. Most blocks accept the full file object and extract what they need automatically, so you typically pass the entire object rather than individual properties." },
{ question: "Which blocks can output files?", answer: "Gmail outputs email attachments, Slack outputs downloaded files, TTS generates audio files, Video Generator and Image Generator produce media files. Storage blocks like S3, Supabase, Google Drive, and Dropbox can also retrieve files for use in downstream blocks." },
{ question: "Do I need to extract base64 or URL from file objects manually?", answer: "No. Most blocks accept the full file object and handle the format conversion automatically. Simply pass the entire file reference (e.g., <gmail.attachments[0]>) and the receiving block will extract the data it needs." },
{ question: "How do file fields work in the Start block's input format?", answer: "When you define a field with type 'file[]' in the Start block's input format, the execution engine automatically processes incoming file data (base64 or URL) and uploads it to storage, converting it into UserFile objects before the workflow runs." },
{ question: "How do file fields work in the Start block's input format?", answer: "When you define a field with type 'file[]' in the Start block's input format, the engine automatically processes incoming file data (base64 or URL) and uploads it to storage, converting it into UserFile objects before the workflow runs." },
]} />

View File

@@ -7,10 +7,10 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
Sim's execution engine brings your workflows to life by processing blocks in the correct order, managing data flow, and handling errors gracefully, so you can understand exactly how workflows are executed in Sim.
Sim's execution engine brings your workflows to life by processing blocks in the correct order, managing data flow, and handling errors gracefully, so you can understand exactly how workflows run in Sim.
<Callout type="info">
Every workflow execution follows a deterministic path based on your block connections and logic, ensuring predictable and reliable results.
Every workflow run follows a deterministic path based on your block connections and logic, ensuring predictable and reliable results.
</Callout>
## Documentation Overview
@@ -22,33 +22,42 @@ Sim's execution engine brings your workflows to life by processing blocks in the
</Card>
<Card title="Logging" href="/execution/logging">
Monitor workflow executions with comprehensive logging and real-time visibility
Monitor workflow runs with comprehensive logging and real-time visibility
</Card>
<Card title="Cost Calculation" href="/execution/costs">
Understand how workflow execution costs are calculated and optimized
Understand how workflow run costs are calculated and optimized
</Card>
<Card title="External API" href="/execution/api">
Access execution logs and set up webhooks programmatically via REST API
Access run logs and set up webhooks programmatically via REST API
</Card>
<Card title="API Deployment" href="/execution/api-deployment">
Deploy your workflow as a REST API endpoint with sync, streaming, and async modes
</Card>
<Card title="Chat Deployment" href="/execution/chat">
Deploy your workflow as a conversational chat interface with streaming, file uploads, and voice
</Card>
</Cards>
## Key Concepts
### Topological Execution
Blocks execute in dependency order, similar to how a spreadsheet recalculates cells. The execution engine automatically determines which blocks can run based on completed dependencies.
Blocks run in dependency order, similar to how a spreadsheet recalculates cells. The execution engine automatically determines which blocks can run based on completed dependencies.
### Path Tracking
The engine actively tracks execution paths through your workflow. Router and Condition blocks dynamically update these paths, ensuring only relevant blocks execute.
The engine actively tracks run paths through your workflow. Router and Condition blocks dynamically update these paths, ensuring only relevant blocks run.
### Layer-Based Processing
Instead of executing blocks one-by-one, the engine identifies layers of blocks that can run in parallel, optimizing performance for complex workflows.
### Execution Context
Each workflow maintains a rich context during execution containing:
### Run Context
Each workflow maintains a rich context during a run containing:
- Block outputs and states
- Active execution paths
- Active run paths
- Loop and parallel iteration tracking
- Environment variables
- Routing decisions
@@ -56,23 +65,57 @@ Each workflow maintains a rich context during execution containing:
## Deployment Snapshots
API, Chat, Schedule, and Webhook executions run against the workflows active deployment snapshot. Manual runs from the editor execute the current draft canvas state, letting you test changes before deploying. Publish a new deployment whenever you change the canvas so every trigger uses the updated version.
API, Chat, Schedule, and Webhook runs use the workflows active deployment snapshot. Manual runs from the editor use the current draft canvas state, letting you test changes before deploying. Publish a new deployment whenever you change the canvas so every trigger uses the updated version.
<div className='flex justify-center my-6'>
<div className="flex justify-center my-6">
<Image
src='/static/execution/deployment-versions.png'
alt='Deployment versions table'
src="/static/execution/deployment-versions.png"
alt="Deployment versions table"
width={500}
height={280}
className='rounded-xl border border-border shadow-sm'
className="rounded-xl border border-border shadow-sm"
/>
</div>
The Deploy modal keeps a full version history—inspect any snapshot, compare it against your draft, and promote or roll back with one click when you need to restore a prior release.
### Version History
## Programmatic Execution
The **General** tab in the Deploy modal shows a version history table for every deployment. Each row shows the version name, who deployed it, and when.
Execute workflows from your applications using our official SDKs:
<div className="flex justify-center">
<Image
src="/static/execution/deployment-versions-table.png"
alt="Version history table with multiple deployment versions"
width={600}
height={650}
className="my-6"
/>
</div>
From the version table you can:
- **Rename** a version to give it a meaningful label (e.g., "v2 — added error handling")
- **Add a description** with notes about what changed in that deployment
- **Promote to live** to roll back to an older version — this makes the selected version the active deployment without changing your draft canvas
- **Load into editor** to restore a previous version's workflow into the canvas for editing and redeploying
- **Preview a version** by selecting a row to view that version's workflow in the canvas preview, then toggle between **Live** and the selected version
<div className="flex justify-center">
<Image
src="/static/execution/deployment-version-preview.png"
alt="Previewing a selected deployment version"
width={600}
height={650}
className="my-6"
/>
</div>
<Callout type="info">
Promoting an old version takes effect immediately — all API, Chat, Schedule, and Webhook executions will use the promoted version. Your draft canvas is not affected.
</Callout>
## Programmatic Access
Run workflows from your applications using our official SDKs:
```bash
# TypeScript/JavaScript
@@ -107,21 +150,21 @@ const result = await client.executeWorkflow('workflow-id', {
- Use parallel execution for independent operations
- Cache results with Memory blocks when appropriate
### Monitor Executions
### Monitor Runs
- Review logs regularly to understand performance patterns
- Track costs for AI model usage
- Use workflow snapshots to debug issues
## What's Next?
Start with [Execution Basics](/execution/basics) to understand how workflows run, then explore [Logging](/execution/logging) to monitor your executions and [Cost Calculation](/execution/costs) to optimize your spending.
Start with [Execution Basics](/execution/basics) to understand how workflows run, then explore [Logging](/execution/logging) to monitor your runs and [Cost Calculation](/execution/costs) to optimize your spending.
<FAQ items={[
{ question: "What are the execution timeout limits?", answer: "Synchronous executions (API, chat) have a default timeout of 5 minutes on the Free plan and 50 minutes on Pro, Team, and Enterprise plans. Asynchronous executions (schedules, webhooks) allow up to 90 minutes across all plans. These limits are configurable by the platform administrator." },
{ question: "What are the run timeout limits?", answer: "Synchronous runs (API, chat) have a default timeout of 5 minutes on the Free plan and 50 minutes on Pro, Team, and Enterprise plans. Asynchronous runs (schedules, webhooks) allow up to 90 minutes across all plans. These limits are configurable by the platform administrator." },
{ question: "How does parallel execution work?", answer: "The engine identifies layers of blocks with no dependencies on each other and runs them concurrently. Within loops and parallel blocks, the engine supports up to 20 parallel branches by default and up to 1,000 loop iterations. Nested subflows (loops inside parallels, or vice versa) are supported up to 10 levels deep." },
{ question: "Can I cancel a running execution?", answer: "Yes. The engine supports cancellation through an abort signal mechanism. When you cancel an execution, the engine checks for cancellation between block executions (at roughly 500ms intervals when using Redis-backed cancellation). Any in-progress blocks complete, and the execution returns with a cancelled status." },
{ question: "What is a deployment snapshot?", answer: "A deployment snapshot is a frozen copy of your workflow at the time you click Deploy. Trigger-based executions (API, chat, schedule, webhook) run against the active snapshot, not your draft canvas. Manual runs from the editor execute the current draft canvas state, so you can test changes before deploying. You can view, compare, and roll back snapshots from the Deploy modal." },
{ question: "How are execution costs calculated?", answer: "Costs are tracked per block based on the AI model used. Each block log records input tokens, output tokens, and the computed cost using the model's pricing. The total workflow cost is the sum of all block-level costs for that execution. You can review costs in the execution logs." },
{ question: "What happens when a block fails during execution?", answer: "When a block throws an error, the engine captures the error message in the block log, finalizes any incomplete logs with timing data, and halts the execution with a failure status. If the failing block has an error output handle connected to another block, that error path is followed instead of halting entirely." },
{ question: "Can I re-run part of a workflow without starting from scratch?", answer: "Yes. The run-from-block feature lets you select a specific block and re-execute from that point. The engine computes which upstream blocks need to be re-run (the dirty set) and preserves cached outputs from blocks that have not changed, so only the affected portion of the workflow re-executes." },
{ question: "Can I cancel a running workflow?", answer: "Yes. The engine supports cancellation through an abort signal mechanism. When you cancel a run, the engine checks for cancellation between blocks (at roughly 500ms intervals when using Redis-backed cancellation). Any in-progress blocks complete, and the run returns with a cancelled status." },
{ question: "What is a deployment snapshot?", answer: "A deployment snapshot is a frozen copy of your workflow at the time you click Deploy. Trigger-based runs (API, chat, schedule, webhook) use the active snapshot, not your draft canvas. Manual runs from the editor use the current draft canvas state, so you can test changes before deploying. You can view, compare, and roll back snapshots from the Deploy modal." },
{ question: "How are run costs calculated?", answer: "Costs are tracked per block based on the AI model used. Each block log records input tokens, output tokens, and the computed cost using the model's pricing. The total workflow cost is the sum of all block-level costs for that run. You can review costs in the run logs." },
{ question: "What happens when a block fails during a run?", answer: "When a block throws an error, the engine captures the error message in the block log, finalizes any incomplete logs with timing data, and halts the run with a failure status. If the failing block has an error output handle connected to another block, that error path is followed instead of halting entirely." },
{ question: "Can I re-run part of a workflow without starting from scratch?", answer: "Yes. The run-from-block feature lets you select a specific block and re-run from that point. The engine computes which upstream blocks need to be re-run (the dirty set) and preserves cached outputs from blocks that have not changed, so only the affected portion of the workflow re-runs." },
]} />

View File

@@ -6,7 +6,7 @@ import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
Sim provides comprehensive logging for all workflow executions, giving you complete visibility into how your workflows run, what data flows through them, and where issues might occur.
Sim provides comprehensive logging for all workflow runs, giving you complete visibility into how your workflows run, what data flows through them, and where issues might occur.
## Logging System
@@ -14,7 +14,7 @@ Sim offers two complementary logging interfaces to match different workflows and
### Real-Time Console
During manual or chat workflow execution, logs appear in real-time in the Console panel on the right side of the workflow editor:
During manual or chat workflow runs, logs appear in real-time in the Console panel on the right side of the workflow editor:
<div className="flex justify-center">
<Image
@@ -27,14 +27,14 @@ During manual or chat workflow execution, logs appear in real-time in the Consol
</div>
The console shows:
- Block execution progress with active block highlighting
- Block progress with active block highlighting
- Real-time outputs as blocks complete
- Execution timing for each block
- Timing for each block
- Success/error status indicators
### Logs Page
All workflow executions—whether triggered manually, via API, Chat, Schedule, or Webhook—are logged to the dedicated Logs page:
All workflow runs—whether triggered manually, via API, Chat, Schedule, or Webhook—are logged to the dedicated Logs page:
<div className="flex justify-center">
<Image
@@ -72,7 +72,7 @@ View the complete data flow for each block with tabs to switch between:
<Tabs items={['Output', 'Input']}>
<Tab>
**Output Tab** shows the block's execution result:
**Output Tab** shows the block's result:
- Structured data with JSON formatting
- Markdown rendering for AI-generated content
- Copy button for easy data extraction
@@ -87,17 +87,17 @@ View the complete data flow for each block with tabs to switch between:
</Tab>
</Tabs>
### Execution Timeline
### Run Timeline
For workflow-level logs, view detailed execution metrics:
For workflow-level logs, view detailed run metrics:
- Start and end timestamps
- Total workflow duration
- Individual block execution times
- Individual block run times
- Performance bottleneck identification
## Workflow Snapshots
For any logged execution, click "View Snapshot" to see the exact workflow state at execution time:
For any logged run, click "View Snapshot" to see the exact workflow state at the time of the run:
<div className="flex justify-center">
<Image
@@ -111,12 +111,12 @@ For any logged execution, click "View Snapshot" to see the exact workflow state
The snapshot provides:
- Frozen canvas showing the workflow structure
- Block states and connections as they were during execution
- Block states and connections as they were during the run
- Click any block to see its inputs and outputs
- Useful for debugging workflows that have since been modified
<Callout type="info">
Workflow snapshots are only available for executions after the enhanced logging system was introduced. Older migrated logs show a "Logged State Not Found" message.
Workflow snapshots are only available for runs after the enhanced logging system was introduced. Older migrated logs show a "Logged State Not Found" message.
</Callout>
## Log Retention
@@ -134,11 +134,11 @@ The snapshot provides:
### For Production
- Monitor the Logs page regularly for errors or performance issues
- Set up filters to focus on specific workflows or time periods
- Use live mode during critical deployments to watch executions in real-time
- Use live mode during critical deployments to watch runs in real-time
### For Debugging
- Always check the execution timeline to identify slow blocks
- Compare inputs between working and failing executions
- Always check the run timeline to identify slow blocks
- Compare inputs between working and failing runs
- Use workflow snapshots to see the exact state when issues occurred
## Next Steps
@@ -150,10 +150,10 @@ The snapshot provides:
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "How long are execution logs retained?", answer: "Free plans retain logs for 7 days — after that, logs are archived to cloud storage and deleted from the database. Pro, Team, and Enterprise plans retain logs indefinitely with no automatic cleanup." },
{ question: "What data is captured in each execution log?", answer: "Each log entry includes the execution ID, workflow ID, trigger type, start and end timestamps, total duration in milliseconds, cost breakdown (total cost, token counts, and per-model breakdowns), execution data with trace spans, final output, and any associated files. The log details sidebar lets you inspect block-level inputs and outputs." },
{ question: "How long are run logs retained?", answer: "Free plans retain logs for 7 days — after that, logs are archived to cloud storage and deleted from the database. Pro, Team, and Enterprise plans retain logs indefinitely with no automatic cleanup." },
{ question: "What data is captured in each run log?", answer: "Each log entry includes the run ID, workflow ID, trigger type, start and end timestamps, total duration in milliseconds, cost breakdown (total cost, token counts, and per-model breakdowns), run data with trace spans, final output, and any associated files. The log details sidebar lets you inspect block-level inputs and outputs." },
{ question: "Are API keys visible in the logs?", answer: "No. API keys and credentials are automatically redacted in the log input tab for security. You can safely inspect block inputs without exposing sensitive values." },
{ question: "What is a workflow snapshot?", answer: "A workflow snapshot is a frozen copy of the workflow's structure (blocks, connections, and configuration) captured at execution time. It lets you see the exact state of the workflow when a particular execution ran, which is useful for debugging workflows that have been modified since the execution." },
{ question: "Can I access logs programmatically?", answer: "Yes. The External API provides endpoints to query logs with filtering by workflow, time range, trigger type, duration, cost, and model. You can also set up webhook, email, or Slack notifications for real-time alerts when executions complete." },
{ question: "What does Live mode do on the Logs page?", answer: "Live mode automatically refreshes the Logs page in real-time so new execution entries appear as they are logged, without requiring manual page refreshes. This is useful during deployments or when monitoring active workflows." },
{ question: "What is a workflow snapshot?", answer: "A workflow snapshot is a frozen copy of the workflow's structure (blocks, connections, and configuration) captured at the time of a run. It lets you see the exact state of the workflow when a particular run happened, which is useful for debugging workflows that have been modified since." },
{ question: "Can I access logs programmatically?", answer: "Yes. The External API provides endpoints to query logs with filtering by workflow, time range, trigger type, duration, cost, and model. You can also set up webhook, email, or Slack notifications for real-time alerts when runs complete." },
{ question: "What does Live mode do on the Logs page?", answer: "Live mode automatically refreshes the Logs page in real-time so new log entries appear as they are recorded, without requiring manual page refreshes. This is useful during deployments or when monitoring active workflows." },
]} />

View File

@@ -1,3 +1,3 @@
{
"pages": ["index", "basics", "files", "api", "logging", "costs"]
"pages": ["index", "basics", "files", "api", "api-deployment", "chat", "logging", "costs"]
}

View File

@@ -51,7 +51,7 @@ Welcome to Sim, the open-source AI workspace where teams build, deploy, and mana
<Card title="MCP Integration" href="/mcp">
Connect external services with Model Context Protocol
</Card>
<Card title="SDKs" href="/sdks">
<Card title="SDKs" href="/api-reference">
Integrate Sim into your applications
</Card>
</Cards>

View File

@@ -0,0 +1,140 @@
---
title: Integrations
description: Connect third-party services and OAuth accounts for your workflows
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
Integrations are authenticated connections to third-party services like Gmail, Slack, GitHub, Dropbox, and more. Sim handles the OAuth flow, token storage, and automatic token refresh — you connect once and select the account in any block that needs it.
You can connect **multiple accounts per service** — for example, two separate Gmail accounts for different workflows.
## Managing Integrations
To manage integrations, open your workspace **Settings** and navigate to the **Integrations** tab.
<Image
src="/static/integrations/integrations-list.png"
alt="Integrations tab showing connected accounts with service icons, names, and Details/Disconnect buttons"
width={700}
height={500}
/>
The list shows all your connected accounts with the service icon, display name, and provider. Each entry has a **Details** button and a **Disconnect** button.
## Connecting an Account
Click **+ Connect** in the top right to open the connection modal.
<Image
src="/static/integrations/connect-service-picker.png"
alt="Connect Integration modal showing a searchable list of available services"
width={500}
height={400}
/>
Search for or select the service you want to connect, then fill in the connection details:
<Image
src="/static/integrations/connect-modal.png"
alt="Connect Gmail modal showing permissions requested, display name field, and description field"
width={500}
height={450}
/>
1. Review the **Permissions requested** — these are the scopes Sim will request from the provider
2. Enter a **Display name** to identify this connection (e.g. "Work Gmail" or "Marketing Slack")
3. Optionally add a **Description**
4. Click **Connect** and complete the authorization flow
## Using Integrations in Workflows
Blocks that require authentication (e.g. Gmail, Slack, Google Sheets) display a credential selector. Select the connected account you want that block to use.
<Image
src="/static/credentials/oauth-selector.png"
alt="Gmail block showing the account selector dropdown with connected accounts"
width={500}
height={350}
/>
You can also connect additional accounts directly from the block by selecting **Connect another [service] account** at the bottom of the dropdown.
<Callout type="info">
If a block requires an integration and none is selected, the workflow will fail at that step.
</Callout>
## Using a Credential ID
Each integration has a unique credential ID you can use to reference it dynamically. This is useful when you have multiple accounts for the same service and want to switch between them programmatically — for example, routing different workflow runs to different Gmail accounts based on a variable.
To copy a credential ID, open **Details** on any integration and click the clipboard icon next to the Display Name.
<Image
src="/static/integrations/copy-credential-id.png"
alt="Integration details showing the Copy credential ID tooltip on the clipboard icon next to the Display Name"
width={700}
height={150}
/>
In any block that requires an integration, click **Switch to manual ID** next to the credential selector to switch from the dropdown to a text field.
<Image
src="/static/integrations/switch-to-manual-id.png"
alt="Block showing the Switch to manual ID button next to the account selector"
width={500}
height={200}
/>
Paste or reference the credential ID in that field. You can use a `{{SECRET}}` reference or a block output variable to make it dynamic.
<Image
src="/static/integrations/manual-credential-id.png"
alt="Block showing the Enter credential ID text field after switching to manual mode"
width={500}
height={200}
/>
## Integration Details
Click **Details** on any integration to open its detail view.
<Image
src="/static/integrations/integration-details.png"
alt="Integration details view showing Display Name, Description, Members, Reconnect, and Disconnect"
width={700}
height={420}
/>
From here you can:
- Edit the **Display Name** and **Description**
- Manage **Members** — invite teammates by email and assign them an **Admin** or **Member** role
- **Reconnect** — re-authorize the connection if it has expired or if you need to update permissions
- **Disconnect** — remove the integration entirely
Click **Save** to apply changes, or **Back** to return to the list.
<Callout type="warn">
If you disconnect an integration that is used in a workflow, that workflow will fail at any block referencing it. Update blocks before disconnecting.
</Callout>
## Access Control
Each integration has role-based access:
- **Admin** — can view, edit, disconnect, reconnect, and manage member access
- **Member** — can use the integration in workflows (read-only)
When you connect an integration, you are automatically set as its Admin. You can share it with teammates from the Details view.
<FAQ items={[
{ question: "Does Sim handle OAuth token refresh automatically?", answer: "Yes. When an integration is used during execution, Sim checks whether the access token has expired and automatically refreshes it using the stored refresh token before making the API call. You do not need to handle token refresh manually." },
{ question: "Can I connect multiple accounts for the same service?", answer: "Yes. You can connect multiple accounts per service (for example, two separate Gmail accounts). Each block lets you select which account to use from the credential dropdown. This is useful when different workflows need different identities or permissions." },
{ question: "What is a credential ID and when should I use it?", answer: "Each integration has a unique credential ID that you can use instead of the dropdown selector. This lets you pass the credential dynamically — for example, from a variable or a previous block's output — so the same workflow can use different accounts depending on the context. Copy the ID from the Details view and use Switch to manual ID in any block to paste or reference it." },
{ question: "What happens if an OAuth token can no longer be refreshed?", answer: "If a refresh fails (e.g. the user revoked access or the refresh token expired), the workflow will fail at the block using that integration. Open Settings → Integrations, find the connection, and use the Reconnect button to re-authorize it." },
{ question: "Are OAuth tokens encrypted at rest?", answer: "Yes. OAuth tokens are encrypted before being stored in the database and are never exposed in the workflow editor, logs, or API responses." },
{ question: "What happens if I disconnect an integration that is used in a workflow?", answer: "Any block referencing the disconnected integration will fail at runtime. Make sure to update those blocks before disconnecting, or reconnect the integration to restore access." },
]} />

View File

@@ -0,0 +1,5 @@
{
"title": "Integrations",
"pages": ["index", "google-service-account"],
"defaultOpen": false
}

View File

@@ -5,13 +5,16 @@ description: Automatically sync documents from external sources into your knowle
import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
Connectors let you pull documents directly from external services into your knowledge base. Instead of manually uploading files, a connector continuously syncs content from sources like Notion, Google Drive, GitHub, Slack, and more — keeping your knowledge base up to date automatically.
Connectors continuously sync documents from external services into your knowledge base, so you never have to upload files manually. New content is added, changed content is re-processed, and deleted content is removed — all automatically.
## Available Connectors
Sim ships with 30 built-in connectors spanning productivity tools, cloud storage, development platforms, and more.
<Image src="/static/connectors/connectors-sources.png" alt="Connect Source picker showing a searchable list of available connectors including Airtable, Asana, Confluence, Discord, Dropbox, Evernote, Fireflies, GitHub, and Gmail" width={800} height={500} />
Sim ships with 30 built-in connectors:
| Category | Connectors |
|----------|-----------|
@@ -29,24 +32,25 @@ Sim ships with 30 built-in connectors spanning productivity tools, cloud storage
## Adding a Connector
From inside a knowledge base, click **+ New connector** in the top right to open the connector picker. Select a service, then complete the setup steps:
<Steps>
<Step>
### Select a source
Open a knowledge base and click **Add Connector**. You'll see the full list of available connectors — pick the service you want to sync from.
</Step>
<Step>
### Authenticate
Most connectors use **OAuth** — select an existing credential from the dropdown, or click **Connect new account** to authorize through the service's login flow. Tokens are refreshed automatically, so you won't need to re-authenticate unless you revoke access.
Most connectors use **OAuth** — select an existing credential from the dropdown or click **Connect new account** to authorize through the service. Tokens are refreshed automatically.
A few connectors (Evernote, Obsidian, Fireflies) use **API keys** instead. Paste your key or developer token directly, and it will be stored securely.
A few connectors use **API keys** instead:
| Connector | Where to get the key |
|-----------|---------------------|
| **Evernote** | Developer Token (starts with `S=`) from your Evernote account settings |
| **Obsidian** | Install the [Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api) plugin, then copy the key from its settings |
| **Fireflies** | Generate from the Integrations page in your Fireflies account |
<Callout type="info">
If you rotate an API key in the external service, you'll need to update it in Sim as well. OAuth tokens are refreshed automatically, but API keys are not.
If you rotate an API key in the external service, update it in Sim as well OAuth tokens refresh automatically, but API keys do not.
</Callout>
</Step>
@@ -54,103 +58,135 @@ A few connectors (Evernote, Obsidian, Fireflies) use **API keys** instead. Paste
### Configure
Each connector has its own configuration fields that control what gets synced. Some examples:
Each connector has source-specific fields that control what gets synced. Examples:
- **Notion**: Choose between syncing an entire workspace, a specific database, or a single page tree
- **GitHub**: Specify a repository, branch, and optional file extension filter
- **Confluence**: Enter your Atlassian domain and optionally filter by space key or content type
- **Obsidian**: Provide your vault URL and optionally restrict to a folder path
- **Notion** sync an entire workspace, a specific database, or a single page tree
- **GitHub** — specify a repository, branch, and optional file extension filter
- **Confluence** — enter your Atlassian domain and optionally filter by space key or content type
- **Obsidian** — provide your vault URL (`https://127.0.0.1:27124` by default) and optionally restrict to a folder path
- **Fireflies** — optionally filter by host email or cap the number of transcripts synced
All configuration is validated when you save — if a repository doesn't exist or a domain is unreachable, you'll get an immediate error.
Configuration is validated on save — if a repository doesn't exist or a domain is unreachable, you'll see an error immediately.
</Step>
<Step>
### Choose sync frequency
Select how often the connector should re-sync:
| Frequency | Description |
|-----------|-------------|
| Frequency | Notes |
|-----------|-------|
| Every hour | Best for fast-moving sources |
| Every 6 hours | Good balance for most use cases |
| Every 6 hours | Good balance for most sources |
| **Daily** (default) | Suitable for content that changes infrequently |
| Weekly | For stable, rarely-updated sources |
| Manual only | Sync only when you trigger it |
| Manual only | Sync only when you trigger it manually |
Sub-hourly frequencies require a Max or Enterprise plan.
</Step>
<Step>
### Configure metadata tags (optional)
If the connector supports metadata tags, you'll see checkboxes for each tag type (e.g., Labels, Last Modified, Notebook). All are enabled by default — uncheck any you don't need.
If the connector supports metadata tags, you'll see checkboxes for each available tag type (e.g., Labels, Last Modified, Notebook). All are enabled by default — uncheck any you don't need.
See the [Metadata Tags](#metadata-tags) section below for details.
Tag slots are shared across all documents in a knowledge base. See [Tags](/knowledgebase/tags) for details.
</Step>
<Step>
### Connect & Sync
Click **Connect & Sync** to save the connector and trigger the first sync immediately. Documents will begin appearing in your knowledge base as they are processed.
Click **Connect & Sync** to save the connector and trigger the first sync. Documents will start appearing as they're processed.
</Step>
</Steps>
## How Syncing Works
## Managing Connectors
On each sync, the connector fetches documents from the external service and compares them against what's already in your knowledge base. Only documents that have actually changed are reprocessed — new content is added, updated content is re-chunked and re-embedded, and documents that no longer exist in the source are removed.
Open **Connected Sources** from the knowledge base to see all active connectors. Each card shows the connector's status, the last sync time and document count, and the next scheduled sync:
This means syncing is efficient even for large document sets. A connector with thousands of documents will only do meaningful work when something changes.
<Image src="/static/connectors/connectors-sync-history.png" alt="Connected Sources panel showing a Google Docs connector with Active status, last sync details, and a sync history log with dated entries" width={800} height={450} />
### Handling Failures
The action buttons on each connector card:
If a single document fails to fetch (e.g., due to a permission issue or timeout), the sync continues with the remaining documents. The failed document will be retried on the next sync cycle.
| Button | Action |
|--------|--------|
| **↻** (Refresh) | Trigger a manual sync immediately. Disabled while syncing or disabled; a 5-minute cooldown applies after each manual trigger |
| **⚙** (Settings) | Open the edit modal to change source config or sync frequency |
| **⏸ / ▶** (Pause / Resume) | Pause scheduled syncs without removing the connector. Resume works from both paused and disabled states |
| **🗑** (Delete) | Remove the connector. A confirmation modal appears with an option to also delete all synced documents |
| **** (Chevron) | Expand to show sync history |
If an entire sync fails (e.g., the service is down or credentials expired), the connector automatically backs off and retries later. The backoff resets as soon as a sync succeeds.
### Editing a Connector
## Metadata Tags
Click the settings icon to open the edit modal. It has two tabs:
Connectors can automatically populate [tags](/docs/knowledgebase/tags) with metadata from the source, letting you filter documents in the Knowledge block based on information from the external service.
**Settings** — change any source-specific config fields (e.g., switch the GitHub branch) and update the sync frequency.
For example, a Notion connector might tag documents with their **Labels**, **Last Modified** date, and **Created** date. A GitHub connector might tag documents with their **Repository** and **File Path**. This metadata becomes available for [tag-based filtering](/docs/knowledgebase/tags) in your workflows.
**Documents** — browse all documents this connector has synced and manage exclusions (see [Excluding Documents](#excluding-documents) below).
### Opting Out
### Sync History
You can disable specific metadata tags during connector setup. Disabled tags won't be populated, leaving those tag slots available for other connectors or manual tagging.
Expand any connector card by clicking the chevron to see a log of recent syncs:
<Callout type="info">
Tag slots are shared across all documents in a knowledge base. If you have multiple connectors, each one's metadata tags draw from the same pool of available slots.
</Callout>
- Each row shows the date/time and a summary of what changed: **+N** (added, green), **~N** (updated, amber), **-N** (deleted, red), **!N** (failed, red), or **No changes**
- A spinner indicates a sync currently in progress
- Error rows show a red icon and the failure message
The log retains the most recent 10 sync runs.
## Excluding Documents
You can manually exclude specific documents from a connector's sync. Excluded documents are skipped on every subsequent sync, even if they change in the source. This is useful for filtering out templates, drafts, or other content you don't want in your knowledge base.
Sometimes a connector syncs documents you don't want in your knowledge base — drafts, templates, confidential pages, and so on. You can exclude them individually.
## Source Links
<Image src="/static/connectors/connectors-excluded.png" alt="Edit Google Docs modal showing the Documents tab with Active (37) and Excluded (0) filter buttons and a 'No excluded documents' message" width={800} height={450} />
Every synced document retains a link back to the original in the external service. This lets you trace any knowledge base document to its source — whether that's a Notion page, a GitHub file, a Confluence article, or a Slack conversation.
To exclude a document, open the connector's settings modal, go to the **Documents** tab, and click **Exclude** next to any document. Excluded documents are skipped on every subsequent sync even if the source content changes.
To reverse an exclusion, switch to the **Excluded** tab and click **Restore** — the document will be pulled in on the next sync.
## How Syncing Works
On each run the connector fetches documents from the source and compares them against what's already stored. Only changed documents are reprocessed — new content is added, updated content is re-chunked and re-embedded, deleted content is removed. A connector syncing thousands of documents will only do real work when something actually changes.
### Connector Status
| Status | Meaning |
|--------|---------|
| **Active** | Running normally on schedule |
| **Syncing** | A sync is currently in progress |
| **Paused** | Scheduled syncs are suspended; manual sync is still available |
| **Error** | The last sync failed; will retry on the next scheduled run with backoff |
| **Disabled** | Syncing has been paused automatically after 10 consecutive failures |
A disabled connector requires intervention — either reconnect the OAuth account or use the Resume button to re-enable syncing.
### Handling Failures
If a single document fails (e.g., a permission issue or timeout), the sync continues and retries that document next time. If an entire sync fails, the connector backs off and retries with increasing delays. After 10 consecutive full-sync failures the connector is automatically set to **Disabled** to avoid spinning indefinitely.
## Metadata Tags
Connectors can auto-populate [tags](/knowledgebase/tags) with metadata from the source — for example, a Notion connector can tag documents with their Labels and Last Modified date; a GitHub connector can tag documents with Repository and File Path. These tags are then available for filtered search in the Knowledge block.
You can disable specific tag types during setup or at any time from the connector settings to free up tag slots for manual tagging or other connectors.
<Callout type="info">
Tag slots are shared across all documents in a knowledge base. If multiple connectors each populate tags, they draw from the same pool of 17 slots.
</Callout>
## Multiple Connectors
You can add multiple connectors to a single knowledge base. For example, you might sync internal documentation from Confluence alongside code from GitHub and meeting notes from Fireflies — all searchable together through the Knowledge block.
<Image src="/static/connectors/connectors-list.png" alt="Knowledge base document list showing synced Google Docs documents with Name, Size, Tokens, Chunks, Uploaded date, Status, and Tags columns" width={800} height={300} />
Each connector manages its own documents independently. Metadata tag slots are shared across the knowledge base, so keep an eye on slot usage if you're combining several connectors that each populate tags.
## Common Use Cases
- **Internal knowledge base**: Sync your team's Notion workspace and Confluence spaces so AI agents can answer questions about internal processes, policies, and documentation
- **Customer support**: Connect HubSpot or Salesforce alongside your help docs from WordPress or Google Docs to give support agents full context on customers and product information
- **Engineering assistant**: Sync a GitHub repository and Jira or Linear issues so an AI agent can reference code, specs, and ticket history when answering developer questions
- **Meeting intelligence**: Pull in Fireflies transcripts alongside Slack conversations to build a searchable archive of decisions and discussions
- **Research and notes**: Sync Evernote notebooks or an Obsidian vault to make your personal notes available to AI workflows
You can add as many connectors as you need to a single knowledge base. Each manages its own documents independently, and all content is searchable together through the Knowledge block. Keep tag slot usage in mind when combining connectors that each populate metadata tags.
<FAQ items={[
{ question: "How often do connectors sync?", answer: "You can choose from hourly, every 6 hours, daily (default), weekly, or manual-only sync frequencies. Each connector can have its own schedule." },
{ question: "What happens if a source document is deleted?", answer: "On the next sync, the connector detects that the document no longer exists in the source and removes it from your knowledge base automatically." },
{ question: "Can I connect multiple services to one knowledge base?", answer: "Yes. You can add as many connectors as you need to a single knowledge base. Each connector manages its documents independently." },
{ question: "Do I need to re-authenticate connectors?", answer: "OAuth-based connectors refresh tokens automatically. API key-based connectors (Evernote, Obsidian, Fireflies) need manual updates if you rotate the key." },
{ question: "What if a connector sync fails?", answer: "If a single document fails, the rest of the sync continues. If the entire sync fails (e.g., service is down), the connector backs off and retries automatically." },
{ question: "Can I exclude specific documents from syncing?", answer: "Yes. You can manually exclude documents from any connector. Excluded documents are skipped on every subsequent sync, even if they change in the source." },
{ question: "Do metadata tags count against a limit?", answer: "Tag slots are shared across all documents in a knowledge base. If you have multiple connectors, their metadata tags draw from the same pool of available slots." },
{ question: "How often do connectors sync?", answer: "You choose from hourly, every 6 hours, daily (default), weekly, or manual-only. Sub-hourly frequencies require a Max or Enterprise plan. Each connector has its own schedule." },
{ question: "What happens if a source document is deleted?", answer: "On the next sync the connector detects the document is gone and removes it from your knowledge base automatically." },
{ question: "What happens when I delete a connector?", answer: "The connector is removed and future syncs stop. You're given the option to also delete all documents that were synced by that connector. If you don't check that option, they stay in the knowledge base as-is." },
{ question: "What does the Disabled status mean?", answer: "After 10 consecutive full-sync failures, the connector is automatically disabled to stop retrying. Reconnect the OAuth account or click Resume to re-enable it." },
{ question: "Do metadata tags count against a limit?", answer: "Yes. Tag slots are shared across all documents in a knowledge base — 17 slots total. Multiple connectors draw from the same pool, so plan accordingly if several connectors each auto-populate tags." },
{ question: "Do I need to re-authenticate connectors?", answer: "OAuth connectors refresh tokens automatically. API key connectors (Evernote, Obsidian, Fireflies) need manual updates if you rotate the key in the external service." },
]} />

View File

@@ -2,117 +2,112 @@
title: Tags and Filtering
---
import { Video } from '@/components/ui/video'
import { Callout } from 'fumadocs-ui/components/callout'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
Tags provide a powerful way to organize your documents and create precise filtering for your vector searches. By combining tag-based filtering with semantic search, you can retrieve exactly the content you need from your knowledgebase.
Tags let you attach structured metadata to documents so the Knowledge block can filter results precisely — by department, date, priority, status, or any dimension you define.
## Adding Tags to Documents
## How Tags Work
You can add custom tags to any document in your knowledgebase to organize and categorize your content for easier retrieval.
Tags have two layers:
<div className="mx-auto w-full overflow-hidden rounded-lg">
<Video src="knowledgebase-tag.mp4" width={700} height={450} />
</div>
1. **Tag definitions** — created at the knowledge base level. A definition has a name (e.g., "Department") and a type (Text, Number, Date, or Boolean). Definitions are shared across all documents.
2. **Tag values** — set per document. Once a definition exists, you assign a value to it on each document that needs it (e.g., `Department = "engineering"`).
### Tag Management
- **Custom tags**: Create your own tag system that fits your workflow
- **Multiple tags per document**: Apply as many tags as needed to each document. Each knowledgebase has 17 tag slots total: 7 text, 5 number, 2 date, and 3 boolean slots, shared by all documents in the knowledgebase
- **Tag organization**: Group related documents with consistent tagging
## Tag Slots
### Tag Best Practices
- **Consistent naming**: Use standardized tag names across your documents
- **Descriptive tags**: Use clear, meaningful tag names
- **Regular cleanup**: Remove unused or outdated tags periodically
Each knowledge base has **17 tag slots** distributed across four types:
## Using Tags in Knowledge Blocks
| Type | Slots | Accepted values |
|------|-------|-----------------|
| **Text** | 7 | Any string — matching is case-insensitive |
| **Number** | 5 | Any valid number |
| **Date** | 2 | `YYYY-MM-DD` format |
| **Boolean** | 3 | `true` or `false` |
Tags become powerful when combined with the Knowledge block in your workflows. You can filter your searches to specific tagged content, ensuring your AI agents get the most relevant information.
The type dropdown in the creation form shows current slot usage for each type (e.g., `Text (0/7)` means none of the 7 text slots are in use yet).
<div className="mx-auto w-full overflow-hidden rounded-lg">
<Video src="knowledgebase-tag2.mp4" width={700} height={450} />
</div>
<Callout type="info">
Slots are shared across all documents and connectors in a knowledge base. Connectors that auto-populate metadata tags draw from the same pool. Plan your schema with that in mind.
</Callout>
## Defining Tags
Tag definitions live at the knowledge base level. To manage them, click the knowledge base name in the header to open the context menu and select **Tags**:
<Image src="/static/tags/tags-kb-menu.png" alt="Knowledge base header showing the dropdown menu with Rename, Tags, and Delete options" width={700} height={400} />
This opens the Tags modal, which lists all defined tags and shows how many documents each one is assigned to. Click **Add Tag** to define a new one:
<Image src="/static/tags/tags-create.png" alt="Tags modal showing 0 defined tags, a Tag Name input field, and a Type dropdown set to Text (0/7), with Cancel and Create Tag buttons" width={700} height={450} />
Enter a **Tag Name** and pick a **Type**, then click **Create Tag**. The name must be unique within the knowledge base. The type dropdown only shows types that still have available slots. Press Enter to submit or Escape to cancel.
To delete a tag definition, click the trash icon next to it. Deleting a definition removes the tag value from every document it was assigned to — the modal shows you which documents are affected before you confirm.
Clicking any existing tag definition opens a dialog showing all documents that have a value set for it, along with their current tag values.
## Setting Tag Values on Documents
Once a definition exists, you assign values document by document. Right-click any document (or click the `…` menu) to open the document context menu, then select **Tags**:
This opens the tag panel for that document where you can set a value for each defined tag.
## Viewing Tags in the Document List
The **Tags** column in the document list shows the current tag values for each document at a glance. Documents with no tags assigned show ` `:
<Image src="/static/tags/tags-document-list.png" alt="Knowledge base document list showing Name, Size, Tokens, Chunks, Uploaded, Status, and Tags columns — Document1.txt shows no tags ( ) while Document2.txt shows the value 'Waleed'" width={900} height={200} />
Use the **Filter** and **Sort** controls in the top right to narrow the list by tag values or sort by them.
## Using Tags in the Knowledge Block
In a workflow, open the Knowledge block and configure **Tag Filters** to restrict which documents are searched:
<Image src="/static/tags/tags-knowledge-block.png" alt="Knowledge block editor showing Operation: search, Knowledge Base: test, Search Query field (optional), Number of Results, and a Tag Filters section with Filter 1 containing Tag: Name, Operator: equals, and a Value field" width={900} height={500} />
Each filter has three parts:
- **Tag** — select a tag definition from the knowledge base
- **Operator** — depends on the tag type (see below)
- **Value** — the value to match against
Add as many filters as you need with the **+** button. Multiple filters are combined with AND logic — a document must match all filters to be included in the search.
### Operators by Type
| Type | Available operators |
|------|-------------------|
| **Text** | `equals`, `not equals`, `contains`, `does not contain`, `starts with`, `ends with` |
| **Number** | `equals`, `not equals`, `greater than`, `greater than or equal`, `less than`, `less than or equal`, `between` |
| **Date** | `equals`, `after`, `on or after`, `before`, `on or before`, `between` |
| **Boolean** | `is`, `is not` |
Tag values in filter fields can be static strings or workflow variable references (e.g., `<start.department>`), so filtering can adapt dynamically at runtime.
## Search Modes
The Knowledge block supports three different search modes depending on what you provide:
The Knowledge block behaves differently depending on what you provide:
### 1. Tag-Only Search
When you **only provide tags** (no search query):
- **Direct retrieval**: Fetches all documents that have the specified tags
- **No vector search**: Results are based purely on tag matching
- **Fast performance**: Quick retrieval without semantic processing
- **Exact matching**: Only documents with all specified tags are returned
| What you provide | Behaviour |
|-----------------|-----------|
| **Tags only** (no search query) | Fetches all documents that match the tag filters — pure tag matching, no vector search |
| **Query only** (no tag filters) | Semantic vector search across all documents in the knowledge base |
| **Both tags and query** | Tag filters run first to narrow the document set, then vector search runs within that subset |
**Use case**: When you need all documents from a specific category or project
The combined mode is the most precise — tag filtering cuts down the candidate set cheaply before the more expensive vector similarity comparison runs.
### 2. Vector Search Only
When you **only provide a search query** (no tags):
- **Semantic search**: Finds content based on meaning and context
- **Full knowledgebase**: Searches across all documents
- **Relevance ranking**: Results ordered by semantic similarity
- **Natural language**: Use questions or phrases to find relevant content
## Connector-Populated Tags
**Use case**: When you need the most relevant content regardless of organization
Connectors can auto-populate tags with metadata from the source. A Notion connector might set **Last Modified** and **Labels**; a GitHub connector might set **Repository** and **File Path**. These work exactly like manually defined tags and are available in Knowledge block filters.
### 3. Combined Tag Filtering + Vector Search
When you **provide both tags and a search query**:
1. **First**: Filter documents to only those with the specified tags
2. **Then**: Perform vector search within that filtered subset
3. **Result**: Semantically relevant content from your tagged documents only
**Use case**: When you need relevant content from a specific category or project
### Search Configuration
#### Tag Filtering
- **Multiple tags**: Use multiple tags with AND or OR logic to control whether documents must match all or any of the specified tags
- **Tag combinations**: Mix different tag types for precise filtering
- **Case sensitivity**: Tag matching is case-insensitive
- **Partial matching**: Text fields support partial matching operators such as contains, starts_with, and ends_with in addition to exact matching
#### Vector Search Parameters
- **Query complexity**: Natural language questions work best
- **Result limits**: Configure how many chunks to retrieve
- **Relevance threshold**: Set minimum similarity scores
- **Context window**: Adjust chunk size for your use case
## Integration with Workflows
### Knowledge Block Configuration
1. **Select knowledgebase**: Choose which knowledgebase to search
2. **Add tags**: Specify filtering tags (optional)
3. **Enter query**: Add your search query (optional)
4. **Configure results**: Set number of chunks to retrieve
5. **Test search**: Preview results before using in workflow
### Dynamic Tag Usage
- **Variable tags**: Use workflow variables as tag values
- **Conditional filtering**: Apply different tags based on workflow logic
- **Context-aware search**: Adjust tags based on conversation context
- **Multi-step filtering**: Refine searches through workflow steps
### Performance Optimization
- **Efficient filtering**: Tag filtering happens before vector search for better performance
- **Caching**: Frequently used tag combinations are cached for speed
- **Parallel processing**: Multiple tag searches can run simultaneously
- **Resource management**: Automatic optimization of search resources
## Getting Started with Tags
1. **Plan your tag structure**: Decide on consistent naming conventions
2. **Start tagging**: Add relevant tags to your existing documents
3. **Test combinations**: Experiment with tag + search query combinations
4. **Integrate into workflows**: Use the Knowledge block with your tagging strategy
5. **Refine over time**: Adjust your tagging approach based on search results
Tags transform your knowledgebase from a simple document store into a precisely organized, searchable intelligence system that your AI workflows can navigate with surgical precision.
You can disable specific metadata tag types during connector setup or in connector settings to free up slots for manual use. See [Connectors](/knowledgebase/connectors) for details.
<FAQ items={[
{ question: "How many tag slots are available per knowledgebase?", answer: "Each knowledgebase supports up to 17 tag slots total across four field types: 7 text slots, 5 number slots, 2 date slots, and 3 boolean slots. These slots are shared across all documents in the knowledgebase." },
{ question: "What tag field types are supported?", answer: "Four field types are supported: text (free-form string values), number (numeric values), date (date values in YYYY-MM-DD format), and boolean (true/false values). Each type has its own pool of available slots." },
{ question: "Is tag matching case-sensitive?", answer: "No, tag matching is case-insensitive. You can use any capitalization when filtering by tags and it will match regardless of how the tag value was originally entered." },
{ question: "How does combined tag and vector search work?", answer: "When you provide both tags and a search query, tag filtering is applied first to narrow down the document set, then vector search runs within that filtered subset. This approach is more efficient because it reduces the number of vectors that need similarity comparison." },
{ question: "What is the default number of results returned from a knowledge search?", answer: "The default is 10 results. You can configure this with the topK parameter, which accepts values from 1 to 100." },
{ question: "What embedding model does Sim use for knowledge base search?", answer: "Sim uses OpenAI's text-embedding-3-small model with 1536 dimensions for generating document embeddings and performing vector similarity search." },
{ question: "How many tag slots are available?", answer: "17 total: 7 text, 5 number, 2 date, 3 boolean. These are shared across all documents and connectors in a knowledge base." },
{ question: "Can I rename a tag definition?", answer: "No. Tag definitions cannot be renamed after creation. Delete the old definition and create a new one with the correct name. Deleting will remove the tag value from all documents it was assigned to." },
{ question: "Is tag matching case-sensitive?", answer: "No. Text tag matching is case-insensitive — 'Engineering' and 'engineering' are treated the same." },
{ question: "Can tag filter values come from workflow variables?", answer: "Yes. Enter a variable reference like <start.department> as the filter value. It resolves to the actual value at runtime, so a single workflow can filter different documents on each run." },
{ question: "What happens to tag values when I delete a tag definition?", answer: "Deleting a definition removes the tag value from every document it was assigned to and frees the slot. The modal shows you which documents are affected before you confirm." },
]} />

View File

@@ -3,6 +3,7 @@ title: Deploy Workflows as MCP
description: Expose your workflows as MCP tools for external AI assistants and applications
---
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { Callout } from 'fumadocs-ui/components/callout'
import { FAQ } from '@/components/ui/faq'
@@ -18,10 +19,47 @@ MCP servers group your workflow tools together. Create and manage them in worksp
</div>
1. Navigate to **Settings → MCP Servers**
2. Click **Create Server**
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-servers-settings.png"
alt="MCP Servers settings page"
width={700}
height={450}
className="my-6"
/>
</div>
2. Click **Add**
3. Enter a name and optional description
4. Copy the server URL for use in your MCP clients
5. View and manage all tools added to the server
4. Choose access: **API Key** (private, requires `X-API-Key` header) or **Public** (no authentication)
5. Optionally select deployed workflows to add as tools immediately
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-server-add-modal.png"
alt="Add New MCP Server modal"
width={550}
height={380}
className="my-6"
/>
</div>
6. Click **Add Server**
7. Click **Details** to view the MCP server
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-server-details.png"
alt="MCP Server details view"
width={700}
height={450}
className="my-6"
/>
</div>
8. Copy the server URL for use in your MCP clients
9. View and manage all tools added to the server
## Adding a Workflow as a Tool
@@ -33,9 +71,21 @@ Once your workflow is deployed, you can expose it as an MCP tool:
1. Open your deployed workflow
2. Click **Deploy** and go to the **MCP** tab
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-deploy-modal.png"
alt="Workflow Deployment MCP tab"
width={380}
height={470}
className="my-6"
/>
</div>
3. Configure the tool name and description
4. Add descriptions for each parameter (helps AI understand inputs)
5. Select which MCP servers to add it to
6. Click **Save Tool**
<Callout type="info">
The workflow must be deployed before it can be added as an MCP tool.
@@ -54,9 +104,50 @@ Your workflow's input format fields become tool parameters. Add descriptions to
## Connecting MCP Clients
Use the server URL from settings to connect external applications:
Sim generates a ready-to-paste configuration for every supported client. To get it:
1. Navigate to **Settings → MCP Servers**
2. Click **Details** on your server
3. Under **MCP Client**, select your client — **Cursor**, **Claude Code**, **Claude Desktop**, **VS Code**, or **Sim**
4. Copy the configuration, replacing `$SIM_API_KEY` with your Sim API key
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-client-config.png"
alt="MCP client configuration panel"
width={700}
height={450}
className="my-6"
/>
</div>
### Cursor
Cursor supports direct URL configuration. Add to your Cursor MCP settings (`.cursor/mcp.json`):
```json
{
"mcpServers": {
"my-sim-workflows": {
"url": "YOUR_SERVER_URL",
"headers": { "X-API-Key": "$SIM_API_KEY" }
}
}
}
```
Cursor also provides a one-click install button in the server detail view.
### Claude Code
Run this command in your terminal:
```bash
claude mcp add "my-sim-workflows" --url "YOUR_SERVER_URL" --header "X-API-Key:$SIM_API_KEY"
```
### Claude Desktop
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
@@ -64,17 +155,33 @@ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_
"mcpServers": {
"my-sim-workflows": {
"command": "npx",
"args": ["-y", "mcp-remote", "YOUR_SERVER_URL"]
"args": ["-y", "mcp-remote", "YOUR_SERVER_URL", "--header", "X-API-Key:$SIM_API_KEY"]
}
}
}
```
### Cursor
Add the server URL in Cursor's MCP settings using the same mcp-remote pattern.
### VS Code
Add to your VS Code MCP settings (`.vscode/mcp.json`):
```json
{
"mcpServers": {
"my-sim-workflows": {
"command": "npx",
"args": ["-y", "mcp-remote", "YOUR_SERVER_URL", "--header", "X-API-Key:$SIM_API_KEY"]
}
}
}
```
<Callout type="info">
For public servers, omit the `X-API-Key` header and `--header` arguments. Public servers don't require authentication.
</Callout>
<Callout type="warn">
Include your API key header (`X-API-Key`) for authenticated access when using mcp-remote or other HTTP-based MCP transports.
`$SIM_API_KEY` is a placeholder. For Claude Desktop and VS Code configs, replace it with your actual API key since these clients don't expand environment variables in JSON config files. Claude Code and Cursor handle variable expansion natively.
</Callout>
## Server Management

View File

@@ -18,27 +18,83 @@ MCP is an open standard that enables AI assistants to securely connect to extern
- Execute custom tools and scripts
- Maintain secure, controlled access to external resources
## Configuring MCP Servers
## Adding an MCP Server as a Tool
MCP servers provide collections of tools that your agents can use. Configure them in workspace settings:
MCP servers provide collections of tools that your agents can use.
<div className="mx-auto w-full overflow-hidden rounded-lg">
<Video src="mcp/settings-mcp-tools.mp4" width={700} height={450} />
</div>
1. Navigate to your workspace settings
2. Go to the **MCP Servers** section
3. Click **Add MCP Server**
4. Enter the server configuration details
5. Save the configuration
To add one:
1. Navigate to **Settings → MCP Tools**
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-settings.png"
alt="MCP Tools settings page"
width={700}
height={450}
className="my-6"
/>
</div>
2. Click **Add** to open the configuration modal
3. Enter a **Server Name** and **Server URL**
4. Add any required **Headers** (e.g. API keys)
5. Click **Add MCP** to save
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-add-modal.png"
alt="Add New MCP Server modal"
width={450}
height={290}
className="my-6"
/>
</div>
<Callout type="info">
You can also configure MCP servers directly from the toolbar in an Agent block for quick setup.
</Callout>
### Server Configuration Options
| Field | Description |
|-------|-------------|
| **Name** | Display name for the server |
| **URL** | The MCP server endpoint |
| **Transport** | Currently supports `streamable-http` |
| **Headers** | Key-value pairs for authentication or custom headers |
| **Timeout** | Connection timeout in milliseconds (default: 30,000) |
### Environment Variables in Configuration
Server URLs and headers support environment variable substitution using `{{VAR_NAME}}` syntax. This keeps sensitive values like API keys out of the server configuration.
```
URL: https://api.example.com/mcp
Authorization: Bearer {{MCP_API_TOKEN}}
```
When you type `{{` in the URL or header fields, a dropdown appears showing available workspace environment variables.
### Testing and Validation
Click **Test Connection** before saving to verify the server is reachable and discover available tools. The test response shows the number of tools found and the protocol version.
After saving, each server displays its available tools with parameter names, types, and required flags. If a server's tools change (e.g., after a server update), click **Refresh** to fetch the latest schemas. This automatically updates any agent blocks using those tools.
Tool validation badges appear on servers with issues — for example, if a tool was removed from the server but is still referenced in a workflow. Click the badge to see which workflows are affected.
### Domain Allowlisting
Self-hosted deployments can restrict which MCP server domains are allowed by setting the `ALLOWED_MCP_DOMAINS` environment variable (comma-separated list). When set, only servers on approved domains can be added. When unset, all domains are allowed.
### Refresh Tools
Click **Refresh** on a server to fetch the latest tool schemas and automatically update any agent blocks using those tools with the new parameter definitions.
To auto-refresh an MCP tool already in use by an agent, go to **Settings → MCP Tools**, open the server's details, and click **Refresh**. This fetches the latest tool schemas and automatically updates any agent blocks using those tools with the new parameter definitions.
## Using MCP Tools in Agents
@@ -46,7 +102,7 @@ Once MCP servers are configured, their tools become available within your agent
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-2.png"
src="/static/blocks/mcp-agent-dropdown.png"
alt="Using MCP Tool in Agent Block"
width={700}
height={450}
@@ -55,9 +111,25 @@ Once MCP servers are configured, their tools become available within your agent
</div>
1. Open an **Agent** block
2. In the **Tools** section, you'll see available MCP tools
3. Select the tools you want the agent to use
4. The agent can now access these tools during execution
2. In the **Tools** section, click **Add tool…**
3. Under **MCP Servers**, click a server to see its tools
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-agent-tools.png"
alt="MCP tools list for a selected server"
width={400}
height={400}
className="my-6"
/>
</div>
4. Select individual tools, or choose **Use all N tools** to add every tool from that server
5. The agent can now access these tools during execution
<Callout type="info">
If you haven't configured a server yet, click **Add MCP Server** at the top of the dropdown to open the setup modal without leaving the block.
</Callout>
## Standalone MCP Tool Block
@@ -65,7 +137,7 @@ For more granular control, you can use the dedicated MCP Tool block to execute s
<div className="flex justify-center">
<Image
src="/static/blocks/mcp-3.png"
src="/static/blocks/mcp-tool-block.png"
alt="Standalone MCP Tool Block"
width={700}
height={450}
@@ -79,17 +151,14 @@ The MCP Tool block allows you to:
- Use the tool's output in subsequent workflow steps
- Chain multiple MCP tools together
### When to Use MCP Tool vs Agent
## When to Use MCP Tool vs Agent
**Use Agent with MCP tools when:**
- You want the AI to decide which tools to use
- You need complex reasoning about when and how to use tools
- You want natural language interaction with the tools
**Use MCP Tool block when:**
- You need deterministic tool execution
- You want to execute a specific tool with known parameters
- You're building structured workflows with predictable steps
| | **Agent with MCP tools** | **MCP Tool block** |
|---|---|---|
| **Execution** | AI decides which tools to call | Deterministic — runs the tool you pick |
| **Parameters** | AI chooses at runtime | You set them explicitly |
| **Best for** | Dynamic, conversational flows | Structured, repeatable steps |
| **Reasoning** | Handles complex multi-step logic | One tool, one call |
## Permission Requirements
@@ -151,6 +220,6 @@ import { FAQ } from '@/components/ui/faq'
{ question: "Who can configure MCP servers in a workspace?", answer: "Users with Write permission can configure (add and update) MCP servers in workspace settings. Only Admin permission is required to delete MCP servers. Users with Read permission can view available MCP tools and execute them in agents and MCP Tool blocks. This means all workspace members with at least Read access can use MCP tools in their workflows." },
{ question: "Can I use MCP servers from multiple workspaces?", answer: "MCP servers are configured per workspace. Each workspace maintains its own set of MCP server connections. If you need the same MCP server in multiple workspaces, you need to configure it separately in each workspace's settings." },
{ question: "How do I update MCP tool schemas after a server changes its available tools?", answer: "Click the Refresh button on the MCP server in your workspace settings. This fetches the latest tool schemas from the server and automatically updates any agent blocks that use those tools with the new parameter definitions." },
{ question: "Can permission groups restrict access to MCP tools?", answer: "Yes. Organization admins can create permission groups that disable MCP tools for specific members using the disableMcpTools configuration option. When this is enabled, affected users will not be able to add or use MCP tools in their workflows." },
{ question: "Can permission groups restrict access to MCP tools?", answer: "Yes. On Enterprise-entitled workspaces, any workspace admin can create a permission group that disables MCP tools for its members using the disableMcpTools option. When this is enabled, affected users will not be able to add or use MCP tools in workflows that belong to that workspace." },
{ question: "What happens if an MCP server goes offline during workflow execution?", answer: "If the MCP server is unreachable during execution, the tool call will fail and return an error. In an Agent block, the AI may attempt to handle the failure gracefully. In a standalone MCP Tool block, the workflow step will fail. Check MCP server logs and verify the server is running and accessible to troubleshoot connectivity issues." },
]} />

View File

@@ -11,6 +11,7 @@
"tools",
"connections",
"---Features---",
"mothership",
"mcp",
"copilot",
"mailer",
@@ -18,12 +19,13 @@
"knowledgebase",
"tables",
"variables",
"integrations",
"credentials",
"---Platform---",
"execution",
"permissions",
"self-hosting",
"./enterprise/index",
"enterprise",
"./keyboard-shortcuts/index"
],
"defaultOpen": false

View File

@@ -0,0 +1,120 @@
---
title: Files & Documents
description: Upload, create, edit, and generate files — documents, presentations, images, and more.
---
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
<Video src="mothership/files-pipeline-deals-summarizer.mp4" width={700} height={450} />
Describe a document, presentation, image, or visualization and Mothership creates it — streaming the content live into the resource panel as it writes. Attach any file to your message and Mothership reads it, processes it, and saves it to your workspace.
## Uploading Files to the Workspace
Attach any file directly to your Mothership message — drag it into the input, paste it, or click the attachment icon. Mothership reads the file as context and saves it to your workspace.
Use this to:
- Hand Mothership a document and ask it to process, summarize, or extract data from it
- Upload a CSV and have it create a table from it
- Drop in a PDF and ask Mothership to turn it into a knowledge base document
- Attach a design mockup and ask Mothership to describe it or generate code from it
Uploaded files appear in the Files panel in the sidebar and are accessible to all workflows in the workspace. Mothership can also fetch a file directly from a URL and save it for you: "Download the JSON at [URL] and save it to the workspace."
## Creating Documents
Mothership can write any text-based file — markdown, plain text, code files, CSV, JSON, or any other format:
- "Write a technical spec for the new auth system as a markdown file"
- "Create a CSV of our test accounts with columns for name, email, and plan tier"
- "Write a Python script that calls our workflow API and processes the response"
- "Draft a postmortem for the outage last Tuesday and save it as a markdown file"
- "Write a personalized outbound email for Acme Corp based on their recent funding announcement"
- "Draft a weekly ops digest summarizing workflow run counts, errors, and top failures for the past 7 days"
Files are saved to your workspace and accessible from the Files panel in the sidebar.
## Editing Existing Files
Open a file using `@filename` or the **+** menu, then describe the change:
- "Update the pricing section to reflect the new tiers"
- "Refactor this Python script to use async/await"
- "Add a section on error handling to this spec"
- "Rewrite the introduction of this report to be more concise"
## Presentations
<Image src="/static/mothership/pptx-example.png" alt="Mothership resource panel showing a generated Mothership-Use-Cases.pptx file open with the title slide and first use case slide visible" width={900} height={500} />
Mothership can generate `.pptx` files:
- "Create a pitch deck for Q3 review — 8 slides covering growth, retention, and roadmap"
- "Turn this research report into a 10-slide presentation"
- "Build a deck that walks through our API onboarding flow"
- "Build a battle card deck for our top 3 competitors — one slide each covering positioning, pricing, and how we win"
- "Create an account plan for Acme Corp — their priorities, our solution fit, and proposed next steps"
The file is saved to your workspace and can be downloaded.
## Images
Mothership can generate images using AI, and can use an existing image as a reference to guide the output:
**Generating images:**
- "Generate a banner image for the new feature announcement — dark background, clean typography"
- "Create a diagram showing the data flow through our webhook pipeline"
- "Make a social card for the blog post with the title and author name"
**Using a reference image:**
- Attach an existing image to your message, then describe what you want: "Generate a new version of this banner with a blue color scheme instead of green"
- "Create a variation of this diagram with the boxes rearranged horizontally [attach image]"
<Image src="/static/mothership/image-example.png" alt="Mothership resource panel showing a generated hero image of a Mothership-branded blimp flying over San Francisco at golden hour, alongside the chat response linking the file" width={900} height={500} />
Generated images are saved as workspace files.
## Charts and Visualizations
Mothership can generate charts and data visualizations from data you describe or reference:
- "Plot the workflow run counts from the metrics table as a bar chart grouped by week"
- "Create a line chart of token usage over the past 30 days from this data [paste data]"
- "Generate a pie chart showing the distribution of lead sources from the leads table"
<Image src="/static/mothership/chart-example.png" alt="Mothership resource panel showing a generated chart file with bar charts for backend 5xx errors and error rate over time" width={900} height={500} />
Visualizations are saved as files and rendered in the resource panel.
## Calculations & Data Processing
For one-off calculations and data transformations, describe what you need and Mothership runs it directly in the chat:
- "Parse this JSON and extract all records where status is 'failed'"
- "Calculate the p95 latency from these timing values: [paste values]"
- "Convert these Unix timestamps to ISO 8601"
- "Deduplicate this list of emails, case-insensitive"
Results come back directly in the chat. Ask Mothership to save the output as a file if you need it.
## File Viewer Modes
When a file opens in the resource panel, you can switch between three views:
<Video src="mothership/toggle-file-view.mp4" width={700} height={450} />
| Mode | What it shows |
|------|--------------|
| **Editor** | Raw editable text |
| **Preview** | Rendered output (markdown, HTML) |
| **Split** | Editor and preview side by side |
<FAQ items={[
{ question: "Where are uploaded and generated files stored?", answer: "All files — uploaded, created, or generated — go to your workspace's Files section. They're accessible from the sidebar and can be referenced in any workflow." },
{ question: "Can I use files created in Mothership in workflows?", answer: "Yes. Workspace files can be referenced in workflows using the File block or by passing them as inputs." },
{ question: "What file types can I upload to Mothership?", answer: "You can attach images, PDFs, text files, JSON, XML, and other document formats directly to a Mothership message." },
{ question: "What can Mothership calculate or process?", answer: "Anything expressible as a short script — parsing JSON, number crunching, string transformations, deduplication, format conversions. Results come back in the chat." },
{ question: "Is there a file size limit for generated files?", answer: "There is no hard limit on generated file size, but very large files may take longer to stream." },
]} />

View File

@@ -0,0 +1,73 @@
---
title: Mothership
description: Your AI command center. Build and manage your entire workspace in natural language.
---
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
<Video src="mothership/create-workflow.mp4" width={700} height={450} />
Describe what you want and Mothership handles it. Build a workflow, run research, generate a presentation, query a table, schedule a recurring job, send a Slack message — Mothership knows your entire workspace and takes action directly.
## What You Can Do
| Area | What Mothership can do |
|------|-----------------------|
| **[Workflows](/mothership/workflows)** | Build, edit, run, debug, deploy, and organize workflows |
| **[Research](/mothership/research)** | Search the web, read pages, crawl sites, produce research reports |
| **[Files & Documents](/mothership/files)** | Upload, create, edit, and generate documents, presentations, and images |
| **[Tables](/mothership/tables)** | Create, query, update, and export workspace tables |
| **[Automation & Configuration](/mothership/tasks)** | Schedule jobs, take immediate actions, connect integrations, manage tools |
| **[Knowledge Bases](/mothership/knowledge)** | Create knowledge bases, add documents, and query content in plain language |
## How It Works
Mothership receives a snapshot of your entire workspace with every message — all workflows, tables, knowledge bases, files, credentials, jobs, and integrations. This is why you can refer to things by name without specifying IDs or paths:
- "Run the invoice workflow"
- "Add a row to the leads table"
- "Deploy the summarizer as a chat"
No configuration, no context-setting. Just describe what you want:
- "Build a lead enrichment workflow that scores inbound signups and writes the results to the leads table"
- "Research our top 5 competitors and save a battle card for each one"
- "Schedule a daily job that checks for new high-fit prospects and posts them to #outbound in Slack"
- "Create a workflow that takes a contract PDF, extracts the key terms, and emails a summary to legal"
For complex tasks, Mothership delegates to specialized subagents automatically. You'll see them appear as collapsible sections in the chat while they work — building, researching, writing files, executing actions.
{/* TODO: Screenshot of the Mothership chat showing a subagent section expanded mid-task — e.g., the Build or Research subagent actively working, with its collapsible header and steps visible in the thread. */}
## Adding Context
Bring any workspace object into the conversation via the **+** menu, `@`-mentions, or drag-and-drop from the sidebar. Mothership also opens resources automatically when it creates or modifies them.
<Video src="mothership/context-menu.mp4" width={700} height={450} />
{/* TODO: Screenshot of the resource panel with multiple tabs open — a workflow tab, a table tab, and a file tab — showing different resource types side by side. */}
| What to add | How it appears |
|-------------|---------------|
| **Workflow** | Interactive canvas in the resource panel |
| **Table** | Full table editor in the resource panel |
| **File** | File viewer with editor, split, and preview modes |
| **Knowledge Base** | Knowledge base management UI |
| **Folder** | Folder contents |
| **Past task** | A previous Mothership conversation |
## Layout
Mothership has two panes. On the left: the chat thread, where your messages and Mothership's responses appear. On the right: the resource panel, where workflows, tables, files, and knowledge bases open as tabs. The panel is resizable; tabs are draggable and closeable.
<Video src="mothership/split-view.mp4" width={700} height={450} />
<FAQ items={[
{ question: "How is Mothership different from Copilot?", answer: "Copilot is scoped to a single workflow — it helps you build and edit that workflow. Mothership has access to your entire workspace and can build workflows, manage data, run research, schedule jobs, take actions across integrations, and more." },
{ question: "What model does Mothership use?", answer: "Mothership always uses Claude Opus 4.6. There is no model selector." },
{ question: "How do I reference an existing workflow or table?", answer: "Type @ followed by the name in the input, use the + menu, or drag the item from the sidebar into the chat." },
{ question: "How long can a Mothership task run?", answer: "Up to one hour. For tasks that exceed that, set up a scheduled job or break the work into steps." },
{ question: "Can Mothership work on multiple things at once?", answer: "Mothership processes one message at a time. You can queue messages — they will be processed in order." },
]} />

View File

@@ -0,0 +1,85 @@
---
title: Knowledge Bases
description: Create, populate, and query knowledge bases from Mothership.
---
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
<Video src="mothership/kb.mp4" width={700} height={450} />
Create a knowledge base, add documents to it, and query it in plain language — all through conversation. Knowledge bases you create in Mothership are immediately available to Agent blocks in any workflow.
## Creating Knowledge Bases
Describe the knowledge base and Mothership creates it:
- "Create a knowledge base called 'Product Docs'"
- "Set up a knowledge base for our support team — call it 'Support KB'"
- "Create a competitive intelligence knowledge base"
- "Create a knowledge base from our sales playbook and attach it to the outbound agent workflow"
- "Set up a customer success knowledge base — I'll add our onboarding guides and past case studies to it"
## Adding Documents
Add documents by attaching files to your message, pasting text, or pointing Mothership at a URL:
- "Add this PDF to the Product Docs knowledge base [attach file]"
- "Add the following text to the Support KB as a new document: [paste content]"
- "Fetch the page at [URL] and add it to the competitive intelligence knowledge base"
- "Add these three uploaded case studies to the customer success knowledge base"
Mothership processes and indexes each document automatically. Once indexed, the content is searchable by any Agent block that has the knowledge base attached.
{/* TODO: Screenshot of Mothership confirming a document was added and indexed — showing the document name and its indexed status in the knowledge base. */}
## Querying Knowledge Bases
Ask Mothership a question and it searches the specified knowledge base to answer:
- "What does the Product Docs knowledge base say about our refund policy?"
- "Search the Support KB for anything related to SSO setup errors"
- "What are the key differences between our Pro and Enterprise plans, based on the product docs?"
- "Find everything in the competitive intelligence knowledge base about [competitor]'s pricing"
## Connectors
For knowledge bases that should stay current automatically, connectors sync content from external services on a schedule — no manual uploads needed. New content is added, changed content is re-processed, and deleted content is removed on every run.
Connectors are configured through the knowledge base settings, not through Mothership chat. Once connected, all synced content is immediately searchable by Mothership and by any Agent block with the knowledge base attached.
Sim ships with 30 built-in connectors, including Notion, Google Drive, Slack, GitHub, Confluence, HubSpot, Salesforce, Gmail, and more.
Examples of what you can sync:
- **Notion** — sync a workspace, a database, or a specific page tree
- **Google Drive / Dropbox / OneDrive** — sync documents from cloud storage
- **GitHub** — sync a repository's markdown and code files
- **Slack** — sync channel history
- **Confluence / Jira** — sync your internal wiki or issue tracker
- **HubSpot / Salesforce** — sync CRM records into a searchable knowledge base
See [Connectors](/knowledgebase/connectors) for setup steps, sync frequency options, and managing connector status.
## Managing Knowledge Bases
List, inspect, and clean up knowledge bases in plain language:
- "What knowledge bases are in this workspace?"
- "How many documents are in the Support KB?"
- "Remove the outdated pricing doc from the Product Docs knowledge base"
- "Delete the old-competitive-intel knowledge base"
## Using Knowledge Bases in Workflows
Knowledge bases created in Mothership are immediately available to Agent blocks in any workflow. Attach a knowledge base to an Agent block and it will use semantic search to retrieve relevant content at runtime.
See [Knowledge Base](/knowledgebase) for full details on document processing settings, search configuration, and connector syncing.
<FAQ items={[
{ question: "How do I attach a knowledge base to a workflow?", answer: "Open the Agent block in the workflow editor, find the Knowledge Base setting, and select the knowledge base by name. Mothership can also do this for you: 'Attach the Product Docs knowledge base to the research agent block in the content pipeline workflow.'" },
{ question: "What file types can I add to a knowledge base?", answer: "PDFs, markdown files, plain text, and web pages fetched from URLs. Mothership handles the parsing and indexing automatically." },
{ question: "Can I add documents to a knowledge base from a workflow run?", answer: "Yes. The Knowledge Base write tool is available to Agent blocks and can be used to add documents programmatically during a workflow run." },
{ question: "How do I keep a knowledge base up to date?", answer: "Set up a connector to sync from an external source automatically — see Connectors above. For one-off updates, ask Mothership to add or replace the document directly." },
]} />

View File

@@ -0,0 +1,4 @@
{
"title": "Mothership",
"pages": ["index", "workflows", "research", "files", "tables", "tasks", "knowledge"]
}

View File

@@ -0,0 +1,44 @@
---
title: Research
description: Ask Mothership to research anything — it searches, reads, and synthesizes from the web.
---
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
<Video src="mothership/research-agent.mp4" width={700} height={450} />
Ask Mothership to research anything and it figures out the best approach — searching the web, reading specific pages, crawling sites, looking up technical docs. Just describe what you want to know.
## Asking Questions
Ask anything — about a company, a competitor, a market, a technical question, or a specific URL:
- "What did Salesforce, HubSpot, and Gong each ship in the past 30 days? Summarize the key product updates."
- "What's Acme Corp's tech stack, recent hires, and open engineering roles?"
- "Find everything published about [competitor] in the past 90 days — press, product changes, job postings."
- "What are the current rate limits on the Anthropic API?"
- "Read [URL] and tell me what changed in this release"
- "What does Stripe's API say about handling webhooks with idempotency keys?"
- "Who are the main players in AI-powered revenue operations, and how do they differentiate?"
Mothership returns an answer directly in the chat. For anything that needs a longer written output, ask it to save the result as a file.
## Research Reports
When you need a structured, saved document rather than a chat answer, ask Mothership to write it up. It searches, reads, and cross-references multiple sources until it has enough to produce a full report. The output is saved as a file in your workspace and opened in the resource panel.
{/* TODO: Screenshot of a completed research report open in the resource panel as a file — showing a structured markdown document with sections, findings, and citations. */}
- "Research the top 10 AI SDR tools — pricing, features, positioning, and what customers say. Save as a competitive analysis."
- "Do a full market landscape for AI in healthcare diagnostics — major players, funding, use cases, and regulatory environment."
- "Research how our top 5 competitors handle multi-tenant auth — pricing, architecture, and any known vulnerabilities. Write it up as a report."
- "Find every public case study on AI agents in financial compliance from the past 2 years. Summarize the key outcomes and save as a markdown file."
- "Build a battle card for [competitor] — their positioning, pricing, strengths, weaknesses, and how we win against them."
<FAQ items={[
{ question: "Does Mothership have access to real-time information?", answer: "Yes. Mothership queries live internet data. Results reflect current information, not a training cutoff." },
{ question: "Can Mothership read pages that require login?", answer: "No. Mothership can only read publicly accessible pages." },
{ question: "Where are research reports saved?", answer: "Reports are saved as files in your workspace and opened in the resource panel. You can find them in the Files section of the sidebar." },
]} />

View File

@@ -0,0 +1,60 @@
---
title: Tables
description: Create, query, and manage workspace tables from Mothership.
---
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
<Image src="/static/mothership/table-example.png" alt="Mothership resource panel showing the pipeline_deals table with company, deal_owner, stage, and amount columns, alongside a chat summary of total pipeline value and breakdown by stage" width={900} height={500} />
Create a table from a description or a CSV, query it in plain language, add or update rows, and export the results — all through conversation. Tables open in the resource panel as soon as they're created or referenced.
## Creating Tables
Describe the schema and Mothership creates the table:
- "Create a leads table with columns for name, email, company, status, and created date"
- "Create a table that matches the structure of this CSV [attach file]"
- "Set up an errors table with: id (text), message (text), workflow (text), timestamp (date), resolved (boolean)"
- "Create a prospect table for outbound — company, domain, employee count, industry, ICP score, and last contacted date"
- "Set up an enrichment results table to store output from the lead enrichment workflow: email, company, title, LinkedIn URL, fit score"
## Querying Data
Ask questions about table contents in plain language:
- "How many rows in the leads table have status 'qualified'?"
- "Show me all records from the past 7 days where score is above 0.8"
- "What are the top 5 most common error messages in the failures table?"
- "Are there any duplicate emails in the contacts table?"
- "How many prospects have an ICP score above 0.75 and haven't been contacted in the past 30 days?"
- "What's the conversion rate from 'contacted' to 'meeting booked' in the pipeline table this month?"
Mothership translates the question into a structured query and returns the results.
## Adding and Updating Rows
Add individual rows, bulk-update based on a condition, or delete records — all in plain language:
- "Add a row to the leads table: Acme Corp, jane@acme.com, status pending"
- "Mark all rows in the queue table as processed where created_at is before today"
- "Update the price column for all rows where tier is 'pro' to 49"
- "Delete all rows in the test_events table"
## Exporting
Export a full table or a filtered subset as a CSV. The file is saved to your workspace and can be downloaded or referenced in other workflows:
- "Export the leads table to a CSV"
- "Export all rows where status is 'closed' and save as a file"
## Using Tables in Workflows
Tables created in Mothership are immediately available in workflows via the [Table tool](/tools/table). Reference a table by name — no additional configuration needed.
<FAQ items={[
{ question: "Can Mothership join or combine data from multiple tables?", answer: "Yes. Describe the relationship and what you want — Mothership will query both tables and combine the results." },
{ question: "Is there a row limit?", answer: "There is no hard row limit set by Mothership. Performance for very large tables may vary." },
{ question: "Can I use a table created in Mothership as a workflow data source?", answer: "Yes. All workspace tables are accessible to the Table tool in any workflow." },
]} />

View File

@@ -0,0 +1,131 @@
---
title: Automation & Configuration
description: Schedule recurring jobs, take immediate actions, connect integrations, and configure your workspace.
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
<Video src="mothership/job-create.mp4" width={700} height={450} />
Mothership can act on your behalf right now — send a message, create an issue, call an API — or on a schedule, running a prompt automatically every hour, day, or week. It can also connect integrations, set environment variables, add MCP servers, and create custom tools.
## Scheduled Jobs
A scheduled job is a Mothership task that runs on a cron schedule. On each run, Mothership reads the current workspace state and executes the job's prompt as if you had just sent it.
### Creating a Job
Describe the recurring task and how often it should run:
- "Every morning at 8am, check the leads table for new entries and post a summary to #sales in Slack"
- "Every Monday at 9am, pull last week's workflow run counts and write a report to the workspace"
- "Run the data sync workflow every 6 hours"
- "On the first of every month, export the billing table to CSV and email it to finance@example.com"
- "Every weekday at 7am, check for new funding announcements from companies in our ICP and post the top 5 to #market-intel in Slack"
- "Every Sunday night, run the lead enrichment workflow on all prospects added in the past week and update their scores in the table"
- "Daily at 6am, pull the previous day's workflow errors, summarize the top issues, and post to #eng-alerts"
Mothership sets the cron expression and stores the job prompt. The first run happens at the next scheduled time.
### Viewing Job Logs
- "Show me the last 5 runs of the weekly report job"
- "Did the sync job run successfully this morning?"
- "What did the Monday digest job do last week?"
Logs show run time, status (completed, failed), and a summary of what the agent did.
### Managing Jobs
- "Pause the morning summary job"
- "Change the sync job to run every 3 hours instead of 6"
- "Delete the onboarding digest job"
- "What scheduled jobs are currently active?"
## Taking Direct Action
For requests that should happen right now — without building a workflow — just ask. Mothership acts immediately using the credentials connected to your workspace.
{/* TODO: Screenshot of Mothership chat showing the "Taking action" subagent label active during a direct action — e.g., posting to Slack or sending an email. Shows the subagent inline in the chat thread. */}
| Request | What happens |
|---------|-------------|
| "Send a Slack message to #eng that the deploy finished" | Posts to Slack immediately |
| "Email the Q3 report to jane@example.com" | Sends via connected Gmail or Outlook |
| "Create a GitHub issue: auth tokens not rotating on logout" | Opens an issue in the specified repo |
| "Add a contact to HubSpot: Acme Corp, ceo@acme.com" | Creates the contact via HubSpot API |
| "Call the webhook at [URL] with this JSON payload" | Makes the HTTP request |
If an integration isn't connected, Mothership walks you through connecting it.
## Connecting Integrations
Mothership can connect new OAuth integrations and API credentials on demand:
- "Connect my Google account"
- "Add the Slack workspace for our team"
- "Set up GitHub with my personal access token"
{/* TODO: Screenshot of Mothership walking through connecting an integration — e.g., the Integration subagent active with an OAuth prompt or confirmation that a credential was connected. */}
Once connected, credentials are available to Mothership for direct actions and scheduled jobs, and to all workflows in the workspace.
<Callout type="info">
Connected credentials are shared across the workspace. Any workflow that uses the same integration will automatically use the same credential.
</Callout>
See [Credentials](/credentials) for managing connected accounts.
## Environment Variables
Environment variables are workspace-scoped values — API keys, connection strings, and configuration that workflows reference via `{{ENV_VAR}}` syntax rather than hardcoding. Set them once and every workflow in the workspace can use them.
- "Set the DATABASE_URL environment variable to 'postgres://...'"
- "Add an OPENAI_API_KEY environment variable"
- "Add a WEBHOOK_SECRET variable for the inbound webhook workflow"
- "Update the SCORING_API_URL variable to point to the new endpoint"
- "What environment variables are currently set?"
{/* TODO: Screenshot of Mothership confirming an environment variable was set — e.g., a response message showing the variable name was saved. */}
## MCP Servers
MCP (Model Context Protocol) servers expose tools from external services that Agent blocks can call inside workflows. Connecting an MCP server makes all of its tools available in the workflow editor's tool picker — no custom integration code required.
Mothership can add and manage MCP servers connected to your workspace:
- "Add the Stripe MCP server using my API key"
- "Remove the old analytics MCP server"
- "What MCP servers are connected to this workspace?"
- "Update the endpoint for the internal tools MCP server to [URL]"
Once added, MCP tools appear in the workflow editor's tool picker and can be called from any Agent block.
{/* TODO: Screenshot of Mothership confirming an MCP server was added or updated — showing the server name and its status. */}
## Custom Tools
Custom tools are single HTTP endpoints you define manually — useful for internal APIs and services that don't have a built-in Sim integration or an MCP server. Once created, they appear in the workflow editor alongside built-in tools and can be called from any Agent block.
Mothership can build custom tools from a description:
- "Create a custom tool that calls our internal scoring API at [URL] with a POST request and returns the score field"
- "Build a tool for our Zendesk instance that creates a ticket with a subject and body"
- "Create a tool that hits our internal enrichment API with a domain and returns company size, industry, and funding stage"
- "Add a tool that calls our CRM's REST API to look up a contact by email and return their account owner"
Custom tools appear in the workflow editor and are callable from any Agent block alongside built-in tools.
{/* TODO: Screenshot of Mothership with the Custom Tool subagent active — showing it building a tool definition. */}
<FAQ items={[
{ question: "What's the difference between a scheduled job and a deployed workflow?", answer: "A scheduled job runs a Mothership prompt on a cron schedule — Mothership decides what to do each time based on current workspace state. A deployed workflow runs a fixed, deterministic graph of blocks. Use jobs when you want Mothership to reason and adapt; use workflows when you want predictable, auditable execution." },
{ question: "Can a scheduled job trigger a workflow?", answer: "Yes. Include it in the job prompt: 'Run the invoice sync workflow and then post the results to Slack.'" },
{ question: "How do I know what integrations are connected?", answer: "Ask Mothership: 'What integrations are connected to this workspace?' or check Settings → Credentials." },
{ question: "Can direct actions be undone?", answer: "No. Actions like sending emails, posting to Slack, or creating records are immediate and irreversible. Confirm the details before asking Mothership to act." },
{ question: "Are environment variables visible to all workflows?", answer: "Yes. Environment variables are workspace-scoped and available to every workflow via {{ENV_VAR}} syntax." },
{ question: "What's the difference between MCP servers and custom tools?", answer: "MCP servers expose a set of tools from an external service over the MCP protocol — you connect an existing MCP-compatible server. Custom tools are single HTTP endpoints you define manually — useful for internal APIs that don't have an MCP server." },
]} />

View File

@@ -0,0 +1,118 @@
---
title: Workflows
description: Create, edit, run, debug, deploy, and organize workflows from Mothership.
---
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
<Video src="mothership/create-workflow.mp4" width={700} height={450} />
Describe a workflow and Mothership builds it. Reference an existing one by name and it edits it. No canvas navigation required — every change appears in the resource panel in real time.
## Creating Workflows
Describe what the workflow should do — what triggers it, what it should do, which integrations it needs, and what it should return. Mothership builds it and opens the canvas in the resource panel.
- "Build a workflow that takes a URL, scrapes the page, summarizes it with Claude, and sends the summary to a Slack channel"
- "Create a workflow triggered by a webhook that extracts invoice data from a PDF and writes it to the billing table"
- "Build an outbound workflow: take a company name and domain, enrich it with firmographic data, score the fit, and draft a personalized cold email"
- "Create a lead enrichment workflow that takes an email from a form submission, looks up the company, and writes the enriched record to the leads table"
- "Build a customer onboarding workflow: when a new user signs up, send a welcome email, create a HubSpot contact, and post a notification to #new-customers in Slack"
## Editing Workflows
{/* TODO: Screenshot of Mothership with the Edit subagent active and a change applied to an open workflow — e.g., a new block added or a configuration updated, visible on the canvas in the resource panel. */}
Open an existing workflow with `@workflow-name` or the **+** menu, then describe the change. Mothership reads the current structure before modifying it — you don't need to explain what already exists.
- "Add a condition that routes to a different branch if the confidence score is below 0.7"
- "Replace the GPT-4o model with Claude Opus 4.6 on the summarizer block"
- "Add a Slack notification at the end that includes the output"
## Running Workflows
<Video src="mothership/run-workflow.mp4" width={700} height={450} />
Ask Mothership to run a workflow and it handles the execution:
- "Run the data sync workflow"
- "Run the invoice processor with this PDF [attach file]"
- "Test the lead scoring workflow with these inputs: name=Acme, score=0.4"
Execution streams back to the chat. The workflow in the resource panel shows live block-by-block state.
## Reading Logs
Mothership can retrieve and interpret execution logs for any workflow in the workspace:
- "Show me the last 10 runs of the pipeline workflow"
- "Why did the invoice workflow fail yesterday?"
- "What did the extractor block return in the most recent run?"
Logs include per-block execution state, outputs, errors, and timing.
## Debugging
When a workflow fails, tell Mothership to debug it:
- "Debug the last failed run of the content pipeline"
- "The summarizer block is returning empty output — figure out why"
Mothership reads the failure logs, identifies the cause, applies a fix, and can re-run to confirm.
{/* TODO: Screenshot of the Debug subagent section in the Mothership chat showing it reading logs and applying a fix. */}
## Deploying
Mothership can deploy a workflow as any of the three deployment types:
| Deployment type | What it creates |
|----------------|----------------|
| **API** | A REST endpoint at `https://sim.ai/api/workflows/{id}/execute` |
| **Chat** | A hosted conversational interface with a shareable URL |
| **MCP tool** | An MCP server that exposes the workflow as a tool |
Ask: "Deploy the invoice workflow as an API and generate an API key."
Mothership can also roll back: "Revert the billing workflow to the version from last Tuesday."
See [API Deployment](/execution/api-deployment) and [Chat Deployment](/execution/chat) for full details on each deployment type.
## Organizing Workflows
Mothership can create and manage folders to keep your workspace organized.
**Folders:**
- "Create a folder called 'Data Pipelines'"
- "Move the invoice workflow into the billing folder"
- "Move the billing folder inside the finance folder"
- "Delete the old-experiments folder"
**Renaming and moving:**
- "Rename the 'test_v2' workflow to 'lead-scorer'"
- "Move the summarizer workflow to the research folder"
{/* TODO: Screenshot showing Mothership confirming a folder or workflow organization action — e.g., a message confirming "Moved 'invoice-processor' into 'billing' folder" with the resource panel showing the folder open. */}
## Workflow Variables
Mothership can set global variables on a workflow — values accessible across all blocks in that workflow at runtime:
- "Set the API_ENDPOINT variable on the sync workflow to 'https://api.example.com/v2'"
- "Update the MAX_RETRIES variable on the pipeline workflow to 5"
Variables set this way are available via `<variable.VARIABLE_NAME>` syntax inside any block in the workflow.
## Deleting Workflows
- "Delete the old_api_prototype workflow"
- "Delete all workflows in the deprecated folder"
<FAQ items={[
{ question: "Can Mothership edit a workflow while it's deployed?", answer: "Yes. Editing a workflow does not affect the live deployment. The deployed version is a snapshot — you need to ask Mothership to redeploy to push changes to production." },
{ question: "Can I run a workflow with specific inputs from Mothership?", answer: "Yes. Describe the inputs in your message and Mothership passes them to the workflow's start block." },
{ question: "How does Mothership know what my workflow does?", answer: "When you reference a workflow, Mothership loads its full structure — every block, connection, and configuration — before acting on it." },
{ question: "What happens to a workflow's deployment when I delete it?", answer: "The workflow and all its deployments are permanently removed. Any API endpoints, chat URLs, or MCP tools that pointed to that workflow will stop working." },
]} />

View File

@@ -2,10 +2,31 @@
title: "Roles and Permissions"
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Video } from '@/components/ui/video'
When you invite team members to your organization or workspace, you'll need to choose what level of access to give them. This guide explains what each permission level allows users to do, helping you understand team roles and what access each permission level provides.
## Workspaces and Organizations
Sim has two kinds of workspaces:
- **Personal workspaces** live under your individual account. The number you can create depends on your plan.
- **Shared (organization) workspaces** live under an organization and are available on Team and Enterprise plans. Any organization Owner or Admin can create them. Members invited to a shared workspace automatically join the organization and count toward your seat total.
### Workspace Limits by Plan
| Plan | Personal Workspaces | Shared Workspaces |
|------|---------------------|-------------------|
| **Free** | 1 | — |
| **Pro** | Up to 3 | — |
| **Max** | Up to 10 | — |
| **Team / Enterprise** | Unlimited | Unlimited (seat-gated invites) |
<Callout type="info">
When a Team or Enterprise subscription is cancelled or downgraded, existing shared workspaces stay accessible to current members. New invitations are blocked until the organization is upgraded again.
</Callout>
## How to Invite Someone to a Workspace
<div className="mx-auto w-full overflow-hidden rounded-lg">
@@ -88,6 +109,10 @@ Every workspace has one **Owner** (the person who created it) plus any number of
- Can do everything except delete the workspace or remove the owner
- Can be removed from the workspace by the owner or other admins
<Callout type="info">
For shared (organization) workspaces, the organization's Owner and Admins are treated as Admins of every workspace in the organization, even without an explicit per-workspace invite.
</Callout>
---
## Common Scenarios
@@ -145,28 +170,41 @@ Periodically review who has access to what, especially when team members change
## Organization Roles
When inviting someone to your organization, you can assign one of two roles:
An organization has three roles: **Owner**, **Admin**, and **Member**.
### Organization Owner
**What they can do:**
- Everything an Admin can do
- Transfer organization ownership to another user
- Only one Owner exists per organization
### Organization Admin
**What they can do:**
- Invite and remove team members from the organization
- Create new workspaces
- Manage billing and subscription settings
- Access all workspaces within the organization
- Create new shared workspaces under the organization
- Manage billing, seat count, and subscription settings
- Access all shared workspaces within the organization as a workspace Admin
- Promote members to Admin or demote Admins to Member
<Callout type="info">
Owners and Admins have the same day-to-day permissions. The only action reserved for the Owner is transferring ownership.
</Callout>
### Organization Member
**What they can do:**
- Access workspaces they've been specifically invited to
- Access shared workspaces they've been specifically invited to
- View the list of organization members
- Cannot invite new people or manage organization settings
- Cannot invite new people, create shared workspaces, or manage organization settings
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "What is the difference between organization roles and workspace permissions?", answer: "Organization roles (Admin or Member) control who can manage the organization itself, including inviting people, creating workspaces, and handling billing. Workspace permissions (Read, Write, Admin) control what a user can do within a specific workspace, such as viewing, editing, or managing workflows. A user needs both an organization role and a workspace permission to work within a workspace." },
{ question: "Can I restrict which integrations or model providers a team member can use?", answer: "Yes. Organization admins can create permission groups with fine-grained controls, including restricting allowed integrations and allowed model providers to specific lists. You can also disable access to MCP tools, custom tools, skills, and various platform features like the knowledge base, API keys, or Copilot on a per-group basis." },
{ question: "What is the difference between organization roles and workspace permissions?", answer: "Organization roles (Owner, Admin, or Member) control who can manage the organization itself, including inviting people, creating shared workspaces, and handling billing. Workspace permissions (Read, Write, Admin) control what a user can do within a specific workspace, such as viewing, editing, or managing workflows. A user needs both an organization role and a workspace permission to work within a shared workspace." },
{ question: "How many workspaces can I create?", answer: "Free users get 1 personal workspace. Pro users get up to 3 personal workspaces. Max users get up to 10 personal workspaces. Team and Enterprise plans support unlimited shared workspaces under the organization — new invites are gated by your seat count." },
{ question: "What happens to my shared workspaces if I cancel or downgrade my Team plan?", answer: "Existing shared workspaces remain accessible to current members, but new invitations are disabled until you upgrade back to a Team or Enterprise plan. No workspaces or members are deleted — the organization is simply dormant until billing is re-enabled." },
{ question: "Can I restrict which integrations or model providers a team member can use?", answer: "Yes, on Enterprise-entitled workspaces. Any workspace admin can create permission groups with fine-grained controls, including restricting allowed integrations and allowed model providers to specific lists. You can also disable access to MCP tools, custom tools, skills, and various platform features like the knowledge base, API keys, or Copilot on a per-group basis. Permission groups are scoped per workspace — a user can belong to different groups in different workspaces." },
{ question: "What happens when a personal environment variable has the same name as a workspace variable?", answer: "The personal environment variable takes priority. When a workflow runs, if both a personal and workspace variable share the same name, the personal value is used. This allows individual users to override shared workspace configuration when needed." },
{ question: "Can an Admin remove the workspace owner?", answer: "No. The workspace owner cannot be removed from the workspace by anyone. Only the workspace owner can delete the workspace or transfer ownership to another user. Admins can do everything else, including inviting and removing other users and managing workspace settings." },
{ question: "What are permission groups and how do they work?", answer: "Permission groups are an advanced access control feature that lets organization admins define granular restrictions beyond the standard Read/Write/Admin roles. A permission group can hide UI sections (like trace spans, knowledge base, API keys, or deployment options), disable features (MCP tools, custom tools, skills, invitations), and restrict which integrations and model providers members can access. Members can be assigned to groups, and new members can be auto-added." },
{ question: "What are permission groups and how do they work?", answer: "Permission groups are an Enterprise access control feature that lets workspace admins define granular restrictions beyond the standard Read/Write/Admin roles. Groups are scoped to a single workspace: each user can be in at most one group per workspace, and a user can be in different groups across different workspaces. A permission group can hide UI sections (like trace spans, knowledge base, API keys, or deployment options), disable features (MCP tools, custom tools, skills, invitations), and restrict which integrations and model providers its members can access. Members can be assigned manually, and new members can be auto-added on join. Execution-time enforcement is based on the workflow's workspace, not the user's current UI context." },
{ question: "How should I set up permissions for a new team member?", answer: "Start with the lowest permission level they need. Invite them to the organization as a Member, then add them to the relevant workspace with Read permission if they only need visibility, Write if they need to create and run workflows, or Admin if they need to manage the workspace and its users. You can always increase permissions later." },
]} />

View File

@@ -1,4 +0,0 @@
{
"title": "SDKs",
"pages": ["python", "typescript"]
}

View File

@@ -1,772 +0,0 @@
---
title: Python
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
The official Python SDK for Sim allows you to execute workflows programmatically from your Python applications using the official Python SDK.
<Callout type="info">
The Python SDK supports Python 3.8+ with async execution support, automatic rate limiting with exponential backoff, and usage tracking.
</Callout>
## Installation
Install the SDK using pip:
```bash
pip install simstudio-sdk
```
## Quick Start
Here's a simple example to get you started:
```python
from simstudio import SimStudioClient
# Initialize the client
client = SimStudioClient(
api_key="your-api-key-here",
base_url="https://sim.ai" # optional, defaults to https://sim.ai
)
# Execute a workflow
try:
result = client.execute_workflow("workflow-id")
print("Workflow executed successfully:", result)
except Exception as error:
print("Workflow execution failed:", error)
```
## API Reference
### SimStudioClient
#### Constructor
```python
SimStudioClient(api_key: str, base_url: str = "https://sim.ai")
```
**Parameters:**
- `api_key` (str): Your Sim API key
- `base_url` (str, optional): Base URL for the Sim API
#### Methods
##### execute_workflow()
Execute a workflow with optional input data.
```python
result = client.execute_workflow(
"workflow-id",
input={"message": "Hello, world!"},
timeout=30.0 # 30 seconds
)
```
**Parameters:**
- `workflow_id` (str): The ID of the workflow to execute
- `input` (dict, optional): Input data to pass to the workflow
- `timeout` (float, optional): Timeout in seconds (default: 30.0)
- `stream` (bool, optional): Enable streaming responses (default: False)
- `selected_outputs` (list[str], optional): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`)
- `async_execution` (bool, optional): Execute asynchronously (default: False)
**Returns:** `WorkflowExecutionResult | AsyncExecutionResult`
When `async_execution=True`, returns immediately with a task ID for polling. Otherwise, waits for completion.
##### get_workflow_status()
Get the status of a workflow (deployment status, etc.).
```python
status = client.get_workflow_status("workflow-id")
print("Is deployed:", status.is_deployed)
```
**Parameters:**
- `workflow_id` (str): The ID of the workflow
**Returns:** `WorkflowStatus`
##### validate_workflow()
Validate that a workflow is ready for execution.
```python
is_ready = client.validate_workflow("workflow-id")
if is_ready:
# Workflow is deployed and ready
pass
```
**Parameters:**
- `workflow_id` (str): The ID of the workflow
**Returns:** `bool`
##### get_job_status()
Get the status of an async job execution.
```python
status = client.get_job_status("task-id-from-async-execution")
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
if status["status"] == "completed":
print("Output:", status["output"])
```
**Parameters:**
- `task_id` (str): The task ID returned from async execution
**Returns:** `Dict[str, Any]`
**Response fields:**
- `success` (bool): Whether the request was successful
- `taskId` (str): The task ID
- `status` (str): One of `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
- `metadata` (dict): Contains `startedAt`, `completedAt`, and `duration`
- `output` (any, optional): The workflow output (when completed)
- `error` (any, optional): Error details (when failed)
- `estimatedDuration` (int, optional): Estimated duration in milliseconds (when processing/queued)
##### execute_with_retry()
Execute a workflow with automatic retry on rate limit errors using exponential backoff.
```python
result = client.execute_with_retry(
"workflow-id",
input={"message": "Hello"},
timeout=30.0,
max_retries=3, # Maximum number of retries
initial_delay=1.0, # Initial delay in seconds
max_delay=30.0, # Maximum delay in seconds
backoff_multiplier=2.0 # Exponential backoff multiplier
)
```
**Parameters:**
- `workflow_id` (str): The ID of the workflow to execute
- `input` (dict, optional): Input data to pass to the workflow
- `timeout` (float, optional): Timeout in seconds
- `stream` (bool, optional): Enable streaming responses
- `selected_outputs` (list, optional): Block outputs to stream
- `async_execution` (bool, optional): Execute asynchronously
- `max_retries` (int, optional): Maximum number of retries (default: 3)
- `initial_delay` (float, optional): Initial delay in seconds (default: 1.0)
- `max_delay` (float, optional): Maximum delay in seconds (default: 30.0)
- `backoff_multiplier` (float, optional): Backoff multiplier (default: 2.0)
**Returns:** `WorkflowExecutionResult | AsyncExecutionResult`
The retry logic uses exponential backoff (1s → 2s → 4s → 8s...) with ±25% jitter to prevent thundering herd. If the API provides a `retry-after` header, it will be used instead.
##### get_rate_limit_info()
Get the current rate limit information from the last API response.
```python
rate_limit_info = client.get_rate_limit_info()
if rate_limit_info:
print("Limit:", rate_limit_info.limit)
print("Remaining:", rate_limit_info.remaining)
print("Reset:", datetime.fromtimestamp(rate_limit_info.reset))
```
**Returns:** `RateLimitInfo | None`
##### get_usage_limits()
Get current usage limits and quota information for your account.
```python
limits = client.get_usage_limits()
print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"])
print("Async requests remaining:", limits.rate_limit["async"]["remaining"])
print("Current period cost:", limits.usage["currentPeriodCost"])
print("Plan:", limits.usage["plan"])
```
**Returns:** `UsageLimits`
**Response structure:**
```python
{
"success": bool,
"rateLimit": {
"sync": {
"isLimited": bool,
"limit": int,
"remaining": int,
"resetAt": str
},
"async": {
"isLimited": bool,
"limit": int,
"remaining": int,
"resetAt": str
},
"authType": str # 'api' or 'manual'
},
"usage": {
"currentPeriodCost": float,
"limit": float,
"plan": str # e.g., 'free', 'pro'
}
}
```
##### set_api_key()
Update the API key.
```python
client.set_api_key("new-api-key")
```
##### set_base_url()
Update the base URL.
```python
client.set_base_url("https://my-custom-domain.com")
```
##### close()
Close the underlying HTTP session.
```python
client.close()
```
## Data Classes
### WorkflowExecutionResult
```python
@dataclass
class WorkflowExecutionResult:
success: bool
output: Optional[Any] = None
error: Optional[str] = None
logs: Optional[List[Any]] = None
metadata: Optional[Dict[str, Any]] = None
trace_spans: Optional[List[Any]] = None
total_duration: Optional[float] = None
```
### AsyncExecutionResult
```python
@dataclass
class AsyncExecutionResult:
success: bool
task_id: str
status: str # 'queued'
created_at: str
links: Dict[str, str] # e.g., {"status": "/api/jobs/{taskId}"}
```
### WorkflowStatus
```python
@dataclass
class WorkflowStatus:
is_deployed: bool
deployed_at: Optional[str] = None
needs_redeployment: bool = False
```
### RateLimitInfo
```python
@dataclass
class RateLimitInfo:
limit: int
remaining: int
reset: int
retry_after: Optional[int] = None
```
### UsageLimits
```python
@dataclass
class UsageLimits:
success: bool
rate_limit: Dict[str, Any]
usage: Dict[str, Any]
```
### SimStudioError
```python
class SimStudioError(Exception):
def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None):
super().__init__(message)
self.code = code
self.status = status
```
**Common error codes:**
- `UNAUTHORIZED`: Invalid API key
- `TIMEOUT`: Request timed out
- `RATE_LIMIT_EXCEEDED`: Rate limit exceeded
- `USAGE_LIMIT_EXCEEDED`: Usage limit exceeded
- `EXECUTION_ERROR`: Workflow execution failed
## Examples
### Basic Workflow Execution
<Steps>
<Step title="Initialize the client">
Set up the SimStudioClient with your API key.
</Step>
<Step title="Validate the workflow">
Check if the workflow is deployed and ready for execution.
</Step>
<Step title="Execute the workflow">
Run the workflow with your input data.
</Step>
<Step title="Handle the result">
Process the execution result and handle any errors.
</Step>
</Steps>
```python
import os
from simstudio import SimStudioClient
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
def run_workflow():
try:
# Check if workflow is ready
is_ready = client.validate_workflow("my-workflow-id")
if not is_ready:
raise Exception("Workflow is not deployed or ready")
# Execute the workflow
result = client.execute_workflow(
"my-workflow-id",
input={
"message": "Process this data",
"user_id": "12345"
}
)
if result.success:
print("Output:", result.output)
print("Duration:", result.metadata.get("duration") if result.metadata else None)
else:
print("Workflow failed:", result.error)
except Exception as error:
print("Error:", error)
run_workflow()
```
### Error Handling
Handle different types of errors that may occur during workflow execution:
```python
from simstudio import SimStudioClient, SimStudioError
import os
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
def execute_with_error_handling():
try:
result = client.execute_workflow("workflow-id")
return result
except SimStudioError as error:
if error.code == "UNAUTHORIZED":
print("Invalid API key")
elif error.code == "TIMEOUT":
print("Workflow execution timed out")
elif error.code == "USAGE_LIMIT_EXCEEDED":
print("Usage limit exceeded")
elif error.code == "INVALID_JSON":
print("Invalid JSON in request body")
else:
print(f"Workflow error: {error}")
raise
except Exception as error:
print(f"Unexpected error: {error}")
raise
```
### Context Manager Usage
Use the client as a context manager to automatically handle resource cleanup:
```python
from simstudio import SimStudioClient
import os
# Using context manager to automatically close the session
with SimStudioClient(api_key=os.getenv("SIM_API_KEY")) as client:
result = client.execute_workflow("workflow-id")
print("Result:", result)
# Session is automatically closed here
```
### Batch Workflow Execution
Execute multiple workflows efficiently:
```python
from simstudio import SimStudioClient
import os
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
def execute_workflows_batch(workflow_data_pairs):
"""Execute multiple workflows with different input data."""
results = []
for workflow_id, input_data in workflow_data_pairs:
try:
# Validate workflow before execution
if not client.validate_workflow(workflow_id):
print(f"Skipping {workflow_id}: not deployed")
continue
result = client.execute_workflow(workflow_id, input_data)
results.append({
"workflow_id": workflow_id,
"success": result.success,
"output": result.output,
"error": result.error
})
except Exception as error:
results.append({
"workflow_id": workflow_id,
"success": False,
"error": str(error)
})
return results
# Example usage
workflows = [
("workflow-1", {"type": "analysis", "data": "sample1"}),
("workflow-2", {"type": "processing", "data": "sample2"}),
]
results = execute_workflows_batch(workflows)
for result in results:
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
```
### Async Workflow Execution
Execute workflows asynchronously for long-running tasks:
```python
import os
import time
from simstudio import SimStudioClient
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
def execute_async():
try:
# Start async execution
result = client.execute_workflow(
"workflow-id",
input={"data": "large dataset"},
async_execution=True # Execute asynchronously
)
# Check if result is an async execution
if hasattr(result, 'task_id'):
print(f"Task ID: {result.task_id}")
print(f"Status endpoint: {result.links['status']}")
# Poll for completion
status = client.get_job_status(result.task_id)
while status["status"] in ["queued", "processing"]:
print(f"Current status: {status['status']}")
time.sleep(2) # Wait 2 seconds
status = client.get_job_status(result.task_id)
if status["status"] == "completed":
print("Workflow completed!")
print(f"Output: {status['output']}")
print(f"Duration: {status['metadata']['duration']}")
else:
print(f"Workflow failed: {status['error']}")
except Exception as error:
print(f"Error: {error}")
execute_async()
```
### Rate Limiting and Retry
Handle rate limits automatically with exponential backoff:
```python
import os
from simstudio import SimStudioClient, SimStudioError
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
def execute_with_retry_handling():
try:
# Automatically retries on rate limit
result = client.execute_with_retry(
"workflow-id",
input={"message": "Process this"},
max_retries=5,
initial_delay=1.0,
max_delay=60.0,
backoff_multiplier=2.0
)
print(f"Success: {result}")
except SimStudioError as error:
if error.code == "RATE_LIMIT_EXCEEDED":
print("Rate limit exceeded after all retries")
# Check rate limit info
rate_limit_info = client.get_rate_limit_info()
if rate_limit_info:
from datetime import datetime
reset_time = datetime.fromtimestamp(rate_limit_info.reset)
print(f"Rate limit resets at: {reset_time}")
execute_with_retry_handling()
```
### Usage Monitoring
Monitor your account usage and limits:
```python
import os
from simstudio import SimStudioClient
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
def check_usage():
try:
limits = client.get_usage_limits()
print("=== Rate Limits ===")
print("Sync requests:")
print(f" Limit: {limits.rate_limit['sync']['limit']}")
print(f" Remaining: {limits.rate_limit['sync']['remaining']}")
print(f" Resets at: {limits.rate_limit['sync']['resetAt']}")
print(f" Is limited: {limits.rate_limit['sync']['isLimited']}")
print("\nAsync requests:")
print(f" Limit: {limits.rate_limit['async']['limit']}")
print(f" Remaining: {limits.rate_limit['async']['remaining']}")
print(f" Resets at: {limits.rate_limit['async']['resetAt']}")
print(f" Is limited: {limits.rate_limit['async']['isLimited']}")
print("\n=== Usage ===")
print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}")
print(f"Limit: ${limits.usage['limit']:.2f}")
print(f"Plan: {limits.usage['plan']}")
percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100
print(f"Usage: {percent_used:.1f}%")
if percent_used > 80:
print("⚠️ Warning: You are approaching your usage limit!")
except Exception as error:
print(f"Error checking usage: {error}")
check_usage()
```
### Streaming Workflow Execution
Execute workflows with real-time streaming responses:
```python
from simstudio import SimStudioClient
import os
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
def execute_with_streaming():
"""Execute workflow with streaming enabled."""
try:
# Enable streaming for specific block outputs
result = client.execute_workflow(
"workflow-id",
input={"message": "Count to five"},
stream=True,
selected_outputs=["agent1.content"] # Use blockName.attribute format
)
print("Workflow result:", result)
except Exception as error:
print("Error:", error)
execute_with_streaming()
```
The streaming response follows the Server-Sent Events (SSE) format:
```
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
data: [DONE]
```
**Flask Streaming Example:**
```python
from flask import Flask, Response, stream_with_context
import requests
import json
import os
app = Flask(__name__)
@app.route('/stream-workflow')
def stream_workflow():
"""Stream workflow execution to the client."""
def generate():
response = requests.post(
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
headers={
'Content-Type': 'application/json',
'X-API-Key': os.getenv('SIM_API_KEY')
},
json={
'message': 'Generate a story',
'stream': True,
'selectedOutputs': ['agent1.content']
},
stream=True
)
for line in response.iter_lines():
if line:
decoded_line = line.decode('utf-8')
if decoded_line.startswith('data: '):
data = decoded_line[6:] # Remove 'data: ' prefix
if data == '[DONE]':
break
try:
parsed = json.loads(data)
if 'chunk' in parsed:
yield f"data: {json.dumps(parsed)}\n\n"
elif parsed.get('event') == 'done':
yield f"data: {json.dumps(parsed)}\n\n"
print("Execution complete:", parsed.get('metadata'))
except json.JSONDecodeError:
pass
return Response(
stream_with_context(generate()),
mimetype='text/event-stream'
)
if __name__ == '__main__':
app.run(debug=True)
```
### Environment Configuration
Configure the client using environment variables:
<Tabs items={['Development', 'Production']}>
<Tab value="Development">
```python
import os
from simstudio import SimStudioClient
# Development configuration
client = SimStudioClient(
api_key=os.getenv("SIM_API_KEY")
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
)
```
</Tab>
<Tab value="Production">
```python
import os
from simstudio import SimStudioClient
# Production configuration with error handling
api_key = os.getenv("SIM_API_KEY")
if not api_key:
raise ValueError("SIM_API_KEY environment variable is required")
client = SimStudioClient(
api_key=api_key,
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
)
```
</Tab>
</Tabs>
## Getting Your API Key
<Steps>
<Step title="Log in to Sim">
Navigate to [Sim](https://sim.ai) and log in to your account.
</Step>
<Step title="Open your workflow">
Navigate to the workflow you want to execute programmatically.
</Step>
<Step title="Deploy your workflow">
Click on "Deploy" to deploy your workflow if it hasn't been deployed yet.
</Step>
<Step title="Create or select an API key">
During the deployment process, select or create an API key.
</Step>
<Step title="Copy the API key">
Copy the API key to use in your Python application.
</Step>
</Steps>
## Requirements
- Python 3.8+
- requests >= 2.25.0
## License
Apache-2.0
import { FAQ } from '@/components/ui/faq'
<FAQ items={[
{ question: "Do I need to deploy a workflow before I can execute it via the SDK?", answer: "Yes. Workflows must be deployed before they can be executed through the SDK. You can use the validate_workflow() method to check whether a workflow is deployed and ready. If it returns False, deploy the workflow from the Sim UI first and create or select an API key during deployment." },
{ question: "What is the difference between sync and async execution?", answer: "Sync execution (the default) blocks until the workflow completes and returns the full result. Async execution (async_execution=True) returns immediately with a task ID that you can poll using get_job_status(). Use async mode for long-running workflows to avoid request timeouts. Async job statuses include queued, processing, completed, failed, and cancelled." },
{ question: "How does the SDK handle rate limiting?", answer: "The SDK provides built-in rate limiting support through the execute_with_retry() method. It uses exponential backoff (1s, 2s, 4s, 8s...) with 25% jitter to avoid thundering herd problems. If the API returns a retry-after header, that value is used instead. You can configure max_retries, initial_delay, max_delay, and backoff_multiplier. Use get_rate_limit_info() to check your current rate limit status." },
{ question: "Can I use the Python SDK as a context manager?", answer: "Yes. The SimStudioClient supports Python's context manager protocol. Use it with the 'with' statement to automatically close the underlying HTTP session when you are done, which is especially useful for scripts that create and discard client instances." },
{ question: "How do I handle different types of errors from the SDK?", answer: "The SDK raises SimStudioError with a code property for API-specific errors. Common error codes are UNAUTHORIZED (invalid API key), TIMEOUT (request timed out), RATE_LIMIT_EXCEEDED (too many requests), USAGE_LIMIT_EXCEEDED (billing limit reached), and EXECUTION_ERROR (workflow failed). Use the error code to implement targeted error handling and recovery logic." },
{ question: "How do I monitor my API usage and remaining quota?", answer: "Use the get_usage_limits() method to check your current usage. It returns sync and async rate limit details (limit, remaining, reset time, whether you are currently limited), plus your current period cost, usage limit, and plan tier. This lets you monitor consumption and alert before hitting limits." },
]} />

File diff suppressed because it is too large Load Diff

View File

@@ -140,7 +140,7 @@ import { FAQ } from '@/components/ui/faq'
{ question: "How does the agent decide when to load a skill?", answer: "The agent sees an available_skills section in its system prompt listing each skill's name and description. When the agent determines that a skill is relevant to the current task, it calls the load_skill tool with the skill name. The full skill content is then returned as a tool response. This is why writing a specific, keyword-rich description is critical -- it is the only thing the agent reads before deciding whether to activate a skill." },
{ question: "Do skills work with all LLM providers?", answer: "Yes. The load_skill mechanism uses standard tool-calling, which is supported by all LLM providers in Sim. No provider-specific configuration is needed. The skill system works the same way whether you are using Anthropic, OpenAI, Google, or any other supported provider." },
{ question: "When should I use skills vs. agent instructions?", answer: "Use skills for knowledge that applies across multiple workflows or changes frequently. Skills are reusable packages that can be attached to any agent. Use agent instructions for task-specific context that is unique to a single agent and workflow. If you find yourself copying the same instructions into multiple agents, that content should be a skill instead." },
{ question: "Can permission groups disable skills for certain users?", answer: "Yes. Organization admins can create permission groups with the disableSkills option enabled. When a user is assigned to such a permission group, the skills dropdown in agent blocks will be disabled and they will not be able to add or use skills in their workflows." },
{ question: "Can permission groups disable skills for certain users?", answer: "Yes. On Enterprise-entitled workspaces, any workspace admin can create a permission group with the disableSkills option enabled. When a user is assigned to such a group in a workspace, the skills dropdown in agent blocks is disabled and they cannot add or use skills in workflows belonging to that workspace." },
{ question: "What is the recommended maximum length for skill content?", answer: "Keep skills focused and under 500 lines. If a skill grows too large, split it into multiple specialized skills. Shorter, focused skills are more effective because the agent can load exactly what it needs. A broad skill with too much content can overwhelm the agent and reduce the quality of its responses." },
{ question: "Where do I create and manage skills?", answer: "Go to Settings and select Skills under the Tools section. From there you can add new skills with a name (kebab-case identifier, max 64 characters), description (max 1024 characters), and content (full instructions in markdown). You can also edit or delete existing skills from this page." },
]} />

View File

@@ -0,0 +1,629 @@
---
title: AgentPhone
description: Provision numbers, send SMS and iMessage, and place voice calls with AgentPhone
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="agentphone"
color="linear-gradient(135deg, #1a1a1a 0%, #0a2a14 100%)"
/>
{/* MANUAL-CONTENT-START:intro */}
[AgentPhone](https://agentphone.to/) is an API-first voice and messaging platform built for AI agents. AgentPhone lets you provision real phone numbers, place outbound AI voice calls, send SMS and iMessage, manage conversations and contacts, and monitor usage — all through a simple REST API designed for programmatic access.
**Why AgentPhone?**
- **Agent-Native Telephony:** Purpose-built for AI agents — provision numbers, place calls, and send messages without carrier contracts or telephony plumbing.
- **Voice + Messaging in One API:** Drive outbound AI voice calls alongside SMS, MMS, and iMessage from the same account and phone numbers.
- **Conversation & Transcript Management:** Every call returns an ordered transcript; every message thread is tracked as a conversation with full history and metadata.
- **Contacts Built In:** Create, search, update, and delete contacts on the account so your agents can reference people by name instead of raw phone numbers.
- **Usage Visibility:** Inspect plan limits, current counts, and daily/monthly aggregation so workflows can stay inside guardrails.
**Using AgentPhone in Sim**
Sim's AgentPhone integration connects your agentic workflows directly to AgentPhone using an API key. With 22 operations spanning numbers, calls, conversations, contacts, and usage, you can build powerful voice and messaging automations without writing backend code.
**Key benefits of using AgentPhone in Sim:**
- **Dynamic number provisioning:** Reserve US or Canadian numbers on the fly — per agent, per customer, or per workflow — and release them when no longer needed.
- **Outbound AI voice calls:** Place calls from an agent with an optional greeting, voice override, or system prompt, and read the full transcript back as structured data once the call completes.
- **Two-way messaging:** Send SMS, MMS, or iMessage, fetch conversation history, and react to incoming iMessages — all from inside your workflow.
- **Contact and metadata management:** Keep an account-level contact list and attach custom JSON metadata to conversations so downstream blocks can branch on state.
- **Operational insight:** Pull current usage stats and daily/monthly breakdowns to monitor consumption and enforce plan limits before making the next call.
Whether you're building an outbound AI voice agent, running automated SMS follow-ups, managing two-way customer conversations, or monitoring phone usage across your organization, AgentPhone in Sim gives you direct, secure access to the full AgentPhone API — no middleware required. Simply configure your API key, select the operation you need, and let Sim handle the rest.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Give your workflow a phone. Provision SMS- and voice-enabled numbers, send messages and tapback reactions, place outbound voice calls, manage conversations and contacts, and track usage — all through a single AgentPhone API key.
## Tools
### `agentphone_create_call`
Initiate an outbound voice call from an AgentPhone agent
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `agentId` | string | Yes | Agent that will handle the call |
| `toNumber` | string | Yes | Phone number to call in E.164 format \(e.g. +14155551234\) |
| `fromNumberId` | string | No | Phone number ID to use as caller ID. Must belong to the agent. If omitted, the agent's first assigned number is used. |
| `initialGreeting` | string | No | Optional greeting spoken when the recipient answers |
| `voice` | string | No | Voice ID override for this call \(defaults to the agent's configured voice\) |
| `systemPrompt` | string | No | When provided, uses a built-in LLM for the conversation instead of forwarding to your webhook |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Unique call identifier |
| `agentId` | string | Agent handling the call |
| `status` | string | Initial call status |
| `toNumber` | string | Destination phone number |
| `fromNumber` | string | Caller ID used for the call |
| `phoneNumberId` | string | ID of the phone number used as caller ID |
| `direction` | string | Call direction \(outbound\) |
| `startedAt` | string | ISO 8601 timestamp |
### `agentphone_create_contact`
Create a new contact in AgentPhone
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `phoneNumber` | string | Yes | Phone number in E.164 format \(e.g. +14155551234\) |
| `name` | string | Yes | Contact's full name |
| `email` | string | No | Contact's email address |
| `notes` | string | No | Freeform notes stored on the contact |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Contact ID |
| `phoneNumber` | string | Phone number in E.164 format |
| `name` | string | Contact name |
| `email` | string | Contact email address |
| `notes` | string | Freeform notes |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 update timestamp |
### `agentphone_create_number`
Provision a new SMS- and voice-enabled phone number
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `country` | string | No | Two-letter country code \(e.g. US, CA\). Defaults to US. |
| `areaCode` | string | No | Preferred area code \(US/CA only, e.g. "415"\). Best-effort — may be ignored if unavailable. |
| `agentId` | string | No | Optionally attach the number to an agent immediately |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Unique phone number ID |
| `phoneNumber` | string | Provisioned phone number in E.164 format |
| `country` | string | Two-letter country code |
| `status` | string | Number status \(e.g. active\) |
| `type` | string | Number type \(e.g. sms\) |
| `agentId` | string | Agent the number is attached to |
| `createdAt` | string | ISO 8601 timestamp when the number was created |
### `agentphone_delete_contact`
Delete a contact by ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `contactId` | string | Yes | Contact ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | ID of the deleted contact |
| `deleted` | boolean | Whether the contact was deleted successfully |
### `agentphone_get_call`
Fetch a call and its full transcript
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `callId` | string | Yes | ID of the call to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Call ID |
| `agentId` | string | Agent that handled the call |
| `phoneNumberId` | string | Phone number ID |
| `phoneNumber` | string | Phone number used for the call |
| `fromNumber` | string | Caller phone number |
| `toNumber` | string | Recipient phone number |
| `direction` | string | inbound or outbound |
| `status` | string | Call status |
| `startedAt` | string | ISO 8601 timestamp |
| `endedAt` | string | ISO 8601 timestamp |
| `durationSeconds` | number | Call duration in seconds |
| `lastTranscriptSnippet` | string | Last transcript snippet |
| `recordingUrl` | string | Recording audio URL |
| `recordingAvailable` | boolean | Whether a recording is available |
| `transcripts` | array | Ordered transcript turns for the call |
| ↳ `id` | string | Transcript turn ID |
| ↳ `transcript` | string | User utterance |
| ↳ `confidence` | number | Speech recognition confidence |
| ↳ `response` | string | Agent response \(when available\) |
| ↳ `createdAt` | string | ISO 8601 timestamp |
### `agentphone_get_call_transcript`
Get the full ordered transcript for a call
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `callId` | string | Yes | ID of the call to retrieve the transcript for |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `callId` | string | Call ID |
| `transcript` | array | Ordered transcript turns for the call |
| ↳ `role` | string | Speaker role \(user or agent\) |
| ↳ `content` | string | Turn content |
| ↳ `createdAt` | string | ISO 8601 timestamp |
### `agentphone_get_contact`
Fetch a single contact by ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `contactId` | string | Yes | Contact ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Contact ID |
| `phoneNumber` | string | Phone number in E.164 format |
| `name` | string | Contact name |
| `email` | string | Contact email address |
| `notes` | string | Freeform notes |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 update timestamp |
### `agentphone_get_conversation`
Get a conversation along with its recent messages
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `conversationId` | string | Yes | Conversation ID |
| `messageLimit` | number | No | Number of recent messages to include \(default 50, max 100\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Conversation ID |
| `agentId` | string | Agent ID |
| `phoneNumberId` | string | Phone number ID |
| `phoneNumber` | string | Phone number |
| `participant` | string | External participant phone number |
| `lastMessageAt` | string | ISO 8601 timestamp |
| `messageCount` | number | Number of messages in the conversation |
| `metadata` | json | Custom metadata stored on the conversation |
| `createdAt` | string | ISO 8601 timestamp |
| `messages` | array | Recent messages in the conversation |
| ↳ `id` | string | Message ID |
| ↳ `body` | string | Message text |
| ↳ `fromNumber` | string | Sender phone number |
| ↳ `toNumber` | string | Recipient phone number |
| ↳ `direction` | string | inbound or outbound |
| ↳ `channel` | string | sms, mms, or imessage |
| ↳ `mediaUrl` | string | Attached media URL |
| ↳ `receivedAt` | string | ISO 8601 timestamp |
### `agentphone_get_conversation_messages`
Get paginated messages for a conversation
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `conversationId` | string | Yes | Conversation ID |
| `limit` | number | No | Number of messages to return \(default 50, max 200\) |
| `before` | string | No | Return messages received before this ISO 8601 timestamp |
| `after` | string | No | Return messages received after this ISO 8601 timestamp |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Messages in the conversation |
| ↳ `id` | string | Message ID |
| ↳ `body` | string | Message text |
| ↳ `fromNumber` | string | Sender phone number |
| ↳ `toNumber` | string | Recipient phone number |
| ↳ `direction` | string | inbound or outbound |
| ↳ `channel` | string | sms, mms, or imessage |
| ↳ `mediaUrl` | string | Attached media URL |
| ↳ `receivedAt` | string | ISO 8601 timestamp |
| `hasMore` | boolean | Whether more messages are available |
### `agentphone_get_number_messages`
Fetch messages received on a specific phone number
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `numberId` | string | Yes | ID of the phone number |
| `limit` | number | No | Number of messages to return \(default 50, max 200\) |
| `before` | string | No | Return messages received before this ISO 8601 timestamp |
| `after` | string | No | Return messages received after this ISO 8601 timestamp |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Messages received on the number |
| ↳ `id` | string | Message ID |
| ↳ `from_` | string | Sender phone number \(E.164\) |
| ↳ `to` | string | Recipient phone number \(E.164\) |
| ↳ `body` | string | Message text |
| ↳ `direction` | string | inbound or outbound |
| ↳ `channel` | string | Channel \(sms, mms, etc.\) |
| ↳ `receivedAt` | string | ISO 8601 timestamp |
| `hasMore` | boolean | Whether more messages are available |
### `agentphone_get_usage`
Retrieve current usage statistics for the AgentPhone account
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `plan` | json | Plan name and limits \(name, limits: numbers/messagesPerMonth/voiceMinutesPerMonth/maxCallDurationMinutes/concurrentCalls\) |
| `numbers` | json | Phone number usage \(used, limit, remaining\) |
| `stats` | json | Usage stats: totalMessages, messagesLast24h/7d/30d, totalCalls, callsLast24h/7d/30d, totalWebhookDeliveries, successfulWebhookDeliveries, failedWebhookDeliveries |
| `periodStart` | string | Billing period start |
| `periodEnd` | string | Billing period end |
### `agentphone_get_usage_daily`
Get a daily breakdown of usage (messages, calls, webhooks) for the last N days
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `days` | number | No | Number of days to return \(1-365, default 30\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Daily usage entries |
| ↳ `date` | string | Day \(YYYY-MM-DD\) |
| ↳ `messages` | number | Messages that day |
| ↳ `calls` | number | Calls that day |
| ↳ `webhooks` | number | Webhook deliveries that day |
| `days` | number | Number of days returned |
### `agentphone_get_usage_monthly`
Get monthly usage aggregation (messages, calls, webhooks) for the last N months
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `months` | number | No | Number of months to return \(1-24, default 6\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Monthly usage entries |
| ↳ `month` | string | Month \(YYYY-MM\) |
| ↳ `messages` | number | Messages that month |
| ↳ `calls` | number | Calls that month |
| ↳ `webhooks` | number | Webhook deliveries that month |
| `months` | number | Number of months returned |
### `agentphone_list_calls`
List voice calls for this AgentPhone account
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `limit` | number | No | Number of results to return \(default 20, max 100\) |
| `offset` | number | No | Number of results to skip \(min 0\) |
| `status` | string | No | Filter by status \(completed, in-progress, failed\) |
| `direction` | string | No | Filter by direction \(inbound, outbound\) |
| `type` | string | No | Filter by call type \(pstn, web\) |
| `search` | string | No | Search by phone number \(matches fromNumber or toNumber\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Calls |
| ↳ `id` | string | Call ID |
| ↳ `agentId` | string | Agent that handled the call |
| ↳ `phoneNumberId` | string | Phone number ID used for the call |
| ↳ `phoneNumber` | string | Phone number used for the call |
| ↳ `fromNumber` | string | Caller phone number |
| ↳ `toNumber` | string | Recipient phone number |
| ↳ `direction` | string | inbound or outbound |
| ↳ `status` | string | Call status |
| ↳ `startedAt` | string | ISO 8601 timestamp |
| ↳ `endedAt` | string | ISO 8601 timestamp |
| ↳ `durationSeconds` | number | Call duration in seconds |
| ↳ `lastTranscriptSnippet` | string | Last transcript snippet |
| ↳ `recordingUrl` | string | Recording audio URL |
| ↳ `recordingAvailable` | boolean | Whether a recording is available |
| `hasMore` | boolean | Whether more results are available |
| `total` | number | Total number of matching calls |
### `agentphone_list_contacts`
List contacts for this AgentPhone account
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `search` | string | No | Filter by name or phone number \(case-insensitive contains\) |
| `limit` | number | No | Number of results to return \(default 50\) |
| `offset` | number | No | Number of results to skip \(min 0\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Contacts |
| ↳ `id` | string | Contact ID |
| ↳ `phoneNumber` | string | Phone number in E.164 format |
| ↳ `name` | string | Contact name |
| ↳ `email` | string | Contact email address |
| ↳ `notes` | string | Freeform notes |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| ↳ `updatedAt` | string | ISO 8601 update timestamp |
| `hasMore` | boolean | Whether more results are available |
| `total` | number | Total number of contacts |
### `agentphone_list_conversations`
List conversations (message threads) for this AgentPhone account
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `limit` | number | No | Number of results to return \(default 20, max 100\) |
| `offset` | number | No | Number of results to skip \(min 0\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Conversations |
| ↳ `id` | string | Conversation ID |
| ↳ `agentId` | string | Agent ID |
| ↳ `phoneNumberId` | string | Phone number ID |
| ↳ `phoneNumber` | string | Phone number |
| ↳ `participant` | string | External participant phone number |
| ↳ `lastMessageAt` | string | ISO 8601 timestamp |
| ↳ `lastMessagePreview` | string | Last message preview |
| ↳ `messageCount` | number | Number of messages in the conversation |
| ↳ `metadata` | json | Custom metadata stored on the conversation |
| ↳ `createdAt` | string | ISO 8601 timestamp |
| `hasMore` | boolean | Whether more results are available |
| `total` | number | Total number of conversations |
### `agentphone_list_numbers`
List all phone numbers provisioned for this AgentPhone account
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `limit` | number | No | Number of results to return \(default 20, max 100\) |
| `offset` | number | No | Number of results to skip \(min 0\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | array | Phone numbers |
| ↳ `id` | string | Phone number ID |
| ↳ `phoneNumber` | string | Phone number in E.164 format |
| ↳ `country` | string | Two-letter country code |
| ↳ `status` | string | Number status |
| ↳ `type` | string | Number type \(e.g. sms\) |
| ↳ `agentId` | string | Attached agent ID |
| ↳ `createdAt` | string | ISO 8601 creation timestamp |
| `hasMore` | boolean | Whether more results are available |
| `total` | number | Total number of phone numbers |
### `agentphone_react_to_message`
Send an iMessage tapback reaction to a message (iMessage only)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `messageId` | string | Yes | ID of the message to react to |
| `reaction` | string | Yes | Reaction type: love, like, dislike, laugh, emphasize, or question |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Reaction ID |
| `reactionType` | string | Reaction type applied |
| `messageId` | string | ID of the message that was reacted to |
| `channel` | string | Channel \(imessage\) |
### `agentphone_release_number`
Release (delete) a phone number. This action is irreversible.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `numberId` | string | Yes | ID of the phone number to release |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | ID of the released phone number |
| `released` | boolean | Whether the number was released successfully |
### `agentphone_send_message`
Send an outbound SMS or iMessage from an AgentPhone agent
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `agentId` | string | Yes | Agent sending the message |
| `toNumber` | string | Yes | Recipient phone number in E.164 format \(e.g. +14155551234\) |
| `body` | string | Yes | Message text to send |
| `mediaUrl` | string | No | Optional URL of an image, video, or file to attach |
| `numberId` | string | No | Phone number ID to send from. If omitted, the agent's first assigned number is used. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Message ID |
| `status` | string | Delivery status |
| `channel` | string | sms, mms, or imessage |
| `fromNumber` | string | Sender phone number |
| `toNumber` | string | Recipient phone number |
### `agentphone_update_contact`
Update a contact
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `contactId` | string | Yes | Contact ID |
| `phoneNumber` | string | No | New phone number in E.164 format |
| `name` | string | No | New contact name |
| `email` | string | No | New email address |
| `notes` | string | No | New freeform notes |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Contact ID |
| `phoneNumber` | string | Phone number in E.164 format |
| `name` | string | Contact name |
| `email` | string | Contact email address |
| `notes` | string | Freeform notes |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 update timestamp |
### `agentphone_update_conversation`
Update conversation metadata (stored state). Pass null to clear existing metadata.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `conversationId` | string | Yes | Conversation ID |
| `metadata` | json | No | Custom key-value metadata to store on the conversation. Pass null to clear existing metadata. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Conversation ID |
| `agentId` | string | Agent ID |
| `phoneNumberId` | string | Phone number ID |
| `phoneNumber` | string | Phone number |
| `participant` | string | External participant phone number |
| `lastMessageAt` | string | ISO 8601 timestamp |
| `messageCount` | number | Number of messages |
| `metadata` | json | Custom metadata stored on the conversation |
| `createdAt` | string | ISO 8601 timestamp |
| `messages` | array | Messages in the conversation |
| ↳ `id` | string | Message ID |
| ↳ `body` | string | Message body |
| ↳ `fromNumber` | string | Sender phone number |
| ↳ `toNumber` | string | Recipient phone number |
| ↳ `direction` | string | inbound or outbound |
| ↳ `channel` | string | Channel \(sms, mms, etc.\) |
| ↳ `mediaUrl` | string | Media URL if any |
| ↳ `receivedAt` | string | ISO 8601 timestamp |

Some files were not shown because too many files have changed in this diff Show More