Waleed 46703e395f fix: six pre-existing integration defects surfaced by the docs audit (#7195)
* fix(docs): pin the generator's sort locale to en-US

localeCompare with no locale argument uses the runtime default, which
varies with LANG and the ICU build. Against the real 254 catalog names,
tr-TR reorders 141 positions, et-EE 45, cs-CZ 2 and lt-LT diverges at
index 40 — so a contributor on any of those regenerates a different
integrations.json and fails CI with no obvious cause.

Pins en-US at all four sort sites. The committed artifacts are unchanged:
regenerating before and after leaves the tree byte-identical.

Adds a guard test asserting the committed catalog matches an explicit
en-US ordering, plus one that fails if an unpinned localeCompare returns.

* fix(vanta): remove the MIME Type field whose value was always discarded

The block rendered an advanced MIME Type input and forwarded it as the
tool's mimeType, but the upload path never reads it: file-input.ts sets
`resolved.contentType || userFile.type || input.mimeType || …`, and every
return path of downloadServableFileFromStorage yields a non-empty content
type — a literal, getMimeTypeFromExtension's GENERIC_MIME_TYPE fallback,
or resolveServableDocBytes' constants and getContentType fallback. The
placeholder claimed it was 'used when the file has no type of its own',
which never happens.

Removes the subBlock, its mapper write and its inputs entry, and marks the
tool param hidden so it is no longer advertised to the model on a path
where it cannot take effect. The param itself stays, because the base64
branch still reads it.

Letting a typed value win instead was rejected: for a compiled artifact
the storage-resolved type is the only one matching the bytes actually
sent, so an override would break the case the resolver exists to fix.

* fix(github): resolve the PR head SHA for file comments

github_comment sent commit_id as undefined for every file comment: the
param was hidden with no subBlock and no mapper write, so GitHub — which
marks commit_id required on POST /pulls/{n}/comments — answered 422 on a
path the commentType dropdown exposes.

When commitId is absent the tool now fetches the pull request first and
uses head.sha, mirroring how Jira resolves cloudId from domain.

Also removes the position param, which GitHub marks deprecated ("Use
line instead"); line is already a real subBlock.

* fix(google-drive): expose the page token so pagination is reachable

list, search, list_comments, list_permissions and list_revisions each
declared a hidden pageToken and forwarded it to Google, but the block had
no subBlock of that name and never has — so every list was capped at one
page and the nextPageToken output had nowhere to go.

Adds a per-operation Page Token field mirroring the block's existing
per-operation pageSize fields, collapses them onto the canonical
pageToken in the params mapper, and flips the tool param to user-only so
it is documented and settable.

* fix(confluence): stop documenting a cloudId users cannot supply

All 46 Confluence tools marked cloudId 'user-only', publishing it on 46
doc rows, but the block has no cloudId subBlock so no user could ever
fill it. createConfluenceClient already resolves the cloud id from the
domain through the shared Atlassian resolver, exactly as Jira does, and
Jira marks the same param hidden.

Marks cloudId hidden to match. domain stays user-only and settable — it
is now the only user-provided param on every Confluence tool.

* fix(docs): teach the source scanner about regex literals

blankStringsAndComments was a single regex with no concept of a regex
literal, so two shapes silently truncated a block's subBlock list:

  /don't/   the apostrophe opened a phantom string that swallowed the
            following entries
  /[}]/     the brace in the character class closed the enclosing object

Both returned a short list with no warning — a confident wrong answer,
which for the hidden-param filter means silently deleting a user-settable
row. No block file uses a regex literal today, so this was latent.

Replaces the regex with a linear scanner that distinguishes a regex
literal from a division by the previous significant character, blanks
regex bodies whole (their last character is arbitrary source, same reason
comments are blanked whole), and tracks ${} nesting so a backtick inside
a template expression cannot end the template early.

The scanner now returns null when it ends inside an unterminated
construct. All three call sites treat that as UNKNOWN rather than
guessing, so the filter switches off instead of stripping.

Generated artifacts are byte-identical and the warning count is unchanged.

* fix(github): gate the commit lookup on path, coerce line, name the failing field

Three corrections to the file-comment fix, from a validation sweep.

needsCommitLookup did not check `path`, and ran before the `path` branch in
request.url. A file comment with an empty File Path — reachable, since path
is not required on the block — went GET /pulls/{n} then POST /comments with
path undefined, a 422. On staging it posted to /pulls/{n}/reviews, which
GitHub documents as creating a pending review and where commit_id is
optional. The lookup is now gated on path, so only a request headed for
/comments triggers it.

The block has no tools.config.params, so `line` reached the tool as the
string the short-input produced while GitHub types it as an integer — file
comments would still have 422'd, one API call later. Coerced in
request.body, which runs at execution; anything non-finite is omitted
rather than sent as NaN.

readGitHubErrorMessage returned only the top-level message, so a 422 read
"Validation Failed" with no indication of which field was rejected. The
errors[] detail is now appended. Responses without errors[] are unchanged.

* test(github): cover the comment routing cases the gate changed

Adds the file_comment-without-a-path case (which the commit lookup now skips),
the untouched-block default where commentType is unset, a pr_comment carrying a
path, and the line coercion on both the direct and resolved-commit paths.

* fix(google-drive): let an agent feed the page token back in

A page token is an opaque continuation value produced by a previous tool
response, not an account-specific id the user has to supply, so 'user-only'
hid it from agent blocks: they saw nextPageToken in the result and could not
send it back, silently reporting page one as the whole answer. Every other
pagination token in the tool set is 'user-or-llm'.

Also covers the case the mapper guard actually defends — a per-operation page
token surviving an operation switch, which reaches inputs because
shouldSerializeSubBlock skips condition evaluation for advanced fields.

* docs(generator): name the load-bearing newline rule and report an unscannable source

- Record why '\\n' is in REGEX_ALLOWED_AFTER: formatters emit a binary '/' at
  end-of-line, so every line-leading '/' in blocks/*.ts is a real regex, including
  the ones in table.ts and table_v2.ts. Removing it silently mis-scans those two.
- Split a scanner failure out of the spread-only 'ids: null' case. Both scans come
  back empty for the same reason when blankStringsAndComments bails, so the mapper's
  renames were dropped with no warning; it now reports a parseError and warns. The
  spread case is unchanged, and its TSDoc no longer claims a cause that was false.
- Route every catalog sort through an exported compareCatalogNames so the ordering
  test exercises the generator's comparator instead of re-deriving it, and match
  localeCompare arguments whole so localeCompare() and a variable locale are caught.
- Note that downloadServableFileFromStorage guarantees a non-empty content type, so
  the Vanta mimeType fallback chain reads as deliberately defensive.

Artifacts regenerate byte-identically and the generator warning set is unchanged.

* fix(vanta): declare the removed uploadMimeType subblock as dropped

check-block-registry fails a PR that deletes a subblock id without a
migration entry, because a deployed workflow can still hold a value under
that id. The serializer already discards an orphan silently, but the repo's
contract is that the removal is declared rather than inferred.

Uses the _removed_ form, scoped to upload_document_file: the value has no
replacement field to move to.

* fix(github): run the two-phase PR comment on the secure transport

The file-comment flow posted its comment from `transformResponse` with a bare
global `fetch`, so that request carried no abort signal, no response ceiling and
no DNS/SSRF validation — cancelling a workflow still left the comment posted.

`transformResponse` cannot receive the signal; `directExecution` can. Both tools
now run the lookup and the POST through a new `secureGitHubRequest`, mirroring
`secureBitbucketRead`, with the signal forwarded to each. Routing, line coercion
and the `errors[]` detail are unchanged, and a failed response still throws an
error carrying `status`/`statusText`/`data` as the transport does.

Request bodies and the comment payload are explicitly typed instead of
`Record<string, any>`.

* fix(vanta): keep mimeType an ordinary upload parameter

`mimeType` was marked hidden, but `visibility: 'hidden'` is reserved for
system-injected params such as OAuth tokens. It also left the base64 upload path
with no way to set a content type, since `fileContent` is hidden too.

* fix(generator): lex regex-in-keyword-position and template interpolation

The scanner chose regex-vs-division from the previous significant character, so
a regex in operand position (`return /x/`, `typeof /x/`, `case /x/`, ...) lexed
as a division off the keyword's last letter and its body stayed in the
structural view; `azure_devops.ts` is inert today only because the braces in its
`return /^\d{4}-\d{2}-\d{2}$/` happen to balance.

The `${}` depth counter was also not string-aware, so a brace inside a quoted
expression miscounted, the closing backtick was lost and the block was reported
unreadable — which silently stops filtering resolver-derived hidden params.

Both scans now run on one set of lexer primitives: `${}` expressions are lexed
with the same string, comment, regex and template handling as top-level code.
Regenerated docs, tool metadata and the integration catalog are byte-identical
and the generator's warning count is unchanged.

* fix(github): stop forwarding the GitHub token across a redirect origin

secureGitHubRequest passed no redirectPolicy, and the transport only strips
credential headers when one is present, so an api.github.com redirect to another
origin carried the workspace's Authorization: Bearer header to the new host.

Adopts the standard policy already used by the internal Google Drive client.
stripAuthOnRedirect stays off: GitHub redirects same-origin for legitimate
reasons (a renamed repository answers 301), and dropping auth there would turn
a working call into a 401.

* fix(github): reject a fractional comment line instead of truncating it

toLineNumber ran Math.trunc, so line 3.9 posted the review comment on line 3 —
a silent change to what the caller asked for, on a field where landing on the
wrong line of the diff is invisible until someone reads the comment. A
non-integer now fails with a message naming the field, matching how a missing
head commit SHA fails on this path. Blank and unparseable input is still
omitted: line is optional and nothing usable was supplied.

* fix(github): select the comment endpoint by comment type, not by path

The endpoint was chosen by the presence of path, while the body was chosen by
commentType, so a pr_comment naming a file posted a review body to
POST /pulls/{n}/comments. GitHub documents body, commit_id and path as required
there, so that request can only ever 422 — it has been broken since before this
PR. The endpoint now follows the comment type: only a file comment carrying a
path uses /comments, everything else stays on /reviews. The test that codified
the broken routing is corrected, and the full type/path matrix is pinned.

* test(confluence): drop the cloudId visibility invariant test

Removed at request. The visibility change itself is unaffected; it loses
only the guard that would have caught a future edit reverting one of the
46 files.

* fix(github): send an explicit User-Agent and stop downgrading a redirected comment POST

`secureGitHubRequest` powers the GitHub comment tool's `directExecution`
path, which bypasses the declarative transport. Two behaviors the transport
provided did not survive the move.

User-Agent: the transport sets `User-Agent: Sim` on every request it formats
(`request-transport.ts`), and `secureFetchWithPinnedIP` adds none of its own —
it builds the request with raw `node:https` and passes headers through
verbatim. Production runs on Bun, whose `node:http` shim injects
`user-agent: Bun/x.y.z`, so GitHub does not reject these calls today; the
defect is that Sim's deliberate attribution is silently replaced by a runtime
version string, and that the tool depends on an undocumented runtime behavior
that does not hold under Node, where GitHub answers 403 "Request forbidden by
administrative rules". Set in the helper rather than in the tool's header map
so every future caller inherits it; a caller-supplied value still wins.

Redirect method: the policy was `mode: 'standard'`, under which
`resolveRedirectHop` rewrites a 301/302'd POST to a bodyless GET regardless of
origin. GitHub answers 301 within api.github.com for a renamed repository, so
commenting on a PR there would GET `/pulls/{n}/comments`, receive a JSON array,
fail the payload shape check, and report success with no comment created.
`legacy` keeps the method and body across that hop. Cross-origin credential
stripping is unaffected — the guarded follower strips Authorization,
Proxy-Authorization and Cookie whenever `sendCredentialsOnCrossOriginRedirect`
is false, in either mode.

* fix(vanta): drop the dead whenOperation from the removed-subblock entry

migrateBlockSubblockIds handles a _removed_ target before it consults
whenOperation, so the scope was never applied. Mine was the only _removed_
entry in the file carrying one.

Unconditional deletion is also what this case wants. Subblock values are
keyed by id and are not cleared when the operation changes, so a user who
filled the MIME field and then switched the block to another operation has
the value stored under that operation; a scoped delete would strand it
permanently. The field no longer exists for any operation, so it should go
regardless of the stored operation.

* fix(docs-gen): close a regex-vs-division gap and make three guards testable

`REGEX_ALLOWED_AFTER` was missing `'/'`, so a regex directly after a division
operator lexed as a second division: in `x / y / /[}]/` the character class was
left in the structural view and its `}` closed the enclosing object early,
truncating the block's subBlock ids with no warning. Add `'/'`, and guard the
`'+'`/`'-'` entries with a `++`/`--` lookbehind so a postfix update still reads
as a value and `i++ / 2` stays a division rather than a phantom regex that runs
to end-of-input and reports the block unreadable.

The `.`/`#` property guard and the `'\n'` entry both survived their mutants.
`counts.in / 2, m: preturn / 2` is self-cancelling — the mis-lexed regex closes
on the second slash and blanks nothing structural — so the fixtures now leave an
odd number of slashes on the line. The `'\n'` entry had no coverage at all: its
fixture is now the wrapped `.match(` newline `/re/` shape that `blocks/table.ts`
and `blocks/table_v2.ts` produce, which is what that entry (not the preceding
`(`, which the newline overwrites) actually decides. Its comment claimed a count
of line-leading regexes that drifts with the sources; restate it without one.

Drop the two locale tests that could not fail. CI runs under an `en-US` default,
where an unpinned `localeCompare` returns exactly what the pinned one does, so no
behavioural comparison discriminates; and asserting the committed
`integrations.json` against the comparator that produced it agrees by
construction. The source grep for a literal locale argument is the real guard.

Generated output is byte-identical and the extraction differential over
`apps/sim/blocks/blocks/` is empty.
2026-08-27 23:31:53 -07:00

Sim.ai Documentation Slack X

Ask DeepWiki Set Up with Cursor

Sim — Integrate, Context, Build, and Monitor AI agents

A workspace to build, deploy and manage AI agents and workflows.

Quickstart

Cloud-hosted: sim.ai

Open sim.ai

Self-hosted

npx sim-setup

Open http://localhost:3000

The Sim platform — chat on the left, the visual workflow builder on the right

Capabilities

  • Connect 1,000+ integrations and every major LLM
  • Add Slack, Notion, HubSpot, Salesforce, databases, and more
  • Build agents visually, conversationally, or with code
  • Ingest files, knowledge bases, and structured table data
  • Monitor runs, logs, schedules, and workflow activity

One workspace, every surface

Chat and workflows are just the start — tables, files, and knowledge all live in the same workspace.

Tables in Sim — structured data your agents can query

Tables — a database, built in

Files in Sim — documents for your team and every agent

Files — one store for your team and every agent

Knowledge bases in Sim — synced docs your agents can search

Knowledge — your agents' memory

Self-hosting

Requirements: Node.js 20+ and Docker.

npx sim-setup is an interactive wizard that creates a small sim/ deployment directory, provisions the database, generates secrets, writes .env, connects a Chat API key, and starts the published Sim images with Docker Compose. It does not clone the repository.

When it finishes, open http://localhost:3000.

Inside a cloned Sim repository, run bun run sim-setup to unlock the source-only local development and Kubernetes modes.

Reconfigure an optional capability without rerunning the full wizard:

npx sim-setup config
npx sim-setup add email
npx sim-setup add storage
npx sim-setup add sandbox
npx sim-setup add jobs
npx sim-setup add cache
npx sim-setup add knowledge
npx sim-setup add chat
npx sim-setup add llm
npx sim-setup add integration slack

npx sim-setup config detects the effective local-dev, Docker Compose, or current-context Helm configuration and reports configured, missing, or invalid capabilities and OAuth integrations without printing credential values. This is separate from npx sim-setup status, which reports whether installed services are running and healthy.

Manage your install from its directory:

npx sim-setup start | stop | restart   # bring your install up / down / cycle
npx sim-setup update                   # pull and apply Compose images
npx sim-setup status                   # what's installed and healthy
npx sim-setup logs                     # follow logs
npx sim-setup doctor                   # diagnose configuration problems
npx sim-setup down                     # remove containers (data kept)
npx sim-setup reset                    # archive .env and wipe managed data

The setup package detects how you're running and acts accordingly. Use --dir <path> to create or manage a deployment somewhere other than ./sim.

Sim also supports local models via Ollama and vLLM. See the self-hosting docs for details.

Chat API Keys

Chat is a Sim-managed service. npx sim-setup connects a Chat API key for you — sign in when it opens your browser and the key is stored automatically. To view, create, or revoke keys later, go to sim.ai/selfhost/settings/chat-keys.

Environment Variables

See the environment variables reference for the full list, or apps/sim/.env.example for defaults.

Tech Stack

Next.js · Bun · PostgreSQL · Drizzle · Better Auth · Tailwind — and the rest of the stack

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Built by the Sim team in San Francisco

Languages
TypeScript 77%
MDX 20.8%
JavaScript 1.9%
CSS 0.1%