Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d38aac1
feat(library): OpenClaw vs Claude Cowork: Setup, Pricing, Security, a…
waleedlatif1 Oct 22, 2026
8df21ea
feat(library): CrewAI vs LangGraph vs AutoGen: Which Multi-Agent Fram…
waleedlatif1 Oct 22, 2026
6b67ca4
feat(library): LangChain vs LangGraph: What’s the Difference and Whic…
waleedlatif1 Oct 22, 2026
8924116
feat(library): n8n vs Zapier vs Make: Which Automation Platform Is Be…
waleedlatif1 Oct 22, 2026
eea2dcc
fix(library): correct facts in four new comparison posts (#9303)
waleedlatif1 Oct 22, 2026
003c352
improvement(ci): run affected suites and reuse production builds (#9297)
waleedlatif1 Oct 22, 2026
53f4e91
feat(ec2): add Amazon EC2 integration (#9304)
waleedlatif1 Oct 22, 2026
7992b56
fix(logs): keep trace secret projection from blocking the event loop …
waleedlatif1 Oct 22, 2026
93c71ad
fix(logs): skip workflow state on logs query trace reads (#9307)
waleedlatif1 Oct 22, 2026
d3da633
fix(executions): skip archive materialization on status polls that re…
waleedlatif1 Oct 22, 2026
8bf876f
fix(deps): update Next.js to 16.4.1, stop untrusted Office uploads re…
waleedlatif1 Oct 22, 2026
f81b7da
fix(logs): cap logs-list pages that carry execution data (#9309)
waleedlatif1 Oct 22, 2026
22864e9
fix(parsers): yield the event loop during PDF extraction and CSV row …
waleedlatif1 Oct 22, 2026
9f73c4a
fix(logs): yield to the event loop while exporting large trace rows (…
waleedlatif1 Oct 22, 2026
b26b436
ci: balance macOS Electron smoke shards by measured duration (#9315)
waleedlatif1 Oct 22, 2026
d0c10bb
improvement(file-parsers): parse spreadsheets on a worker thread (#9313)
waleedlatif1 Oct 22, 2026
8972b6b
improvement(executor): size payload compaction bottom-up instead of r…
waleedlatif1 Oct 22, 2026
0847b21
feat(monitoring): log event-loop lag, stalls, and in-flight route han…
waleedlatif1 Oct 22, 2026
053c933
fix(logs): mark withheld trace span content so logs query stops repor…
waleedlatif1 Oct 22, 2026
0d3dda9
fix(logs): treat a logs query path as withheld only when its content …
waleedlatif1 Oct 22, 2026
7fcfdf6
fix(telemetry): send browser events to the configured collector and k…
waleedlatif1 Oct 22, 2026
28e34a8
fix(executor): resume payload compaction on a fresh stack every 256 l…
waleedlatif1 Oct 22, 2026
0751de2
fix(ec2): describe the states each instance state-change tool actuall…
waleedlatif1 Oct 22, 2026
64992ca
fix(file-parsers): share the event-loop slice across PDF parses and y…
waleedlatif1 Oct 22, 2026
8e73220
docs(telemetry): describe the relay's disable gate and the anonymous …
waleedlatif1 Oct 22, 2026
972b92e
fix(ci): isolate desktop shard CLI regressions (#9324)
waleedlatif1 Oct 23, 2026
9a66fee
fix(tests): derive document grant expiry from one timestamp (#9325)
waleedlatif1 Oct 23, 2026
71bd22e
improvement(ci): balance integration jobs and retain documentation bu…
waleedlatif1 Oct 23, 2026
dd6efb0
improvement(insights): shorten the unassigned usage tooltip (#9330)
waleedlatif1 Oct 23, 2026
ff98c86
feat(knowledge): import knowledge base bundles through the API and `s…
waleedlatif1 Oct 23, 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
50 changes: 50 additions & 0 deletions .github/CI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# CI selection and acceptance ownership

The `changes` job runs Turborepo 2.9.14's `affectedTasks` query before installing application dependencies or provisioning services. `turbo.json` records package dependency propagation; `apps/sim/turbo.json` partitions the app into shared runtime, server, workspace UI, and acceptance tasks. `.github/ci-suites.json` turns those tasks into job matrices. The uploaded `ci-plan` artifact names changed files, selected tasks, attribution reasons, and any fallback.

A PR compares its merge base with the checked-out merge commit. Main/staging pushes execute all checks; the parent `ci` workflow skips checks on `dev` pushes, while PRs targeting `dev` still run checks. A manual `checks` run executes all checks unless supplied a full base commit SHA to reproduce a scoped plan. Missing history, an empty diff, unknown files outside the workspace graph, root script/CI changes, or a planner error run every check. Missing selection outputs fail the aggregate gate. The gate also rejects an omitted dependency, any failed/cancelled job, and any skip that its explicit selection flag did not authorize.

| Change | Expensive checks selected |
| --- | --- |
| Independent SDK | That workspace's tests and type-check; no app build, services, or browsers |
| App unit test | App test shards and test type check |
| Integration test | Both provision paths; no HTTP/browser job merely for the test edit |
| Workspace UI | Database integration, mobile, SEO and live Electron, plus app tests/build |
| Landing content | SEO, app tests/build; no database integration or Electron |
| Shared runtime or transitive dependency | All dependent boundaries |
| One E2E harness | Its acceptance group and the shared production build |

The server boundary deliberately includes all API routes and backend domains. Shared modules and unclassified app source remain broad inputs: this is safer than pretending a route's static imports describe database, dynamic registry, or background-worker dependencies. Files read outside a package graph need explicit inputs: CLI acceptance imports `packages/sim-cli/src/embed.ts`, app tests read docs content, and live Electron and realtime acceptance cross app boundaries. When adding such a dependency, update the corresponding task inputs/dependencies and the real Git/Turbo selector scenarios.

Integration also depends on workspace UI because the desktop executor integration suite runs the production workspace stream dispatcher against PostgreSQL and Redis. Its explicit harness inputs include the database and realtime Vitest configs and integration setup files, which runtime tasks exclude.

SEO depends on workspace UI and server code: landing pages reuse workspace pricing and preview components, provider models/pricing, and the block registry. These directory boundaries are not isolated application dependency graphs.

`affectedTasks` already uses task inputs. Do not enable `futureFlags.affectedUsingTaskInputs` just for this planner: that flag also changes pruning, which can pull CI-only cross-app task dependencies into production Docker contexts.

Affected type-checks follow the package graph too; repository audits and lint run whenever the checks workflow runs. Workspace-only unit jobs use a free GitHub runner and do not start the app test shards.

Database integration and lightweight HTTP suites run on free standard GitHub runners with matching GitHub cache setup. A same-source integration comparison of 16 jobs per provider (32 total) had a slowest shard of 6m 04s on 2-vCPU Blacksmith versus 6m 55s on free GitHub, with identical coverage. GitHub trades the observed 51 seconds for zero runner compute cost. This is one paired sample, not a long-run percentile estimate. These explicitly selected GitHub jobs do not follow `CI_PROVIDER`.

Memory-heavy builds and browser jobs retain appropriately sized Blacksmith runners. Electron uses 4-vCPU Blacksmith: both shards passed within 14 seconds of the 8-vCPU control; the free GitHub rest shard was slower and its lease run stalled.

Every HTTP and browser group consumes the same production build in an isolated job/database. Electron also uses that build and skips its development-route warm-up. Local HTTP and Electron development runs retain their dev-server behavior. Build inputs exclude test-only files so a harness edit can reuse an unchanged production build.

SCIM's production app uses a `sim.ai` fixture hostname mapped to loopback, since `NEXT_PUBLIC_FORCE_HOSTED` is ignored in production. Readiness, HTTP tests, and internal API requests still connect to loopback. The SCIM suite verifies that ending an Enterprise subscription rejects both owner administration and an existing bearer token, and that restoring it restores access.

## Coverage removed or consolidated

- SEO is plain HTTP, independent of browser and viewport. Its complete suite runs once in the SEO job, instead of repeating in each of four mobile shards.
- The MCP suite's homepage-returns-200 control duplicated SEO's stronger real-homepage Markdown/content-negotiation check. SEO owns that positive control; MCP retains host isolation, ownership challenge, discovery, API catalogs, unknown-page/API behavior, and refusal of Markdown.
- CLI CI uses one cold/warm-up iteration and one measured iteration instead of three identical measured repetitions. Both retain run completion, selected output, file-reference size, trace, and omitted-workflow-snapshot assertions. The standalone benchmark keeps its configurable sample size.
- A precompiled Electron app needs no route-compilation warm-up, which repeated read/import/chat-switch/Stop flows before each shard. The actual acceptance tests still exercise those behaviors.

The long-import and lease-expiration checks remain: they prove distinct survival/uncertain-outcome behavior across real process failure. Likewise, the two integration provision paths remain because migrations create constraints/triggers that schema push does not.

Local planner validation is `bun run test:scripts scripts/ci-changes.test.ts scripts/ci-gate.test.ts`. It executes real Turbo queries in small Git fixtures; no application build or database is required. Full builds, services, browser suites, and Electron checks run on hosted CI.

The separate `desktop-e2e` workflow retains its desktop-specific path filters. Its macOS shell smoke suite runs whole files across three isolated runners, with one worker per runner. `scripts/desktop-e2e.ts` asks Playwright to discover the complete suite, then assigns longer files first to the currently lightest shard using `apps/desktop/e2e-durations.json`. Exact escaped file filters preserve serial fixtures. Unmeasured files receive the median measured weight, deleted files cannot select tests, and invalid or empty selections fail. Normal local runs, packaging smoke and the weekly Electron canary keep their existing commands.

The initial weights are median test durations in seconds from runs [39734295953](https://github.com/simstudioai/sim/actions/runs/39734295953), [39833640524](https://github.com/simstudioai/sim/actions/runs/39833640524), and [39836596108](https://github.com/simstudioai/sim/actions/runs/39836596108). Refresh them from complete `desktop-smoke-reports-*` artifacts when the workload changes: sum `tests[].results[].duration` by `spec.file` in each run, take the median across runs, and divide milliseconds by 1,000. The weights steer scheduling only; Playwright discovery owns coverage. Before adopting new weights, compare repeated paired runs and the complete case/status multiset, including skipped cases and retries.

Each shard uploads its coverage/timing report, the complete discovery report, and its deterministic shard plan. `bun run test:scripts scripts/duration-shards.test.ts scripts/desktop-e2e.test.ts` checks partition coverage, unknown files, deterministic ordering, and balancing. The CLI regression executes tiny browser-free Playwright fixtures to prove that imported test factories and paths containing regex characters are selected correctly; it launches no Electron or browser.
62 changes: 62 additions & 0 deletions .github/ci-suites.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"http": [
{
"group": "scim",
"task": "ci:e2e-scim"
},
{
"group": "cli",
"task": "ci:e2e-cli"
},
{
"group": "stop-after",
"task": "ci:e2e-stop-after"
},
{
"group": "desktop-inbox",
"task": "ci:e2e-desktop-inbox"
},
{
"group": "seo",
"task": "ci:e2e-seo"
},
{
"group": "project-files",
"task": "ci:e2e-project-files"
}
],
"production": [
{
"group": "mobile",
"name": "mobile, chromium, 1/2",
"browser": "chromium",
"shard": "1/2",
"artifact": "mobile-chromium-1",
"task": "ci:e2e-mobile"
},
{
"group": "mobile",
"name": "mobile, chromium, 2/2",
"browser": "chromium",
"shard": "2/2",
"artifact": "mobile-chromium-2",
"task": "ci:e2e-mobile"
},
{
"group": "mobile",
"name": "mobile, webkit, 1/2",
"browser": "webkit",
"shard": "1/2",
"artifact": "mobile-webkit-1",
"task": "ci:e2e-mobile"
},
{
"group": "mobile",
"name": "mobile, webkit, 2/2",
"browser": "webkit",
"shard": "2/2",
"artifact": "mobile-webkit-2",
"task": "ci:e2e-mobile"
}
]
}
69 changes: 5 additions & 64 deletions .github/scripts/ci-changes.sh
Original file line number Diff line number Diff line change
@@ -1,65 +1,6 @@
#!/usr/bin/env bash
# Prints which checks a pull request's changes can affect, as `<flag>=true|false` lines for
# $GITHUB_OUTPUT. A flag is `false` only when every changed file is on an allowlist whose jobs
# fully cover it. A file on no allowlist sets every flag but `desktop_live` (which keeps its own
# skip list) to `true`; a change under .github/, an empty diff, or any failure to work out the
# diff sets all of them.
#
# full integration, e2e, e2e-build, e2e-mobile: any file not allowlisted below
# test the Vitest shards and test-types: content, docs app, and desktop changes,
# plus `full`
# build the app's `next build`: content changes, plus `full`
# desktop_live the live Electron suite: any file outside its own skip list
#
# lint always runs and is not gated.
#
# Usage: ci-changes.sh <base-sha>
set -u

# Posts, author profiles, and the public assets only posts reference. The test shards read them
# (lib/content/og-image.test.ts) and `next build` prerenders every post with its MDX components,
# which check:library-content compiles but never renders. Components under content/ are code.
content='^apps/sim/content/([^/]+/)*[^/]+\.mdx?$|^apps/sim/content/authors/[^/]+\.json$|^apps/sim/public/(authors|blog|changelog|library)/'
# Its own workspace tests, its type-check and docs-manifest audit in lint, and apps/sim tests
# that read its pages (tools/netsuite/netsuite.test.ts).
docs='^apps/docs/'
# Nothing outside the Electron app imports it; the desktop bridge and protocol packages are
# consumed by apps/sim and stay unlisted.
desktop='^apps/desktop/'
# Agent guidance, read only by the lint job's audits.
guidance='^(\.agents|\.claude|\.cursor)/|^([^/]+/)*(AGENTS|CLAUDE)\.md$|^README\.md$'
# The live desktop suite drives apps/desktop against the app, so it skips more than the rest.
desktop_live_skip="$guidance"'|^(apps/docs/|apps/pii/|apps/sim/content/|apps/sim/public/(authors|blog|changelog|library)/|packages/(python-sdk|ts-sdk)/)|\.mdx?$|(^|/)LICENSE$'

emit() {
printf 'full=%s\ntest=%s\nbuild=%s\ndesktop_live=%s\n' "$1" "$2" "$3" "$4"
}
run_all() {
emit true true true true
echo "Running every check: $1" >&2
exit 0
}
matches() { printf '%s\n' "$names" | grep -qE "$1"; }
outside() { printf '%s\n' "$names" | grep -qvE "$1"; }

base=${1:-}
[ -n "$base" ] || run_all 'no base commit'
git fetch --quiet --depth=1 origin "$base" || run_all "could not fetch $base"
names=$(git diff --no-renames --name-only "$base" HEAD) || run_all "could not diff against $base"
[ -n "$names" ] || run_all 'no changed files listed'
if matches '^\.github/'; then run_all 'a CI file changed'; fi

desktop_live=false
if outside "$desktop_live_skip"; then desktop_live=true; fi
if outside "$content|$docs|$desktop|$guidance"; then
emit true true true "$desktop_live"
unlisted=$(printf '%s\n' "$names" | grep -vE "$content|$docs|$desktop|$guidance" | head -n 1)
echo "Running every check (desktop_live=$desktop_live): $unlisted is not on an allowlist" >&2
exit 0
fi

test=false build=false
if matches "$content|$docs|$desktop"; then test=true; fi
if matches "$content"; then build=true; fi
emit false "$test" "$build" "$desktop_live"
echo "Scoped checks: test=$test build=$build desktop_live=$desktop_live" >&2
# Select tasks from the committed Turborepo graph; unknown files or failed attribution run all.
# Usage: ci-changes.sh [base-sha]. No base runs the complete push/dispatch gate.
set -euo pipefail
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
exec bun "$script_dir/../../scripts/ci-plan.ts" "${1:-}"
17 changes: 11 additions & 6 deletions .github/scripts/ci-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
#
# A skip passes only for a gated job whose `changes` flag is exactly `false`:
#
# integration, e2e, e2e-build, e2e-mobile full
# test, test-types test
# build build
# desktop-live desktop_live
# Each job maps to its own explicit selection flag below. No inferred skip is accepted.
#
# Every other skip fails: an ungated job (lint, `changes` itself), a gated job whose flag is
# `true`, missing, or anything else (a failed or skipped `changes` leaves no outputs). A failure
Expand All @@ -24,13 +21,21 @@ if ! jq -e 'type == "object"' <<< "$needs" > /dev/null 2>&1; then
fi

failed="$(jq -r '
{"integration": "full", "e2e": "full", "e2e-build": "full", "e2e-mobile": "full", "test": "test",
"test-types": "test", "build": "build", "desktop-live": "desktop_live"} as $gate
{"integration": "integration", "e2e": "http", "e2e-build": "e2e_build", "e2e-production": "production", "test": "test",
"test-types": "test_app", "build": "build", "desktop-live": "desktop_live"} as $gate
| . as $needs
| (($gate | keys) + ["changes", "lint"]) as $required
| ($required - keys) as $missing
| if ($missing | length) > 0 then
$missing[] | "\(.): missing"
else
$needs
| (.changes.outputs // {}) as $flags
| to_entries[]
| select(.value.result != "success")
| select(.value.result != "skipped" or $gate[.key] == null or $flags[$gate[.key]] != "false")
| "\(.key): \(.value.result)"
end
' <<< "$needs")"
if [ -n "$failed" ]; then
echo "::error::Checks did not pass:"
Expand Down
23 changes: 23 additions & 0 deletions .github/scripts/ci-plan.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
query($base: String!, $head: String!) {
packages { items { name path } }
affectedTasks(base: $base, head: $head, tasks: [
"test", "build", "type-check", "ci:integration", "ci:desktop-live",
"ci:e2e-scim", "ci:e2e-cli", "ci:e2e-stop-after", "ci:e2e-desktop-inbox",
"ci:e2e-project-files", "ci:e2e-mobile", "ci:e2e-seo"
]) {
items {
name
fullName
package { name }
reason {
__typename
... on TaskFileChanged { filePath }
... on TaskGlobalFileChanged { filePath }
... on TaskGlobalDepsChanged { filePath }
... on TaskDependencyTaskChanged { taskName packageName }
... on TaskPackageDependencyChanged { packageName }
... on TaskAllChanged { description }
}
}
}
}
30 changes: 19 additions & 11 deletions .github/scripts/http-e2e.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
# Runs one end-to-end suite group over real HTTP, each against its own app: `next dev`, except
# mobile, which serves the production build the `e2e-build` job uploads with `next start` (the
# job extracts it into apps/sim/.next first).
# CI groups, which reuse the production build from `e2e-build`.
# The job extracts it into apps/sim/.next before running these suites.
#
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile> (run from apps/sim)
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile|seo> (run from apps/sim)
#
# The job provides DATABASE_URL, BETTER_AUTH_SECRET and ENCRYPTION_KEY; each group sets the rest of
# its app's environment here. Reports and server logs land in $RUNNER_TEMP/e2e.
Expand All @@ -20,7 +20,7 @@
# telemetry flush runs detached and still writes .next/dev).
set -euo pipefail

group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile>}
group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile|seo>}
report_dir="$RUNNER_TEMP/e2e"
ready_timeout_seconds=600
mkdir -p "$report_dir"
Expand All @@ -33,8 +33,9 @@ realtime_pid=''
realtime_tag=''
realtime_log=''
browsers_pid=''
local_app_url=''
# `dev` or `start`; a group sets it before start_app.
app_server=dev
app_server=${E2E_APP_SERVER:-dev}

finish() {
local status=$?
Expand Down Expand Up @@ -78,7 +79,8 @@ start_app() {
status_log="$report_dir/$name-http-status.log"
fi
app_tag="$name-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
export NEXT_PUBLIC_APP_URL="http://127.0.0.1:$port"
local_app_url="http://127.0.0.1:$port"
export NEXT_PUBLIC_APP_URL="${E2E_APP_PUBLIC_URL:-$local_app_url}"
export BETTER_AUTH_URL="$NEXT_PUBLIC_APP_URL"
export DISABLE_TELEMETRY=true NEXT_TELEMETRY_DISABLED=1
if [ "$app_server" = start ]; then
Expand All @@ -96,7 +98,7 @@ start_app() {
--port "$port" > "$server_log" 2>&1 &
fi
server_pid=$!
wait_ready "$server_pid" "$NEXT_PUBLIC_APP_URL/api/health" "$label app"
wait_ready "$server_pid" "$local_app_url/api/health" "$label app"
}

# start_realtime <name> <port>: the Socket.IO server the app's browser clients connect to, so pages
Expand Down Expand Up @@ -143,12 +145,12 @@ case "$group" in
export DB_TX_TRIPWIRE=throw
export NEXT_PUBLIC_CHAT_DISABLED=true
start_app scim 3017 SCIM record-http-status
SCIM_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
SCIM_E2E_BASE_URL="$local_app_url" \
SCIM_E2E_DATABASE_URL="$DATABASE_URL" \
SCIM_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
SCIM_E2E_REPORT_PATH="$report_dir/scim-e2e-report.json" \
bun run test:scim:e2e
VERSION_COMPARE_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
VERSION_COMPARE_E2E_BASE_URL="$local_app_url" \
VERSION_COMPARE_E2E_DATABASE_URL="$DATABASE_URL" \
VERSION_COMPARE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
VERSION_COMPARE_E2E_REPORT_PATH="$report_dir/version-compare-http-report.json" \
Expand All @@ -174,7 +176,7 @@ case "$group" in
bun --no-env-file scripts/test-mcp-host-e2e.ts
CLI_LATENCY_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
CLI_LATENCY_E2E_DATABASE_URL="$DATABASE_URL" \
CLI_LATENCY_E2E_RUNS=3 \
CLI_LATENCY_E2E_RUNS=1 \
CLI_LATENCY_E2E_WARMUP=1 \
CLI_LATENCY_E2E_REPORT_PATH="$report_dir/cli-run-latency-report.json" \
bun run test:cli-run-latency:e2e
Expand Down Expand Up @@ -246,7 +248,13 @@ case "$group" in
MOBILE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
MOBILE_E2E_REPORT_PATH="$report_dir/mobile-e2e-report.json" \
bun run test:mobile:e2e
# The same production build answers as a crawler sees it: statuses, robots, sitemap, JSON-LD.
;;

seo)
app_server=start
Comment thread
waleedlatif1 marked this conversation as resolved.
export NEXT_PUBLIC_FORCE_HOSTED=false
start_app seo 3024 'landing SEO' record-http-status
# Plain HTTP assertions have no browser-specific behavior.
LANDING_SEO_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
LANDING_SEO_E2E_REPORT_PATH="$report_dir/landing-seo-e2e-report.json" \
bun run test:landing-seo:e2e
Expand Down
Loading
Loading