Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bec4639
feat(mothership): run scheduled jobs as durable unattended chat turns…
waleedlatif1 Oct 23, 2026
f9d3c62
docs(library): update best-ai-agents-support-ticket-triage (#9337)
waleedlatif1 Oct 23, 2026
5b69c9a
docs(library): update dify-alternatives (#9336)
waleedlatif1 Oct 23, 2026
9482fb3
docs(library): update how-to-create-an-ai-agent (#9335)
waleedlatif1 Oct 23, 2026
9ac12a2
fix(workflow-tests): keep the error of a mock that throws (#9333)
waleedlatif1 Oct 23, 2026
0b1155f
docs(library): update ai-agent-examples-by-department-and-industry (#…
waleedlatif1 Oct 23, 2026
75e7a8a
chore(deps): upgrade Bun to 1.4.4 (#9338)
waleedlatif1 Oct 23, 2026
0f755fe
fix(models): mark OpenAI gpt-4.1-nano deprecated (#9340)
waleedlatif1 Oct 23, 2026
4d26987
fix(mcp): classify unresolvable and egress-refused server URLs in dis…
waleedlatif1 Oct 23, 2026
63ca24f
fix(gmail): drop an expired history cursor instead of re-saving it ev…
waleedlatif1 Oct 23, 2026
e5ad3ae
fix(logs): keep provenance sidecars intact through log PII redaction …
waleedlatif1 Oct 23, 2026
7d1b047
fix(workflows): accept json input type and return 400 for unsupported…
waleedlatif1 Oct 23, 2026
2fc2008
fix(mothership): close the scheduled-job review gaps (#9345)
waleedlatif1 Oct 23, 2026
42f3409
fix(db): isolate billing and auth connection pools, release the redep…
waleedlatif1 Oct 23, 2026
60f7996
fix(logs): stop loading execution data and snapshots that reads do no…
waleedlatif1 Oct 23, 2026
1a69d2a
fix(workflows): accept legacy input field types instead of rejecting …
waleedlatif1 Oct 23, 2026
d76f2cd
improvement(execution): run sandbox workers on a dedicated worker hos…
waleedlatif1 Oct 23, 2026
772fcf7
fix(egress): retry a gateway CONNECT once on connection failure and l…
waleedlatif1 Oct 23, 2026
b9e6ffb
improvement(auth): encrypt stored integration tokens (#9346)
waleedlatif1 Oct 23, 2026
fe743ef
docs(library): update what-is-an-mcp-server (#9352)
waleedlatif1 Oct 23, 2026
0a6f8f9
docs(library): update best-ai-automation-tools-2026 (#9353)
waleedlatif1 Oct 23, 2026
546a378
docs(library): update top-ai-assistants-2026 (#9356)
waleedlatif1 Oct 23, 2026
7f3a075
docs(library): update langgraph-alternatives (#9357)
waleedlatif1 Oct 23, 2026
4df5d85
improvement(library): rank related posts by tag rarity, add backlinks…
waleedlatif1 Oct 23, 2026
0753608
improvement(library): single JSON-LD source, Markdown articles, and I…
waleedlatif1 Oct 24, 2026
511a064
fix(execution): close retired remote sandbox workers with a normal cl…
waleedlatif1 Oct 24, 2026
60b02de
fix(ci): pick the CodeDeploy action when the app Deploy stage has sid…
waleedlatif1 Oct 24, 2026
14e3315
fix(library): correct vendor facts and remove self-ranking in compari…
waleedlatif1 Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion .claude/rules/sim-sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
description: Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code.
paths:
- "apps/sim/lib/execution/isolated-vm-worker.cjs"
- "apps/sim/lib/execution/isolated-vm-host.cjs"
- "apps/sim/lib/execution/isolated-vm-remote.ts"
- "apps/sim/lib/execution/isolated-vm.ts"
- "apps/sim/lib/execution/sandbox/**"
- "apps/sim/sandboxes/**"
Expand Down Expand Up @@ -75,7 +77,31 @@ payload or `ivm.Reference` wrapper in the worker:
- [ ] Does the worker read a new env var? Add it to the `buildWorkerEnv()`
allowlist in `isolated-vm.ts` **and** to the allowlist regression test in
`isolated-vm.test.ts` — the worker does not inherit the app environment,
so an un-allowlisted var is simply absent in the child.
so an un-allowlisted var is simply absent in the child. If the app owns
its value, put it in `appWorkerEnv()` and add it with its accepted shape
to `CLIENT_WORKER_ENV` in `isolated-vm-host.cjs`, so a remote worker
gets the same value.
- [ ] Does the host send the worker a new message type? Add it to
`ALLOWED_INBOUND_TYPES` in `isolated-vm-host.cjs`, or remote workers
never receive it.

## Where the worker runs

The pool in `isolated-vm.ts` opens each worker either as a local child process
or, when `IVM_REMOTE_WORKER_URL` is set (with `IVM_REMOTE_WORKER_TOKEN`; a URL
without a valid token fails executions rather than falling back) and the
`isolated-vm-local-workers` kill switch is off, as a WebSocket connection to
`isolated-vm-host.cjs`, which runs the
same worker in a dedicated service that holds no platform credentials. Every
rule in this file applies in both modes.

- `fetch()` and brokers always run in the app. The host only relays the
worker's messages, so it needs no network access of its own and never gets
a broker implementation.
- A remote target never falls back to a local child process. An unreachable
host fails executions with a system error; keep it that way.
- Changes to the worker message protocol must stay compatible with the
previous release: the host and its clients roll out independently.

## What the worker *may* hold

Expand Down
28 changes: 26 additions & 2 deletions .cursor/rules/sim-sandbox.mdc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: "Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code."
globs: ["apps/sim/lib/execution/isolated-vm-worker.cjs","apps/sim/lib/execution/isolated-vm.ts","apps/sim/lib/execution/sandbox/**","apps/sim/sandboxes/**"]
globs: ["apps/sim/lib/execution/isolated-vm-worker.cjs","apps/sim/lib/execution/isolated-vm-host.cjs","apps/sim/lib/execution/isolated-vm-remote.ts","apps/sim/lib/execution/isolated-vm.ts","apps/sim/lib/execution/sandbox/**","apps/sim/sandboxes/**"]
---

<!-- Generated from .claude/rules/sim-sandbox.md by `bun run skills:sync`. Edit the source, not this file. -->
Expand Down Expand Up @@ -73,7 +73,31 @@ payload or `ivm.Reference` wrapper in the worker:
- [ ] Does the worker read a new env var? Add it to the `buildWorkerEnv()`
allowlist in `isolated-vm.ts` **and** to the allowlist regression test in
`isolated-vm.test.ts` — the worker does not inherit the app environment,
so an un-allowlisted var is simply absent in the child.
so an un-allowlisted var is simply absent in the child. If the app owns
its value, put it in `appWorkerEnv()` and add it with its accepted shape
to `CLIENT_WORKER_ENV` in `isolated-vm-host.cjs`, so a remote worker
gets the same value.
- [ ] Does the host send the worker a new message type? Add it to
`ALLOWED_INBOUND_TYPES` in `isolated-vm-host.cjs`, or remote workers
never receive it.

## Where the worker runs

The pool in `isolated-vm.ts` opens each worker either as a local child process
or, when `IVM_REMOTE_WORKER_URL` is set (with `IVM_REMOTE_WORKER_TOKEN`; a URL
without a valid token fails executions rather than falling back) and the
`isolated-vm-local-workers` kill switch is off, as a WebSocket connection to
`isolated-vm-host.cjs`, which runs the
same worker in a dedicated service that holds no platform credentials. Every
rule in this file applies in both modes.

- `fetch()` and brokers always run in the app. The host only relays the
worker's messages, so it needs no network access of its own and never gets
a broker implementation.
- A remote target never falls back to a local child process. An unreachable
host fails executions with a system error; keep it that way.
- Changes to the worker message protocol must stay compatible with the
previous release: the host and its clients roll out independently.

## What the worker *may* hold

Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ runs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

- name: Setup Node
if: inputs.node-version != ''
Expand Down
7 changes: 5 additions & 2 deletions .github/scripts/wait-for-ecs-cutover.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,15 @@ while [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" = 'None' ]; do
DEPLOYMENT_ID=$(printf '%s\n' "$deploy_actions" | DEPLOY_STATE="$deploy_state" EXECUTION_ID="$EXECUTION_ID" python3 -c '
import json, os, re, sys
state = json.loads(os.environ["DEPLOY_STATE"])
actions = json.load(sys.stdin)
# The Deploy stage can also run sidecar actions (e.g. a CodeBuild image deploy); only the
# CodeDeploy blue/green action carries the ECS cutover.
actions = [a for a in json.load(sys.stdin)
if a.get("input", {}).get("actionTypeId", {}).get("provider") == "CodeDeployToECS"]
if not state or state.get("latestExecution", {}).get("pipelineExecutionId") != os.environ["EXECUTION_ID"] or not actions:
print("")
sys.exit(0)
if len({a["actionName"] for a in actions}) != 1:
raise SystemExit("ERROR: expected one Deploy action in the app pipeline")
raise SystemExit("ERROR: expected one CodeDeploy ECS action in the app pipeline Deploy stage")
latest = max(actions, key=lambda a: a["startTime"])
matches = [a["latestExecution"] for a in state.get("actionStates", [])
if a["actionName"] == latest["actionName"]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4
- name: Install only the pinned task planner
continue-on-error: true
run: bun install --global "turbo@$(jq -r .devDependencies.turbo package.json)"
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,9 @@ jobs:
needs.promote.result == 'success' &&
needs.trigger-upload.result == 'success' &&
needs.promote.outputs.promoted == 'true'
outputs:
# The new app serves traffic; indexnow keys off this, not the later Trigger.dev promotion.
cutover: ${{ steps.cutover.outcome }}
# Mostly waiting on the ECS cutover after a dependency install: the smallest runner is enough.
runs-on: *runner-2vcpu
# Leave setup/promotion headroom above the 70-minute cutover poll.
Expand Down Expand Up @@ -596,6 +599,7 @@ jobs:
# An unchanged tag may belong to a failed or still-running earlier deploy.
# Verify its latest cutover rather than treating tag equality as success.
- name: Wait for ECS traffic cutover
id: cutover
env:
OVERALL_TIMEOUT: 4200
APP_IMAGE_CHANGED: ${{ needs.promote.outputs.app_image_changed }}
Expand Down Expand Up @@ -636,6 +640,59 @@ jobs:
fi
bunx [email protected] promote "$VERSION" "${TARGET_ARGS[@]}"

# Production only, once the new app serves traffic: submit the library and comparison URLs this
# push changed to IndexNow. Best effort: nothing depends on it, and a failure never fails the run.
indexnow:
name: indexnow
needs: [promote, trigger-promote]
if: >-
!cancelled() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
needs.promote.outputs.promoted == 'true' &&
needs.trigger-promote.outputs.cutover == 'success'
runs-on: *runner-2vcpu
timeout-minutes: 5
continue-on-error: true
permissions:
contents: read
actions: read
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.4

# Diff from the head of the last earlier run whose submission succeeded, not the previous
# push: a push that never deployed, or whose submission failed, would otherwise drop its
# changed URLs for good. Falls back to the previous push when no such run is in view. The
# diff needs only the two trees, so that one commit is fetched alone; an all-zero `before`
# (the branch's first push) has no base, which the script skips.
- name: Submit changed URLs to IndexNow
env:
GH_TOKEN: ${{ github.token }}
PUSH_BEFORE: ${{ github.event.before }}
run: |
BASE_SHA=""
while read -r run_id head_sha; do
conclusion="$(gh api --paginate "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/jobs?per_page=100" \
--jq '.jobs[] | select(.name == "indexnow") | .conclusion')"
if [[ "$conclusion" == "success" ]]; then
BASE_SHA="$head_sha"
break
fi
done < <(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs?branch=main&event=push&per_page=30" \
--jq ".workflow_runs[] | select(.id < $GITHUB_RUN_ID) | \"\\(.id) \\(.head_sha)\"")
BASE_SHA="${BASE_SHA:-$PUSH_BEFORE}"
echo "Submitting URLs changed in $BASE_SHA..$GITHUB_SHA"
if [[ ! "$BASE_SHA" =~ ^0+$ ]]; then
git fetch --no-tags --depth=1 origin "$BASE_SHA"
fi
bun scripts/ping-indexnow.ts "$BASE_SHA" "$GITHUB_SHA"

# Build ARM64 images for GHCR (main branch only, runs in parallel with
# tests). Pushes only the immutable sha tag — latest-arm64/version-arm64
# are applied by ghcr after the gate, so a failing run
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/desktop-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

# Electron, electron-builder and Playwright download their binaries on every cold install. All
# three caches are keyed by version inside, so an older entry restored by prefix is harmless.
Expand Down Expand Up @@ -151,7 +151,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

# Electron, electron-builder and Playwright download their binaries on every cold install. All
# three caches are keyed by version inside, so an older entry restored by prefix is harmless.
Expand Down Expand Up @@ -213,7 +213,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

- name: Setup Node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

- name: Setup Node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/helm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

# Docker Compose and Kubernetes must run the same background jobs on the
# same schedules; this fails the build if the two drift apart. The script
Expand Down Expand Up @@ -286,7 +286,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

# Derives appVersion rather than checking it, so the published chart cannot
# be pinned to an older Sim than the release it ships with. The committed
Expand Down Expand Up @@ -481,7 +481,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
bun-version: 1.4.4

# Derives appVersion rather than checking it, so the published chart cannot
# be pinned to an older Sim than the release it ships with. The committed
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "module",
"main": "dist/main.cjs",
"engines": {
"bun": ">=1.4.2",
"bun": ">=1.4.4",
"node": ">=20.0.0"
},
"scripts": {
Expand Down
38 changes: 35 additions & 3 deletions apps/docs/scripts/fetch-git-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* shallow clone (every file untouched since the boundary commit would get that commit's date), so
* the docs sitemap shipped with no `lastModified` at all. Fetch the commit history before the
* build. `--filter=blob:none` skips file contents: `git log --name-only` needs commits and trees.
* Vercel's clone has no `origin` remote, so the history comes from the public repository URL its
* system environment names, pinned to the commit being built.
*
* A failed fetch leaves the clone shallow, so pages ship undated as before. That is logged rather
* than failing the deploy, because dates are an enhancement and the docs must still ship.
Expand All @@ -11,7 +13,8 @@ import { execFileSync } from 'node:child_process'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'

const logger = createLogger('DocsGitHistory')
/** A build step's outcome belongs in the build log, which the production default (errors only) would hide. */
const logger = createLogger('DocsGitHistory', { logLevel: 'INFO' })

/** Bounds the fetch so a stalled remote costs at most this much of the deployment, never all of it. */
const FETCH_TIMEOUT_MS = 120_000
Expand All @@ -24,11 +27,40 @@ function git(args: string[], timeout?: number): string {
}).trim()
}

if (process.env.VERCEL) {
const {
VERCEL,
VERCEL_GIT_PROVIDER,
VERCEL_GIT_REPO_OWNER,
VERCEL_GIT_REPO_SLUG,
VERCEL_GIT_COMMIT_SHA,
} = process.env

if (VERCEL) {
try {
if (git(['rev-parse', '--is-shallow-repository']) === 'true') {
git(['fetch', '--unshallow', '--filter=blob:none', '--no-tags', 'origin'], FETCH_TIMEOUT_MS)
if (
VERCEL_GIT_PROVIDER !== 'github' ||
!VERCEL_GIT_REPO_OWNER ||
!VERCEL_GIT_REPO_SLUG ||
!VERCEL_GIT_COMMIT_SHA
) {
throw new Error('Vercel did not provide the GitHub repository and commit for this build')
}
const repositoryUrl = `https://github.com/${VERCEL_GIT_REPO_OWNER}/${VERCEL_GIT_REPO_SLUG}.git`
git(
[
'fetch',
'--unshallow',
'--filter=blob:none',
'--no-tags',
repositoryUrl,
VERCEL_GIT_COMMIT_SHA,
],
FETCH_TIMEOUT_MS
)
logger.info('Fetched full commit history for docs last-modified dates')
} else {
logger.info('Clone already has full commit history')
}
} catch (error) {
logger.warn(
Expand Down
2 changes: 1 addition & 1 deletion apps/realtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "Apache-2.0",
"type": "module",
"engines": {
"bun": ">=1.4.2",
"bun": ">=1.4.4",
"node": ">=20.0.0"
},
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function generateMetadata({
const { slug } = await params
const post = await getPostBySlug(slug)
if (!post || post.draft) return {}
return buildPostMetadata(post)
return buildPostMetadata(post, BLOG_SECTION)
}

export const revalidate = 86400
Expand All @@ -33,7 +33,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
const { slug } = await params
const post = await getPostBySlug(slug)
if (!post || post.draft) notFound()
const related = await getRelatedPosts(slug, 3)
const related = await getRelatedPosts(slug)

return (
<ContentPostPage
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/changelog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function generateStaticParams() {
export async function generateMetadata({ params }: ChangelogEntryPageProps): Promise<Metadata> {
const { slug } = await params
const entry = await getEntryBySlug(slug)
return entry ? buildPostMetadata(entry) : {}
return entry ? buildPostMetadata(entry, CHANGELOG_SECTION) : {}
}

export default async function ChangelogEntryPage({ params }: ChangelogEntryPageProps) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Link from 'next/link'
import type { CompetitorProfile } from '@/lib/compare/data'
import { BrandIconTile } from '@/app/(landing)/comparisons/components/brand-icon-tile'
import { comparisonPath } from '@/app/(landing)/comparisons/utils'
import { ChevronArrow } from '@/app/(landing)/components/chevron-arrow'

interface ComparisonLinkRowProps {
Expand All @@ -12,7 +13,7 @@ export function ComparisonLinkRow({ competitor }: ComparisonLinkRowProps) {
const Icon = competitor.brand?.icon
return (
<Link
href={`/comparisons/${competitor.id}`}
href={comparisonPath(competitor)}
className='group/link flex items-center gap-4 px-6 py-4 transition-colors hover-hover:bg-[var(--surface-hover)]'
aria-label={`Sim vs ${competitor.name} comparison`}
>
Expand Down
Loading
Loading