diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index 681311eb9cf..00000000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..acb8bc4cca7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,162 @@ +# Sim Development Guidelines + +This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.claude/rules/*.md` (indexed in `apps/sim/AGENTS.md`): Claude loads each one by path, and any other agent reads the file a section points to before editing in that area. Skills live in `.agents/skills/`. + +## Global Standards + +- **Package manager**: `bun` and `bunx`, never `npm` and `npx`. +- **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`. +- **Comments**: name things so the code explains itself. TSDoc documents exported APIs and non-obvious modules. An inline `//` is only for a terse, non-obvious *why*, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, `utils-lint-allow`, …). History belongs in the commit message. No `====` separators, commented-out code, or ESLint directives (`eslint-disable`/`eslint-enable`; nothing runs ESLint; `check:comment-hygiene` enforces all three). The `/you-might-not-need-a-comment` skill applies this to a diff. +- **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers. For other randomness, `@sim/utils/random` (`randomInt`, `randomFloat`, `randomItem`, `generateRandomBytes`, `generateRandomHex`) — never `Math.random()` or `crypto.randomBytes()`. +- **Common utilities**: use the shared helpers from the `@sim/utils/` subpaths instead of inline implementations (`check:utils` bans most of the inline forms below): + - `sleep(ms)` from `@sim/utils/helpers` — never `new Promise(resolve => setTimeout(resolve, ms))` + - `toError(e)` from `@sim/utils/errors` — normalize caught values to `Error`; never `e instanceof Error ? e : new Error(String(e))` + - `getErrorMessage(e, fallback?)` from `@sim/utils/errors` — never `e instanceof Error ? e.message : 'fallback'` + - `structuredClone(value)` — built-in deep clone; never `JSON.parse(JSON.stringify(...))` + - `omit(obj, keys)` / `filterUndefined(obj)` from `@sim/utils/object` — never `Object.fromEntries(Object.entries(...).filter(...))` + - `isRecordLike(value)` from `@sim/utils/object` — never redeclare `typeof value === 'object' && value !== null && !Array.isArray(value)` + - `toRecord(value)` / `toRecordOrNull(value)` / `toArray(value)` from `@sim/utils/object` — coerce an untyped payload value; never inline `isRecordLike(v) ? v : {}` or `Array.isArray(v) ? v : []`. Where the source is already typed, keep the inline `Array.isArray` check: it narrows, while `toArray` asserts + - `toStringOrNull(value)` / `toNumberOrNull(value)` / `toBooleanOrNull(value)` from `@sim/utils/coerce` — read one scalar out of an untyped payload; never declare a local one-liner byte-identical to one of these. Keep a local helper that differs: `undefined` instead of `null` changes the wire shape, and a `Number.isFinite` or string-parse variant is a stricter check these omit + - `truncate(str, maxLength, suffix?)` from `@sim/utils/string` — never inline slice + ellipsis + - `escapeRegExp(value)` from `@sim/utils/string` — never inline `replace(/[.*+?^${}()|[\]\\]/g, '\\$&')` + - `compareStrings(left, right)` from `@sim/utils/string` — code-unit ordering for hashes, fingerprints, and cross-process comparisons; never `localeCompare` there + - `backoffWithJitter(attempt, retryAfterMs, options?)` / `parseRetryAfter(header)` from `@sim/utils/retry` — never reimplement exponential backoff inline +- **Deployment flags in the browser**: client code inside a workspace, organization, or standalone settings surface reads `hosted`, `billingEnabled`, `chatEnabled`, and the enterprise feature set through `useDeploymentShape()` (components) or `getDeploymentShape()` (block conditions, stores, helpers) from `@/lib/core/config/deployment-shape`, never `isHosted`/`isBillingEnabled`/... from `env-flags`. Those constants freeze at module init from the root layout's `NEXT_PUBLIC_*` transport, which Next's bare 404 shell and `global-error` never emit, so a recovered tab would render Sim Cloud as self-hosted; the reader is seeded from the server-resolved workspace host context, organization layout, or standalone settings layout instead. Server code keeps reading `env-flags`. +- **Type-checking**: `bun run type-check` at the root checks every workspace (`bun run type-check` inside a workspace checks just that one). `apps/sim`'s tsconfig excludes `*.test.ts(x)`, so `type-check` skips its tests; CI's `test-types` job ratchets their errors per file and TS code instead (`check:test-types`, whose header says how to fix a finding): a test file may not gain one. Never remove the `@typescript/native` alias from the root `devDependencies`: nothing imports it, but without it a bare `tsc` resolves to the ~10x slower TypeScript 6 compiler. `bun run check:native-typecheck` enforces this; its header explains the bin-resolution trap. + +## Architecture + +### Repository layout + +``` +apps/ +├── sim/ # Next.js app: UI, API routes, workflow builder, executor +│ ├── app/ # App router — pages and API routes (app/api/**) +│ ├── blocks/ # Block definitions and registry +│ ├── tools/ # Tool definitions and registry +│ ├── triggers/ # Trigger definitions and registry +│ ├── connectors/ # Knowledge base connectors +│ ├── executor/ # Workflow execution engine +│ ├── providers/ # LLM provider integrations +│ ├── components/ # Shared app UI (ui/, icons, …) +│ ├── hooks/ # Shared hooks (queries/, selectors/) +│ ├── stores/ # Zustand stores +│ ├── lib/ # App-wide modules, incl. lib/api/contracts and lib//application +│ └── ee/ # Enterprise features +├── realtime/ # Bun Socket.IO server (collaborative workflow builder) +├── desktop/ # Electron shell around the hosted web app +├── docs/ # Documentation site +└── pii/ # Python PII detection service (not in the JS/turbo build) + +packages/ +├── emcn/ # @sim/emcn — design system (chip family, tokens, icons) +├── db/ # @sim/db — Drizzle schema, migrations, client +├── auth/ # @sim/auth — shared Better Auth verifier +├── platform-authz/ # @sim/platform-authz — workspace + workflow authz (subpath exports) +├── audit/ logger/ security/ utils/ runtime-secrets/ deployment-config/ +├── realtime-protocol/ browser-protocol/ terminal-protocol/ desktop-bridge/ +├── workflow-types/ workflow-persistence/ workflow-renderer/ +├── testing/ # @sim/testing — test factories and mocks +├── tsconfig/ # shared tsconfig presets +└── cli/ sim-cli/ sim-setup/ ts-sdk/ python-sdk/ # published CLIs and SDKs +``` + +### Package boundaries + +- `apps/* → packages/*` only. Packages never import from `apps/*`. +- `apps/realtime` avoids Next.js, React, the block/tool registry, provider SDKs, and the executor. Never add imports from `@/lib/webhooks/providers/*`, `@/executor/*`, `@/blocks/*`, or `@/tools/*` to any package it consumes; it calls back into `apps/sim` only over internal HTTP with `INTERNAL_API_SECRET`. CI enforces this via `scripts/check-monorepo-boundaries.ts` and `scripts/check-realtime-prune-graph.ts`. +- Auth is shared across both apps via the Better Auth "Shared Database Session" pattern (same `BETTER_AUTH_SECRET`, same DB via `@sim/db`). + +### Application Operation Boundary + +- Every protected read, write, canonical resource lookup, or authorization-sensitive reference resolution enters through an authorized application use case. +- Define one stable semantic operation with its minimum role, workspace-key policy, allowed principal kinds, and delegated services. Internal APIs, v2 APIs, Copilot, and trusted tools call the same use case when the domain behavior is the same. +- Surface adapters authenticate and construct a `Principal`, apply request-rate policy, parse contracts, map input, and present their own result. They never query protected data, decide resource authorization, implement business transactions, or record semantic audit (`check:source-conventions` ratchets value imports of `@sim/db` under `app/api/`). +- Application use cases load canonical context, compare asserted scope, authorize current access, execute managers/repositories, project semantic audit, and trigger shared domain effects. Managers accept canonical IDs and scope, never credentials or principals. Application code stays surface-neutral (`check:boundaries`): never `next/server` or `app/api/**`, and never a runtime import of a route contract object, presenter, or Copilot handler (contract types, schemas, and constants are fine); a surface fact, such as a cursor's route, comes in as input. +- Copilot is a surface adapter. Use `createCopilotApplicationAdapter` and the domain's registered operation object; never a Copilot-only authorization or business implementation. +- Protected compound mutations belong in one top-level semantic application operation, never a sequence of independently committing mutations in a route or tool adapter. +- Never substitute a billing owner, uploader, creator, or API-key owner for the acting principal. Fail fast when the identity model or operation policy cannot express the caller. +- Use the `migrate-application-operation` skill whenever creating or migrating a protected endpoint, tool command, or resource method. + +The `'use client'` server boundary, the app/worker runtime env split, and feature folder layout are in `.claude/rules/sim-architecture.md`. + +## Code Conventions + +- **Naming**: components PascalCase (`WorkflowList`); hooks `use*`; files kebab-case (`workflow-list.tsx`); constants SCREAMING_SNAKE_CASE; interfaces PascalCase with a suffix (`WorkflowListProps`); stores `stores//store.ts`. Inside `lib/`, `executor/`, `providers/`, `stores/`, `hooks/`, `serializer/`, and a package's `src/`, a file never starts with its folder's name (`logs/views.ts`, not `logs/log-views.ts`), and a file in `utils/` or `helpers/` never repeats that role (`date.ts`, not `date-utils.ts`); `feature/feature.tsx` stays the component convention. `check:file-names` ratchets this. +- **Imports**: absolute (`@/...`) only, never relative (a barrel `index.ts` re-exports its own siblings relatively). `check:source-conventions` ratchets relative imports in `apps/sim`. A folder with 3+ exports gets an `index.ts` barrel; never re-export from a non-barrel file. `import type` for type-only imports. Order and lazy-loading through barrels: `.claude/rules/sim-imports.md`. +- **TypeScript**: no `any` and no non-null `!` (use precise types or `unknown` with guards; `check:explicit-any` ratchets both); no export nothing imports (`check:unused-exports`); a props interface for every component; `as const` for constant objects/arrays; explicit ref types (`useRef(null)`). +- **Unused bindings** fail lint (biome `noUnusedVariables`, `noUnusedFunctionParameters`): delete the dead variable, import, or parameter and update callers; write `catch {}` when the error is unused. Prefix `_` only for a parameter that must hold its position because a later one is used, or a type parameter kept for API shape. `const { a, ...rest } = obj` to omit keys is allowed. The rules carry no autofix, so `bun run lint` will not rename anything for you. +- **Components**: `'use client'` only for hooks or browser APIs (`check:client-boundary` guards the server boundary). Structure order, extraction thresholds, and list-render rules: `.claude/rules/sim-components.md`. Render-performance idioms (lazy-init refs, hoisting, `Map` pre-indexing, `[...arr].sort()`, never `toSorted()`): `.claude/rules/sim-react-performance.md`. For effect/state/memo/callback anti-patterns use the `/you-might-not-need-*` skills and verify against the running UI. +- **State ownership**: React Query owns server data — never `useState` + `fetch`; shareable client view-state (tabs, filters, search, pagination, selected id) lives in the URL via `nuqs`; Zustand owns global client state; `useState` owns UI-only state. Hooks: `.claude/rules/sim-hooks.md`. Stores (`devtools`, `persist` only with an explicit `partialize` whitelist, workflow value invariants): `.claude/rules/sim-stores.md`. URL state: `.claude/rules/sim-url-state.md`. +- **Utils**: inline a helper with one consumer; create `utils.ts` when 2+ files share it — in `lib/` (app-wide) or `feature/utils/` (feature-scoped). Check `lib/` before writing a new one. +- **Lists and menus** mirror the order the user already reads elsewhere (toolbar, settings nav), encoded in one exported order constant (resource menus share `RESOURCE_MENU_ORDER`, a product order that does not mirror the sidebar); a separator marks only a change in what the action acts on (typically one, before the destructive action): `.claude/rules/sim-list-ordering.md`. +- **Caching**: `lru-cache` with a `max` ceiling, never a hand-rolled TTL `Map`; a lifecycle map is not a cache; cache the gate, never the credential: `.claude/rules/sim-caching.md`. + +## API Contracts and Routes + +- Request/response shapes for every route under `apps/sim/app/api/**` live in `apps/sim/lib/api/contracts/**`, built with `defineRouteContract`. Export the contract; export a named schema or type alias only when another module imports it (`check:unused-exports`). Routes never import `zod` or define route-local boundary schemas; clients never write ad-hoc wire types or `z.input`/`z.output`. +- Every route handler runs inside `withRouteHandler`. Ordinary internal and v2 routes use the shared builders (`defineInternalJsonRoute`, `defineV2JsonRoute`, binary/stream variants), which already apply it — never double-wrap. Raw `withRouteHandler` is only for documented protocol or lifecycle exceptions. Never export a bare `async function GET/POST/...` (`check:source-conventions`). +- Same-origin JSON calls go through `requestJson(contract, ...)` from `@/lib/api/client/request`. A raw `fetch` is only for streaming, binary downloads, multipart uploads, signed URLs, OAuth redirects, or external origins, and carries `// boundary-raw-fetch: `. +- The other script-enforced exceptions are `// double-cast-allowed:`, `// boundary-raw-json:`, and `// untyped-response:`. Never add one to silence a fixable finding. +- `bun run check:api-validation:strict` must pass. Full contract rules, route pattern, annotation placement, the end-to-end order, and the schema review checklist: `.claude/rules/sim-api-contracts.md`. React Query hooks (key factories, named `staleTime` constants, `signal`, invalidation, server prefetch): `.claude/rules/sim-queries.md`. + +## Styling and EMCN + +- Tailwind only. Inline `style` only for a genuinely dynamic value or a CSS variable; type size is a named token, never `text-[Npx]` (`check:source-conventions` ratchets both). Never update global styles; keep styling local to the component. `cn()` from `@sim/emcn` for conditional classes. `size-*` for equal height and width (icons default `size-[14px]`), never `h-N w-N`. +- Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons from `@sim/emcn/icons`; CSS modules by file path. Never deep-import other component subpaths. +- The chip family is the canonical chrome: `ChipInput`, `ChipTextarea`, `ChipModal`/`ChipModalField`, `ChipSelect`/`ChipCombobox`/`ChipDropdown`, `ChipSwitch` (a segmented choice; a boolean toggle stays `Switch`), `ChipDatePicker`, `Chip`/`ChipLink`, `ChipTag`; `DropdownMenu` for context/action menus. Components own their chrome: consumers pass props (`error`, `icon`, `endAdornment`, `inputClassName`) and `className` carries only layout/sizing. Every labeled field inside a `ChipModalBody` is a `ChipModalField`. +- Consumer rules, tokens, text scale, and modal rhythm: `.claude/rules/sim-styling.md`. Authoring components in `packages/emcn`: `.claude/rules/emcn-components.md`. Product UI copy: `.claude/rules/sim-ui-copy.md`. Marketing copy and positioning: `.claude/rules/constitution.md`. + +## Testing + +Most unit tests in a codebase like this restate the code they test. They pass on the first run, break on every refactor, and catch nothing that type-check, `next build`, `bun run check:audits`, or a real end-to-end run would miss. Test for confidence, not coverage. + +- **Never write unit tests after you write code.** A test written to describe code that already exists restates the implementation and proves nothing. If the change needs proof, prove it end to end. +- **Highly prefer E2E tests.** Use them to verify complex features work, against the real boundary: real Postgres/Redis (`*.integration.ts`), the running app over real HTTP (`apps/sim/scripts/test-*-e2e.ts`), or the packaged desktop app (`apps/desktop/e2e`, Playwright). At the end of an E2E test, produce a verifiable and repeatable artifact — a JSON report of each check with status and duration, an HTTP status log, a trace, or a screenshot — written to a caller-supplied `_REPORT_PATH` and uploaded by CI on failure. `apps/sim/scripts/test-scim-e2e.ts` is the reference. +- **If you must test a system in isolation, first write down all the ways it could fail, then write the code.** Each failure mode (bad input, boundary, concurrency, partial failure, permission denial, resource cap) becomes one test that fails before the code exists. +- A regression test must fail on the pre-fix code. Revert each guard of the fix and watch its test go red before you trust it. +- Never write tests that restate declarations (block/tool/provider config, registries, constants, schemas accepting valid input), mock every collaborator and assert `toHaveBeenCalledWith` on the mocks, check rendered text or class names, or test mocks and factories themselves. +- Never hand-roll a mock or test helper that `apps/sim/vitest.setup.ts` or `@sim/testing` already provides; a module mocked in a third file gets one central mock. `bun run check:test-patterns` enforces this. + +Use the `test-audit` skill whenever you write, change, review, or sweep tests — it holds the authoring gate, the junk patterns, and the retention bar. Test layers, file naming, and Vitest mechanics (global mocks, `@sim/testing`, performance rules) are in `.claude/rules/sim-testing.md`. + +## Integrations + +Build order: **Tools** → **Block** → **Icon** → optional **Trigger**, starting from the service's API docs. Use the skills: `/add-integration` (end-to-end), `/add-tools`, `/add-block`, `/add-trigger`. Two rules the skills assume: + +- **Tool IDs are `snake_case`** (`service_action`), registered in `tools/registry.ts`; blocks register in `blocks/registry-maps.ts` (`BLOCK_REGISTRY` + `BLOCK_META_REGISTRY`, alphabetically). +- **Type coercions go in `tools.config.params`** (runs at execution, after variable resolution), never in `tools.config.tool` (runs at serialization, where `Number()` destroys dynamic `` references). + +Remaining block/tool/trigger rules: `.claude/rules/sim-integrations.md`. Canvas sentences: `apps/sim/blocks/AGENTS.md`. + +## Tables + +Table column types are registry entries in `apps/sim/lib/table/column-types/` — one file per type owning its label, icon, storage cast, coercion, validation, conversion compatibility, formatting, and editor. `Record` on `registry.ts` and `registry.server.ts` is a compile-time completeness gate: adding a type to the union errors until both entries exist. + +Never add a `case 'sometype':` outside `column-types/`, except the one documented import-path switch (`coerceValue` in `lib/table/import.ts`), which every new type extends — a missing arm fails silently (a wrong `jsonbCast` breaks every filter on the column). If a consumer needs per-type knowledge, add a registry field. Use `/add-column-type` for the full procedure. + +## How your work is checked + +Before declaring a change done, run the local gate from the repo root; CI runs these and fails on any of them: + +```bash +bun run lint # biome format + lint, autofixes (CI runs lint:check) +bun run type-check # every workspace +bun run check:audits # every check:* audit, generated-artifact checks included +bun run test # script tests, then every workspace's Vitest suite +git fetch origin staging # the block-registry check diffs against it +bun run apps/sim/scripts/check-block-registry.ts origin/staging +``` + +Author a `packages/db` schema change or migration with the `db-migrate` skill. CI also runs `bun run check:migrations` (it diffs against a base ref, `origin/staging` by default, so it is not in `check:audits`; run it when you touch `packages/db/migrations/**`), checks that `drizzle-kit generate` in `packages/db` produces no new migration, and runs a non-blocking `bun audit`. When an audit fails, its output and its script's header say what the rule protects; fix the code, never the check. Ratchet baselines (`scripts/*baseline.json`) only shrink: regenerate one with `--update` after removing violations; it refuses to admit new debt. The one exception is `check:tool-registry-boundary`, whose module-count baseline is re-recorded with `--update-baseline` when growth is deliberate (see its skill). + + + +# This is NOT the Turborepo you know + +Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`. + +Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access. + +This block is written and re-added by `turbo` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in `crates/turborepo-cli/src/cli/agent_guidance.rs`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set `"agentGuidance": false` in the root `turbo.json` or `turbo.jsonc` to opt out; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation. + diff --git a/apps/desktop/package.json b/apps/desktop/package.json index f52bcfe37de..4c10754afc6 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -42,7 +42,7 @@ "@xterm/headless": "6.0.0", "electron-updater": "6.8.9", "micromatch": "4.0.8", - "safe-regex2": "5.1.0", + "safe-regex2": "5.1.2", "pdf-lib": "1.17.1" }, "devDependencies": { @@ -51,19 +51,19 @@ "@sim/emcn": "workspace:*", "@sim/tsconfig": "workspace:*", "@types/micromatch": "4.0.10", - "@types/node": "24.2.1", + "@types/node": "24.19.2", "@types/react": "^19", "@types/react-dom": "^19", "electron": "43.5.0", "electron-builder": "26.15.3", - "esbuild": "0.28.1", + "esbuild": "0.28.2", "jsdom": "^26.0.0", - "postcss": "^8", + "postcss": "^8.5.30", "postcss-load-config": "6.0.1", "postgres": "^3.4.5", "react": "19.2.4", "react-dom": "19.2.4", "typescript": "^7.0.2", - "vitest": "^5.0.1" + "vitest": "^5.0.4" } } diff --git a/apps/docs/package.json b/apps/docs/package.json index 87d61d75d13..32dc1dee5fc 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -27,9 +27,9 @@ "@sim/logger": "workspace:*", "@sim/utils": "workspace:*", "@sim/workflow-renderer": "workspace:*", - "@xyflow/react": "12.11.3", + "@xyflow/react": "12.12.0", "class-variance-authority": "^0.7.1", - "drizzle-orm": "^0.45.2", + "drizzle-orm": "^0.45.4", "fumadocs-core": "16.8.5", "fumadocs-mdx": "14.3.2", "fumadocs-openapi": "10.8.1", @@ -39,21 +39,22 @@ "react": "19.2.4", "react-dom": "19.2.4", "remark-breaks": "^4.0.0", - "shiki": "4.3.1", + "remark-gfm": "4.0.1", + "shiki": "4.5.0", + "streamdown": "2.5.0", "framer-motion": "^12.5.0", "zod": "4.3.6" }, "devDependencies": { "@sim/tsconfig": "workspace:*", - "@tailwindcss/postcss": "^4.0.12", - "@types/node": "24.2.1", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.0.4", - "gray-matter": "^4.0.3", - "jsdom": "^26.0.0", - "postcss": "^8.5.3", - "tailwindcss": "^4.0.12", + "@tailwindcss/postcss": "^4.3.3", + "@types/node": "24.19.2", + "@types/react": "^19", + "@types/react-dom": "^19", + "gray-matter": "4.0.3", + "postcss": "^8.5.30", + "tailwindcss": "^4.3.3", "typescript": "^7.0.2", - "vitest": "^5.0.1" + "vitest": "^5.0.4" } } diff --git a/apps/realtime/package.json b/apps/realtime/package.json index daf582a42a7..4f3914782d2 100644 --- a/apps/realtime/package.json +++ b/apps/realtime/package.json @@ -32,21 +32,21 @@ "@sim/workflow-persistence": "workspace:*", "@sim/workflow-types": "workspace:*", "@socket.io/redis-adapter": "8.3.0", - "drizzle-orm": "^0.45.2", - "lib0": "0.2.117", + "drizzle-orm": "^0.45.4", + "lib0": "0.2.119", "postgres": "^3.4.5", "redis": "5.10.0", - "socket.io": "^4.8.1", + "socket.io": "^4.8.4", "y-protocols": "1.0.7", - "yjs": "13.6.31", + "yjs": "13.6.33", "zod": "4.3.6" }, "devDependencies": { "@sim/testing": "workspace:*", "@sim/tsconfig": "workspace:*", - "@types/node": "24.2.1", - "socket.io-client": "4.8.1", + "@types/node": "24.19.2", + "socket.io-client": "4.8.4", "typescript": "^7.0.2", - "vitest": "^5.0.1" + "vitest": "^5.0.4" } } diff --git a/apps/sim/app/(landing)/comparisons/[provider]/page.tsx b/apps/sim/app/(landing)/comparisons/[provider]/page.tsx index 61321bbc0ea..e84a9667843 100644 --- a/apps/sim/app/(landing)/comparisons/[provider]/page.tsx +++ b/apps/sim/app/(landing)/comparisons/[provider]/page.tsx @@ -9,6 +9,7 @@ import { LIBRARY_SECTION } from '@/lib/library/seo' import { COMPARISON_SECTIONS, getFactGroup } from '@/app/(landing)/comparisons/comparison-sections' import { BrandIconTile, SimIconTile } from '@/app/(landing)/comparisons/components/brand-icon-tile' import { ComparisonCards } from '@/app/(landing)/comparisons/components/comparison-cards' +import { ComparisonLinkRow } from '@/app/(landing)/comparisons/components/comparison-link-row' import { ComparisonTable } from '@/app/(landing)/comparisons/components/comparison-table' import { ProseText } from '@/app/(landing)/comparisons/components/prose-text' import { getLibraryPostsForCompetitor } from '@/app/(landing)/comparisons/library-links' @@ -91,6 +92,7 @@ export default async function ComparisonProviderPage({ const relatedPosts = await getLibraryPostsForCompetitor(competitor) const verdict = buildBottomLine(competitor) const CompetitorIcon = competitor.brand?.icon + const otherCompetitors = ALL_COMPETITORS.filter((other) => other.id !== competitor.id) const breadcrumbJsonLd = { '@context': 'https://schema.org', @@ -378,6 +380,22 @@ export default async function ComparisonProviderPage({ ) : null} +
+

+ More comparisons +

+
    + {otherCompetitors.map((other) => ( +
  • + +
  • + ))} +
+
+

= new Set(NON_LANDING_LIGHT_SEGMENTS) +const APP_SURFACE = { defaultTheme: 'system', storageKey: 'sim-theme' } as const + /** * The marketing surface: the root plus every `app/(landing)` segment. These * pages keep their own stored theme, written only by the landing footer's @@ -50,16 +52,8 @@ const LIGHT_MODE_SEGMENTS: ReadonlySet = new Set(NON_LANDING_LIGHT_SEGME * every signed-in user, which would render the marketing site in the OS theme * and overwrite any choice the footer toggle made. */ -const LANDING_SEGMENTS: ReadonlySet = new Set(LANDING_ROUTES) - -const APP_SURFACE = { defaultTheme: 'system', storageKey: 'sim-theme' } as const const LANDING_SURFACE = { defaultTheme: 'light', storageKey: 'sim-landing-theme' } as const -function isLandingPath(pathname: string): boolean { - const firstSegment = pathname.split('/')[1] - return firstSegment === '' || LANDING_SEGMENTS.has(firstSegment) -} - export function ThemeProvider({ children, ...props }: ThemeProviderProps) { const pathname = usePathname() diff --git a/apps/sim/app/api/mcp/oauth/start/route.ts b/apps/sim/app/api/mcp/oauth/start/route.ts index 7d84573a5f2..0bcbf06ef49 100644 --- a/apps/sim/app/api/mcp/oauth/start/route.ts +++ b/apps/sim/app/api/mcp/oauth/start/route.ts @@ -49,7 +49,7 @@ const DCR_UNSUPPORTED_MESSAGE = /** * The MCP SDK throws a plain `Error` (no typed class or code) when an auth server lacks a * `registration_endpoint`, so this string-match is the only available signal. Pinned to - * `@modelcontextprotocol/sdk` v1.31.0 `registerClient` (client/auth.js): "Incompatible auth + * `@modelcontextprotocol/sdk` v1.32.1 `registerClient` (client/auth.js): "Incompatible auth * server: does not support dynamic client registration". If a version bump rephrases this, the * 422 branch silently stops firing (users get a generic 500) — update the substring here. */ diff --git a/apps/sim/app/api/webhooks/agentmail/route.test.ts b/apps/sim/app/api/webhooks/agentmail/route.test.ts index 91368c9e1e7..e386790bd17 100644 --- a/apps/sim/app/api/webhooks/agentmail/route.test.ts +++ b/apps/sim/app/api/webhooks/agentmail/route.test.ts @@ -15,16 +15,8 @@ const { mockVerify, mockExecuteInboxTask } = vi.hoisted(() => ({ mockExecuteInboxTask: vi.fn(), })) -vi.mock('svix', () => ({ - Webhook: class { - private readonly secret: string - constructor(secret: string) { - this.secret = secret - } - verify(payload: string, headers: Record) { - return mockVerify(this.secret, payload, headers) - } - }, +vi.mock('@sim/security/standard-webhooks', () => ({ + verifyStandardWebhook: mockVerify, })) vi.mock('@/lib/core/admission/gate', () => admissionGateMock) @@ -92,9 +84,7 @@ describe('POST /api/webhooks/agentmail', () => { mockTryAdmit.mockReturnValue({ release: mockRelease }) mockExecuteInboxTask.mockResolvedValue(undefined) - mockVerify.mockImplementation(() => { - throw new Error('signature mismatch') - }) + mockVerify.mockReturnValue({ valid: false, reason: 'no_matching_signature' }) }) it('checks the signature against only the secret the payload routes to', async () => { @@ -106,12 +96,14 @@ describe('POST /api/webhooks/agentmail', () => { expect(mockEq).toHaveBeenCalledWith(schemaMock.workspace.inboxProviderId, TARGET_INBOX_ID) expect(dbChainMockFns.limit).toHaveBeenCalledWith(1) expect(mockVerify).toHaveBeenCalledTimes(1) - expect(mockVerify).toHaveBeenCalledWith('whsec_b', expect.any(String), expect.any(Object)) + expect(mockVerify).toHaveBeenCalledWith( + expect.objectContaining({ secret: 'whsec_b', headerPrefix: 'svix' }) + ) }) it('rejects a payload naming an inbox no workspace owns, without hashing it', async () => { queueTableRows(schemaMock.workspace, []) - mockVerify.mockReturnValue(undefined) + mockVerify.mockReturnValue({ valid: true }) const response = await POST( webhookRequest(envelope({ inbox_id: 'agent-unknown@agentmail.to' })) diff --git a/apps/sim/app/api/webhooks/agentmail/route.ts b/apps/sim/app/api/webhooks/agentmail/route.ts index fc5767d8bbb..8a3bb8dc96f 100644 --- a/apps/sim/app/api/webhooks/agentmail/route.ts +++ b/apps/sim/app/api/webhooks/agentmail/route.ts @@ -8,12 +8,12 @@ import { workspace, } from '@sim/db' import { createLogger } from '@sim/logger' +import { verifyStandardWebhook } from '@sim/security/standard-webhooks' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { tasks } from '@trigger.dev/sdk' import { and, eq, gt, ne, sql } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { Webhook } from 'svix' import { agentMailEnvelopeSchema, agentMailMessageSchema, @@ -41,22 +41,8 @@ const MAX_EMAILS_PER_HOUR = 20 const AGENTMAIL_BODY_LABEL = 'AgentMail webhook body' -/** Svix throws on a bad signature, stale timestamp, or malformed header. */ -function verifiesAgainst( - secret: string, - rawBody: string, - headers: Record -): boolean { - try { - new Webhook(secret).verify(rawBody, headers) - return true - } catch { - return false - } -} - /** - * Bound the unauthenticated AgentMail webhook body before buffering it for Svix + * Bound the unauthenticated AgentMail webhook body before buffering it for * signature verification, so an oversized payload cannot exhaust pod memory. */ async function readAgentMailBody(req: Request): Promise { @@ -106,7 +92,7 @@ export const POST = withRouteHandler(async (req: Request) => { } /** - * Route to one tenant before hashing: svix HMACs the whole body synchronously, + * Route to one tenant before hashing: verification HMACs the whole body synchronously, * so checking every workspace secret would let one unauthenticated request cost * a hash per tenant. `inbox_id` is attacker-controlled but only selects which * secret to check — verification still binds the payload's inbox to the workspace. @@ -129,7 +115,15 @@ export const POST = withRouteHandler(async (req: Request) => { .where(eq(workspace.inboxProviderId, inboxId)) .limit(1) - if (!result || !verifiesAgainst(result.webhookSecret, rawBody, headersResult.data)) { + if ( + !result || + !verifyStandardWebhook({ + secret: result.webhookSecret, + rawBody, + headers: req.headers, + headerPrefix: 'svix', + }).valid + ) { logger.warn('Webhook signature verification failed', { payloadInboxId: inboxId }) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } diff --git a/apps/sim/app/api/webhooks/agentmail/thread-scope.integration.ts b/apps/sim/app/api/webhooks/agentmail/thread-scope.integration.ts index 639eb7e93b0..7b642c7825a 100644 --- a/apps/sim/app/api/webhooks/agentmail/thread-scope.integration.ts +++ b/apps/sim/app/api/webhooks/agentmail/thread-scope.integration.ts @@ -4,7 +4,7 @@ import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/ * `In-Reply-To` header — a value every recipient of the agent's reply holds. Against * real PostgreSQL, a reply delivered to one workspace's inbox must never reach a chat, * task, or transcript owned by another workspace, while a reply inside the workspace - * still continues its thread. The webhook is signed with the real Svix scheme. + * still continues its thread. The webhook is signed per the Standard Webhooks spec. */ import { authBanMock } from '@sim/testing/mocks/auth-ban.mock' import { @@ -40,6 +40,7 @@ vi.mock('@/lib/mothership/inbox/executor', () => ({ executeInboxTask: mockExecuteInboxTask, })) +import { createHmac } from 'node:crypto' import { db } from '@sim/db' import { copilotChats, @@ -53,7 +54,6 @@ import { import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { NextRequest } from 'next/server' -import { Webhook } from 'svix' import { persistCopilotChatTurn } from '@/lib/mothership/chat/messages-store' import { buildPersistedUserMessage } from '@/lib/mothership/chat/persisted-message' import { POST } from '@/app/api/webhooks/agentmail/route' @@ -142,15 +142,19 @@ async function deliver( }, }) const svixId = `msg_${generateId()}` - const timestamp = new Date() + const timestamp = String(Math.floor(Date.now() / 1000)) + const key = Buffer.from(ws.secret.slice('whsec_'.length), 'base64') + const signature = createHmac('sha256', key) + .update(`${svixId}.${timestamp}.${body}`) + .digest('base64') return POST( new NextRequest('http://localhost:3000/api/webhooks/agentmail', { method: 'POST', headers: { 'content-type': 'application/json', 'svix-id': svixId, - 'svix-timestamp': String(Math.floor(timestamp.getTime() / 1000)), - 'svix-signature': new Webhook(ws.secret).sign(svixId, timestamp, body), + 'svix-timestamp': timestamp, + 'svix-signature': `v1,${signature}`, }, body, }), diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/extensions.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/extensions.ts index cbe6bd271c7..ec146a5f7a7 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/extensions.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/extensions.ts @@ -168,8 +168,8 @@ function guardParagraphLeading(text: string): string { * paragraph renders as just its inline children; this override wraps that with the leading guard. */ const BlockSafeParagraph = Paragraph.extend({ - parseMarkdown: (token, helpers) => { - const parsed = Paragraph.config.parseMarkdown?.(token, helpers) ?? [] + parseMarkdown(token, helpers) { + const parsed = this.parent?.(token, helpers) ?? [] return Array.isArray(parsed) ? parsed : splitBlockImageParagraph(parsed) }, renderMarkdown: (node: JSONContent, h, context) => { @@ -271,8 +271,8 @@ export function createMarkdownContentExtensions( nodeViews.mention ?? MarkdownMention, TaskList, TaskItem.extend({ - parseMarkdown: (token, helpers) => { - const parsed = TaskItem.config.parseMarkdown?.(token, helpers) ?? [] + parseMarkdown(token, helpers) { + const parsed = this.parent?.(token, helpers) ?? [] if (Array.isArray(parsed)) return parsed const content = (parsed.content ?? []).flatMap(splitBlockImageParagraph) if (content[0]?.type !== 'paragraph') content.unshift({ type: 'paragraph' }) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/field-upload.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/field-upload.test.tsx index f8119e61534..ea1256e9fc0 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/field-upload.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/field-upload.test.tsx @@ -57,6 +57,10 @@ async function render( ) await act(async () => vi.advanceTimersByTimeAsync(10)) } + +/** ProseMirror buffers inline atoms at a block start with an empty separator ``. */ +const RENDERED_IMAGE = 'img:not(.ProseMirror-separator)' + function editor() { return host.querySelector('.tiptap')!.editor } @@ -116,9 +120,9 @@ describe('field upload completion boundary', () => { expect(host.querySelector('strong')?.textContent).toBe('Tail') expect(host.querySelector('a')?.getAttribute('href')).toBe('/destination') expect( - Array.from(host.querySelectorAll('img')).map((image) => image.getAttribute('src')) + Array.from(host.querySelectorAll(RENDERED_IMAGE)).map((image) => image.getAttribute('src')) ).toEqual(['/api/files/view/uploaded', '/other.png']) - expect(host.querySelector('img')?.getAttribute('alt')).toBe('Original alt') + expect(host.querySelector(RENDERED_IMAGE)?.getAttribute('alt')).toBe('Original alt') expect(owner.getMarkdown()).toContain('width="123"') expect(owner.getMarkdown()).not.toContain('/inline?') expect(() => owner.state.doc.check()).not.toThrow() @@ -151,10 +155,11 @@ describe('field upload completion boundary', () => { expect(upload).not.toHaveBeenCalled() expect(host.querySelector('h2')?.textContent).toBe('Caption') expect(owner.state.doc.textContent).toContain('Tail') - expect(Array.from(host.querySelectorAll('img')).map((image) => image.alt)).toEqual([ - 'One', - 'Two', - ]) + expect( + Array.from(host.querySelectorAll(RENDERED_IMAGE)).map( + (image) => image.alt + ) + ).toEqual(['One', 'Two']) const images: Array<{ src: string; width: string; href: string | null }> = [] owner.state.doc.descendants((node) => { if (node.type.name === 'image' || node.type.name === 'inlineImage') { diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-input-rule.test.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-input-rule.test.ts index 0c458c877fd..cba31abbc26 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-input-rule.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-input-rule.test.ts @@ -217,7 +217,7 @@ describe.each([ expect(editor.getText()).toBe(`Prefix ${source}\n\nAfter`) }) - it('undoes a collaborative conversion without removing peer text', () => { + it('undoes a collaborative conversion to its source, then the input, without removing peer text', () => { const seed = markdownToYDoc('TARGET\n\nAfter') const a = createPeer(seed) const b = createPeer(seed) @@ -232,9 +232,14 @@ describe.each([ Y.applyUpdate(a.doc, Y.encodeStateAsUpdate(b.doc)) expect(a.editor.commands.undo()).toBe(true) Y.applyUpdate(b.doc, Y.encodeStateAsUpdate(a.doc)) + expect(a.editor.getText()).toBe(`Prefix ${source}\n\nPeer text`) + expect(b.editor.getJSON()).toEqual(a.editor.getJSON()) + expect(a.editor.commands.undo()).toBe(true) + Y.applyUpdate(b.doc, Y.encodeStateAsUpdate(a.doc)) expect(a.editor.getText()).toBe('TARGET\n\nPeer text') expect(b.editor.getJSON()).toEqual(a.editor.getJSON()) expect(a.editor.commands.redo()).toBe(true) + expect(a.editor.commands.redo()).toBe(true) Y.applyUpdate(b.doc, Y.encodeStateAsUpdate(a.doc)) assertConverted(a.editor) expect(a.editor.getText()).toContain('Peer text') diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-schema.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-schema.ts index 90ac478a52d..971a4d35c97 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-schema.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-schema.ts @@ -5,7 +5,6 @@ import { Plugin } from '@tiptap/pm/state' import { Lexer, Tokenizer } from 'marked' import { imageTypeAt } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-node' import { normalizeLinkHref } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-fidelity' -import { createTextInputRulePlugins } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/text-input-rule' /** * React-free schema half of the image node. Lives apart from {@link ./image} (its React resize node @@ -187,10 +186,8 @@ const ImageSchema = Image.extend({ }) export const MarkdownImage = ImageSchema.extend({ - addProseMirrorPlugins() { - return createTextInputRulePlugins( - this.editor, - this.name, + addInputRules() { + return [ new InputRule({ find: new RegExp(`${inputRegex.source}(?![\\s\\S])`), handler: ({ state, range, match }) => { @@ -201,8 +198,8 @@ export const MarkdownImage = ImageSchema.extend({ .replaceRangeWith(from, range.to, type.create({ alt, src, title: title ?? null })) .scrollIntoView() }, - }) - ) + }), + ] }, addCommands() { return { diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/link-input-rule.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/link-input-rule.ts index 9b6fdd2bde4..0af971c3c64 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/link-input-rule.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/link-input-rule.ts @@ -1,6 +1,5 @@ import { Extension, InputRule } from '@tiptap/core' import { normalizeLinkHref } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-fidelity' -import { createTextInputRulePlugins } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/text-input-rule' /** * Typed markdown link: `[text](url)` or `[text](url "title")`, completed by the closing `)`. The URL @@ -20,10 +19,8 @@ const LINK_INPUT_RULE = /(? { @@ -41,7 +38,7 @@ export const MarkdownLinkInputRule = Extension.create({ tr.addMark(range.from, markEnd, linkType.create({ href, title: title || null })) tr.removeStoredMark(linkType) }, - }) - ) + }), + ] }, }) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts index d7ba61acb45..763d71426cd 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/list-item.ts @@ -1,20 +1,33 @@ -import { ListItem } from '@tiptap/extension-list' +import { type RenderContext, renderNestedMarkdownContent } from '@tiptap/core' +import { getListMarker, ListItem } from '@tiptap/extension-list' import { splitBlockImageParagraph } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/block-image-paragraph' +function listItemMarker(context: RenderContext): string { + if (context.parentType !== 'orderedList') return '- ' + const { start, type } = context.meta?.parentAttrs ?? {} + const first = (typeof start === 'number' && start) || 1 + return getListMarker(typeof type === 'string' ? type : null, first - 1 + context.index, '. ') +} + /** * Markdown items may start with a nested block, but the list-item schema requires a paragraph * first. Supply that empty paragraph before Yjs hydration can discard the invalid subtree. + * + * Nested blocks keep the two-space indent under every marker. Upstream aligns ordered-item + * children to the marker width, but its ordered-list tokenizer strips one column less, so a + * fenced code block inside a numbered item would gain a leading space on every save. Drop + * this override once the upstream ordered-list tokenizer strips the full marker width. */ export const MarkdownListItem = ListItem.extend({ - parseMarkdown: (token, helpers) => { - const parsed = ListItem.config.parseMarkdown?.(token, helpers) ?? [] + parseMarkdown(token, helpers) { + const parsed = this.parent?.(token, helpers) ?? [] if (Array.isArray(parsed)) return parsed const content = (parsed.content ?? []).flatMap(splitBlockImageParagraph) if (content[0]?.type !== 'paragraph') content.unshift({ type: 'paragraph' }) return { ...parsed, content } }, renderMarkdown: (node, helpers, context) => { - const rendered = ListItem.config.renderMarkdown?.(node, helpers, context) ?? '' + const rendered = renderNestedMarkdownContent(node, helpers, listItemMarker(context)) /** Marked misreads an opening empty `- ` line as prose when it has a trailing space. */ return rendered.replace(/^-[ \t]+(?=\n|$)/, '-') }, diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/ordered-list.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/ordered-list.ts index ee630359935..1cf1ac644ea 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/ordered-list.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/ordered-list.ts @@ -14,7 +14,6 @@ import { excludeTableBlockInputRules } from '@/app/workspace/[workspaceId]/files */ export function createJoiningOrderedList() { const delimiters = new WeakMap() - const parse = OrderedList.config.parseMarkdown return OrderedList.extend({ addInputRules() { @@ -22,8 +21,8 @@ export function createJoiningOrderedList() { joinListInputRules(this.parent?.() ?? [], this.type, { compatible: orderedListContinues }) ) }, - parseMarkdown: (token, helpers) => { - if (!token.ordered || !token.items?.length) return parse?.(token, helpers) ?? [] + parseMarkdown(token, helpers) { + if (!token.ordered || !token.items?.length) return this.parent?.(token, helpers) ?? [] /** * The upstream tokenizer retains each item's raw marker but ignores delimiter changes. @@ -61,7 +60,7 @@ export function createJoiningOrderedList() { ), })), } - const parsedList = parse?.(resolved, helpers) ?? [] + const parsedList = this.parent?.(resolved, helpers) ?? [] if (Array.isArray(parsedList)) return parsedList /** Use the registered item parser so empty items retain their schema-required paragraph. */ const parsed = { ...parsedList, content: helpers.parseChildren(resolved.items) } diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/round-trip-safety.test.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/round-trip-safety.test.ts index a5d251eadbf..b0e3e378e45 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/round-trip-safety.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/round-trip-safety.test.ts @@ -167,7 +167,13 @@ describe('isRoundTripSafe', () => { }) it('rejects non-idempotent churn', () => { - expect(isRoundTripSafe('render `` a`b `` inline')).toBe(false) + expect(isRoundTripSafe('10. ten\n ```\n code\n ```')).toBe(false) + }) + + it('keeps fenced code inside a numbered item editable and byte-stable', () => { + const source = '1. step\n ```\n code\n ```\n2. next\n' + expect(serializeMarkdownDocument(source)).toBe(source) + expect(isRoundTripSafe(source)).toBe(true) }) it('keeps HTML images with unsupported attributes in source mode', () => { diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/text-input-rule.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/text-input-rule.ts deleted file mode 100644 index 70d0578e1cd..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/text-input-rule.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { type Editor, InputRule, inputRulesPlugin, isExtensionRulesEnabled } from '@tiptap/core' - -/** - * Materializes the actual text-input event before converting its syntax. Native rule ranges may - * extend past the current document for multi-character input; the native plugin still owns undo - * and composition, whose text is already in the document. - */ -export function createTextInputRulePlugins(editor: Editor, name: string, rule: InputRule) { - const extension = editor.extensionManager.extensions.find((item) => item.name === name) - if (!extension || !isExtensionRulesEnabled(extension, editor.options.enableInputRules)) return [] - - let pendingInput: { from: number; to: number; text: string } | null = null - const plugin = inputRulesPlugin({ - editor, - rules: [ - new InputRule({ - find: rule.find, - undoable: rule.undoable, - handler: (props) => { - if (!pendingInput) return rule.handler(props) - const { from, to, text } = pendingInput - props.state.tr.insertText(text, from, to) - return rule.handler({ - ...props, - range: { from: props.range.from, to: from + text.length }, - }) - }, - }), - ], - }) - const handleTextInput = plugin.props.handleTextInput - plugin.props.handleTextInput = (view, from, to, text, defaultTr) => { - pendingInput = { from, to, text } - try { - return handleTextInput?.call(plugin, view, from, to, text, defaultTr) - } finally { - pendingInput = null - } - } - return [plugin] -} diff --git a/apps/sim/content/library/best-dust-alternatives-2026/index.mdx b/apps/sim/content/library/best-dust-alternatives-2026/index.mdx new file mode 100644 index 00000000000..72bd3519994 --- /dev/null +++ b/apps/sim/content/library/best-dust-alternatives-2026/index.mdx @@ -0,0 +1,122 @@ +--- +slug: best-dust-alternatives-2026 +title: 'Best Dust Alternatives in 2026 for AI Agent Workflows' +seoTitle: 'Best Dust Alternatives for AI Agent Workflows' +description: 'Compare Dust alternatives for AI agent workflows: Sim, n8n, Gumloop, Relevance AI, Zapier Agents, and Glean on hosting and workflow depth.' +date: 2026-10-19 +updated: 2026-10-19 +authors: + - andrew +readingTime: 7 +tags: [AI Agents, Workflow Automation, AI Tools, Sim] +ogImage: /library/best-dust-alternatives-2026/cover.jpg +draft: false +faq: + - q: "Can Dust run multi-step automations?" + a: "Yes, within its agent model. Dust agents use tools that can take actions in third-party platforms, can call other agents, and can run on schedules or webhook triggers. The agent decides which tools to use for each request, so Dust is less suited to automations that must follow the same explicit sequence of steps every run." + - q: "Can Sim handle the knowledge Q&A we use Dust for?" + a: "Yes. Sim knowledge bases let agents retrieve relevant document passages, and you can use them inside workflows. If you need knowledge bases that sync automatically, KB Live Sync is included on Max and Enterprise." + - q: "Which tools can we self-host?" + a: "Anyone can self-host Sim's Apache 2.0 core for free with Docker Compose or Kubernetes. Sim's pricing table lists supported self-hosting as an Enterprise feature alongside SSO and SOC 2, and enterprise features such as SSO and audit logs require an Enterprise subscription for production use. n8n supports self-hosting under its source-available Sustainable Use License. Glean offers a Customer Hosted deployment in your own GCP or AWS account that Glean still operates. Dust's pricing page lists hosted plans with single-tenant deployment on Enterprise and no self-hosted plan. Gumloop's pricing page lists no self-hosted plan, with an optional VPC (a private network in your own cloud account) on Enterprise. Relevance AI's docs describe a multi-tenant hosted service with a US, EU/UK, or AU data region chosen at signup." + - q: "What does \"workflow depth\" mean here?" + a: "Workflow depth describes how much control you have over the steps an agent takes after receiving a request. For example, Sim lets you branch on conditions, repeat steps, run steps in parallel, and pause for human input." +--- + +## TL;DR + +- **Sim** fits teams using Dust that need [multi-step agent workflows](https://docs.sim.ai/workflows) with explicit branching, human review, and knowledge retrieval in one tool. +- **n8n** fits technical teams that want [self-hosted automation](https://docs.n8n.io/deploy/host-n8n) with over 500 integrations and custom code, under a source-available license. +- **Gumloop** fits teams that want agents that use connected tools and [run from schedules, app events, or webhooks](https://docs.gumloop.com/core-concepts/agent_triggers). +- **Relevance AI** fits enterprise teams that want an [AI workforce with agents and tools](https://relevanceai.com/pricing) rather than building each workflow from scratch. +- **Zapier Agents** fits teams that already rely on Zapier and want agents that [act across 9,000+ apps](https://zapier.com/agents). +- **Glean** fits teams comparing enterprise search and knowledge assistants with Dust, rather than prioritizing workflow automation. + +## Why teams outgrow Dust for workflow automation + +Dust works well when you need agents that answer questions across company material. Its [Business plan](https://dust.tt/home/pricing) connects Slack, Notion, GitHub, Drive, and more than 20 other sources, and agents combine instructions, skills, knowledge, and tools. Dust also runs agents on [schedules and webhook triggers](https://docs.dust.tt/docs/user-documentation/agents/triggers), so its agents can do more than answer questions. Business plan seats are Free with 500 lifetime credits, Pro at €24 per seat per month billed yearly (€30 monthly), and Max at €120 per seat per month billed yearly (€150 monthly), excluding VAT. + +The difference is how the steps are defined. In Dust, [the agent evaluates each request and picks a combination of tools](https://docs.dust.tt/docs/user-documentation/agents/tools) to use. Dust's tools documentation does not describe explicit branches, loops, or a step-by-step visual builder. That model suits open-ended requests, but recurring tasks that must follow the same steps every time, with controlled tool access at each step, are easier to build and audit in a workflow builder. Our guide to [AI agent workflow builders](https://www.sim.ai/library/ai-agent-workflow-builders-multi-step-tasks) explains why this distinction matters for multi-step tasks. + +The comparison below weighs pricing, hosting options, workflow depth, knowledge features, and integrations. Prices and product details are current as of October 2026. If Dust already handles your knowledge questions, focus on how each alternative defines workflow steps and controls access to external tools. + +## Comparison at a glance + +Sim is listed first for custom agent workflows, while Dust provides the knowledge-assistant baseline. Listed prices are starting points, not comparable usage allowances. + +| Tool | Starting price | Hosting and self-hosting | Workflow depth | Knowledge features | Integrations | +|---|---|---|---|---|---| +| Sim | [Free (1,000 one-time credits); Pro $25/user/month](https://www.sim.ai/pricing) | [Apache 2.0 core](https://github.com/simstudioai/sim) anyone can self-host; supported self-hosting listed as an Enterprise feature | Visual blocks for branching, loops, parallel runs, human review, and code | [Knowledge bases](https://docs.sim.ai/knowledgebase); KB Live Sync on Max | [273](https://www.sim.ai/integrations) | +| n8n | [€20/month billed annually (2,500 executions)](https://n8n.io/pricing/) | [Yes](https://docs.n8n.io/deploy/host-n8n), under a [source-available license](https://docs.n8n.io/n8n-community-license) | Visual workflows plus a [JavaScript and Python Code node](https://docs.n8n.io/build/code-in-n8n/using-the-code-node) | [Vector store nodes for RAG](https://docs.n8n.io/build/integrate-ai/understand-ai-components/retrieve-relevant-context) | [Over 500](https://n8n.io/) | +| Gumloop | [Pro from $37/month after a 14-day free trial](https://www.gumloop.com/pricing) | No self-hosted plan listed; VPC optional on Enterprise | Agents run from schedules, app events, and webhooks | Company Brain | Connectors plus hosted MCP servers | +| Relevance AI | [Enterprise custom](https://relevanceai.com/pricing); docs list [Pro from $19/month](https://relevanceai.com/docs/get-started/pricing) | [Hosted, multi-tenant; US, EU/UK, or AU region](https://relevanceai.com/docs/admin/security) | Agents, tools, and Workforces | [Knowledge bases that retrieve relevant information](https://relevanceai.com/docs/get-started/core-concepts/knowledge) | [2,000+](https://relevanceai.com/pricing) | +| Zapier Agents | [Free (400 activities/month); Agents Pro $50/month](https://zapier.com/pricing) | Managed cloud | Runs capped at 10 activities (Free) or 40 (paid) | [Company knowledge](https://zapier.com/agents) | [9,000+ apps](https://zapier.com/agents) | +| Glean | Contact sales | [Glean Hosted or Customer Hosted](https://docs.glean.com/get-started/prepare/about-deployment) | [Agent orchestration](https://www.glean.com/ai-agents/agent-orchestration) | Enterprise search | [275+ connectors](https://www.glean.com/platform/connectors) | +| Dust | [Free (500 lifetime credits); Pro €24/seat/month billed yearly](https://dust.tt/home/pricing) | Hosted; single-tenant deployment on Enterprise | Agent tools, multi-agent workflows, and triggers | Connectors to 20+ data sources | Slack, Notion, GitHub, Drive, 20+ more, and any tool via MCP | + +### Sim + +**Best for** + +Sim fits you if Dust handles your company knowledge questions but you need to build multi-step agent workflows with explicit branching. The [Sim and Dust comparison](https://www.sim.ai/comparisons/dust) covers the two approaches side by side. + +**What it is** + +Sim is an open-source AI workspace for teams. Its Workflows module is a visual builder, and it sits alongside Chat, Tables, Files, and [Knowledge Base](https://docs.sim.ai/knowledgebase), whose knowledge bases agents can search for relevant document passages. You connect blocks, configure their inputs, and review each step of a run in the [run logs](https://docs.sim.ai/logs-debugging/logging), where a trace shows every block with its timing, status, inputs, and outputs. + +**Pros** + +Sim gives you direct control over how an agent proceeds. [Agent](https://docs.sim.ai/workflows/blocks/agent), [Condition](https://docs.sim.ai/workflows/blocks/condition), and [Router](https://docs.sim.ai/workflows/blocks/router) blocks let you define different paths. [Loop](https://docs.sim.ai/workflows/blocks/loop) and [Parallel](https://docs.sim.ai/workflows/blocks/parallel) blocks handle repeated or concurrent work. A [Human in the Loop](https://docs.sim.ai/workflows/blocks/human-in-the-loop) block pauses a run until a person submits input, such as an approval, and a downstream Condition can route on that answer. A [Function](https://docs.sim.ai/workflows/blocks/function) block runs custom code. That structure makes a multi-step workflow easier to inspect than a single agent carrying a long set of instructions. For more design context, see [AI agent orchestration frameworks explained](https://www.sim.ai/library/ai-agent-orchestration-frameworks-explained). + +[Sim's Apache 2.0 core](https://github.com/simstudioai/sim) can be [self-hosted](https://docs.sim.ai/platform/self-hosting) with Docker Compose or Kubernetes, with [enterprise-licensed features excluded](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). Its knowledge bases cover the retrieval use case you may already rely on Dust for, and Sim connects to [273 integrations](https://www.sim.ai/integrations). Sim can also [deploy workflows as an MCP server](https://docs.sim.ai/workflows/deployment/mcp) (MCP, the Model Context Protocol, lets AI apps call tools), so compatible AI clients can call them as tools. + +**Cons** + +You have to design the workflow paths yourself, which takes more setup than a straightforward knowledge assistant. Enterprise features such as SSO (single sign-on), SCIM user provisioning, audit logs, and access control use the [separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which requires an active Sim Enterprise subscription for production use. + +**Pricing** + +Sim's [cloud plans](https://www.sim.ai/pricing) are Free with 1,000 one-time credits (worth $5) and 30 days of log retention; Pro at $25 per user per month with 6,000 credits plus a 2,000-credit weekly refresh; Max at $100 per user per month with 25,000 credits plus a 4,000-credit weekly refresh; and Enterprise at custom pricing. Paid plans keep logs without a time limit. KB Live Sync starts on Max, while SSO requires Enterprise. Anyone can self-host [Sim's Apache 2.0 core](https://github.com/simstudioai/sim) for free. The pricing table lists Self Hosting, meaning supported self-hosting, as an Enterprise feature alongside SSO and SOC 2, and enterprise features require an [Enterprise subscription for production use](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). Any self-hosted Sim deployment can run [local models](https://docs.sim.ai/platform/self-hosting) through Ollama, vLLM, LM Studio, or LiteLLM. + +### n8n + +n8n fits technical teams that want to self-host automations and connect a wide range of apps, even if building and maintaining workflows takes more work. [n8n says it offers over 500 integrations](https://n8n.io/), and you can [host n8n on your own infrastructure](https://docs.n8n.io/deploy/host-n8n) rather than use its cloud service. For logic that the visual nodes do not cover, the [Code node](https://docs.n8n.io/build/code-in-n8n/using-the-code-node) runs JavaScript or Python. + +n8n's source code is public, but its [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) limits permitted use and distribution. n8n [describes its licenses as fair-code and source-available](https://docs.n8n.io/n8n-community-license) and does not call itself open source. By comparison, [Sim's core uses Apache 2.0](https://github.com/simstudioai/sim), with a [separate license for enterprise code](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). Our [Apache 2.0 versus fair-code guide](https://www.sim.ai/library/apache-2-0-vs-fair-code) explores the practical difference. + +For cloud use, n8n charges [by monthly workflow executions, regardless of complexity](https://n8n.io/pricing/). Its pricing page lists Starter at €20 per month for 2,500 executions and Pro at €50 per month for 10,000, both billed annually. Business costs €667 per month billed annually for 40,000 executions and is currently self-hosted only, and Enterprise pricing is custom. If you self-host, you also run the infrastructure. + +### Gumloop + +Gumloop fits teams that want a hosted platform for AI agents that work across their tools. Its [pricing page](https://www.gumloop.com/pricing) lists the older workflow builder as "Workflows (Legacy)", so new work centers on agents. [Agent triggers](https://docs.gumloop.com/core-concepts/agent_triggers) can run on a schedule, from an app event, or through a webhook, while its [API](https://docs.gumloop.com/api-reference/getting-started) can trigger automations programmatically. + +Pro starts at $37 per month after a 14-day free trial and includes 20,000 credits per month, unlimited agents, BYOK (bring your own model API keys), and one hosted MCP server. Enterprise pricing is custom. The [pricing page](https://www.gumloop.com/pricing) lists no self-hosted plan, though Enterprise offers an optional VPC, a private network in your own cloud account. + +### Relevance AI + +Relevance AI fits enterprise teams that want specialist agents instead of building each workflow from scratch. Its [Enterprise plan](https://relevanceai.com/pricing) centers on agents, tools, Workforces, integrations, and governance features. + +Relevance AI's two official pricing pages disagree. Its [pricing page](https://relevanceai.com/pricing) lists only an Enterprise plan at custom pricing, with 2,000+ integrations, unlimited agents and tools, SSO, RBAC (role-based access control), and audit logs. Its [pricing documentation](https://relevanceai.com/docs/get-started/pricing) lists Pro from $19 per month billed annually ($29 monthly) and Team from $234 per month billed annually ($349 monthly), and says the Free plan is retired. Agents can use [knowledge bases that retrieve relevant information](https://relevanceai.com/docs/get-started/core-concepts/knowledge), a RAG (retrieval-augmented generation) system, to ground their responses. Relevance AI runs on a [multi-tenant architecture](https://relevanceai.com/docs/admin/security) with a US, EU/UK, or AU data region chosen at signup, and its docs do not describe a self-hosted option. Confirm the current plan with Relevance AI and verify the specific apps your agents need. + +### Zapier Agents + +Zapier Agents fits you if you already use Zapier and want AI agents to act through your existing app connections. Its main draw is access to [more than 9,000 apps](https://zapier.com/agents), and you can equip agents with company knowledge. If you need long, custom agent workflows, check the run limits before choosing it. + +Agents use a separate billing unit called activities, which [does not affect your Zapier task usage](https://zapier.com/pricing). The Free plan includes 400 activities per month, and Agents Pro includes 1,500 for $50 per month or $400 per year. A single agent run is capped at 10 activities on the Free plan or 40 on a paid plan, and at the cap the agent asks before continuing. Those caps can constrain a long multi-step run even when you have activities left for the month. + +### Glean + +Glean fits teams whose primary need remains enterprise search and answers drawn from company knowledge, rather than custom multi-step automation. That makes it a closer alternative to Dust's knowledge-assistant use case than to a workflow builder. + +Glean lists [275+ connectors](https://www.glean.com/platform/connectors), enterprise search, and [agent orchestration](https://www.glean.com/ai-agents/agent-orchestration). Contact Glean for pricing and plan on a demo using your own knowledge sources. For data control, Glean offers a [Customer Hosted deployment](https://docs.glean.com/get-started/prepare/about-deployment) that runs in your own GCP or AWS environment, with Glean responsible for infrastructure and application management. + +## How to choose + +If you mainly need answers from company documents, Dust may still fit. Consider Glean if you want another knowledge-focused option, but expect to contact sales for pricing. + +For lightweight automation, choose Zapier Agents if you already use Zapier's app connections, or Gumloop if you want a hosted agent platform with triggers and schedules. If you need custom workflows that branch, loop, and involve multiple agents, Sim lets you combine those steps with [knowledge retrieval](https://docs.sim.ai/knowledgebase). That makes it a closer fit when Dust answers your questions but cannot easily carry out the work that follows. You can also compare broader licensing and deployment choices in our guide to [open-source AI agent platforms](https://www.sim.ai/library/open-source-ai-agent-platforms). + +Workflow depth and self-hosting may settle the choice. Compare Sim with n8n if you want to run workflows on your own infrastructure. Anyone can self-host [Sim's Apache 2.0 core](https://github.com/simstudioai/sim), while its enterprise features need an [Enterprise subscription for production use](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE); n8n uses a source-available license. + +## Conclusion + +If Dust handles your company knowledge questions well, workflow depth should decide whether you add another tool or switch. Sim lets you build agents that branch, loop, and take actions across connected apps while still retrieving information from documents. If you need those workflows, use the [Sim vs Dust comparison](https://www.sim.ai/comparisons/dust) to assess which setup fits. diff --git a/apps/sim/content/library/best-flowise-alternatives-2026/index.mdx b/apps/sim/content/library/best-flowise-alternatives-2026/index.mdx new file mode 100644 index 00000000000..256c02aa302 --- /dev/null +++ b/apps/sim/content/library/best-flowise-alternatives-2026/index.mdx @@ -0,0 +1,131 @@ +--- +slug: best-flowise-alternatives-2026 +title: 'Best Flowise Alternatives in 2026 for Production AI Agents' +seoTitle: 'Best Flowise Alternatives for Production AI Agents' +description: 'Flowise reached end of life on August 31, 2026. Compare Sim, Langflow, Dify, n8n, and LangGraph by license, hosting, deployment, and pricing.' +date: 2026-10-19 +updated: 2026-10-19 +authors: + - andrew +readingTime: 9 +tags: [AI Agents, Open Source, Agent Platforms, Sim] +ogImage: /library/best-flowise-alternatives-2026/cover.jpg +draft: false +faq: + - q: "What does Flowise's sunset mean for existing deployments?" + a: "Your existing deployment can keep running, and the code remains available under Apache 2.0 for you to fork. Flowise reached end of life on August 31, 2026, when official core-team presence on GitHub and Discord ended, so you must maintain a fork or move to another platform if you need future updates." + - q: "Is Flowise safe to keep running after end of life?" + a: "The core team has stopped development and no longer reviews pull requests, so no fixes will come from it. If you keep Flowise in production, you take responsibility for reviewing vulnerabilities, applying fixes, and controlling access to the deployment." + - q: "Should I self-host or use a managed option?" + a: "Self-hosting gives you control over the infrastructure but makes you responsible for operations and updates. A managed option removes that hosting work. Sim offers both: anyone can self-host its Apache 2.0 core for free with Docker Compose or Kubernetes, and Sim Cloud is the managed option. Enterprise features such as SSO (single sign-on), access control, and audit logs are covered by the separate Sim Enterprise License and require an Enterprise subscription for production use." + - q: "Which alternative is easiest for migrating existing flows?" + a: "Langflow offers a visual-builder approach, but a similar visual builder does not mean Flowise flows will import directly. Compare your live flows' components and integrations against the replacement before estimating migration work." + - q: "What do the license differences mean for commercial use?" + a: "Sim's Apache 2.0 core and Langflow's MIT license permit broad commercial use. Sim's enterprise features are covered by the separate Sim Enterprise License, which requires an Enterprise subscription for production use. Dify's license requires a commercial license to operate a multi-tenant service, and n8n's Sustainable Use License limits use to your own internal business purposes or non-commercial or personal use. LangGraph's library is MIT licensed, while running its standalone Agent Server requires a LangSmith license key, so check the terms for the part you plan to deploy." +--- + +## TL;DR + +- [Flowise reached end of life on August 31, 2026](https://flowiseai.com/sunset). Its team froze the code on July 29 and stopped reviewing or accepting pull requests, so production users must maintain a fork or migrate. +- **Sim**, an open-source AI workspace for teams, fits teams that want to build agents visually, deploy them several ways, and self-host an Apache 2.0 core. Its enterprise features use the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). +- **Langflow** fits teams that want a visual builder under an MIT license. +- **Dify** fits teams building knowledge-base-backed AI apps that they want to deploy as a web app, an API, or an MCP server. MCP (Model Context Protocol) is an open protocol that lets AI apps call tools, so other AI apps can call a Dify app as a tool. +- **n8n** fits teams adding AI agents to existing business automations. +- **LangGraph** fits engineers who prefer code-based control over agent behavior and persistent state. + +## Why Flowise teams are migrating now + +Flowise's [July 29, 2026 sunset announcement](https://flowiseai.com/sunset) stopped active feature development and ended review of new pull requests. If your production deployment needs a fix, you can no longer submit a change and expect the maintainers to merge it. + +In August 2026, Flowise [archived its GitHub repository](https://github.com/FlowiseAI/Flowise/discussions/6727). The code stays visible, but the repository is read-only, issues and pull requests are locked, and npm packages and Docker images are marked deprecated. On August 31, official core-team presence on GitHub and Discord ended. + +Your existing deployment can continue to run, and the Apache 2.0 code remains available to fork. With development stopped, any patch you need, including a security fix, has to come from your own fork or a community fork. You now need to maintain that fork, including its dependencies, or migrate to a platform with active development. + +## How we evaluated these alternatives + +We evaluated these alternatives for production use, not for the number of features in their visual builders. With [Flowise's core-team presence ended](https://flowiseai.com/sunset), you need to know who maintains the software and what you must operate yourself. As of October 2026, our criteria are license clarity, self-hosting path, deployment surfaces, observability and governance depth, integration breadth, and pricing transparency. + +Those criteria map to the work you take on when you replace a production deployment: keeping state durable across restarts, diagnosing failed runs, controlling who can reach and change agents, and adding capacity. The comparison looks at what each alternative documents for each of those jobs. For a broader view, compare the options in our guides to [open-source AI agent platforms](https://www.sim.ai/library/open-source-ai-agent-platforms), [AI agent platforms for 2026](https://www.sim.ai/library/best-ai-agent-platforms-2026), and [Apache 2.0 versus fair-code licenses](https://www.sim.ai/library/apache-2-0-vs-fair-code). + +## Comparison table + +| Rank and tool | License | Self-hosting | Deployment surfaces | Observability | Integrations | Pricing | +|---|---|---|---|---|---|---| +| Sunset reference: [Flowise](https://flowiseai.com/sunset) | [Apache 2.0 existing code](https://github.com/FlowiseAI/Flowise/blob/main/LICENSE.md) | [Fork and maintain](https://flowiseai.com/sunset) | Existing deployments | [No further core-team development](https://flowiseai.com/sunset) | Existing nodes | [Code free to fork](https://flowiseai.com/sunset) | +| 1. [Sim](https://www.sim.ai) | [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE); [Enterprise features separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | [Docker Compose or Kubernetes with Helm](https://docs.sim.ai/platform/self-hosting) | [API, chat interface, MCP server, webhooks, and schedules](https://docs.sim.ai/workflows/deployment) | [Block-level trace logs](https://docs.sim.ai/logs-debugging), [run-failure alerts](https://docs.sim.ai/workflows/triggers/sim), and [Enterprise audit logs](https://docs.sim.ai/platform/enterprise/audit-logs) | [270+](https://www.sim.ai/pricing) | [Free $0, Pro $25/user/month, Max $100/user/month, Enterprise custom](https://www.sim.ai/pricing) | +| 2. [Langflow](https://docs.langflow.org/) | [MIT](https://github.com/langflow-ai/langflow/blob/main/LICENSE) | [Desktop app, Python package, or Docker](https://docs.langflow.org/get-started-installation) | API and [MCP server](https://docs.langflow.org/mcp-server) | [Per-component traces](https://docs.langflow.org/traces) | [Components usable as agent tools](https://docs.langflow.org/) | [Free to self-host under MIT](https://github.com/langflow-ai/langflow/blob/main/LICENSE) | +| 3. [Dify](https://dify.ai) | [Modified Apache 2.0](https://github.com/langgenius/dify/blob/main/LICENSE) | [Docker Compose](https://docs.dify.ai/en/self-host/deploy/quick-start/docker-compose) | [Web app, API, and MCP server](https://docs.dify.ai/en/learn/key-concepts) | [Production run logs](https://docs.dify.ai/en/self-host/use-dify/monitor/logs) | [Agent tools](https://docs.dify.ai/en/cloud/use-dify/nodes/agent) | [Sandbox free; Professional $59/month and Team $159/month per workspace, or $590 and $1,590 per year; Enterprise custom](https://dify.ai/pricing) | +| 4. [n8n](https://n8n.io) | [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) | [Self-hosted](https://docs.n8n.io/deploy/host-n8n/configure-n8n/scaling/enable-queue-mode) or [n8n Cloud](https://n8n.io/pricing/) | Workflows with triggers and webhooks | Execution history | [500+ integrations](https://n8n.io/) | [Cloud Starter €20/month and Pro €50/month; self-hosted Business €667/month, billed annually](https://n8n.io/pricing/) | +| 5. [LangGraph](https://github.com/langchain-ai/langgraph) | [MIT library](https://github.com/langchain-ai/langgraph) | Library, or [Agent Server](https://docs.langchain.com/langsmith/deploy-standalone-server) | Your own API, or Agent Server | [Checkpointed graph state](https://docs.langchain.com/oss/python/langgraph/persistence) | Code libraries | Library free; [LangSmith Developer $0 for one seat and Plus $39/seat/month](https://www.langchain.com/pricing) | + +### Sim + +**Best for:** Teams that want to build and deploy agents in one workspace while keeping run logs, access controls, and a self-hosting option. + +**What it is:** Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. It combines Chat, a visual workflow builder, Tables, Files, and a Knowledge Base. The workflow builder includes Agent, Condition, Router, and Human blocks; the Human block pauses a run until a person responds. Anyone can [self-host the Apache 2.0 core for free with Docker Compose or Kubernetes](https://docs.sim.ai/platform/self-hosting), Sim Cloud is the managed option, and enterprise modules are covered by the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). A deployed workflow is available through an API, a hosted chat interface, or an MCP server, and workflows can also start from webhooks and schedules, as described in the [deployment documentation](https://docs.sim.ai/workflows/deployment). Sim can call tools from external MCP servers and [deploy workflows as tools on an MCP server](https://docs.sim.ai/workflows/deployment/mcp). + +**Pros:** [Run logs](https://docs.sim.ai/logs-debugging) record each run's status, duration, and cost, and the trace shows timed spans for blocks, model requests, and tool calls with each step's resolved inputs, outputs, and errors, so you can inspect where a run failed. A [Sim Workspace Events trigger](https://docs.sim.ai/workflows/triggers/sim) can start an alert workflow when a run fails. Enterprise adds [audit logs](https://docs.sim.ai/platform/enterprise/audit-logs) that record who changed what, [data drains](https://docs.sim.ai/platform/enterprise/data-drains) that export logs to destinations such as Amazon S3 and Datadog, [permission groups](https://docs.sim.ai/platform/enterprise/access-control) that restrict models, blocks, and tools, and [configurable data-retention policies](https://docs.sim.ai/platform/enterprise/data-retention). Sim's pricing page documents more than 270 integrations. Its [Flowise comparison](https://www.sim.ai/comparisons/flowise) details the production features side by side. For deeper evaluation criteria, see our guide to [AI observability tools for production agents](https://www.sim.ai/library/6-best-ai-observability-tools-for-production-agents-in-2026). + +**Cons:** Self-hosting Sim's Apache 2.0 core does not include the modules under the Sim Enterprise License, such as SSO (single sign-on), access control, and audit logs. Production use of those enterprise capabilities requires an active Sim Enterprise subscription, and Sim's pricing table lists supported Self Hosting as an Enterprise feature alongside SSO. Teams should compare those boundaries with their governance and support requirements. + +**Pricing:** As of October 2026, [Sim Cloud has four tiers](https://www.sim.ai/pricing). Free costs $0 and includes 1,000 one-time credits (worth $5), with no weekly refresh. Pro costs $25 per user per month and includes 6,000 credits plus 2,000 refreshed weekly. Max costs $100 per user per month and includes 25,000 credits plus 4,000 refreshed weekly. Enterprise has custom pricing and limits. Logs are retained for 30 days on Free, while Pro and Max have no default age expiry. + +### Langflow + +**Best for:** You want a visual builder and an MIT license that gives you broad freedom to modify and self-host the software. + +**What it is:** [Langflow](https://docs.langflow.org/) is an open-source, Python-based framework for building AI applications, with a visual flow editor. You can trigger flows through its API or [expose a project's flows as MCP tools](https://docs.langflow.org/mcp-server). [Install options](https://docs.langflow.org/get-started-installation) include Langflow Desktop, the Python package, and a Docker image. + +**Pros:** The [Human Input component](https://docs.langflow.org/human-in-the-loop) pauses a flow at a checkpoint, then resumes along the branch for the chosen action without re-running completed steps. [Traces](https://docs.langflow.org/traces) record a span for each component, including inputs, outputs, latency, and errors. The project's [MIT license](https://github.com/langflow-ai/langflow/blob/main/LICENSE) places no commercial-use restrictions on the code. + +**Cons:** You remain responsible for securing and maintaining a self-hosted builder. Pin the version you deploy, plan updates, and keep the builder off the public internet. + +**Pricing:** The MIT-licensed software has no license fee for self-hosting. + +### Dify + +**Best for:** Choose Dify when you need to build and deploy an AI app that draws on your own documents through a knowledge base. + +**What it is:** [Dify centers on Workflow and Chatflow apps](https://docs.dify.ai/en/learn/key-concepts) built in a drag-and-drop studio. Its [Agent node](https://docs.dify.ai/en/cloud/use-dify/nodes/agent) supports Function Calling and ReAct strategies, and you can [deploy a finished app as a web app, API, or MCP server](https://docs.dify.ai/en/cloud/use-dify/publish/README). + +**Pros:** Dify gives you several ways to ship an LLM app without building its web interface or API yourself. A [Human Input node](https://docs.dify.ai/en/api-reference/guides/human-input-flow) pauses a workflow for a person's decision, and [run logs](https://docs.dify.ai/en/self-host/use-dify/monitor/logs) record production conversations and workflow runs. + +**Cons:** Dify's [license](https://github.com/langgenius/dify/blob/main/LICENSE) is a modified Apache 2.0. Operating a multi-tenant service requires a commercial license, and you may not remove or modify the logo or copyright information in Dify's frontend. Its [Docker Compose installation](https://docs.dify.ai/en/self-host/deploy/quick-start/docker-compose) runs multiple application and data services, so self-hosting requires more operational work than a single container. + +**Pricing:** As of October 2026, [Dify's hosted plans](https://dify.ai/pricing) start with a free Sandbox plan. Professional costs $59 per workspace per month or $590 per year, and Team costs $159 per workspace per month or $1,590 per year. Enterprise pricing is custom. Confirm licensing terms before offering a multi-tenant service. + +### n8n + +**Best for:** You already use n8n for business automation and want to add AI agents to those workflows. + +**What it is:** [n8n connects applications through visual workflows](https://n8n.io/). Its AI Agent node lets an agent call tools within a broader automation, rather than making agent development the platform's sole focus. + +**Pros:** [n8n says it offers over 500 integrations](https://n8n.io/), so you can connect agents to tools your workflows already use. For higher execution volumes, [queue mode](https://docs.n8n.io/deploy/host-n8n/configure-n8n/scaling/enable-queue-mode) uses Redis as a message broker to distribute runs across worker instances. You can also [require a person to approve specific AI Agent tool calls](https://docs.n8n.io/build/integrate-ai/ai-examples/human-in-the-loop-for-tools) before they run. + +**Cons:** n8n's [license](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) is the Sustainable Use License, with `.ee` files covered by a separate n8n Enterprise License. The Sustainable Use License limits use and modification to your own internal business purposes or non-commercial or personal use. Its general automation focus may be a poor fit if you want a dedicated agent-building product. + +**Pricing:** As of October 2026, [n8n Cloud](https://n8n.io/pricing/) Starter costs €20 per month and Pro costs €50 per month, both billed annually. Self-hosted Business costs €667 per month billed annually, and the self-hosted Community edition is free. Enterprise pricing is custom. + +### LangGraph + +**Best for:** Engineering teams that want to define durable agent behavior in code rather than in a visual builder. + +**What it is:** LangGraph lets you build agents as graphs of steps and decisions. Its [MIT-licensed library](https://github.com/langchain-ai/langgraph) includes [checkpointers](https://docs.langchain.com/oss/python/langgraph/persistence) that persist a thread's graph state, so a paused thread can resume. + +**Pros:** You can [call `interrupt()` inside a graph node](https://docs.langchain.com/oss/python/langgraph/interrupts) to pause execution for human input. Re-invoking the graph with a `Command` resumes the thread from its saved state. + +**Cons:** The library gives you the graph runtime, not a managed deployment service, so serving it means building your own API or running Agent Server. A [standalone Agent Server](https://docs.langchain.com/langsmith/deploy-standalone-server) requires a LangSmith license key, and self-hosted LangSmith is an Enterprise option. + +**Pricing:** The MIT library has no license fee. As of October 2026, [LangSmith](https://www.langchain.com/pricing) Developer costs $0 for one seat, and Plus costs $39 per seat per month. Enterprise pricing is custom. + +## Migrating off Flowise without losing production state + +Audit production use before moving any flows. Check request logs, webhook calls, and scheduled runs to identify which workflows receive traffic. Record their owners, external connections, and expected outputs so you can test the workflows that matter before cutover. + +Back up Flowise's database and any stored chat or execution history you need to retain. Inventory credentials separately, then confirm you can re-enter or transfer each secret securely. In the replacement deployment, verify that workflows and state survive a restart, that the database points to durable storage, and that authentication blocks unauthorized access. Rotate any credentials that may have been exposed. + +Run representative production requests against the replacement before switching traffic, including requests that call external tools. Compare outputs and check that logs capture failures. Keep the Flowise backup and a rollback plan until those checks pass. [Flowise's core-team presence ended on August 31, 2026](https://flowiseai.com/sunset), so you should not depend on a core-team fix if the old deployment fails during migration. + +## The production-ready path forward + +Flowise's end of life means you need a plan to maintain or replace production flows. A familiar visual builder may make migration easier, but your choice should also account for who can change an agent, how you investigate failed runs, and where you can host the software. Sim ranks first in this comparison because it combines a visual builder, multiple deployment surfaces, detailed traces, an Apache 2.0 core, and separately licensed governance options in one workspace. Compare those production requirements in the [Sim and Flowise head-to-head](https://www.sim.ai/comparisons/flowise) before choosing a replacement. diff --git a/apps/sim/content/library/best-langflow-alternatives-2026/index.mdx b/apps/sim/content/library/best-langflow-alternatives-2026/index.mdx new file mode 100644 index 00000000000..231ba8fa235 --- /dev/null +++ b/apps/sim/content/library/best-langflow-alternatives-2026/index.mdx @@ -0,0 +1,114 @@ +--- +slug: best-langflow-alternatives-2026 +title: 'Best Langflow Alternatives in 2026 for Production AI Workflows' +description: 'Compare Langflow alternatives for production AI workflows: Sim, Dify, n8n, and LangGraph on license, self-hosting, run inspection, and pricing.' +seoTitle: 'Best Langflow Alternatives for Production in 2026' +date: 2026-10-19 +updated: 2026-10-19 +authors: + - andrew +readingTime: 8 +tags: [AI Agents, Workflow Automation, Open Source, Comparisons, Sim] +ogImage: /library/best-langflow-alternatives-2026/cover.jpg +draft: false +faq: + - q: "Is Langflow open source, and can you use it in production?" + a: "Yes, Langflow is MIT-licensed, and Langflow OSS gives you full source access and a self-managed deployment option. For SSO, role-based access, and audit trails, IBM directs Langflow-built agents to watsonx Orchestrate." + - q: "What is the easiest self-hosted Langflow alternative?" + a: "Sim offers an interactive sim-setup wizard that provisions the database, generates secrets, and starts Sim with Docker Compose, making it a practical starting point if you want a visual builder you can run yourself. Its self-hosting docs suggest 2 CPU cores, 12 GiB of RAM, and 20 GiB of SSD storage for evaluation. Production use of enterprise features requires an Enterprise subscription." + - q: "Which alternative has the best built-in observability and versioning?" + a: "Sim, Langflow, and Dify all include built-in, step-by-step run traces. Sim's Logs record each block's inputs, outputs, errors, and timing, and Sim keeps a history of each workflow's deployed versions with rollback. LangGraph relies on LangSmith, a separate product, for tracing." + - q: "Can you migrate Langflow flows to another tool?" + a: "This comparison does not cover a direct import path from Langflow into any of these alternatives. Plan to recreate the flow in your chosen tool, then test its behavior and deployment before replacing the Langflow version." +--- + +## TL;DR + +- **Sim**, the open-source AI workspace where teams build, deploy, and manage AI agents, fits teams that want a visual builder they can run on their own infrastructure, with per-block run traces. Anyone can self-host [Sim's Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE) for free; [enterprise features use a separate license](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). SSO (single sign-on with your company's identity provider), audit logs, and supported self-hosting are part of the Enterprise plan. +- **Langflow** fits teams building visual prototypes, especially if they already use IBM watsonx Orchestrate, which [IBM positions](https://www.ibm.com/new/announcements/prototype-to-production-langflow-and-ibm-watsonx-orchestrate-si) as the enterprise runtime for Langflow-built agents. +- **Dify** fits teams willing to operate a larger self-hosted stack under a modified Apache 2.0 license. +- **n8n** fits teams whose AI agents need to work inside broader automation workflows. +- **LangGraph** fits developers who prefer code-level control over visual building. +- **Flowise** is no longer an option for new projects: it [reached end of life on August 31, 2026](https://flowiseai.com/sunset). + +## Why teams look past Langflow for production + +Langflow makes it easy to build and test an agent visually, but IBM describes a harder step after the prototype. In its [announcement about moving Langflow agents into production](https://www.ibm.com/new/announcements/prototype-to-production-langflow-and-ibm-watsonx-orchestrate-si), IBM says teams "build impressive demos, only to hit a wall when scaling securely, integrating workflows, and meeting enterprise requirements." IBM presents watsonx Orchestrate as its answer: an enterprise-grade runtime for Langflow-built agents, "complete with SSO, RBAC, audit trails and orchestration." RBAC is role-based access control, which limits what each person can see and change. + +You can deploy Langflow without Orchestrate. Langflow is MIT-licensed and its [GitHub repository](https://github.com/langflow-ai/langflow) is actively maintained. [IBM lists](https://www.ibm.com/products/langflow) options to deploy a flow as a REST service using Docker or deploy it to Langflow Cloud, and Langflow's docs cover a [production Kubernetes Helm chart](https://docs.langflow.org/deployment-kubernetes-prod). The governance features IBM highlights for enterprise use, including SSO, role-based access, and audit trails, are the ones it ties to the Orchestrate integration. + +For each alternative, the comparison examines license terms, the self-hosting path, and deployment options. It also covers built-in run inspection and published pricing. Pricing and product availability were checked as of October 2026. For a wider market view, see the comparisons of [AI workflow builders](https://www.sim.ai/library/best-ai-workflow-builders) and [open-source AI agent platforms](https://www.sim.ai/library/open-source-ai-agent-platforms). + +## Comparison table + +| Tool | License | Self-Hosting | Deployment Options | Built-in Run Inspection | Published Pricing | +|---|---|---|---|---|---| +| **1. Sim** | [Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE), with [enterprise features under the separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | Yes, via [Docker Compose or Kubernetes (Helm)](https://docs.sim.ai/platform/self-hosting) | Sim Cloud or self-hosted. Deploy workflows as an API, a chat interface, or an MCP tool | [Per-block trace](https://docs.sim.ai/logs-debugging/logging) of inputs, outputs, errors, and timing | [Free $0, Pro $25/user/month, Max $100/user/month, Enterprise custom](https://www.sim.ai/pricing) | +| **2. Dify** | [Modified Apache 2.0](https://github.com/langgenius/dify/blob/main/LICENSE) with added conditions | Yes, [Docker Compose](https://docs.dify.ai/en/self-host/deploy/quick-start/docker-compose) | [Web app, API, website embed, or MCP server](https://docs.dify.ai/en/self-host/use-dify/publish/README) | [Logs with a node-by-node trace](https://docs.dify.ai/en/self-host/use-dify/monitor/logs) | [Cloud: Sandbox free, Professional $590/workspace/year, Team $1,590/workspace/year](https://dify.ai/pricing) | +| **3. n8n** | [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md), which n8n describes as fair-code, not open source | Yes: Community, Business, and Enterprise editions | n8n Cloud or self-hosted | [Execution history](https://docs.n8n.io/build/understand-workflows/understand-executions) per workflow | [Cloud Starter €20/month, Pro €50/month, self-hosted Business €667/month (billed annually), Enterprise custom](https://n8n.io/pricing/) | +| **4. LangGraph** | [MIT](https://github.com/langchain-ai/langgraph) | Yes for the library. A standalone Agent Server needs a [LangSmith license key](https://docs.langchain.com/langsmith/deploy-standalone-server) | Self-managed code or LangSmith Deployment | Through LangSmith, a separate product | [LangSmith Developer $0 (1 seat), Plus $39/seat/month, Enterprise custom](https://www.langchain.com/pricing) | +| **5. Langflow** | [MIT](https://github.com/langflow-ai/langflow) | Yes, Docker or a Kubernetes Helm chart | [Docker REST service, Langflow Cloud, or watsonx Orchestrate](https://www.ibm.com/products/langflow). Flows can also be [served as MCP tools](https://docs.langflow.org/mcp-server) | [Native per-component traces](https://docs.langflow.org/traces) | No license fee for Langflow OSS. IBM's Langflow page does not list Elite Support or Orchestrate prices | + +## Sim + +**Best for** Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. It suits you if you want a visual builder that can move into production with self-hosting, several ways to deploy a workflow, and controls for enterprise access. Alongside the visual builder (Workflows), the workspace includes Chat, where you talk to Sim to build and manage agents in natural language, plus Tables, Files, and a Knowledge Base your agents can draw on. + +**Pros** Sim's core uses the [Apache 2.0 license](https://github.com/simstudioai/sim/blob/main/LICENSE), with [enterprise features covered by the separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). You can [deploy a workflow](https://docs.sim.ai/workflows/deployment) as an API, a chat interface, or an MCP tool. MCP (Model Context Protocol) is a protocol that lets AI apps call tools, so other AI applications can call your workflow. Those options let you use the same workflow in an application, a user-facing conversation, or another agent without rebuilding its logic. Sim connects to [270+ integrations](https://www.sim.ai/integrations) and [28 model providers](https://www.sim.ai/models). + +Sim's [Logs](https://docs.sim.ai/logs-debugging/logging) trace each run block by block, with each block's inputs, outputs, errors, and timing, plus a usage breakdown of the run's cost. When an agent fails midway, you can inspect the block that failed rather than infer the cause from its final response. Each workflow also keeps [deployed versions that can be compared or rolled back](https://docs.sim.ai/workflows/deployment). For more detail on evaluating traces, read the guide to [AI agent observability](https://www.sim.ai/library/ai-agent-observability). If Langflow's deployment and governance options leave you assembling additional infrastructure, [Sim's Langflow comparison](https://www.sim.ai/comparisons/langflow) details its deployment options and Enterprise controls. + +**Cons** The Apache 2.0 core does not make every feature free. Anyone can self-host [Sim's core](https://github.com/simstudioai/sim/blob/main/LICENSE) at no cost with Docker Compose or Kubernetes, while [enterprise features such as SSO, SCIM user provisioning, audit logs, and access control have separate terms](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) and require an Enterprise subscription for production use. [Sim's pricing table](https://www.sim.ai/pricing) also lists supported self-hosting as an Enterprise feature alongside SSO and SOC 2 compliance. Check which features you need before treating the core license as your complete plan. The distinction is also covered in this guide to [Apache 2.0 and fair-code licenses](https://www.sim.ai/library/apache-2-0-vs-fair-code). + +**Pricing** [Sim's Free plan](https://www.sim.ai/pricing) costs $0 and includes 1,000 one-time credits and 30 days of log retention. Pro costs $25 per user per month, with 6,000 credits per month, a weekly refresh of 2,000 credits, and unlimited log retention. Max costs $100 per user per month, with 25,000 credits per month and a weekly refresh of 4,000 credits. Enterprise uses custom pricing and adds access control, SSO, SOC 2 compliance, supported self-hosting, and dedicated support. Enterprise organizations can also set log retention from 1 day to 5 years or keep logs forever. + +## Langflow + +**Best for** Langflow suits you if you want to build and test AI agents visually before choosing how to deploy them. [IBM lists more than 100 integrations](https://www.ibm.com/products/langflow) for the open-source edition. + +**Pros** IBM offers [four Langflow options](https://www.ibm.com/products/langflow). Langflow OSS provides the visual builder, full source access, and community support. Langflow Desktop supports local development and exports flows to a cloud-ready format. IBM Elite Support for Langflow adds professional support for OSS users and targets enterprises that need SLAs, and the watsonx Orchestrate integration provides an enterprise-grade runtime. Langflow also records [native traces](https://docs.langflow.org/traces) with each component's inputs, outputs, timing, errors, and token usage, and can act as an [MCP server](https://docs.langflow.org/mcp-server) that exposes your flows as tools. + +**Cons** For [enterprise deployment](https://www.ibm.com/new/announcements/prototype-to-production-langflow-and-ibm-watsonx-orchestrate-si), IBM points to Orchestrate for SSO, role-based access, and audit trails. If you need those controls and do not already use Orchestrate, adopting Langflow for production can mean adopting a second IBM product. + +**Pricing** Langflow OSS is [MIT-licensed](https://github.com/langflow-ai/langflow), so there is no license fee to self-manage it. [IBM's Langflow product page](https://www.ibm.com/products/langflow) does not list prices for Elite Support or the Orchestrate integration. + +## Dify + +**Best for** Dify suits you if you want to build and deploy AI applications and can operate a larger self-hosted installation. + +**Pros** Dify supports workflows, a [Human Input node](https://docs.dify.ai/en/self-host/use-dify/nodes/human-input) that pauses a run for a person's review, and [knowledge bases](https://docs.dify.ai/en/self-host/use-dify/knowledge/readme) built from imported data. You can [deploy applications](https://docs.dify.ai/en/self-host/use-dify/publish/README) as web apps, APIs, website embeds, or MCP servers. Its [logs](https://docs.dify.ai/en/self-host/use-dify/monitor/logs) let you inspect conversations and workflow runs, including a node-by-node trace that shows where a run failed or slowed down. + +**Cons** Dify uses a [modified Apache 2.0 license](https://github.com/langgenius/dify/blob/main/LICENSE). You need a commercial license to run a multi-tenant service from its source code, and you may not remove or modify the logo or copyright information in its frontend. Its [Docker Compose installation](https://docs.dify.ai/en/self-host/deploy/quick-start/docker-compose) starts seven core services and eight dependent components. The stated minimum is two CPU cores and 4 GiB of RAM, before accounting for your workload. + +**Pricing** [Dify Cloud](https://dify.ai/pricing) offers a free Sandbox plan, Professional at $590 per workspace per year, and Team at $1,590 per workspace per year. The self-hosted Community edition is free, and the self-hosted Enterprise edition, which includes a commercial license, uses custom pricing. + +## n8n + +**Best for** You need a workflow-automation tool that connects services and can add an AI agent to an existing process. n8n places its [AI Agent node inside regular workflows](https://docs.n8n.io/build/integrate-ai/ai-examples/human-in-the-loop-for-tools), so it fits integration-heavy work better than a project centered on agent design. + +**Pros** n8n offers self-hosted editions and [queue mode](https://docs.n8n.io/deploy/host-n8n/configure-n8n/scaling/enable-queue-mode), which uses worker instances to distribute executions. You can also [require human approval](https://docs.n8n.io/build/integrate-ai/ai-examples/human-in-the-loop-for-tools) before an AI Agent runs a selected tool, giving you an approval point for sensitive actions. + +**Cons** n8n's [Sustainable Use License](https://github.com/n8n-io/n8n/blob/master/LICENSE.md) limits use to your own internal business purposes or non-commercial use, and n8n [describes the license as fair-code](https://docs.n8n.io/n8n-community-license) rather than open source. Its [license guidance](https://docs.n8n.io/n8n-community-license/community-license/license-faq) says internal use is fine as long as you are not making n8n available to your customers for them to connect their accounts and build workflows. If you are building that kind of multi-tenant product, check the licensing terms before adopting it. + +**Pricing** [n8n's pricing page](https://n8n.io/pricing/) lists Cloud Starter at €20 per month and Pro at €50 per month, both billed annually. The self-hosted Business plan is €667 per month billed annually, Enterprise is custom, and the self-hosted Community Edition is available from its repository. + +## LangGraph + +**Best for** LangGraph suits you if your engineers want to define agent orchestration in code rather than assemble it in a visual builder. The [Python](https://github.com/langchain-ai/langgraph) and [TypeScript](https://github.com/langchain-ai/langgraphjs) libraries can run on infrastructure you manage. + +**Pros** [Checkpointers](https://docs.langchain.com/oss/python/langgraph/persistence) save agent state so a run can resume after an interruption. For an approval gate, [`interrupt()`](https://docs.langchain.com/oss/python/langgraph/interrupts) pauses execution, and `Command(resume=...)` resumes it with a person's decision. + +**Cons** The core library uses the [MIT license](https://github.com/langchain-ai/langgraph), but running the packaged [standalone Agent Server](https://docs.langchain.com/langsmith/deploy-standalone-server) requires a LangSmith license key, and [self-hosted LangSmith](https://docs.langchain.com/langsmith/deploy-to-self-hosted-overview) requires an Enterprise plan. You also need to build and maintain more of the deployment yourself than you would with a visual platform. + +**Pricing** The MIT-licensed library has no license fee. [LangSmith](https://www.langchain.com/pricing), which provides tracing and managed deployment, offers a Developer plan at $0 for one seat, Plus at $39 per seat per month, and custom Enterprise pricing. + +## A note on Flowise + +Flowise was a common Langflow alternative, but it is no longer viable for new projects. The team [announced the wind-down](https://flowiseai.com/sunset) on July 29, 2026 with an immediate code freeze, archived the repository in August 2026, and reached end of life on August 31, 2026, when official core team presence on Discord and GitHub ended. The [Apache 2.0 code remains available to fork](https://flowiseai.com/sunset), but existing deployments should plan a migration. + +## Choosing the right Langflow alternative + +If you want the open-source AI workspace for teams, with a visual builder you can self-host under an OSI-approved core license, choose Sim. [Sim's Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE) runs on your infrastructure for free, while [production use of enterprise features such as SSO and audit logs requires an Enterprise subscription](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). Check which features you need before deciding which plan fits. + +If your main need is connecting business apps, choose n8n for workflow automation. Review its license before using it to offer workflows to customers. Choose LangGraph if you want to write and operate orchestration code yourself, while accounting for the [LangSmith license key](https://docs.langchain.com/langsmith/deploy-standalone-server) its standalone Agent Server requires. + +If you already use IBM watsonx Orchestrate, keeping Langflow may make sense. [IBM supplies enterprise governance through Orchestrate](https://www.ibm.com/new/announcements/prototype-to-production-langflow-and-ibm-watsonx-orchestrate-si). For a closer look at that trade-off, compare [Sim and Langflow](https://www.sim.ai/comparisons/langflow). diff --git a/apps/sim/content/library/best-pipedream-alternatives-2026/index.mdx b/apps/sim/content/library/best-pipedream-alternatives-2026/index.mdx new file mode 100644 index 00000000000..2d4d0d9e240 --- /dev/null +++ b/apps/sim/content/library/best-pipedream-alternatives-2026/index.mdx @@ -0,0 +1,123 @@ +--- +slug: best-pipedream-alternatives-2026 +title: 'Best Pipedream Alternatives in 2026 with Visual Builders' +description: 'Pipedream Workflows shuts down March 31, 2027. Compare visual alternatives with AI agents: Sim, Zapier, Make, n8n, and Workato on pricing and hosting.' +seoTitle: 'Pipedream Alternatives: 5 Visual Builders' +date: 2026-10-19 +updated: 2026-10-19 +authors: + - andrew +readingTime: 9 +tags: [AI Agents, Automation, Visual Builders, Pipedream, Sim] +ogImage: /library/best-pipedream-alternatives-2026/cover.jpg +draft: false +faq: + - q: "What happens to my existing Pipedream workflows?" + a: "Pipedream says existing workflows keep running normally through March 31, 2027, and all Workflows data is deleted by April 30, 2027. Its export tool downloads the structure of your workflows as JSON, one project at a time. Rebuild and test their triggers, code steps, and connected accounts in your new tool before switching off the originals." + - q: "Can a visual builder still run custom code?" + a: "Yes. Sim's Function block runs JavaScript, Python, or Shell code as one step of a workflow, and its API block sends an HTTP request and passes the response to later steps. n8n's Code node supports JavaScript and Python." + - q: "Does self-hosting keep AI agent data private?" + a: "Self-hosting gives you control over where the workflow runs, but a hosted AI model still receives the data you send it. A self-hosted Sim deployment supports local models through Ollama and vLLM. Check the model connection, logs, and storage before treating any deployment as private." + - q: "How should I compare pricing across tools?" + a: "Estimate a typical month of runs rather than comparing entry prices. Zapier counts successful tasks, Make consumes credits per module action, and n8n counts workflow executions. Sim charges per user on paid plans and includes a monthly credit allowance. A workflow with many steps can therefore produce different bills across tools. Include seats, expected usage, overages, and any hosting costs in your estimate." +--- + +## TL;DR + +- **Sim** is an open-source AI workspace for teams to build, deploy, and manage AI agents. Choose it if you want a [workflow builder with AI agent blocks](https://docs.sim.ai/introduction) and the option to build in Chat or with code. +- **Zapier** suits you if you want straightforward trigger-and-action automations. +- **Make** suits you if you want visual branching with routers and filters in an interface that also hosts AI agents. +- **n8n** suits you if you can manage a more technical builder and want a [free self-hosted Community Edition](https://docs.n8n.io/deploy/host-n8n/community-edition-features) with a dedicated [AI Agent node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/). +- **Workato** suits you if enterprise integration and governance matter more than self-serve simplicity. + +Pipedream's [Workflows product shuts down on March 31, 2027](https://pipedream.com/docs/workflows), so teams using it need an alternative before then. + +## Why teams are leaving Pipedream for a visual builder + +Pipedream's Workflows product is [in maintenance mode, no longer accepts signups or subscriptions, and shuts down on March 31, 2027](https://pipedream.com/docs/workflows). Existing workflows keep running until then, and all Workflows data, including the credentials and OAuth tokens for connected accounts, will be permanently deleted by April 30, 2027. Workday [announced a deal to acquire Pipedream](https://newsroom.workday.com/2025-11-19-Workday-Signs-Definitive-Agreement-to-Acquire-Pipedream) in November 2025 and [has since closed it](https://newsroom.workday.com/2026-02-24-Workday-Announces-Fiscal-2026-Fourth-Quarter-and-Full-Year-Financial-Results). Pipedream now focuses on [Connect](https://pipedream.com/docs/connect), a developer toolkit that adds integrations to your own app or AI agent through SDKs, APIs, and an MCP server. MCP (Model Context Protocol) is a standard that lets AI apps call outside tools. Connect does not replace the visual builder for people who need to create automations without writing code. + +Workflows was built around code. Any step could run [Node.js, Python, Go, or Bash](https://pipedream.com/docs/workflows/building-workflows/code), and Pipedream offers [Data Stores as key-value storage](https://pipedream.com/docs/workflows/building-workflows/code/python/using-data-stores) rather than spreadsheet-like tables. + +For a replacement, assess how far you can build visually before you need code and what its AI agents can do. Then check whether you can run it on your own infrastructure, whether it connects to your apps, and whether its pricing lets you estimate costs for your expected usage. Our [AI workflow automation buyer's checklist](https://www.sim.ai/library/ai-workflow-automation-platform-buyers-checklist) provides a broader set of evaluation questions. + +## Comparison table + +As of October 2026, these are the main differences among the five alternatives: + +| Rank | Tool | Starting price | Visual vs code | Runs on your infrastructure | Integrations | AI agent support | +|---|---|---|---|---|---|---| +| 1 | Sim | [Free](https://www.sim.ai/pricing), 1,000 one-time credits | Visual builder, Chat, and code | Yes, with the Apache 2.0 core; [enterprise features are separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | [270+](https://www.sim.ai/pricing) | Agent blocks and an MCP server | +| 2 | Zapier | [Free](https://zapier.com/pricing), 100 tasks/month | Visual, with optional JavaScript or Python code steps | No self-hosted edition listed | [9,000+ apps](https://zapier.com/pricing) | [Zapier Agents, Chatbots, and MCP](https://zapier.com/pricing) | +| 3 | Make | [Free](https://www.make.com/en/pricing), 1,000 credits/month | Visual, with an HTTP app for any API | Cloud scenarios; an Enterprise [on-prem agent](https://help.make.com/on-premise-agent?v=2) reaches local systems | [3,500+ apps](https://www.make.com/en/pricing) | [Make AI Agents](https://www.make.com/en/ai-agents) in the same visual platform | +| 4 | n8n | [Free self-hosted Community Edition](https://docs.n8n.io/deploy/host-n8n/community-edition-features) | Visual, with JavaScript and Python code steps | Yes | [Over 500](https://n8n.io/) | [Dedicated AI Agent node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/) | +| 5 | Workato | [Free](https://www.workato.com/free), 50K one-time credits | [Workflow orchestration](https://www.workato.com/free) | Cloud platform; an [on-prem agent](https://docs.workato.com/on-prem) connects private systems to Workato cloud | [Thousands of SaaS apps, databases, and ERPs](https://www.workato.com/integrations) | [Agent Studio and Genies](https://www.workato.com/free) | + +### Sim + +**Best for** Teams that want a [Pipedream alternative](https://www.sim.ai/comparisons/pipedream) where non-developers can build AI agents without making code steps the default. + +**What it is** Sim is an open-source AI workspace for teams, where you build, deploy, and manage AI agents. Chat, the workflow builder, Tables, Files, and a Knowledge Base for your agents' documents sit in one workspace. You can build an agent by describing it in Chat, connecting blocks in the workflow builder, or using the API and SDKs. In the workflow builder, you connect blocks into agent logic, configure their inputs, and inspect execution step by step. The [three build methods](https://docs.sim.ai/introduction) let a non-technical colleague start in Chat or the workflow builder while a developer adds programmatic control when needed. For more context on this category, see our comparison of the [best AI workflow builders](https://www.sim.ai/library/best-ai-workflow-builders). + +**Pros** Sim's [MCP server](https://docs.sim.ai/mcp/overview) lets an external AI app, such as Claude, manage workspaces, run and deploy workflows, and work with tables, files, knowledge bases, and logs. A colleague can use an agent to operate workflows without editing the workflow builder or writing a Pipedream-style code step. + +Anyone can self-host Sim's Apache 2.0 core for free with Docker Compose or Kubernetes; [enterprise features have a separate license](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). The [`bunx sim-setup` wizard](https://docs.sim.ai/platform/self-hosting/docker) creates a deployment directory, generates secrets, and starts the published Sim images with Docker Compose. Self-hosted Sim [supports local models](https://docs.sim.ai/platform/self-hosting/docker) through Ollama, vLLM, LM Studio, or LiteLLM. That gives you a way to run core workflows on infrastructure you manage rather than sending every agent request to a hosted model. [Sim's repository](https://github.com/simstudioai/sim) documents the setup and core license. + +**Cons** Self-hosting Sim still requires someone to maintain Docker and the deployment. The Apache 2.0 license covers Sim's core, but enterprise features are under the separate [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), and production use of them requires an active Sim Enterprise subscription. + +**Pricing** Sim's [plans](https://www.sim.ai/pricing) are Free with 1,000 one-time credits (worth $5), Pro at $25 per user per month with 6,000 monthly credits plus a 2,000-credit weekly refresh, Max at $100 per user per month with 25,000 monthly credits plus a 4,000-credit weekly refresh, and Enterprise at custom pricing. The pricing table lists supported Self Hosting as an Enterprise feature, alongside SSO (single sign-on, one company login for every app) and SOC 2 compliance. That support plan is separate from the Apache 2.0 core, which anyone can self-host for free. + +### Zapier + +**Best for** Zapier suits non-technical teams that want to build automations around familiar triggers and actions. + +**What it is** Zapier's builder connects apps through Zaps. A trigger is the event that starts a Zap, and actions are the events the Zap performs afterward. [Code steps can run custom JavaScript or Python](https://help.zapier.com/hc/en-us/articles/45405528551181-Using-Code-by-Zapier) inside a Zap. + +**Pros** Zapier gives you a straightforward way to build routine workflows without writing code. According to its [limits documentation](https://help.zapier.com/hc/en-us/articles/8496181445261-Zap-limits), a task counts when an action step runs successfully, while triggers, filters, and certain built-in tools do not count as tasks. + +**Cons** The [Free plan](https://zapier.com/pricing) is limited to two-step Zaps, so multi-step automation requires a paid plan. Zapier does not list a self-hosted edition. + +**Pricing** The [free plan](https://zapier.com/pricing) includes 100 tasks per month, and Professional starts at $19.99 per month. Paid-plan customers can use [pay-per-task billing](https://help.zapier.com/hc/en-us/articles/15279018245901-How-pay-per-task-billing-works-in-Zapier) to keep workflows running beyond the included task allowance. + +### Make + +**Best for** Choose Make if you want more visual control than Zapier offers without taking on n8n's more technical builder. + +**What it is** Make lets you build automations, called scenarios, in a visual interface. A [router](https://help.make.com/router) branches a scenario into several chains of modules, with filters that set the conditions for each route. [Subscenarios](https://help.make.com/subscenarios) let you split a large scenario into smaller parts and reuse logic across parent scenarios. + +**Pros** Make's branching tools help you inspect and change multi-step workflows without writing code. The [HTTP app](https://apps.make.com/http) connects to API services that have no Make integration. [Make AI Agents](https://www.make.com/en/ai-agents) run in the same visual platform and expose how agents reason and use tools. + +**Cons** For scenarios to reach applications and databases on your local network, Make requires its [on-prem agent](https://help.make.com/on-premise-agent?v=2), which is available only to Enterprise customers. AI modules that use Make's AI Provider consume credits based on tokens as well as operations, which makes AI-heavy scenarios harder to estimate. + +**Pricing** Make [consumes credits](https://help.make.com/credits?fromSubscription=true) when modules perform actions, and non-AI app operations generally use one credit. Make's AI Provider accounts for tokens and operations. The [free plan](https://www.make.com/en/pricing) includes 1,000 credits per month, and paid plans are named Core, Pro, Teams, and Enterprise. + +### n8n + +**Best for** Teams that want to self-host workflows and build AI agents, and can manage a more technical visual builder. + +**What it is** n8n lets you connect apps visually, with a [Code node](https://docs.n8n.io/build/code-in-n8n) for JavaScript or Python when a workflow needs custom logic. It offers a free self-hosted Community Edition alongside paid cloud and self-hosted plans. + +**Pros** n8n's [AI Agent node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/) connects a chat model and one or more tools, and the agent decides which tools to call to complete a task. [AI agents can also use attached memory](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent). Self-hosting gives you control over where workflows run. Our guide to [open-source AI agent platforms](https://www.sim.ai/library/open-source-ai-agent-platforms) explains how licensing and deployment differ across tools in this category. + +**Cons** You take on running and maintaining the deployment when you self-host. The free [Community Edition](https://docs.n8n.io/deploy/host-n8n/community-edition-features) does not include every collaboration and access-control feature available in paid editions. + +**Pricing** The self-hosted Community Edition is free. [Cloud Starter](https://n8n.io/pricing/) costs €20 per month billed annually. n8n prices its cloud plans by monthly workflow executions regardless of workflow complexity. + +### Workato + +**Best for** Organizations that need IT oversight and governance of integrations more than a self-serve builder. + +**What it is** Workato is a cloud integration platform with workflow orchestration and agent capabilities. Its [on-prem agent](https://docs.workato.com/on-prem) runs inside your private network and creates a secure connection to Workato cloud without requiring inbound firewall access. + +**Pros** The on-prem agent lets your IT staff connect internal systems while the integration platform stays cloud-managed. Workato offers [pre-built connectors](https://www.workato.com/integrations) for SaaS apps, databases, and ERPs, both on-prem and in the cloud. + +**Cons** The on-prem agent connects your network to Workato cloud rather than hosting the platform on your infrastructure. The [Enterprise plan](https://www.workato.com/free) has no published price, so you need to ask Workato for a quote. + +**Pricing** Workato's [plans](https://www.workato.com/free) are Free with 50K one-time credits, Pro at $100 per month with 3.5K credits per month and up to 3 users per workspace, and Enterprise at custom pricing. Its pricing model uses Workato credits, so compare the included allowance and expected usage with a sales quote for enterprise requirements. + +## Why Sim fits non-developers who need AI agents + +Sim gives non-developers a visual way to build AI agents without closing off more technical work. You can describe a workflow in Chat, refine it in the workflow builder, and use the API or SDKs when you need code. Those [build methods](https://docs.sim.ai/introduction) let a non-technical colleague work on the same workflow that a developer can extend. + +Sim also lets external AI apps operate the workflows you build. Through its [MCP server](https://docs.sim.ai/mcp/overview), an agent can manage workspaces and run or deploy workflows. Sim publishes its core under Apache 2.0, so anyone can self-host it for free; the [separately licensed enterprise features](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) require an active Enterprise subscription for production use. + +Together, those options make Sim a strong fit if you need an approachable builder, agent control, and a path to hosting the software yourself. If you are moving existing automations, the [Sim and Pipedream comparison](https://www.sim.ai/comparisons/pipedream) covers that decision in more detail. You can also compare the design choices behind [AI-native and traditional workflow automation](https://www.sim.ai/library/ai-native-workflow-automation-vs-traditional-automation). diff --git a/apps/sim/content/library/best-retool-alternatives-ai-agents-2026/index.mdx b/apps/sim/content/library/best-retool-alternatives-ai-agents-2026/index.mdx new file mode 100644 index 00000000000..8303a7c0b69 --- /dev/null +++ b/apps/sim/content/library/best-retool-alternatives-ai-agents-2026/index.mdx @@ -0,0 +1,126 @@ +--- +slug: best-retool-alternatives-ai-agents-2026 +title: 'Best Retool Alternatives in 2026 for Building AI Agents' +seoTitle: 'Best Retool Alternatives for AI Agents (2026)' +description: 'Compare Retool alternatives for building AI agents instead of internal apps: Sim, n8n, Dify, Gumloop, and Relevance AI on agents, hosting, and pricing.' +date: 2026-10-19 +updated: 2026-10-19 +authors: + - andrew +readingTime: 8 +tags: [AI Agents, Retool Alternatives, Agent Builders, Sim] +ogImage: /library/best-retool-alternatives-ai-agents-2026/cover.jpg +draft: false +faq: + - q: "Can Retool build AI agents at all?" + a: "Yes. Retool Agents automate work with AI, but Retool bills agent use by the hour, separately from its AI credits for app building. Each billing month includes $50 of free agent usage, which Retool says covers up to 20 hours depending on the model. See the Retool and Sim comparison if your main deliverable is an agent workflow rather than an internal app." + - q: "Is Sim really free to self-host?" + a: "Yes. Anyone can run Sim's Apache 2.0 core with Docker Compose or Kubernetes without paying a license fee. You still pay for infrastructure and model usage. Supported self-hosting is an Enterprise plan feature, and production use of Enterprise features, including SSO and audit logs, requires an Enterprise subscription." + - q: "What's the cheapest way to run production agents?" + a: "No option is cheapest at every usage level. Sim self-hosting removes the core platform license fee, but you must cover infrastructure and model costs. Sim Cloud's free credits are a one-time grant, not a recurring production allowance." + - q: "Which alternative is most open source?" + a: "Sim's core uses the Apache 2.0 license, with a separate license for enterprise features. Dify uses a modified Apache 2.0 license with added conditions, while n8n's Sustainable Use License is source-available and n8n does not call itself open source. Retool is proprietary, and its self-hosted deployment requires a Retool-issued license key." +--- + +## TL;DR + +- **Sim** is an open-source AI workspace where teams build, deploy, and manage AI agents across 28 LLM providers. Retool describes itself as a [development platform for building internal software](https://retool.com/llms.txt), while [Sim's Apache 2.0 core](https://github.com/simstudioai/sim/blob/main/LICENSE) lets anyone self-host agent workflows, subject to the [enterprise-feature exception](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). +- **n8n** fits automations where an [AI Agent node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/) sits inside a larger, mostly fixed workflow. +- **Dify** fits retrieval-heavy chatbots and prompt-based LLM apps. +- **Gumloop** fits teams that want a hosted agent builder with company-wide admin controls. +- **Relevance AI** fits buyers evaluating multi-agent "AI workforce" products with built-in agent evaluations. + +## Why Retool falls short for building AI agents + +Retool fits internal apps better than agent workflows. Its core product builds apps that connect to production data, while Retool Agents is a separately metered product for automating work with AI. If your deliverable is an agent rather than an app for employees to use, evaluate the agent product and its costs apart from Retool's app builder. + +[Retool bills Agents by the hour](https://docs.retool.com/support/billing-usage/agents). Each billing month includes $50 of free agent usage, which Retool says covers up to 20 hours depending on the model you choose. Billable time runs from when an agent is triggered until it returns an answer, excluding time spent waiting for subagents, authentication, or human approval. Failed runs still count. [Hourly rates vary by model](https://docs.retool.com/data-sources/reference/ai-models), and with your own model provider key, Retool charges a flat hourly fee. Retool's [pricing page](https://retool.com/pricing) says Agents do not draw from the AI credit pool that covers app building and AI Actions. + +For an agent-first project, compare what each alternative lets the agent do, which models and external tools it can use, and where you can deploy the finished workflow. Self-hosting terms and pricing matter too, especially if agents will run regularly rather than serve as occasional additions to an internal app. The [AI workflow automation buyer's checklist](https://www.sim.ai/library/ai-workflow-automation-platform-buyers-checklist) provides a broader set of evaluation criteria. + +## Comparison table + +As of October 2026, Retool appears last as the baseline. The other tools are ranked for building agent workflows. Integration figures are each vendor's own description, and vendors define integrations, executions, and credits differently. MCP (Model Context Protocol) is a protocol that lets AI apps call external tools, and a VPC (virtual private cloud) is a private network in your own cloud account. + +| Rank | Tool | Agent capabilities | Integrations | Self-hosting | Pricing | +|---|---|---|---|---|---| +| 1 | Sim | Agent, Evaluator, Guardrails, and Human in the Loop blocks | [270+](https://www.sim.ai/integrations) | Apache 2.0 core, free for anyone; supported self-hosting on Enterprise | Free with 1,000 one-time credits; Pro $25/seat/month | +| 2 | n8n | [AI Agent node with tool sub-nodes](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/) | [Over 500](https://n8n.io/) | [Free Community edition](https://docs.n8n.io/deploy/host-n8n/community-edition-features) under the Sustainable Use License | [Cloud from €20/month billed annually](https://n8n.io/pricing/) | +| 3 | Dify | Workflows, knowledge retrieval, and agents | Marketplace tools, MCP servers, and APIs | [Free Community edition](https://dify.ai/pricing/dify-enterprise), single workspace, modified Apache 2.0 | [Free Sandbox; Professional $590/workspace/year; Team $1,590/workspace/year](https://dify.ai/pricing) | +| 4 | Gumloop | Agents with triggers and skills | [100+ MCP integrations](https://www.gumloop.com/llms.txt) | No self-hosted plan listed; optional VPC on Enterprise | [Pro from $37/month after a 14-day free trial](https://www.gumloop.com/pricing) | +| 5 | Relevance AI | Agents and multi-agent workforces with evaluations | [2,000+ integrations](https://relevanceai.com/pricing) | No self-hosted plan listed; data residency on Enterprise | [Enterprise, custom](https://relevanceai.com/pricing); [pricing docs](https://relevanceai.com/docs/get-started/pricing) list Pro from $19/month and Team from $234/month billed annually | +| 6 | Retool | Agents, Workflows, and internal apps | [Native connectors, REST APIs, and GraphQL](https://retool.com/integrations) | [Self-hosted or air-gapped on Enterprise](https://retool.com/pricing); [Retool license key required](https://docs.retool.com/self-hosted/self-managed/tutorials/kubernetes/kubernetes-helm) | [Team $10/builder/month billed annually](https://retool.com/pricing); Agents $50 free usage/month, then hourly | + +### Sim + +**Best for:** You want to build and deploy AI agent workflows, with the option to self-host the core software without a license fee. + +**What it is:** Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. You can assemble Workflows in the visual builder, ask Sim in Chat to build and edit them in plain language, and add JavaScript or Python functions when you need code. For agents that need source material, Sim's Knowledge Base combines vector and full-text search and can sync connected sources such as Google Drive and Slack on a schedule. + +**Pros:** Sim's Agent blocks support 28 LLM providers, and the workspace connects to [270+ integrations](https://www.sim.ai/integrations). A self-hosted Sim deployment can also point Agent blocks at local or self-hosted endpoints such as Ollama, vLLM, LM Studio, or LiteLLM. Workspace bring-your-own-key (BYOK) credentials, meaning your own model provider API keys, work on every Sim Cloud plan. + +You can deploy a workflow as an API, a chat interface, or a tool on an MCP server. [MCP deployment](https://docs.sim.ai/workflows/deployment/mcp) makes a workflow available to external clients with API-key-protected or public access. [Sim's self-hosting documentation](https://docs.sim.ai/platform/self-hosting) covers Docker Compose and Kubernetes. The core is available under Apache 2.0 without a license fee, whatever plan you use, and Sim's pricing table lists supported self-hosting as an Enterprise feature. Code in `apps/sim/ee`, including single sign-on (SSO) and audit logs, is covered by the [Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), which requires an active Enterprise subscription for production use. + +Retool can also be self-hosted, but its [pricing page](https://retool.com/pricing) lists self-hosted and air-gapped deployment on the Enterprise plan, and every self-hosted instance needs a Retool license key. Retool's [Docker setup](https://docs.retool.com/self-hosted/tutorials/docker) is for non-production use only, and its [production deployments](https://docs.retool.com/self-hosted/self-managed/) run on Kubernetes with Helm. See the [full Sim and Retool comparison](https://www.sim.ai/comparisons/retool). + +**Cons:** Self-hosting leaves you responsible for operating the deployment. Sim's free cloud plan grants its credits once rather than refreshing them. Production use of enterprise features such as SSO and audit logs requires an Enterprise subscription. + +**Pricing:** [Sim Cloud pricing and limits](https://docs.sim.ai/platform/costs) list Free at $0 with 1,000 one-time credits, Pro at $25 per seat per month with 6,000 credits per month and a 2,000-credit weekly refresh, and Max at $100 per seat per month with 25,000 credits per month and a 4,000-credit weekly refresh. Enterprise pricing is custom. BYOK calls are paid directly to the model provider, while the workflow base charge and unrelated hosted services can still apply. + +For a wider market view, compare the criteria in [Best AI Agent Platforms and Builders in 2026](https://www.sim.ai/library/best-ai-agent-platforms-2026). + +### n8n + +**Best for:** You want AI steps inside predictable automations rather than an agent-first workspace. + +**What it is:** n8n connects apps through visual workflows. Its [AI Agent node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/) connects to a chat model and tools, then decides which tool to call for a task. + +**Pros:** n8n combines app integrations, optional code, and a choice between n8n Cloud and self-hosting. A self-hosted installation without a paid license key can use the [free Community edition](https://docs.n8n.io/deploy/host-n8n/community-edition-features). + +**Cons:** n8n uses the [Sustainable Use License](https://docs.n8n.io/n8n-community-license/community-license/license-faq), which allows internal business uses but restricts some commercial uses. n8n [does not call itself open source](https://docs.n8n.io/choose-n8n/faircode-license/) and describes the license as fair-code. Sim's core uses Apache 2.0 and centers its workspace on building and managing agents, making it the clearer fit when agents are the main deliverable. + +**Pricing:** [n8n Cloud](https://n8n.io/pricing/) Starter costs €20 per month billed annually with 2,500 workflow executions, and Pro costs €50 per month billed annually with 10,000. The self-hosted Business plan costs €667 per month billed annually with 40,000 executions. The Community edition is free to license and leaves infrastructure costs to the operator. + +### Dify + +**Best for:** You want to build a chatbot that draws heavily on your own documents. + +**What it is:** [Dify](https://github.com/langgenius/dify) combines visual workflows, a knowledge pipeline for document ingestion and retrieval, agents, and model management. + +**Pros:** Dify gives retrieval-heavy apps a focused starting point. You can self-host the free Community edition, which is limited to a single workspace, under the [Dify Open Source License](https://github.com/langgenius/dify/blob/main/LICENSE), a modified Apache 2.0 license with additional conditions. + +**Cons:** The license requires a commercial license to operate a multi-tenant environment, and it prevents removing or modifying Dify's logo or copyright information in its frontend. Check those conditions before building a product on a self-hosted Dify instance. + +**Pricing:** [Dify Cloud](https://dify.ai/pricing) offers a free Sandbox with 200 message credits and five apps. Professional costs $590 per workspace per year with 5,000 message credits per month and 50 apps. Team costs $1,590 per workspace per year with 10,000 message credits per month and 200 apps. Enterprise pricing is custom. + +### Gumloop + +**Best for:** You want a hosted agent builder that people across a company can use, with IT controlling access. + +**What it is:** [Gumloop](https://www.gumloop.com/llms.txt) describes itself as a multiplayer AI agent builder. Agents connect to company apps, run on triggers, use reusable skills, and produce artifacts such as dashboards and documents. Its earlier workflow builder is now listed as Workflows (Legacy) on the pricing page. + +**Pros:** Gumloop describes a model-agnostic builder with more than 100 MCP integrations and enterprise controls including SSO, audit logging, usage monitoring, and VPC deployments. Its [pricing page](https://www.gumloop.com/pricing) lists 35+ models, bring-your-own API keys, unlimited agents, and unlimited seats. + +**Cons:** The pricing page lists no self-hosted plan; VPC deployment is an optional Enterprise control. Pro limits concurrency to 5 workflow runs and 25 agent chats. + +**Pricing:** Pro starts at $37 per month after a 14-day free trial and includes 20,000 credits per month. Enterprise pricing is custom. + +### Relevance AI + +**Best for:** You want to evaluate a product built around multi-agent "AI workforces" rather than internal CRUD apps. + +**What it is:** Relevance AI is a product for building and managing AI agents and multi-agent systems. + +**Pros:** Its [enterprise page](https://relevanceai.com/enterprise) lists human-in-the-loop controls, version control, SSO and SAML, audit logs, data residency, and SOC 2 Type II. Its [pricing page](https://relevanceai.com/pricing) lists agent evaluations, more than 2,000 integrations, and SSO, role-based access control (RBAC), and audit logs on Enterprise. + +**Cons:** Neither official pricing source lists a self-hosted option, and the two disagree on which plans are available, so confirm current plans with the vendor before comparing costs. + +**Pricing:** Relevance AI's two official pricing sources disagree. The [pricing page](https://relevanceai.com/pricing) lists only an Enterprise plan with custom pricing. Its [pricing documentation](https://relevanceai.com/docs/get-started/pricing) lists Pro from $19 per month billed annually ($29 monthly) and Team from $234 per month billed annually ($349 monthly), and says the Free plan is retired. + +## How to choose + +Start with what you need to ship. If staff need an app to view and update database records, Retool fits that job even if you add an agent. If people need an agent they can call through an API or chat interface, consider Sim. For scheduled processes with AI at specific steps, consider n8n. For a focused comparison of visual orchestration products, see [AI Agent Workflow Builders for Multi-Step Tasks](https://www.sim.ai/library/ai-agent-workflow-builders-multi-step-tasks). + +Set hosting and model requirements before comparing prices. Anyone can self-host Sim's Apache 2.0 core without a license fee, subject to the [enterprise-feature exception](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). If you need to switch model providers or use your own API key, check those options alongside hosting terms. The [open-source AI agent platform comparison](https://www.sim.ai/library/open-source-ai-agent-platforms) goes deeper on licenses and deployment tradeoffs. + +For building AI agents rather than internal apps, **Sim ranks first in this comparison** because its core is openly licensed, its workspace is designed around agents, and it supports cloud or self-hosted deployment without locking the workflow to one model provider. diff --git a/apps/sim/content/library/best-vellum-alternatives-2026/index.mdx b/apps/sim/content/library/best-vellum-alternatives-2026/index.mdx new file mode 100644 index 00000000000..cd0e74e6ad6 --- /dev/null +++ b/apps/sim/content/library/best-vellum-alternatives-2026/index.mdx @@ -0,0 +1,131 @@ +--- +slug: best-vellum-alternatives-2026 +title: 'Best Vellum Alternatives in 2026 for Non-Engineers' +description: 'Compare Vellum alternatives for non-engineers across visual building, evaluation, deployment, self-hosting, and pricing.' +date: 2026-10-19 +updated: 2026-10-19 +authors: + - andrew +readingTime: 7 +tags: [AI Agents, Vellum Alternatives, AI Tools, Sim] +ogImage: /library/best-vellum-alternatives-2026/cover.jpg +draft: false +faq: + - q: "What makes an AI builder friendly to non-engineers?" + a: "You should be able to connect steps visually, test outputs, and deploy a workflow without writing code. Sim also lets you build and edit workflows through plain-language requests." + - q: "Does Vellum have a free plan?" + a: "Vellum does not publish pricing for its AI development platform on vellum.ai. The vellum.ai pricing page covers only its Personal Assistant product, which offers a free start with your first tasks included, then Mighty at $30 per month, Super at $100, Ultra at $200, and a configurable Custom plan. A banner on that page says the Personal Assistant platform shuts down on November 2, 2026." + - q: "Is Sim really free to self-host?" + a: "Yes. Sim's Apache 2.0-licensed core has no self-hosting license fee, and you can run it with Docker Compose or Kubernetes. You still pay for your infrastructure and any model usage. Enterprise features such as single sign-on (SSO), which lets people log in with their company account, need an Enterprise subscription for production use under the Sim Enterprise License." + - q: "How does evaluation tooling differ?" + a: "Vellum runs Test Suites of test cases with expected outputs, scored by metrics from 0 to 1, for regression testing before you deploy a change to a Prompt or Workflow. Sim offers an Evaluator block for scoring workflow outputs and a Guardrails block for validation. Relevance AI lists agent evaluations on Enterprise and A/B testing on Team and Enterprise, Gumloop has an Evaluations view on each agent's Performance page, and Dify and Langflow focus on debugging tools such as run history, Playground testing, and traces." + - q: "How can I migrate from Vellum?" + a: "Rebuild your workflows in Sim, transfer your prompts and test cases, and compare outputs before switching live traffic. Check the Sim and Vellum comparison for differences in building and deployment options." +--- + +## TL;DR + +- **Sim** ranks first. It is an open-source AI workspace for teams, where non-engineers can build and deploy AI agents in the visual builder or ask Sim in Chat to create and edit workflows. Sim Cloud has a free plan, and [Sim's Apache 2.0 core can be self-hosted, with enterprise features covered by a separate license](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). See the [Sim and Vellum comparison](https://www.sim.ai/comparisons/vellum). +- **Gumloop** is best for business teams that want an AI agent builder that starts from a description of the job. +- **Relevance AI** is best for go-to-market teams that want self-serve agent plans and an enterprise upgrade path. +- **Dify** is best for teams that want a visual builder with a free, self-hostable Community edition. +- **Langflow** is best for experimenting with visual AI flows under an MIT license. + +## Why non-engineers look past Vellum + +[Vellum's documentation describes an end-to-end AI development platform](https://docs.vellum.ai/home/getting-started/overview) where technical and non-technical team members work together. Its [Agent Builder](https://docs.vellum.ai/product/agent-builder/agent-builder-sme) builds Workflows from plain-language instructions. The friction for non-engineers shows up elsewhere. + +First, the platform is organized around a release process. Vellum separates [Prompt Sandboxes, Workflow Sandboxes, Test Suites, Deployments, and Environments](https://docs.vellum.ai/product/deployments/environments), and supports [Release Reviews](https://docs.vellum.ai/product/deployments/release-reviews) modeled on pull request reviews. Its Workflows documentation describes the end goal as deploying a Workflow and calling [an API to invoke it from an application](https://docs.vellum.ai/product/workflows/introduction). That model serves teams shipping AI features inside an existing product. + +Second, it is hard to evaluate the AI development platform from Vellum's public website. As of October 2026, the [vellum.ai pricing page](https://www.vellum.ai/pricing) lists plans for Vellum's personal assistant product, while a separate notice says that the [Personal Assistant platform will shut down on November 2, 2026](https://www.vellum.ai/vellum-assistant-platform-shutdown). The AI development platform described in the docs has no published price on vellum.ai, and [self-hosted Vellum](https://docs.vellum.ai/self-hosting/getting-started/introduction) is arranged by contacting the team. + +The alternatives below are compared on how you build, evaluation and testing tools, deployment options, self-hosting, and published pricing. For a broader market view, see the guides to [AI agent platforms](https://www.sim.ai/library/best-ai-agent-platforms-2026) and [AI agent workflow builders](https://www.sim.ai/library/ai-agent-workflow-builders-multi-step-tasks). + +## Comparison table + +| Rank and tool | How you build | Evaluation and testing | Deployment | Self-hosting | Starting price | +|---|---|---|---|---|---| +| 1. [Sim](https://www.sim.ai/comparisons/vellum) | Visual builder, plus Chat for creating and editing workflows in plain language | [Evaluator](https://docs.sim.ai/workflows/blocks/evaluator) and [Guardrails](https://docs.sim.ai/workflows/blocks/guardrails) blocks | [API, chat interface, scheduled or webhook trigger, or MCP server tool](https://docs.sim.ai/workflows/deployment) (MCP is a protocol that lets AI apps call tools) | [Apache 2.0 core via Docker Compose or Kubernetes; enterprise features are separately licensed](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE) | [Free; Pro from $25 per user per month](https://www.sim.ai/pricing) | +| 2. [Gumloop](https://www.gumloop.com/pricing) | Agent builder that starts by asking what the agent should handle | [Evaluations and Insights on an agent Performance page](https://docs.gumloop.com/core-concepts/agent_performance) | Gumloop cloud, or [VPC deployment on Enterprise](https://www.gumloop.com/enterprise) (a VPC is a private network in your own cloud account) | [VPC deployment in your AWS, Azure, or Google Cloud environment](https://www.gumloop.com/enterprise) | [Pro from $37 per month after a 14-day trial](https://www.gumloop.com/pricing) | +| 3. [Relevance AI](https://relevanceai.com/pricing) | [Invent, an AI solutions engineer that helps teams decide what to automate and turn their expertise into agents](https://relevanceai.com/) | [A/B testing on Team; agent evaluations on Enterprise](https://relevanceai.com/docs/get-started/pricing) | [Agents receive tasks through native app triggers, webhooks, and cron schedules](https://relevanceai.com/enterprise) | [No self-hosted edition is listed](https://relevanceai.com/docs/get-started/pricing) | [Pricing docs list Pro from $19 per month billed annually](https://relevanceai.com/docs/get-started/pricing); the [pricing page](https://relevanceai.com/pricing) lists only custom Enterprise | +| 4. [Dify](https://dify.ai/pricing) | [Workflow Studio visual builder](https://dify.ai/workflows) for agentic workflows | [Node-level testing and run history](https://docs.dify.ai/en/cloud/use-dify/debug/history-and-logs) | [Web app, API, or embed](https://docs.dify.ai/en/cloud/use-dify/publish/README) | [Community edition under a modified Apache 2.0 license](https://github.com/langgenius/dify/blob/main/LICENSE) | [Sandbox free; Professional $59 per workspace per month or $590 annually](https://dify.ai/pricing) | +| 5. [Langflow](https://www.langflow.org/) | Low-code visual builder aimed at technical teams | [Playground testing and built-in traces](https://docs.langflow.org/traces) | [API, embedded chat widget, or MCP server](https://docs.langflow.org/concepts-publish) | [MIT-licensed; Desktop, Docker, or Python package](https://github.com/langflow-ai/langflow/blob/main/LICENSE) | [Free and open source; you host it](https://github.com/langflow-ai/langflow/blob/main/LICENSE) | + +### Sim + +Sim is an open-source AI workspace for teams. It gives non-engineers two ways to build an agent without writing code. You can describe a workflow in Chat and ask Sim to create or edit it, or connect blocks in the visual builder. Developers can also extend workflows with code. [Compared with Vellum's release-oriented platform](https://www.sim.ai/comparisons/vellum), Sim offers a more direct starting point for people who need to build without dedicated engineering help. + +Sim also puts testing controls in the workflow builder. An [Evaluator block](https://docs.sim.ai/workflows/blocks/evaluator) asks a model for numeric scores against metrics you define. A [Guardrails block](https://docs.sim.ai/workflows/blocks/guardrails) validates content and reports whether the check passed; connect a downstream Condition when the result should change the execution path. + +You can deploy a finished workflow through an [API, chat interface, scheduled or webhook trigger, or MCP server tool](https://docs.sim.ai/workflows/deployment). [Sim's Apache 2.0 core can be self-hosted with Docker Compose or Kubernetes, while `apps/sim/ee` is governed by the separate Sim Enterprise License](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). Enterprise features such as single sign-on (SSO), which lets people log in with their company account, and audit logs require an active Enterprise subscription for production use. + +**Best for:** Non-engineer teams that want to build, test, and deploy agents visually while retaining a self-hosting option. + +**Pros:** Chat can draft or edit workflows, and built-in blocks let teams configure output checks without code. + +**Cons:** Sim no longer offers a pre-built workflow template gallery. On Sim Cloud, [Free credits do not refresh and synchronous runs on the Free plan time out after five minutes](https://www.sim.ai/pricing). + +**Pricing:** As of October 2026, [Sim Cloud Free includes 1,000 one-time credits; Pro costs $25 per user per month with 6,000 included credits and a 2,000-credit weekly refresh; Max costs $100 per user per month with 25,000 included credits and a 4,000-credit weekly refresh](https://www.sim.ai/pricing). [Self-hosting Sim's core has no license fee, but enterprise features have separate terms](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE), and you pay for infrastructure and model usage. + +### Gumloop + +Gumloop describes itself as a [multiplayer AI agent builder](https://www.gumloop.com/) that lets business teams build agents while administrators control access. Its [guided setup asks what a new agent should take care of and configures the agent from the answer](https://docs.gumloop.com/core-concepts/profile_settings). + +**Best for:** Business teams that want to build agents by describing the job rather than wiring each step. + +**Pros:** The agent builder starts from a plain description of the job, while [Evaluations and Insights sit on the agent's Performance page](https://docs.gumloop.com/core-concepts/agent_performance). + +**Cons:** There is no self-hosted edition on the pricing page. Data-residency control instead comes through [VPC deployment on Enterprise in an AWS, Azure, or Google Cloud environment](https://www.gumloop.com/enterprise). + +**Pricing:** As of October 2026, [Pro starts at $37 per month after a free 14-day trial](https://www.gumloop.com/pricing). Enterprise uses custom pricing. + +### Relevance AI + +Relevance AI positions itself around an "AI Workforce," and its [enterprise page](https://relevanceai.com/enterprise) pitches the platform to domain experts as well as technical teams. + +**Best for:** Go-to-market teams that want self-serve agent building with an enterprise upgrade path. + +**Pros:** [Pro includes unlimited agents and tools, while Team adds A/B testing and Enterprise adds agent evaluations, enterprise triggers, SSO, role-based access control (RBAC, which sets permissions by role), and audit logs](https://relevanceai.com/docs/get-started/pricing). The platform also lists [more than 2,000 integrations](https://relevanceai.com/integrations). + +**Cons:** [The Free plan is retired for new and existing accounts](https://relevanceai.com/docs/get-started/pricing), and organization-wide controls are reserved for the custom Enterprise tier. + +**Pricing:** Relevance AI's two official pricing pages disagree. As of October 2026, its [pricing page](https://relevanceai.com/pricing) lists only an Enterprise plan with custom pricing, while its [pricing documentation](https://relevanceai.com/docs/get-started/pricing) lists Pro from $19 per month billed annually ($29 monthly) and Team from $234 per month billed annually ($349 monthly), and says the Free plan is retired. Confirm current plans with Relevance AI before you buy. + +### Dify + +Dify is a platform for building AI applications with [Workflow Studio](https://dify.ai/workflows), a visual builder for agentic workflows. You can test nodes and review [run history](https://docs.dify.ai/en/cloud/use-dify/debug/history-and-logs), then [deploy an app](https://docs.dify.ai/en/cloud/use-dify/publish/README) as a web app, API, or embed. + +**Best for:** Teams that want a visual LLM application builder with a self-hosting option. + +**Pros:** [Dify Cloud has a free Sandbox plan, and the Community edition can be self-deployed](https://dify.ai/pricing). + +**Cons:** Dify's [license](https://github.com/langgenius/dify/blob/main/LICENSE) is a modified Apache 2.0 license. It requires a commercial license for specified multi-tenant use and restricts removing or changing logo and copyright information in the frontend, so review the terms before adopting it. + +**Pricing:** As of October 2026, [Dify Cloud's Sandbox plan is free, Professional costs $59 per workspace per month or $590 per year, and Team costs $159 per workspace per month or $1,590 per year](https://dify.ai/pricing). Enterprise is custom-priced. + +### Langflow + +Langflow is a [low-code builder for agentic and retrieval-augmented generation (RAG) applications](https://www.langflow.org/), meaning apps that answer from your own documents. Its [source code is MIT-licensed](https://github.com/langflow-ai/langflow/blob/main/LICENSE), and its documentation is aimed at developers, data scientists, and teams building retrieval and multi-agent applications. + +**Best for:** Teams that want a freely licensed starting point for testing AI flow ideas and can manage the technical setup. + +**Pros:** You can [install Langflow Desktop, use Docker, or install a Python package](https://docs.langflow.org/get-started-installation), test flows in the Playground, and review [execution traces](https://docs.langflow.org/traces). Flows can run through an [API, an embedded chat widget, or an MCP server](https://docs.langflow.org/concepts-publish). + +**Cons:** The [deployment guide](https://docs.langflow.org/deployment-overview) focuses on running Langflow on your own infrastructure. Someone on your team needs to manage that infrastructure. + +**Pricing:** Langflow is free and open source under the [MIT license](https://github.com/langflow-ai/langflow/blob/main/LICENSE). You pay for the infrastructure and models you use. + +## Choosing the right alternative + +- If you need to self-host on a budget, choose Sim. [Sim's core is Apache 2.0 and can be self-hosted, while enterprise features use a separate license](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE); [self-hosted Vellum](https://docs.vellum.ai/self-hosting/getting-started/introduction) is arranged through Vellum's team. +- If you want self-serve plans for go-to-market agents, consider Relevance AI. Its [pricing documentation lists Pro and Team prices](https://relevanceai.com/docs/get-started/pricing), while its [pricing page](https://relevanceai.com/pricing) shows only custom Enterprise. +- If your team wants to describe an agent's job and let the builder set it up, consider Gumloop. +- If you want source-code access, compare Dify and Langflow. Dify combines a hosted cloud with a self-deployable Community edition under [additional license conditions](https://github.com/langgenius/dify/blob/main/LICENSE), while Langflow uses the [MIT license](https://github.com/langflow-ai/langflow/blob/main/LICENSE) and is self-hosted. + +The related comparisons of [open-source AI agent platforms](https://www.sim.ai/library/open-source-ai-agent-platforms) and [AI agent evaluation platforms](https://www.sim.ai/library/best-ai-agent-evaluation-platforms-2026) go deeper on licensing and testing. + +## Conclusion + +For a team without dedicated engineers, the useful test is whether non-technical people can build, evaluate, and deploy an agent without a sales call. Sim ranks first here because Chat and the visual builder support building, its Evaluator and Guardrails blocks support testing, and workflows can deploy through chat interfaces, APIs, triggers, or MCP server tools. [Sim's Apache 2.0 core can also be self-hosted at no license cost, with enterprise features governed by separate terms](https://github.com/simstudioai/sim/blob/main/apps/sim/ee/LICENSE). + +[Try Sim](https://www.sim.ai) or read the [full Vellum comparison](https://www.sim.ai/comparisons/vellum). diff --git a/apps/sim/lib/compare/data/competitors/flowise.ts b/apps/sim/lib/compare/data/competitors/flowise.ts index eda221873d5..dbc0746b7e0 100644 --- a/apps/sim/lib/compare/data/competitors/flowise.ts +++ b/apps/sim/lib/compare/data/competitors/flowise.ts @@ -1,7 +1,12 @@ import { FlowiseIcon } from '@/components/icons' import type { CompetitorProfile } from '@/lib/compare/data/types' -/** Researched and cross-verified against live vendor sources on 2026-07-02. */ +/** + * Researched and cross-verified against live vendor sources on 2026-07-02. The + * end-of-life status in `oneLiner`, `leadAnswer`, and `faqs` was verified against + * flowiseai.com/sunset and the archived GitHub repository on 2026-10-19, as were + * the Cloud plans and self-hosting facts that cite them. + */ export const flowiseProfile: CompetitorProfile = { id: 'flowise', name: 'Flowise', @@ -14,7 +19,41 @@ export const flowiseProfile: CompetitorProfile = { asOf: '2026-07-02', }, oneLiner: - 'Flowise is an open-source, low-code visual builder for LLM chains, RAG pipelines, and multi-agent AI workflows, available self-hosted or as a managed cloud service, and owned by Workday since August 2025.', + 'Flowise is an open-source, low-code visual builder for LLM chains, RAG pipelines, and multi-agent AI workflows, owned by Workday since August 2025, which reached end of life on August 31, 2026 after its team announced it was winding the project down.', + leadAnswer: [ + 'Flowise was an open-source, Apache 2.0-licensed, low-code visual builder for LLM chains, RAG pipelines, and multi-agent workflows, owned by Workday since August 2025. Its team ', + { text: 'announced the sunset of Flowise', href: 'https://flowiseai.com/sunset' }, + ' on July 29, 2026: feature development stopped that day, the GitHub repository was archived in August, and the project reached end of life on August 31, 2026, with the Apache 2.0 code left available to fork. Sim is an open-source AI workspace with an Apache 2.0-licensed core, where teams build, deploy, and manage AI agents visually, conversationally, or with code, on Sim Cloud or self-hosted. Choose a Flowise fork only if you already run Flowise and are prepared to maintain the code yourself. Choose Sim when you need an actively developed platform with production operations built in: schedule and app-event triggers, failure alerts, error-handling branches, native parallel execution, and published execution limits.', + ], + faqs: [ + { + question: 'Is Flowise still maintained?', + answer: + 'No. The Flowise team announced on July 29, 2026 that it was winding the project down: feature development and pull request review stopped that day, the GitHub repository was archived in August 2026, and the project reached end of life on August 31, 2026, when the core team stopped supporting it on Discord and GitHub. The Apache 2.0 source code stays on GitHub for teams to fork. Sim is an actively developed open-source alternative with an Apache 2.0-licensed core that can be self-hosted or used on Sim Cloud.', + }, + { + question: 'How does Sim compare to Flowise for building AI agents?', + answer: + "Both are visual canvas builders with agent nodes and a human approval step, but Sim adds natural-language building and live multiplayer editing. Flowise's Agentflow V2 provides Agent nodes with Condition, Iteration, and Human Input primitives, custom code runs in JavaScript only, and its canvas supports one editor per session with no natural-language flow generator. Sim builds and edits workflows from plain-language requests through Chat and Copilot, runs custom code in JavaScript or Python, lets several people edit the same canvas at once, and works as both an MCP client and an MCP server, where Flowise documents MCP client support only.", + }, + { + question: 'What is a Flowise alternative with stronger production deployment options?', + answer: + "Sim documents more production controls than Flowise: webhook, schedule, chat, REST API, and app-event triggers; async runs that return an execution ID to poll; failure alerts to Slack, email, or a webhook; error branches so a failing block does not halt the run; and published timeouts (50-minute sync runs on paid plans, 90-minute async by default). Flowise flows start from its chat widget, public URL, REST prediction API, or an MCP tool call, and it has no built-in schedule trigger, so time-based runs need an external scheduler. Flowise's prediction endpoint is synchronous, and its documentation does not describe failure alerting, automatic retries or replay, or execution limits.", + }, + { + question: 'Are Sim and Flowise both open source?', + answer: + "Yes. Both Sim and Flowise license their core under Apache 2.0 and can be self-hosted, so the license itself is not a differentiator. Each keeps some features under a separate license: Flowise's RBAC, login-activity logs, and organization workspaces ship under a Commercial License on its Cloud and Enterprise plans, with SSO on Enterprise only, while Sim's enterprise features require an Enterprise subscription for production use. Flowise's archived repository keeps its community of roughly 55,000 GitHub stars, but the project no longer accepts contributions after reaching end of life on August 31, 2026.", + }, + { + question: 'How do Sim and Flowise compare for RAG and knowledge bases?', + answer: + "Flowise gives more control over the ingestion pipeline, while Sim builds a synced, hybrid-search knowledge base into the workspace. Flowise's Document Store upserts into a choice of vector stores such as Pinecone, Weaviate, Milvus, and FAISS, offers the broadest native set of text splitters, and lets you preview and edit chunks. Sim combines vector and full-text search, supports 15 file types with configurable chunking, uses connectors to continuously sync sources such as Google Drive, Confluence, and Slack, and exposes chunk-level detail with a dedicated chunk editor.", + }, + ], + chooseCompetitorAnswer: + 'Choose Flowise only if you already run it and are prepared to maintain a fork of its archived Apache 2.0 code yourself: the open-source project reached end of life on August 31, 2026, and its core team no longer reviews or accepts changes.', standoutFeatures: [ { title: 'Choice of vector-store backend, with the broadest native text-splitter menu', @@ -41,19 +80,31 @@ export const flowiseProfile: CompetitorProfile = { }, }, { - title: 'Larger existing open-source community, on the same Apache 2.0 license as Sim', + title: 'Large open-source community, on the same Apache 2.0 license as Sim', description: - 'Both Flowise and Sim are Apache License 2.0 and self-hostable, so the license itself is not a differentiator. Where Flowise stands out is community scale: its GitHub repo has roughly 54,000 stars and an active Discord community built up since 2023.', + 'Both Flowise and Sim are Apache License 2.0 and self-hostable, so the license itself is not a differentiator. Where Flowise stands out is the community it built: its GitHub repository has roughly 55,000 stars. The repository has been archived since August 2026, and official core-team presence on GitHub and Discord ended at end of life on August 31, 2026.', shortDescription: - 'Same Apache 2.0 license as Sim, but a larger existing community: ~54k GitHub stars, active Discord.', + 'Same Apache 2.0 license as Sim, with ~55k GitHub stars on a repository archived since August 2026.', source: { url: 'https://github.com/FlowiseAI/Flowise', label: 'GitHub: FlowiseAI/Flowise', - asOf: '2026-07-02', + asOf: '2026-10-19', }, }, ], limitations: [ + { + title: 'End of life for the open-source project on August 31, 2026', + description: + 'The Flowise team froze the code on July 29, 2026, stopped reviewing or accepting pull requests, archived the GitHub repository in August 2026, and ended official core-team presence on GitHub and Discord on August 31, 2026. The Apache 2.0 code stays available to fork, so any further fixes come from a fork you maintain. The sunset notice does not say what happens to Flowise Cloud.', + shortDescription: + 'End of life since August 31, 2026: archived repository, no core-team maintenance.', + source: { + url: 'https://flowiseai.com/sunset', + label: 'Flowise: The Future of Flowise', + asOf: '2026-10-19', + }, + }, { title: 'Low enterprise-readiness score in third-party benchmarking', description: @@ -109,27 +160,32 @@ export const flowiseProfile: CompetitorProfile = { }, selfHostOption: { value: - "Yes: Flowise's Community Edition is Apache 2.0 and can be self-hosted, including via Docker, on your own infrastructure.", - shortValue: 'Yes, self-hostable via Docker', + "Yes: Flowise's Community Edition is Apache 2.0 and can be self-hosted, including via Docker, on your own infrastructure. The repository has been archived since August 2026 and the project reached end of life on August 31, 2026, so a self-hosted deployment runs the final release or a fork you maintain.", + shortValue: 'Yes, but the archived code is no longer maintained', confidence: 'verified', sources: [ { url: 'https://github.com/FlowiseAI/Flowise/blob/main/LICENSE.md', label: 'GitHub: Flowise LICENSE.md', - asOf: '2026-07-02', + asOf: '2026-10-19', + }, + { + url: 'https://flowiseai.com/sunset', + label: 'Flowise: The Future of Flowise', + asOf: '2026-10-19', }, ], }, deploymentOptions: { value: - 'Flowise offers self-hosted open-source deployment (Docker/npm), a managed multi-tenant Cloud plan, and an Enterprise tier supporting on-premise or air-gapped deployment for regulated industries.', - shortValue: 'Self-hosted, cloud, and enterprise on-prem/air-gapped', + 'Self-hosted open-source deployment (Docker or npm) of the archived code, and a managed Cloud service that flowiseai.com still lists with Free, Starter, and Pro plans, plus on-premises and cloud deployment for enterprises. The sunset notice covers the open-source project and does not say what happens to Flowise Cloud.', + shortValue: 'Self-hosted (archived code) or Flowise Cloud', confidence: 'verified', sources: [ { - url: 'https://www.lindy.ai/blog/flowise-pricing', - label: 'Lindy: Flowise Pricing, Features, and Alternatives for 2026', - asOf: '2026-07-02', + url: 'https://flowiseai.com', + label: 'Flowise: Pricing', + asOf: '2026-10-19', }, { url: 'https://docs.flowiseai.com/using-flowise/workspaces', @@ -616,42 +672,40 @@ export const flowiseProfile: CompetitorProfile = { pricing: { pricingModel: { value: - 'Flowise Cloud prices by monthly prediction (execution) volume plus storage tier, with separate paid plans; self-hosting is free aside from your own infrastructure and LLM costs.', - shortValue: 'Prediction-volume based tiers (cloud), free self-hosting', - confidence: 'estimated', + 'Flowise Cloud plans are priced by monthly predictions (executions) and storage: Free, Starter at $35/month, and Pro at $65/month. Self-hosting the archived open-source code is free aside from your own infrastructure and LLM costs.', + shortValue: 'Prediction-volume tiers (Cloud), free self-hosting', + confidence: 'verified', sources: [ { - url: 'https://www.lindy.ai/blog/flowise-pricing', - label: 'Lindy: Flowise Pricing, Features, and Alternatives for 2026', - asOf: '2026-07-02', + url: 'https://flowiseai.com', + label: 'Flowise: Pricing', + asOf: '2026-10-19', }, ], }, entryPaidPlan: { value: - 'The cheapest paid Cloud plan (Starter) is reported at $35/month, including unlimited flows, 10,000 predictions/month, and 1GB storage.', - detail: - "Sourced from third-party aggregator coverage; Flowise's own pricing page sits behind a login wall.", + 'The cheapest paid Cloud plan (Starter) is $35/month, with unlimited flows and assistants, 10,000 predictions per month, and 1GB of storage.', shortValue: '$35/month Starter: unlimited flows, 10k predictions, 1GB storage', - confidence: 'estimated', + confidence: 'verified', sources: [ { - url: 'https://www.lindy.ai/blog/flowise-pricing', - label: 'Lindy: Flowise Pricing, Features, and Alternatives for 2026', - asOf: '2026-07-02', + url: 'https://flowiseai.com', + label: 'Flowise: Pricing', + asOf: '2026-10-19', }, ], }, freeTier: { value: - 'Yes: a free Cloud plan exists with 2 flows/assistants, 100 predictions per month, and 5MB storage, with community support and Flowise embed branding.', + 'Yes: the free Cloud plan includes 2 flows and assistants, 100 predictions per month, and 5MB of storage, with community support.', shortValue: 'Yes: 2 flows, 100 predictions/month, 5MB storage', - confidence: 'estimated', + confidence: 'verified', sources: [ { - url: 'https://www.lindy.ai/blog/flowise-pricing', - label: 'Lindy: Flowise Pricing, Features, and Alternatives for 2026', - asOf: '2026-07-02', + url: 'https://flowiseai.com', + label: 'Flowise: Pricing', + asOf: '2026-10-19', }, ], }, @@ -682,14 +736,14 @@ export const flowiseProfile: CompetitorProfile = { }, dataResidency: { value: - 'Yes, indirectly: self-hosting (including on-prem/air-gapped Enterprise deployment) lets an organization fully control data location. There is no dedicated regional-cloud-hosting option for the managed Cloud product.', + 'Yes, indirectly: self-hosting the open-source code, or the on-premises deployment flowiseai.com describes for enterprises, lets an organization control data location. flowiseai.com documents no regional hosting option for the managed Cloud product.', shortValue: 'Yes via self-hosting/on-prem; no documented regional cloud option', confidence: 'estimated', sources: [ { - url: 'https://www.lindy.ai/blog/flowise-pricing', - label: 'Lindy: Flowise Pricing, Features, and Alternatives for 2026', - asOf: '2026-07-02', + url: 'https://flowiseai.com', + label: 'Flowise: Pricing', + asOf: '2026-10-19', }, ], }, @@ -934,48 +988,51 @@ export const flowiseProfile: CompetitorProfile = { support: { supportChannels: { value: - 'Flowise offers community support (GitHub, Discord) on free/self-hosted tiers, priority support on the Pro Cloud plan, and personalized/dedicated support on Enterprise.', - shortValue: 'Community (free), priority (Pro), dedicated (Enterprise)', - confidence: 'estimated', + 'flowiseai.com lists community support on the Free and Starter Cloud plans and priority support on the Pro plan. Official core-team presence on GitHub and Discord ended when the open-source project reached end of life on August 31, 2026.', + shortValue: 'Community (Free, Starter), priority (Pro); core team left GitHub/Discord', + confidence: 'verified', sources: [ { - url: 'https://www.lindy.ai/blog/flowise-pricing', - label: 'Lindy: Flowise Pricing, Features, and Alternatives for 2026', - asOf: '2026-07-02', + url: 'https://flowiseai.com', + label: 'Flowise: Pricing', + asOf: '2026-10-19', + }, + { + url: 'https://flowiseai.com/sunset', + label: 'Flowise: The Future of Flowise', + asOf: '2026-10-19', }, ], }, sla: { - value: - 'A formal SLA (99.99% uptime) is offered on the Enterprise plan per third-party coverage; no SLA is documented for lower tiers.', - detail: 'No official Flowise SLA page confirms this figure.', - shortValue: 'Yes, ~99.99% SLA claimed on Enterprise plan', - confidence: 'estimated', + value: 'Not documented: flowiseai.com publishes no uptime SLA for any plan.', + shortValue: 'No published SLA', + confidence: 'verified', sources: [ { - url: 'https://www.lindy.ai/blog/flowise-pricing', - label: 'Lindy: Flowise Pricing, Features, and Alternatives for 2026', - asOf: '2026-07-08', + url: 'https://flowiseai.com', + label: 'Flowise: Pricing', + asOf: '2026-10-19', }, ], }, community: { value: - "Flowise's GitHub repository has approximately 54,000 stars (Apache 2.0 licensed core), with an active Discord community whose exact member count is not published.", - shortValue: '~54,000 GitHub stars, active Discord community', + "Flowise's GitHub repository has approximately 55,000 stars (Apache 2.0 licensed core). The repository has been archived since August 2026, and official core-team presence on GitHub and Discord ended on August 31, 2026.", + shortValue: '~55,000 GitHub stars on an archived repository', confidence: 'verified', sources: [ { url: 'https://github.com/FlowiseAI/Flowise', label: 'GitHub: FlowiseAI/Flowise', - asOf: '2026-07-02', + asOf: '2026-10-19', }, ], }, companyMaturity: { value: - 'Flowise was founded in April 2023 (Y Combinator-backed), raised approximately $500K in early funding, and was acquired by Workday in August 2025, bringing enterprise backing while keeping the open-source Community Edition intact.', - shortValue: 'Founded 2023 (YC), acquired by Workday Aug 2025', + 'Flowise was founded in April 2023 (Y Combinator-backed), raised approximately $500K in early funding, and was acquired by Workday in August 2025. The open-source project reached end of life on August 31, 2026.', + shortValue: 'Founded 2023 (YC), acquired by Workday Aug 2025, EOL Aug 2026', confidence: 'verified', sources: [ { @@ -983,6 +1040,11 @@ export const flowiseProfile: CompetitorProfile = { label: 'PR Newswire: Workday Acquires Flowise', asOf: '2026-07-02', }, + { + url: 'https://flowiseai.com/sunset', + label: 'Flowise: The Future of Flowise', + asOf: '2026-10-19', + }, ], }, academy: { diff --git a/apps/sim/lib/compare/data/competitors/retool.ts b/apps/sim/lib/compare/data/competitors/retool.ts index 5f4fb90bfb3..44e78bff9f4 100644 --- a/apps/sim/lib/compare/data/competitors/retool.ts +++ b/apps/sim/lib/compare/data/competitors/retool.ts @@ -23,6 +23,31 @@ export const retoolProfile: CompetitorProfile = { }, oneLiner: 'Retool is a low-code platform for building, deploying, and managing internal software (apps, workflows, and AI agents) that connect to databases, APIs, and LLMs.', + leadAnswer: [ + 'Retool is a proprietary low-code platform for building internal software: React-based apps, workflows, and a separate Agents product connected to databases, APIs, and LLMs, on Retool Cloud or self-hosted with a Retool-issued license key. Sim is an open-source AI workspace with an Apache 2.0-licensed core, where teams build, deploy, and manage AI agents visually, conversationally, or with code, on Sim Cloud or self-hosted. Choose Retool when the deliverable is an internal app with a user interface on top of production data, such as an admin panel or dashboard. Choose Sim when the deliverable is the agent or automation itself, built in one workflow builder and deployed as a REST API, chat interface, or MCP tool, with built-in evaluation and guardrail blocks.', + ], + faqs: [ + { + question: 'Sim vs Retool for building internal AI tools: which should I use?', + answer: + "Use Retool when the internal tool needs a full app interface, and Sim when the tool is an AI agent or automation. Retool builds internal business applications with a React frontend and TypeScript backend, and its AppGen generates the UI, data queries, and bindings from a plain-English prompt while inheriting the org's existing authentication and access controls. Retool Agents is a separate product that combines deterministic code, LLM decisions, and human-in-the-loop steps. Sim combines agent, function, and human approval blocks in a single workflow builder, builds and edits workflows from natural language through Chat and Copilot, and deploys them as a REST API, a public chat interface, or an MCP tool.", + }, + { + question: 'What is a Retool alternative for building AI agents rather than internal apps?', + answer: + "Sim is built for AI agents first, while Retool's agent capabilities sit beside an internal app builder. Retool Agents usage is billed by the hour, separately from the monthly AI-credit allocation, and Retool documents no dedicated evaluation or guardrail framework for agent output, nor a way to publish an individual app or workflow as its own MCP tool. Sim includes an Evaluator block that scores outputs against named metrics, a Guardrails block for JSON validation, regex, hallucination scoring, and PII masking, reusable Agent Skills, and publishing of any deployed workflow as an MCP tool.", + }, + { + question: 'Is Retool open source, and can it be self-hosted?', + answer: + "Retool is proprietary and closed-source, while Sim's core is licensed under Apache 2.0. Retool's pricing page lists self-hosting on its Enterprise plan, where a deployment still requires a Retool-issued license key, and its Docker Compose setup is for non-production testing only, so production self-hosting runs on Kubernetes with Helm. Sim can be self-hosted with Docker Compose or Kubernetes (Helm), and its enterprise features, such as SSO and audit logs, require a Sim Enterprise license.", + }, + { + question: 'How do Sim and Retool compare for knowledge bases and RAG?', + answer: + "Sim's knowledge base combines vector and full-text search and keeps sources in sync, while Retool Vectors is a managed vector store for uploaded content. Retool Vectors indexes text, PDFs, and web pages, and it generates embeddings with an OpenAI embedding model (text-embedding-ada-002 by default) that cannot be changed after a vector is created. Sim stores embeddings in pgvector alongside a full-text index, supports 15 file types with configurable chunking, uses connectors to continuously sync sources such as Google Drive, Confluence, and Slack, and shows chunk-level detail with a dedicated chunk editor.", + }, + ], standoutFeatures: [ { title: 'Full internal business applications, not just agent workflows', @@ -138,16 +163,21 @@ export const retoolProfile: CompetitorProfile = { }, selfHostOption: { value: - 'Yes: self-hosted deployment is available on Free, Team, and Business plans at the same pricing as cloud; Enterprise is required for unlimited users and advanced capabilities. A Retool-issued license key is required even when self-hosted.', + 'Yes, on Enterprise: the pricing page lists Retool Cloud, self-hosted, or air-gapped deployment only on the Enterprise plan, while Free, Team, and Business include Retool Cloud. A Retool-issued license key is required even when self-hosted.', detail: 'Self-hosted Retool is deployable via Docker (non-production/testing only) or Kubernetes/Helm (production).', - shortValue: 'Yes, on Free/Team/Business; license key required', + shortValue: 'Yes, on Enterprise; license key required', confidence: 'verified', sources: [ { - url: 'https://retool.com/self-hosted', + url: 'https://retool.com/pricing', + label: 'Retool Pricing', + asOf: '2026-10-19', + }, + { + url: 'https://retool.com/govern-enterprise-apps/self-hosted', label: 'Retool Self Hosted', - asOf: '2026-07-02', + asOf: '2026-10-19', }, { url: 'https://docs.retool.com/self-hosted/tutorials/docker', diff --git a/apps/sim/lib/compare/data/competitors/zapier.ts b/apps/sim/lib/compare/data/competitors/zapier.ts index 55632637da7..d63acf873c1 100644 --- a/apps/sim/lib/compare/data/competitors/zapier.ts +++ b/apps/sim/lib/compare/data/competitors/zapier.ts @@ -29,6 +29,31 @@ export const zapierProfile: CompetitorProfile = { }, oneLiner: 'Zapier is a cloud-based, proprietary no-code/low-code automation platform built around "Zaps": trigger-action workflows connecting thousands of web apps. It recently added AI features, including Copilot for building, Agents for autonomous multi-step tasks, and an MCP server.', + leadAnswer: [ + "Zapier is a proprietary, cloud-only automation platform built around trigger-action Zaps across 9,000+ apps, with Copilot for natural-language building, a separate Agents product, and a hosted MCP server. Sim is an open-source AI workspace with an Apache 2.0-licensed core, where teams build, deploy, and manage AI agents visually, conversationally, or with code, on Sim Cloud or self-hosted. Choose Zapier when you need the largest catalog of prebuilt app connectors for trigger-action automations that run entirely on Zapier's cloud. Choose Sim when AI agents are the core of the workflow, or when you need self-hosting, live multiplayer editing, native parallel branches, or a knowledge base with hybrid search that continuously syncs external sources.", + ], + faqs: [ + { + question: 'How does Sim compare to Zapier for building AI agents?', + answer: + 'Sim builds agents inside the same workflow builder as the rest of the automation, while Zapier runs AI agents in a separate Agents product alongside its trigger-action Zaps. Zapier Agents can reference FAQs, docs, and public links as knowledge, but Zapier does not publicly detail the retrieval implementation or document dedicated evaluation or guardrail tooling. Sim provides agent blocks across 28 LLM provider integrations, a knowledge base with hybrid vector and full-text search plus connectors that continuously sync sources such as Google Drive and Confluence, and Evaluator and Guardrails blocks that score outputs and detect or mask PII. Both products offer a dedicated human approval step that pauses a run mid-workflow.', + }, + { + question: 'Is there a Zapier alternative that can be self-hosted?', + answer: + 'Sim can be self-hosted with Docker Compose or Kubernetes (Helm) under its Apache 2.0-licensed core, while Zapier is closed-source SaaS with no self-hosted or on-prem option. Zapier runs on AWS in the United States with no selectable data residency for standard customers. A self-hosted Sim deployment keeps data on your own infrastructure, and Sim Cloud remains available as a managed alternative. Enterprise features such as SSO and audit logs require a Sim Enterprise license in either mode.', + }, + { + question: "How does Zapier's task-based billing compare to Sim's credit-based billing?", + answer: + "Zapier meters every completed action step as a task, with trigger steps free and each MCP tool call costing 2 tasks, so cost tracks execution volume. Bringing your own AI provider key to Zapier covers only AI-powered steps and chatbots, not the app-action tasks themselves. Zapier's Free plan includes 100 tasks per month limited to two-step Zaps, and its Professional plan starts at $19.99/month (annual billing) for 750 tasks. Sim uses credit-based usage billing, where bring-your-own-key calls skip Sim's model and tool usage charge and only the base per-run charge applies; its Free plan grants 1,000 one-time credits (worth $5) at signup, and Pro is $25 per user/month.", + }, + { + question: 'Can I publish my own automations as MCP tools with Zapier or Sim?', + answer: + "Sim can publish any deployed workflow as a tool on an MCP server, while Zapier's MCP server only exposes Zapier's own library of app actions. Zapier MCP lets AI clients call actions from its 9,000+ app connections, and an MCP Client by Zapier integration lets a Zap call external MCP servers, but there is no documented way to publish your own Zap as an MCP endpoint. Sim works as both an MCP client and an MCP server, with private, API-key protected, or public servers and ready-to-paste client config for Codex, Cursor, Claude Code, Claude Desktop, and VS Code.", + }, + ], standoutFeatures: [ { title: '9,000+ pre-built app integrations', diff --git a/apps/sim/lib/compare/data/sim.ts b/apps/sim/lib/compare/data/sim.ts index c4169ff26d3..a25d488304d 100644 --- a/apps/sim/lib/compare/data/sim.ts +++ b/apps/sim/lib/compare/data/sim.ts @@ -798,7 +798,7 @@ export const simProfile: CompetitorProfile = { }, freeTier: { value: - 'Yes: Free plan with 1,000 one-time credits (worth $5, env-configurable), granted once at signup and never refreshed — no monthly reset and no weekly refresh (weekly refresh is a paid-plan feature)', + 'Yes: Free plan with 1,000 one-time credits (worth $5 on Sim Cloud), granted once at signup and never refreshed — no monthly reset and no weekly refresh (weekly refresh is a paid-plan feature)', shortValue: 'Free plan, 1,000 one-time credits', confidence: 'verified', sources: [ diff --git a/apps/sim/lib/compare/data/types.ts b/apps/sim/lib/compare/data/types.ts index 57827d88881..60ba1f9cbb7 100644 --- a/apps/sim/lib/compare/data/types.ts +++ b/apps/sim/lib/compare/data/types.ts @@ -232,6 +232,13 @@ export interface CompetitorProfile { * decides it. 3-5 sentences. */ betterThanAnswer?: Prose + /** + * Replaces the generated "Choose {name} if…" verdict, which is built from the + * first standout feature. Set it when that feature no longer makes the + * competitor a recommendation, such as a discontinued product. Names the + * competitor and states the condition under which it still fits. + */ + chooseCompetitorAnswer?: string /** * Hand-written FAQs answering this competitor's tracked buyer prompts, shown * ahead of the generated FAQ set. Answer-first, and every answer names both diff --git a/apps/sim/lib/landing/routes.ts b/apps/sim/lib/landing/routes.ts index 82b0019f0b6..42be1a77554 100644 --- a/apps/sim/lib/landing/routes.ts +++ b/apps/sim/lib/landing/routes.ts @@ -15,6 +15,10 @@ * landing footer's toggle, instead of the app's account-synced `system` * default. Leave one out and a visitor on a dark OS lands on a dark * marketing page. + * - The proxy marks these paths, and the marketing documents in + * {@link isMarketingPath}, `noindex` on self-hosted deployments, so a + * third-party copy of the marketing site never competes with sim.ai in search + * or answer engines. Leave one out and that page stays indexable there. * * Imported by `next.config.ts` before the `@/` alias resolves, so this module * must stay dependency-free. @@ -45,3 +49,27 @@ export const LANDING_ROUTES = [ 'terms', 'workflows', ] as const + +const LANDING_SEGMENTS: ReadonlySet = new Set(LANDING_ROUTES) + +/** Whether a pathname is the root or sits under an `app/(landing)` segment. */ +export function isLandingPath(pathname: string): boolean { + const firstSegment = pathname.split('/')[1] + return firstSegment === '' || LANDING_SEGMENTS.has(firstSegment) +} + +/** + * Root-level documents that republish the marketing site's content for crawlers + * and answer engines. They are served outside `app/(landing)`, so + * {@link isLandingPath} does not cover them. + */ +const MARKETING_DOCUMENT_PATHS: ReadonlySet = new Set([ + '/llms.txt', + '/llms-full.txt', + '/changelog.xml', +]) + +/** Whether a pathname serves marketing content: a landing page or a marketing document. */ +export function isMarketingPath(pathname: string): boolean { + return isLandingPath(pathname) || MARKETING_DOCUMENT_PATHS.has(pathname) +} diff --git a/apps/sim/lib/mcp/client.ts b/apps/sim/lib/mcp/client.ts index 0c466bbfe3d..3f843362398 100644 --- a/apps/sim/lib/mcp/client.ts +++ b/apps/sim/lib/mcp/client.ts @@ -150,6 +150,9 @@ export class McpClient { authProvider: useOauth ? this.authProvider : undefined, ...(useOauth ? {} : { requestInit: { headers: this.config.headers } }), fetch: (input, init) => runInOutboundScope(() => transportFetch(input, init)), + // The SDK's default ('same-origin') refuses cross-origin redirects; the guarded + // fetch already follows them with per-hop SSRF checks and cross-origin header stripping. + redirectPolicy: 'follow', }) this.client = new Client( diff --git a/apps/sim/lib/mcp/oauth/probe.ts b/apps/sim/lib/mcp/oauth/probe.ts index c39f521fe01..0af0b40484d 100644 --- a/apps/sim/lib/mcp/oauth/probe.ts +++ b/apps/sim/lib/mcp/oauth/probe.ts @@ -39,7 +39,8 @@ export async function detectMcpAuthType( const pinned = resolvedIP ? createPinnedFetchWithDispatcher(resolvedIP, { profile: MCP_EGRESS_PROFILE }) : undefined - const probeFetch: FetchLike = pinned?.fetch ?? createSsrfGuardedMcpFetch({ serverUrl: url }) + const probeFetch: FetchLike = + pinned?.fetch ?? createSsrfGuardedMcpFetch({ serverUrl: url, followRedirects: 'caller' }) const controller = new AbortController() const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS) diff --git a/apps/sim/lib/mcp/pinned-fetch.ts b/apps/sim/lib/mcp/pinned-fetch.ts index 84a044f9bda..959a29532e9 100644 --- a/apps/sim/lib/mcp/pinned-fetch.ts +++ b/apps/sim/lib/mcp/pinned-fetch.ts @@ -1,6 +1,7 @@ import type { FetchLike } from '@modelcontextprotocol/sdk/shared/transport.js' import { createLogger } from '@sim/logger' import { isPrivateIp } from '@sim/security/ssrf' +import { omit } from '@sim/utils/object' import { createPinnedFetchWithDispatcher, createSsrfGuardedFetchWithDispatcher, @@ -125,6 +126,11 @@ function requestHref(input: string | URL | Request): string { return String(input) } +/** Drops the caller's `redirect` mode, so the guard follows under per-hop validation. */ +function withoutRedirectMode(init: RequestInit | undefined): RequestInit | undefined { + return init?.redirect === undefined ? init : omit(init, ['redirect']) +} + /** * Wraps the transport's same-origin fetch so a request to any other origin is * judged as content and validated per request, never inheriting the configured @@ -137,15 +143,21 @@ function requestHref(input: string | URL | Request): string { * {@link MCP_EGRESS_PROFILE} (honors the allowlist, permits loopback off-hosted) * or, worse, be pinned onto the server's private address. Same-origin requests — * the JSON-RPC transport itself — keep the persistent guarded path. + * + * Both paths follow redirects themselves whatever `redirect` mode arrives: the SDK + * also hands this fetch to its direct `auth()` calls (coordinated refresh), which + * send `redirect: 'manual'` and follow only same-origin hops on their own. */ function splitByConfiguredOrigin( sameOrigin: typeof fetch, serverUrl: string | undefined ): typeof fetch { - if (!serverUrl || !URL.canParse(serverUrl)) return sameOrigin - const configuredOrigin = new URL(serverUrl).origin - const crossOrigin = createSsrfGuardedMcpFetch({ serverUrl }) - return async (input, init) => { + const configuredOrigin = + serverUrl && URL.canParse(serverUrl) ? new URL(serverUrl).origin : undefined + const crossOrigin = configuredOrigin ? createSsrfGuardedMcpFetch({ serverUrl }) : undefined + return async (input, callerInit) => { + const init = withoutRedirectMode(callerInit) + if (!crossOrigin) return sameOrigin(input, init) const target = requestHref(input) const sameAsConfigured = URL.canParse(target) && new URL(target).origin === configuredOrigin return sameAsConfigured ? sameOrigin(input, init) : crossOrigin(target, init) @@ -342,19 +354,24 @@ function releaseStreamOnSettle( * `Response` is a detached in-memory copy. Only our own deadline is relabeled to `McpError`. * * @param timeoutMs Per-request deadline in ms (defaults to 30s; override for tests). + * @param followRedirects Who decides how a redirect is handled. `'guard'` (default) ignores + * the request's `redirect` mode and always follows under per-hop validation, because the + * SDK's `auth()`/discovery sends `redirect: 'manual'` and would otherwise drop a + * cross-origin hop. `'caller'` honors it, for the auth-type probe that needs a 3xx as is. * @throws McpSsrfError if a request URL resolves to a blocked IP address * @throws McpError if a request exceeds `timeoutMs` */ export function createSsrfGuardedMcpFetch( - options: { serverUrl?: string; timeoutMs?: number } = {} + options: { serverUrl?: string; timeoutMs?: number; followRedirects?: 'caller' | 'guard' } = {} ): FetchLike { - const { serverUrl, timeoutMs = OAUTH_FETCH_TIMEOUT_MS } = options + const { serverUrl, timeoutMs = OAUTH_FETCH_TIMEOUT_MS, followRedirects = 'guard' } = options // The origin the operator configured. A leg that stays on it is the server // they chose; everything else was named by that server's metadata. let configuredOrigin: string | undefined if (serverUrl && URL.canParse(serverUrl)) configuredOrigin = new URL(serverUrl).origin - return (async (url, init) => { + return (async (url, callerInit) => { + const init = followRedirects === 'guard' ? withoutRedirectMode(callerInit) : callerInit const target = typeof url === 'string' ? url : url.href const host = URL.canParse(target) ? new URL(target).host : target const sameAsConfigured = diff --git a/apps/sim/lib/mcp/tool-validation.ts b/apps/sim/lib/mcp/tool-validation.ts index 0e91dfe7338..da491a6ba59 100644 --- a/apps/sim/lib/mcp/tool-validation.ts +++ b/apps/sim/lib/mcp/tool-validation.ts @@ -1,4 +1,5 @@ -import { isEqual, omit } from 'es-toolkit' +import { omit } from '@sim/utils/object' +import { isEqual } from 'es-toolkit' import type { McpToolSchema, StoredMcpToolReference } from '@/lib/mcp/types' export type McpToolIssueType = diff --git a/apps/sim/lib/mcp/transport-redirects.server.test.ts b/apps/sim/lib/mcp/transport-redirects.server.test.ts new file mode 100644 index 00000000000..f8d05d2a299 --- /dev/null +++ b/apps/sim/lib/mcp/transport-redirects.server.test.ts @@ -0,0 +1,242 @@ +/** + * Redirects across the MCP SDK and our SSRF guard, over real loopback sockets. + * Nothing between the SDK and the wire is mocked: two local servers on different + * ports are two origins, and loopback is reachable under the self-hosted profile. + */ +import { createServer, type IncomingHttpHeaders, type Server } from 'node:http' +import type { AddressInfo } from 'node:net' +import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js' +import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { McpClient } from '@/lib/mcp/client' +import { mcpAuthGuarded } from '@/lib/mcp/oauth/auth' +import { createGuardedMcpFetch } from '@/lib/mcp/pinned-fetch' + +interface RecordedRequest { + method: string + url: string + headers: IncomingHttpHeaders +} + +interface LocalServer { + origin: string + requests: RecordedRequest[] + close: () => Promise +} + +type Handler = ( + request: RecordedRequest, + body: string, + respond: (status: number, headers: Record, body?: string) => void +) => void + +const servers: LocalServer[] = [] + +async function startServer(handler: Handler): Promise { + const requests: RecordedRequest[] = [] + const server: Server = createServer((req, res) => { + const chunks: Buffer[] = [] + req.on('data', (chunk: Buffer) => chunks.push(chunk)) + req.on('end', () => { + const recorded = { method: req.method ?? '', url: req.url ?? '', headers: req.headers } + requests.push(recorded) + handler(recorded, Buffer.concat(chunks).toString('utf8'), (status, headers, body) => { + res.writeHead(status, headers) + if (body === undefined) res.flushHeaders() + else res.end(body) + }) + }) + }) + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() as AddressInfo + const local: LocalServer = { + origin: `http://127.0.0.1:${port}`, + requests, + close: () => + new Promise((resolve) => { + server.closeAllConnections() + server.close(() => resolve()) + }), + } + servers.push(local) + return local +} + +afterEach(async () => { + await Promise.all(servers.splice(0).map((server) => server.close())) +}) + +describe('MCP transport redirects', () => { + it('follows a cross-origin redirect of the SSE stream through the guard, without the configured credentials', async () => { + const www = await startServer((_request, _body, respond) => { + respond(200, { 'content-type': 'text/event-stream' }) + }) + const apex = await startServer((request, body, respond) => { + if (request.method === 'GET') { + respond(302, { location: `${www.origin}/mcp` }, '') + return + } + if (request.method === 'DELETE') { + respond(200, {}, '') + return + } + const message = JSON.parse(body) as { id?: number; method: string } + if (message.method !== 'initialize') { + respond(202, {}, '') + return + } + respond( + 200, + { 'content-type': 'application/json', 'mcp-session-id': 'session-1' }, + JSON.stringify({ + jsonrpc: '2.0', + id: message.id, + result: { + protocolVersion: LATEST_PROTOCOL_VERSION, + capabilities: {}, + serverInfo: { name: 'apex', version: '1.0.0' }, + }, + }) + ) + }) + + const client = new McpClient({ + config: { + id: 'server-1', + name: 'Apex', + transport: 'streamable-http', + url: `${apex.origin}/mcp`, + headers: { Authorization: 'Bearer configured-secret', 'X-Api-Key': 'configured-key' }, + }, + }) + try { + await client.connect() + await vi.waitFor(() => expect(www.requests).toHaveLength(1), { timeout: 5_000 }) + } finally { + await client.disconnect() + } + + const apexStream = apex.requests.find((request) => request.method === 'GET') + expect(apexStream?.headers.authorization).toBe('Bearer configured-secret') + const [redirected] = www.requests + expect(redirected.method).toBe('GET') + expect(redirected.url).toBe('/mcp') + expect(redirected.headers.authorization).toBeUndefined() + expect(redirected.headers['x-api-key']).toBeUndefined() + expect(redirected.headers['mcp-session-id']).toBeUndefined() + }) +}) + +const OAUTH_FETCHES = [ + { name: 'the default OAuth fetch', fetchFn: () => undefined }, + { + name: 'the transport fetch reused by coordinated refresh', + fetchFn: (serverUrl: string) => createGuardedMcpFetch(serverUrl).fetch, + }, +] as const + +function recordingProvider() { + const authorizationUrls: URL[] = [] + const provider: OAuthClientProvider = { + get redirectUrl() { + return 'http://127.0.0.1/callback' + }, + get clientMetadata() { + return { redirect_uris: ['http://127.0.0.1/callback'] } + }, + clientInformation: () => ({ client_id: 'client-1' }), + tokens: () => undefined, + saveTokens: () => {}, + redirectToAuthorization: (url) => { + authorizationUrls.push(url) + }, + saveCodeVerifier: () => {}, + codeVerifier: () => 'verifier-1', + } + return { provider, authorizationUrls } +} + +function authorizationServerMetadata(origin: string) { + return JSON.stringify({ + issuer: origin, + authorization_endpoint: `${origin}/authorize`, + token_endpoint: `${origin}/token`, + response_types_supported: ['code'], + code_challenge_methods_supported: ['S256'], + }) +} + +describe('MCP OAuth redirects', () => { + it.each(OAUTH_FETCHES)( + 'follows a cross-origin redirect of protected-resource discovery through $name, without its headers', + async ({ fetchFn }) => { + const json = { 'content-type': 'application/json' } + let apexOrigin = '' + const www = await startServer((_request, _body, respond) => { + respond( + 200, + json, + JSON.stringify({ resource: `${apexOrigin}/mcp`, authorization_servers: [apexOrigin] }) + ) + }) + const apex = await startServer((request, _body, respond) => { + if (request.url === '/.well-known/oauth-protected-resource/mcp') { + respond(302, { location: `${www.origin}${request.url}` }, '') + } else if (request.url === '/.well-known/oauth-authorization-server') { + respond(200, json, authorizationServerMetadata(apexOrigin)) + } else { + respond(404, {}, '') + } + }) + apexOrigin = apex.origin + const serverUrl = `${apex.origin}/mcp` + const { provider, authorizationUrls } = recordingProvider() + + await expect( + mcpAuthGuarded(provider, { serverUrl, fetchFn: fetchFn(serverUrl) }) + ).resolves.toBe('REDIRECT') + + expect(apex.requests[0].headers['mcp-protocol-version']).toBeDefined() + expect(www.requests).toHaveLength(1) + expect(www.requests[0].headers['mcp-protocol-version']).toBeUndefined() + expect(www.requests[0].headers.authorization).toBeUndefined() + expect(authorizationUrls).toHaveLength(1) + expect(authorizationUrls[0].searchParams.get('resource')).toBe(serverUrl) + } + ) + + it.each(OAUTH_FETCHES)( + 'refuses a cross-origin 307 of the token request through $name, as it would forward the body', + async ({ fetchFn }) => { + const elsewhere = await startServer((_request, _body, respond) => respond(200, {}, '{}')) + const apex = await startServer((request, _body, respond) => { + if (request.url === '/.well-known/oauth-authorization-server') { + respond( + 200, + { 'content-type': 'application/json' }, + authorizationServerMetadata(apex.origin) + ) + } else if (request.url === '/token') { + respond(307, { location: `${elsewhere.origin}/token` }, '') + } else { + respond(404, {}, '') + } + }) + const serverUrl = `${apex.origin}/mcp` + const { provider } = recordingProvider() + + await expect( + mcpAuthGuarded(provider, { + serverUrl, + authorizationCode: 'code-1', + fetchFn: fetchFn(serverUrl), + }) + ).rejects.toThrow( + 'Blocked by SSRF policy: cross-origin redirect would forward a request body' + ) + + expect(apex.requests.filter((request) => request.url === '/token')).toHaveLength(1) + expect(elsewhere.requests).toHaveLength(0) + } + ) +}) diff --git a/apps/sim/lib/mothership/generated/account.ts b/apps/sim/lib/mothership/generated/account.ts deleted file mode 100644 index 13514c76a54..00000000000 --- a/apps/sim/lib/mothership/generated/account.ts +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED — do not edit. Source of truth: mothership worker packages/contracts/src/account.ts -// Regenerate with `bun run contracts:sync` in the worker. - -import { z } from "zod"; - -export const ListCustomerKeys = z.strictObject({ userId: z.string().trim().min(1).max(200) }); -export const CreateCustomerKey = ListCustomerKeys.extend({ name: z.string().trim().max(200).optional() }); -export const DeleteCustomerKey = ListCustomerKeys.extend({ apiKeyId: z.uuid() }); - -export const CleanupChats = z.strictObject({ chatIds: z.array(z.uuid()).min(1).max(1000) }); - -export const AdminReportRange = z - .strictObject({ - start: z.iso.datetime({ offset: true }), - end: z.iso.datetime({ offset: true }), - userId: z.string().max(200).optional(), - }) - .refine((range) => Date.parse(range.start) <= Date.parse(range.end), "Start must precede end"); -export const IssueLicense = z.strictObject({ - name: z.string().trim().min(1).max(200), - approvalReference: z.string().trim().min(1).max(2000), - expirationDate: z.string().trim().optional(), -}); -export const LicenseDetails = z - .strictObject({ id: z.uuid().optional(), name: z.string().min(1).max(200).optional() }) - .refine((input) => Boolean(input.id || input.name), "License id or name is required"); diff --git a/apps/sim/lib/mothership/generated/computer-use.ts b/apps/sim/lib/mothership/generated/computer-use.ts deleted file mode 100644 index 71f908ed036..00000000000 --- a/apps/sim/lib/mothership/generated/computer-use.ts +++ /dev/null @@ -1,300 +0,0 @@ -// GENERATED — do not edit. Source of truth: mothership worker packages/contracts/src/computer-use.ts -// Regenerate with `bun run contracts:sync` in the worker. - -import { z } from "zod"; - -const BundleId = z - .string() - .min(1) - .max(255) - .describe( - "Exact bundleId returned by list_apps or a known installed macOS app. get_app_state may launch it.", - ); -const SnapshotId = z - .string() - .min(1) - .max(128) - .describe("Latest unconsumed snapshotId from get_app_state for this app. Actions consume it."); -const ElementId = z.string().min(1).max(128).describe("Element reference from that snapshot."); -const WindowId = z.string().min(1).max(128).describe("Window reference from that snapshot."); -/** Input fields shared across actions keep one description: Anthropic flattens the union and anyOfs differing copies. */ -const TargetWindowId = WindowId.optional().describe( - "Window reference from an earlier snapshot of this app. If omitted, get_app_state and press_key use the app's focused window; coordinate actions use the window the snapshot selected.", -); -const IncludeScreenshot = z - .boolean() - .optional() - .describe( - "Set true to include a screenshot in the returned state. On actions it overrides observeAfter.includeScreenshot.", - ); -const Coordinate = z.number().nonnegative().max(100_000); -const ObserveAfter = { - observeAfter: z - .strictObject({ includeScreenshot: z.boolean().optional() }) - .optional() - .describe( - "Every mutation returns fresh app state by default. Set includeScreenshot to true to include an image; otherwise no screenshot is captured. Observation failure never retries the action.", - ), - includeScreenshot: IncludeScreenshot, -}; -const InputSequenceStep = z.discriminatedUnion("action", [ - z.strictObject({ action: z.literal("type_text"), text: z.string().max(32_000) }), - z.strictObject({ action: z.literal("press_key"), key: z.string().min(1).max(128) }), -]); -const ElementTarget = { bundleId: BundleId, snapshotId: SnapshotId, elementId: ElementId }; -const PointTarget = { - bundleId: BundleId, - snapshotId: SnapshotId, - windowId: TargetWindowId, - x: Coordinate, - y: Coordinate, -}; -const ClickOptions = { - button: z.enum(["left", "right"]).optional(), - clickCount: z.number().int().min(1).max(3).optional(), -}; -const ScrollOffsets = { - deltaX: z - .number() - .min(-10_000) - .max(10_000) - .optional() - .describe( - "Horizontal scroll distance in points; positive scrolls right, negative left. Omit for 0 when deltaY is set.", - ), - deltaY: z - .number() - .min(-10_000) - .max(10_000) - .optional() - .describe( - "Vertical scroll distance in points; positive scrolls down, negative up. Omit for 0 when deltaX is set.", - ), -}; - -const hasScrollDistance = (input: { deltaX?: number | undefined; deltaY?: number | undefined }) => - input.deltaX !== undefined || input.deltaY !== undefined; - -/** Strict branches require an observed, unambiguous target before native input. */ -export const ComputerUseSchema = z.union([ - z.strictObject({ action: z.literal("status") }), - z.strictObject({ action: z.literal("list_apps") }), - z.strictObject({ action: z.literal("activate_app"), bundleId: BundleId, ...ObserveAfter }), - z.strictObject({ - action: z.literal("get_app_state"), - bundleId: BundleId, - windowId: TargetWindowId, - includeScreenshot: IncludeScreenshot, - }), - z.strictObject({ action: z.literal("click"), ...ElementTarget, ...ObserveAfter, ...ClickOptions }), - z.strictObject({ action: z.literal("click"), ...PointTarget, ...ObserveAfter, ...ClickOptions }), - z.strictObject({ - action: z.literal("type_text"), - ...ObserveAfter, - ...ElementTarget, - text: z.string().max(32_000).describe("Literal text to type into the observed editable element."), - }), - z.strictObject({ - action: z.literal("press_key"), - ...ObserveAfter, - bundleId: BundleId, - snapshotId: SnapshotId, - windowId: TargetWindowId, - elementId: ElementId.optional(), - key: z - .string() - .min(1) - .max(128) - .describe( - "One key or chord, such as Enter, Tab, Escape, or Cmd+A. With elementId, focuses that element first.", - ), - }), - z - .strictObject({ action: z.literal("scroll"), ...ElementTarget, ...ObserveAfter, ...ScrollOffsets }) - .refine(hasScrollDistance, "Scroll needs deltaX or deltaY."), - z - .strictObject({ action: z.literal("scroll"), ...PointTarget, ...ObserveAfter, ...ScrollOffsets }) - .refine(hasScrollDistance, "Scroll needs deltaX or deltaY."), - z.strictObject({ - action: z.literal("drag"), - ...PointTarget, - ...ObserveAfter, - toX: Coordinate, - toY: Coordinate, - }), - z.strictObject({ - action: z.literal("set_value"), - ...ObserveAfter, - ...ElementTarget, - value: z.string().max(32_000).describe("Replacement value for an accessibility-writable element."), - }), - z.strictObject({ - action: z.literal("input_sequence"), - ...ElementTarget, - ...ObserveAfter, - activateFirst: z - .boolean() - .optional() - .describe( - "Explicitly bring this app to the foreground before the batch. Defaults to false; leave omitted for background input. An inactive app alone does not require activation. Do not set true when the user requires background operation. Editor, window, and geometry checks still apply.", - ), - steps: z - .array(InputSequenceStep) - .min(1) - .max(32) - .refine( - (steps) => - steps.reduce((total, step) => total + (step.action === "type_text" ? step.text.length : 0), 0) <= - 32_000, - "Combined sequence text exceeds 32000 UTF-16 code units", - ) - .describe( - "Sequential input into this observed editor. Stops if editor/window focus changes. At most 32 steps and 32000 total text code units; never automatically retry a partial sequence.", - ), - }), - z.strictObject({ - action: z.literal("perform_action"), - ...ObserveAfter, - ...ElementTarget, - accessibilityAction: z.string().min(1).max(128).describe("Exact action advertised by this element."), - }), -]); -export type ComputerUseInput = z.infer; - -export const ComputerUseStatusSchema = z.strictObject({ - kind: z.literal("status"), - platform: z.literal("darwin"), - accessibility: z.boolean(), - screenRecording: z.boolean(), -}); -export type ComputerUseStatus = z.infer; - -export const ComputerUseAppSchema = z.strictObject({ - bundleId: BundleId, - name: z.string().max(1024), - pid: z.number().int().positive().optional(), - isActive: z.boolean(), -}); -export type ComputerUseApp = z.infer; - -export const ComputerUseWindowSchema = z.strictObject({ - windowId: WindowId, - title: z.string().max(4096), - x: z.number(), - y: z.number(), - width: z.number().nonnegative(), - height: z.number().nonnegative(), -}); -export type ComputerUseWindow = z.infer; - -const ComputerUseNodeSchema = z.strictObject({ - elementId: ElementId, - role: z.string().max(128), - parentId: ElementId.optional(), - label: z.string().max(8192).optional(), - value: z.string().max(32_000).optional(), - enabled: z.boolean().optional(), - focused: z - .boolean() - .optional() - .describe( - "Accessibility focus within this app; does not imply the app is foreground or an item is selected.", - ), - editable: z.boolean().optional(), - placeholder: z.string().max(1024).optional(), - actions: z.array(z.string().max(128)).max(128), - windowId: WindowId.optional(), - x: z.number().optional().describe("Global screen coordinate in points, not window-local."), - y: z.number().optional().describe("Global screen coordinate in points, not window-local."), - width: z.number().nonnegative().optional(), - height: z.number().nonnegative().optional(), -}); - -export const ComputerUseScreenshotSchema = z.strictObject({ - base64: z.string().min(1).max(11_200_000), - mimeType: z.literal("image/png"), - width: z.number().int().positive(), - height: z.number().int().positive(), -}); - -export const ComputerUseSnapshotSchema = z.strictObject({ - kind: z.literal("state"), - bundleId: BundleId, - isActive: z - .boolean() - .optional() - .describe( - "Whether this app was foreground when observed. Omitted means unknown; inactive apps may still support background controls.", - ), - snapshotId: SnapshotId, - windowId: WindowId, - windows: z.array(ComputerUseWindowSchema).max(100), - nodes: z.array(ComputerUseNodeSchema).max(2000), - truncated: z.boolean(), - screenshot: ComputerUseScreenshotSchema.optional(), - screenshotError: z.string().max(2000).optional(), -}); -export type ComputerUseSnapshot = z.infer; - -export const ComputerUseResultSchema = z.discriminatedUnion("kind", [ - ComputerUseStatusSchema, - z.strictObject({ kind: z.literal("apps"), apps: z.array(ComputerUseAppSchema).max(1000) }), - ComputerUseSnapshotSchema, - z - .strictObject({ - kind: z.literal("action"), - action: z.enum([ - "activate_app", - "click", - "type_text", - "press_key", - "scroll", - "drag", - "set_value", - "perform_action", - "input_sequence", - ]), - bundleId: BundleId, - dispatched: z.literal(true), - verified: z.boolean(), - sequence: z - .strictObject({ - completedSteps: z.number().int().min(0).max(32), - totalSteps: z.number().int().min(1).max(32), - error: z.string().min(1).max(2000).optional(), - }) - .optional(), - observation: ComputerUseSnapshotSchema.optional(), - observationError: z.string().min(1).max(2000).optional(), - }) - .superRefine((result, ctx) => { - if (result.action === "input_sequence" && !result.sequence) - ctx.addIssue({ - code: "custom", - path: ["sequence"], - message: "Input sequences require dispatch progress", - }); - if (result.sequence && result.sequence.completedSteps > result.sequence.totalSteps) - ctx.addIssue({ - code: "custom", - path: ["sequence", "completedSteps"], - message: "Completed steps cannot exceed total steps", - }); - }), -]); -export type ComputerUseResult = z.infer; - -export const ComputerUseNativeErrorSchema = z.strictObject({ - code: z.string().min(1).max(128), - message: z.string().min(1).max(2000), - dispatchState: z.literal("not_started").optional(), -}); -export type ComputerUseNativeError = z.infer; - -export const ComputerUseNativeReplySchema = z.union([ - z.strictObject({ id: z.string().min(1).max(128), result: ComputerUseResultSchema }), - z.strictObject({ - id: z.string().min(1).max(128), - error: ComputerUseNativeErrorSchema, - }), -]); diff --git a/apps/sim/lib/mothership/generated/local-files.ts b/apps/sim/lib/mothership/generated/local-files.ts deleted file mode 100644 index c61a102217d..00000000000 --- a/apps/sim/lib/mothership/generated/local-files.ts +++ /dev/null @@ -1,40 +0,0 @@ -// GENERATED — do not edit. Source of truth: mothership worker packages/contracts/src/local-files.ts -// Regenerate with `bun run contracts:sync` in the worker. - -import { z } from "zod"; - -export const ReadLocalFileSchema = z.strictObject({ - path: z - .string() - .min(1) - .max(4096) - .describe("Absolute path or ~/ path on the user's machine. A directory lists its children."), - offset: z.number().int().nonnegative().optional().describe("Byte offset for a text read, default 0."), - limit: z - .number() - .int() - .min(1) - .max(64000) - .optional() - .describe( - "Maximum text bytes, default 64000. Images/PDFs use visual content; PDFs include the first 20 pages.", - ), -}); -export const ImportLocalFilesSchema = z.strictObject({ - path: z - .string() - .min(1) - .max(4096) - .describe("Absolute path or ~/ path of a file or directory on the user's machine."), - targetWorkspaceId: z - .uuid() - .describe( - "Explicit destination Sim workspace ID. In organization mode, discover accessible workspaces before choosing the destination.", - ), - folderId: z - .uuid() - .optional() - .describe( - "Optional existing destination folder ID. Directories retain their root folder and subdirectories.", - ), -}); diff --git a/apps/sim/lib/mothership/generated/protocol.ts b/apps/sim/lib/mothership/generated/protocol.ts index fe803f93934..d73605eeed4 100644 --- a/apps/sim/lib/mothership/generated/protocol.ts +++ b/apps/sim/lib/mothership/generated/protocol.ts @@ -496,13 +496,6 @@ export const TitleRequest = z.strictObject({ }); export type TitleRequest = z.infer; -/** The 409 body for a duplicate send while a sibling instance streams (S32). */ -export interface ActiveStreamConflict { - error: "active_stream"; - streamId: string; - status: string; -} - /** The 426 body for protocol version skew (S43). */ export interface ProtocolMismatch { error: "protocol_version_mismatch"; diff --git a/apps/sim/lib/webhooks/providers/clerk.ts b/apps/sim/lib/webhooks/providers/clerk.ts index 09257d38ecc..631d1e03fd5 100644 --- a/apps/sim/lib/webhooks/providers/clerk.ts +++ b/apps/sim/lib/webhooks/providers/clerk.ts @@ -1,6 +1,4 @@ import { createLogger } from '@sim/logger' -import { safeCompare } from '@sim/security/compare' -import { hmacSha256Base64 } from '@sim/security/hmac' import { NextResponse } from 'next/server' import type { AuthContext, @@ -9,49 +7,10 @@ import type { FormatInputResult, WebhookProviderHandler, } from '@/lib/webhooks/providers/types' +import { verifyStandardWebhookRequest } from '@/lib/webhooks/providers/utils' const logger = createLogger('WebhookProvider:Clerk') -/** - * Verify a Clerk webhook signature using the Svix signing scheme. - * Clerk uses Svix under the hood: HMAC-SHA256 of `${svix-id}.${svix-timestamp}.${body}` - * signed with the base64-decoded `whsec_...` secret, compared against the - * space-delimited, versioned (`v1,`) `svix-signature` header. - */ -function verifySvixSignature( - secret: string, - msgId: string, - timestamp: string, - signatures: string, - rawBody: string -): boolean { - try { - const ts = Number.parseInt(timestamp, 10) - const now = Math.floor(Date.now() / 1000) - if (Number.isNaN(ts) || Math.abs(now - ts) > 5 * 60) { - return false - } - - const secretBytes = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') - const toSign = `${msgId}.${timestamp}.${rawBody}` - const expectedSignature = hmacSha256Base64(toSign, secretBytes) - - const providedSignatures = signatures.split(' ') - for (const versionedSig of providedSignatures) { - const parts = versionedSig.split(',') - if (parts.length !== 2) continue - const sig = parts[1] - if (safeCompare(sig, expectedSignature)) { - return true - } - } - return false - } catch (error) { - logger.error('Error verifying Clerk Svix signature:', error) - return false - } -} - export const clerkHandler: WebhookProviderHandler = { async verifyAuth({ request, @@ -65,19 +24,15 @@ export const clerkHandler: WebhookProviderHandler = { return new NextResponse('Unauthorized - Clerk signing secret is required', { status: 401 }) } - const svixId = request.headers.get('svix-id') - const svixTimestamp = request.headers.get('svix-timestamp') - const svixSignature = request.headers.get('svix-signature') - - if (!svixId || !svixTimestamp || !svixSignature) { - logger.warn(`[${requestId}] Clerk webhook missing Svix signature headers`) - return new NextResponse('Unauthorized - Missing Clerk signature headers', { status: 401 }) - } - - if (!verifySvixSignature(signingSecret, svixId, svixTimestamp, svixSignature, rawBody)) { - logger.warn(`[${requestId}] Clerk Svix signature verification failed`) - return new NextResponse('Unauthorized - Invalid Clerk signature', { status: 401 }) - } + const failure = verifyStandardWebhookRequest({ + request, + rawBody, + requestId, + secret: signingSecret, + providerLabel: 'Clerk', + headerPrefix: 'svix', + }) + if (failure) return failure return null }, diff --git a/apps/sim/lib/webhooks/providers/granola.ts b/apps/sim/lib/webhooks/providers/granola.ts index ce3738181df..b8dfb7dcb9f 100644 --- a/apps/sim/lib/webhooks/providers/granola.ts +++ b/apps/sim/lib/webhooks/providers/granola.ts @@ -1,6 +1,4 @@ import { createLogger } from '@sim/logger' -import { safeCompare } from '@sim/security/compare' -import { hmacSha256Base64 } from '@sim/security/hmac' import { toRecordOrNull } from '@sim/utils/object' import { NextResponse } from 'next/server' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' @@ -14,57 +12,16 @@ import type { SubscriptionResult, WebhookProviderHandler, } from '@/lib/webhooks/providers/types' +import { verifyStandardWebhookRequest } from '@/lib/webhooks/providers/utils' import { GRANOLA_TRIGGER_TO_EVENT_TYPES } from '@/triggers/granola/utils' const logger = createLogger('WebhookProvider:Granola') const GRANOLA_WEBHOOK_ENDPOINTS_URL = 'https://public-api.granola.ai/v1/webhook-endpoints' -/** Granola advises rejecting deliveries whose timestamp is more than a few minutes old. */ -const GRANOLA_WEBHOOK_TIMESTAMP_SKEW_SECONDS = 5 * 60 - /** Scopes used when the trigger leaves the field blank. */ const DEFAULT_GRANOLA_SCOPES = ['personal', 'public'] -/** - * Verify a Granola webhook signature using the Standard Webhooks scheme: - * HMAC-SHA256 of `${webhook-id}.${webhook-timestamp}.${body}` keyed with the - * base64-decoded signing secret (after the `whsec_` prefix), compared against - * the `webhook-signature` header which may carry one or more space-separated - * `v1,` entries. - * @see https://docs.granola.ai/webhooks - */ -function verifyGranolaSignature( - secret: string, - msgId: string, - timestamp: string, - signatures: string, - rawBody: string -): boolean { - try { - const ts = Number.parseInt(timestamp, 10) - const now = Math.floor(Date.now() / 1000) - if (Number.isNaN(ts) || Math.abs(now - ts) > GRANOLA_WEBHOOK_TIMESTAMP_SKEW_SECONDS) { - return false - } - - const secretBytes = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') - const expectedSignature = hmacSha256Base64(`${msgId}.${timestamp}.${rawBody}`, secretBytes) - - for (const versionedSig of signatures.split(' ')) { - const parts = versionedSig.split(',') - if (parts.length !== 2) continue - if (parts[0] === 'v1' && safeCompare(parts[1], expectedSignature)) { - return true - } - } - return false - } catch (error) { - logger.error('Error verifying Granola webhook signature:', error) - return false - } -} - /** * Parse a comma-separated or JSON-array trigger field into a string list. * Trigger fields are free text, so both forms are accepted. @@ -187,21 +144,14 @@ export const granolaHandler: WebhookProviderHandler = { return new NextResponse('Unauthorized - Granola signing secret is required', { status: 401 }) } - const webhookId = request.headers.get('webhook-id') - const webhookTimestamp = request.headers.get('webhook-timestamp') - const webhookSignature = request.headers.get('webhook-signature') - - if (!webhookId || !webhookTimestamp || !webhookSignature) { - logger.warn(`[${requestId}] Granola webhook missing Standard Webhooks signature headers`) - return new NextResponse('Unauthorized - Missing Granola signature headers', { status: 401 }) - } - - if ( - !verifyGranolaSignature(signingSecret, webhookId, webhookTimestamp, webhookSignature, rawBody) - ) { - logger.warn(`[${requestId}] Granola signature verification failed`) - return new NextResponse('Unauthorized - Invalid Granola signature', { status: 401 }) - } + const failure = verifyStandardWebhookRequest({ + request, + rawBody, + requestId, + secret: signingSecret, + providerLabel: 'Granola', + }) + if (failure) return failure return null }, diff --git a/apps/sim/lib/webhooks/providers/incidentio.ts b/apps/sim/lib/webhooks/providers/incidentio.ts index ff0eecba698..1f6acc5294a 100644 --- a/apps/sim/lib/webhooks/providers/incidentio.ts +++ b/apps/sim/lib/webhooks/providers/incidentio.ts @@ -1,6 +1,4 @@ import { createLogger } from '@sim/logger' -import { safeCompare } from '@sim/security/compare' -import { hmacSha256Base64 } from '@sim/security/hmac' import { toStringOrNull } from '@sim/utils/coerce' import { toRecordOrNull } from '@sim/utils/object' import { NextResponse } from 'next/server' @@ -11,53 +9,10 @@ import type { FormatInputResult, WebhookProviderHandler, } from '@/lib/webhooks/providers/types' +import { verifyStandardWebhookRequest } from '@/lib/webhooks/providers/utils' const logger = createLogger('WebhookProvider:Incidentio') -const INCIDENTIO_WEBHOOK_TIMESTAMP_SKEW_SECONDS = 5 * 60 - -/** - * Verify an incident.io webhook signature using the Svix signing scheme. - * incident.io webhooks are powered by Svix: HMAC-SHA256 of - * `${webhook-id}.${webhook-timestamp}.${body}` signed with the base64-decoded - * `whsec_...` secret, compared against the `webhook-signature` header which may - * carry one or more space-separated `v1,` entries. - * @see https://docs.incident.io/integrations/webhooks - */ -function verifyIncidentioSignature( - secret: string, - msgId: string, - timestamp: string, - signatures: string, - rawBody: string -): boolean { - try { - const ts = Number.parseInt(timestamp, 10) - const now = Math.floor(Date.now() / 1000) - if (Number.isNaN(ts) || Math.abs(now - ts) > INCIDENTIO_WEBHOOK_TIMESTAMP_SKEW_SECONDS) { - return false - } - - const secretBytes = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') - const toSign = `${msgId}.${timestamp}.${rawBody}` - const expectedSignature = hmacSha256Base64(toSign, secretBytes) - - const providedSignatures = signatures.split(' ') - for (const versionedSig of providedSignatures) { - const parts = versionedSig.split(',') - if (parts.length !== 2) continue - const sig = parts[1] - if (safeCompare(sig, expectedSignature)) { - return true - } - } - return false - } catch (error) { - logger.error('Error verifying incident.io Svix signature:', error) - return false - } -} - /** * Locate a named entity (incident/alert) within an incident.io webhook body. * @@ -97,29 +52,14 @@ export const incidentioHandler: WebhookProviderHandler = { }) } - const webhookId = request.headers.get('webhook-id') - const webhookTimestamp = request.headers.get('webhook-timestamp') - const webhookSignature = request.headers.get('webhook-signature') - - if (!webhookId || !webhookTimestamp || !webhookSignature) { - logger.warn(`[${requestId}] incident.io webhook missing Svix signature headers`) - return new NextResponse('Unauthorized - Missing incident.io signature headers', { - status: 401, - }) - } - - if ( - !verifyIncidentioSignature( - signingSecret, - webhookId, - webhookTimestamp, - webhookSignature, - rawBody - ) - ) { - logger.warn(`[${requestId}] incident.io Svix signature verification failed`) - return new NextResponse('Unauthorized - Invalid incident.io signature', { status: 401 }) - } + const failure = verifyStandardWebhookRequest({ + request, + rawBody, + requestId, + secret: signingSecret, + providerLabel: 'incident.io', + }) + if (failure) return failure return null }, diff --git a/apps/sim/lib/webhooks/providers/linq.ts b/apps/sim/lib/webhooks/providers/linq.ts index 819b87ce0c7..56ac42570fd 100644 --- a/apps/sim/lib/webhooks/providers/linq.ts +++ b/apps/sim/lib/webhooks/providers/linq.ts @@ -1,6 +1,4 @@ import { createLogger } from '@sim/logger' -import { safeCompare } from '@sim/security/compare' -import { hmacSha256Base64 } from '@sim/security/hmac' import { NextResponse } from 'next/server' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' import type { @@ -13,52 +11,12 @@ import type { SubscriptionResult, WebhookProviderHandler, } from '@/lib/webhooks/providers/types' +import { verifyStandardWebhookRequest } from '@/lib/webhooks/providers/utils' import { LINQ_API_BASE, linqHeaders } from '@/tools/linq/utils' import { LINQ_ALL_WEBHOOK_EVENT_TYPES, LINQ_TRIGGER_TO_EVENT_TYPE } from '@/triggers/linq/utils' const logger = createLogger('WebhookProvider:Linq') -/** Max clock skew tolerated between the webhook timestamp and now (seconds). */ -const MAX_TIMESTAMP_SKEW_SECONDS = 5 * 60 - -/** - * Verify a Linq webhook signature using the Standard Webhooks scheme. - * Linq signs `${webhook-id}.${webhook-timestamp}.${rawBody}` with HMAC-SHA256 using - * the base64-decoded `whsec_...` signing secret, and delivers the result as one or - * more space-separated `v1,` signatures in the `webhook-signature` header. - */ -function verifyLinqSignature( - secret: string, - msgId: string, - timestamp: string, - signatures: string, - rawBody: string -): boolean { - try { - const ts = Number.parseInt(timestamp, 10) - const now = Math.floor(Date.now() / 1000) - if (Number.isNaN(ts) || Math.abs(now - ts) > MAX_TIMESTAMP_SKEW_SECONDS) { - return false - } - - const secretBytes = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') - const toSign = `${msgId}.${timestamp}.${rawBody}` - const expectedSignature = hmacSha256Base64(toSign, secretBytes) - - for (const versionedSig of signatures.split(' ')) { - const parts = versionedSig.split(',') - if (parts.length !== 2) continue - if (safeCompare(parts[1], expectedSignature)) { - return true - } - } - return false - } catch (error) { - logger.error('Error verifying Linq webhook signature:', error) - return false - } -} - /** Parse a comma/whitespace-separated list of phone numbers into a clean array. */ function parsePhoneNumbers(value: unknown): string[] { if (typeof value !== 'string') return [] @@ -81,21 +39,14 @@ export const linqHandler: WebhookProviderHandler = { return new NextResponse('Unauthorized - Linq signing secret is required', { status: 401 }) } - const webhookId = request.headers.get('webhook-id') - const webhookTimestamp = request.headers.get('webhook-timestamp') - const webhookSignature = request.headers.get('webhook-signature') - - if (!webhookId || !webhookTimestamp || !webhookSignature) { - logger.warn(`[${requestId}] Linq webhook missing Standard Webhooks signature headers`) - return new NextResponse('Unauthorized - Missing Linq signature headers', { status: 401 }) - } - - if ( - !verifyLinqSignature(signingSecret, webhookId, webhookTimestamp, webhookSignature, rawBody) - ) { - logger.warn(`[${requestId}] Linq webhook signature verification failed`) - return new NextResponse('Unauthorized - Invalid Linq signature', { status: 401 }) - } + const failure = verifyStandardWebhookRequest({ + request, + rawBody, + requestId, + secret: signingSecret, + providerLabel: 'Linq', + }) + if (failure) return failure return null }, diff --git a/apps/sim/lib/webhooks/providers/loops.ts b/apps/sim/lib/webhooks/providers/loops.ts index 9df04c84858..756610de6f2 100644 --- a/apps/sim/lib/webhooks/providers/loops.ts +++ b/apps/sim/lib/webhooks/providers/loops.ts @@ -1,6 +1,4 @@ import { createLogger } from '@sim/logger' -import { safeCompare } from '@sim/security/compare' -import { hmacSha256Base64 } from '@sim/security/hmac' import { NextResponse } from 'next/server' import type { AuthContext, @@ -9,54 +7,10 @@ import type { FormatInputResult, WebhookProviderHandler, } from '@/lib/webhooks/providers/types' +import { verifyStandardWebhookRequest } from '@/lib/webhooks/providers/utils' const logger = createLogger('WebhookProvider:Loops') -const LOOPS_WEBHOOK_TIMESTAMP_SKEW_SECONDS = 5 * 60 - -/** - * Verify a Loops webhook signature. - * Loops uses a Svix-compatible signing scheme (its own implementation, not Svix-hosted): - * HMAC-SHA256 of `${webhookId}.${timestamp}.${body}` signed with the base64-decoded signing - * secret (provided as `prefix_base64string`). Delivery metadata arrives in the `Webhook-Id` and - * `Webhook-Timestamp` headers, and the `Webhook-Signature` header carries one or more - * space-separated `version,signature` pairs (e.g. `v1,`). - * @see https://loops.so/docs/webhooks - */ -function verifyLoopsSignature( - secret: string, - webhookId: string, - timestamp: string, - signatureHeader: string, - rawBody: string -): boolean { - try { - const ts = Number.parseInt(timestamp, 10) - const now = Math.floor(Date.now() / 1000) - if (Number.isNaN(ts) || Math.abs(now - ts) > LOOPS_WEBHOOK_TIMESTAMP_SKEW_SECONDS) { - return false - } - - const base64Secret = secret.includes('_') ? secret.slice(secret.indexOf('_') + 1) : secret - const secretBytes = Buffer.from(base64Secret, 'base64') - const toSign = `${webhookId}.${timestamp}.${rawBody}` - const expectedSignature = hmacSha256Base64(toSign, secretBytes) - - const providedSignatures = signatureHeader.split(' ') - for (const versionedSig of providedSignatures) { - const parts = versionedSig.split(',') - const sig = parts.length === 2 ? parts[1] : versionedSig - if (sig && safeCompare(sig, expectedSignature)) { - return true - } - } - return false - } catch (error) { - logger.error('Error verifying Loops signature:', error) - return false - } -} - export const loopsHandler: WebhookProviderHandler = { async verifyAuth({ request, @@ -70,19 +24,18 @@ export const loopsHandler: WebhookProviderHandler = { return new NextResponse('Unauthorized - Loops signing secret is required', { status: 401 }) } - const webhookId = request.headers.get('webhook-id') - const timestamp = request.headers.get('webhook-timestamp') - const signature = request.headers.get('webhook-signature') + // Loops secrets are `_` and the prefix is not always `whsec`; + // with no `_`, indexOf is -1 and the whole secret is kept. + const base64Secret = signingSecret.slice(signingSecret.indexOf('_') + 1) - if (!webhookId || !timestamp || !signature) { - logger.warn(`[${requestId}] Loops webhook missing signature headers`) - return new NextResponse('Unauthorized - Missing Loops signature headers', { status: 401 }) - } - - if (!verifyLoopsSignature(signingSecret, webhookId, timestamp, signature, rawBody)) { - logger.warn(`[${requestId}] Loops signature verification failed`) - return new NextResponse('Unauthorized - Invalid Loops signature', { status: 401 }) - } + const failure = verifyStandardWebhookRequest({ + request, + rawBody, + requestId, + secret: base64Secret, + providerLabel: 'Loops', + }) + if (failure) return failure return null }, diff --git a/apps/sim/lib/webhooks/providers/resend.ts b/apps/sim/lib/webhooks/providers/resend.ts index c04289e3a00..d3045a5ab96 100644 --- a/apps/sim/lib/webhooks/providers/resend.ts +++ b/apps/sim/lib/webhooks/providers/resend.ts @@ -1,6 +1,4 @@ import { createLogger } from '@sim/logger' -import { safeCompare } from '@sim/security/compare' -import { hmacSha256Base64 } from '@sim/security/hmac' import { NextResponse } from 'next/server' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' import type { @@ -13,6 +11,7 @@ import type { SubscriptionResult, WebhookProviderHandler, } from '@/lib/webhooks/providers/types' +import { verifyStandardWebhookRequest } from '@/lib/webhooks/providers/utils' import { RESEND_ALL_WEBHOOK_EVENT_TYPES, RESEND_TRIGGER_TO_EVENT_TYPE, @@ -20,45 +19,6 @@ import { const logger = createLogger('WebhookProvider:Resend') -/** - * Verify a Resend webhook signature using the Svix signing scheme. - * Resend uses Svix under the hood: HMAC-SHA256 of `${svix-id}.${svix-timestamp}.${body}` - * signed with the base64-decoded `whsec_...` secret. - */ -function verifySvixSignature( - secret: string, - msgId: string, - timestamp: string, - signatures: string, - rawBody: string -): boolean { - try { - const ts = Number.parseInt(timestamp, 10) - const now = Math.floor(Date.now() / 1000) - if (Number.isNaN(ts) || Math.abs(now - ts) > 5 * 60) { - return false - } - - const secretBytes = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') - const toSign = `${msgId}.${timestamp}.${rawBody}` - const expectedSignature = hmacSha256Base64(toSign, secretBytes) - - const providedSignatures = signatures.split(' ') - for (const versionedSig of providedSignatures) { - const parts = versionedSig.split(',') - if (parts.length !== 2) continue - const sig = parts[1] - if (safeCompare(sig, expectedSignature)) { - return true - } - } - return false - } catch (error) { - logger.error('Error verifying Resend Svix signature:', error) - return false - } -} - export const resendHandler: WebhookProviderHandler = { async verifyAuth({ request, @@ -72,19 +32,15 @@ export const resendHandler: WebhookProviderHandler = { return new NextResponse('Unauthorized - Resend signing secret is required', { status: 401 }) } - const svixId = request.headers.get('svix-id') - const svixTimestamp = request.headers.get('svix-timestamp') - const svixSignature = request.headers.get('svix-signature') - - if (!svixId || !svixTimestamp || !svixSignature) { - logger.warn(`[${requestId}] Resend webhook missing Svix signature headers`) - return new NextResponse('Unauthorized - Missing Resend signature headers', { status: 401 }) - } - - if (!verifySvixSignature(signingSecret, svixId, svixTimestamp, svixSignature, rawBody)) { - logger.warn(`[${requestId}] Resend Svix signature verification failed`) - return new NextResponse('Unauthorized - Invalid Resend signature', { status: 401 }) - } + const failure = verifyStandardWebhookRequest({ + request, + rawBody, + requestId, + secret: signingSecret, + providerLabel: 'Resend', + headerPrefix: 'svix', + }) + if (failure) return failure return null }, diff --git a/apps/sim/lib/webhooks/providers/utils.ts b/apps/sim/lib/webhooks/providers/utils.ts index 5207a79ec36..f6487fefa3f 100644 --- a/apps/sim/lib/webhooks/providers/utils.ts +++ b/apps/sim/lib/webhooks/providers/utils.ts @@ -2,6 +2,7 @@ import type { Logger } from '@sim/logger' import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { sha256Hex } from '@sim/security/hash' +import { verifyStandardWebhook } from '@sim/security/standard-webhooks' import { NextResponse } from 'next/server' import type { AuthContext, EventFilterContext } from '@/lib/webhooks/providers/types' @@ -49,6 +50,49 @@ interface HmacVerifierOptions { requireSecret?: boolean } +interface StandardWebhookRequestOptions { + request: Pick + rawBody: string + requestId: string + secret: string + providerLabel: string + /** Header names the provider sends; see {@link verifyStandardWebhook}. */ + headerPrefix?: 'webhook' | 'svix' +} + +/** + * Verifies a Standard Webhooks (Svix-format) delivery and maps a failure to the provider's + * 401: `Missing