Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f69c624
fix(desktop): origin settings for prod, github login improvements (#6…
icecrasher321 Jul 29, 2026
e369997
fix(db): encode Date binds in raw sql, document the fetch_types contr…
TheodoreSpeaks Jul 29, 2026
6ff6255
feat(outlook): add Microsoft Graph calendar operations (#6041)
mzxchandra Jul 29, 2026
21c28d7
fix(tables): truncate sort option labels (#6052)
BillLeoutsakosvl346 Jul 29, 2026
0618541
fix(knowledge): change "Create Tag" to "Apply Tag" when applying a ta…
j15z Jul 29, 2026
985f9e6
feat(tables): saved views with filter, sort, and column presets (#5961)
TheodoreSpeaks Jul 29, 2026
ee7c061
fix(copilot): stop the special-tag parser discarding text it cannot r…
j15z Jul 29, 2026
4793607
chore(db): drop the legacy folder tables and adopt the deferred folde…
waleedlatif1 Jul 29, 2026
0f12799
fix(folders): stop archived rows skewing sortOrder, hide them from ad…
waleedlatif1 Jul 29, 2026
9edf892
docs(folders): correct four comments that claimed more than the code …
waleedlatif1 Jul 29, 2026
25d8019
feat(pi): Babysit foundations — shared PR/push extraction, five GitHu…
BillLeoutsakosvl346 Jul 29, 2026
4f776b7
fix(desktop): fix 11 security findings from a deepsec scan of the des…
waleedlatif1 Jul 29, 2026
1a23438
fix(desktop): release script (#6060)
Sg312 Jul 29, 2026
161b21f
feat(pi): require the user's own API key for Create PR even on hosted…
j15z Jul 30, 2026
998fd5a
fix(ci): scope the Next.js build cache sticky disk per branch (#6072)
waleedlatif1 Jul 30, 2026
0a0acc2
fix(config): drop three dead next.config entries and correct two stal…
waleedlatif1 Jul 30, 2026
f78367c
feat(logfire): add Pydantic Logfire block, tools, and docs (#6075)
icecrasher321 Jul 30, 2026
f0b79c5
chore(deps): upgrade next 16.2.11 -> 16.2.12 (#6077)
waleedlatif1 Jul 30, 2026
911b958
feat(exa): refresh Exa integration against current API, retire dead r…
waleedlatif1 Jul 30, 2026
4b239fa
fix(tests): stop pi-lifetime asserting the ceiling on a racing bounda…
waleedlatif1 Jul 30, 2026
94778eb
feat(pi): add update PR mode (#6031)
BillLeoutsakosvl346 Jul 30, 2026
897eebd
fix(blocks): stop registry.ts reading BLOCK_REGISTRY at module scope …
waleedlatif1 Jul 30, 2026
3e62546
perf(ci): disable the Turbopack persistent build cache (3.2x faster b…
waleedlatif1 Jul 30, 2026
18ddc22
feat(library): Best AI Agents for Scheduling and Calendar Management …
icecrasher321 Jul 30, 2026
d67e02f
perf(tests): skip jest-dom in node-environment test files (#6088)
waleedlatif1 Jul 30, 2026
8260891
chore(gitignore): ignore the repo-root uploads spill directory (#6089)
waleedlatif1 Jul 30, 2026
11c0d3b
feat(organizations): sweep a joiner's owned workspaces into the org o…
icecrasher321 Jul 30, 2026
edef789
feat(desktop): floating hover-peek sidebar (#6086)
waleedlatif1 Jul 30, 2026
1ce2aef
fix(triggers): stop cloned trigger blocks reusing the source's webhoo…
waleedlatif1 Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
39 changes: 39 additions & 0 deletions .github/workflows/ci-cache-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI Cache Cleanup

# DRAINING LEGACY DISKS ONLY. test-build.yml no longer mounts a Next.js build
# cache — the Turbopack persistent cache measured 3.2x SLOWER than no cache, so it
# is off. But every PR open while the per-branch key was live left a 5-12 GB volume
# behind, and nothing else reclaims them. This keeps deleting them as those PRs
# close.
#
# Delete this workflow once the backlog is drained (no PR predating the cache
# removal is still open). It is a no-op for new PRs, which never create a disk.

on:
pull_request:
types: [closed]

permissions:
contents: read

jobs:
delete-nextjs-cache:
name: Delete Next.js build cache disk
# Sticky disks only exist on Blacksmith; the GitHub break-glass path uses
# actions/cache, which expires on its own.
if: vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith'
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5

steps:
# A hard-coded legacy drain key. It no longer mirrors anything — the
# Mount Next.js build cache step it used to match was removed with the
# cache. Do not retarget or delete it while PRs from before that removal
# are still open, or their 5-12 GB disks are never reclaimed.
# Non-blocking: PRs skipped by ci.yml's paths-ignore never made a disk,
# and neither does any PR opened after the removal.
- name: Delete sticky disk
uses: useblacksmith/stickydisk-delete@b41313d28b8647d72114c9ba3c96bb04061562b6 # v1
continue-on-error: true
with:
delete-key: ${{ github.repository }}-nextjs-cache-pull_request${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ github.head_ref }}
38 changes: 22 additions & 16 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ jobs:
- name: Install ripgrep
run: command -v rg || (sudo apt-get update && sudo apt-get install -y ripgrep)

- name: Run tests with coverage
# Named for what it does: `bun run test` is `vitest run`, with no
# `--coverage`. See the Codecov note below.
- name: Run tests
env:
NODE_OPTIONS: '--no-warnings --max-old-space-size=8192'
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
Expand All @@ -199,6 +201,14 @@ jobs:
fi
echo "✅ Schema and migrations are in sync"

# DEAD PATH: nothing generates `apps/sim/coverage`. The test step runs
# `vitest run` without `--coverage`, and vitest.config.ts declares no
# coverage provider, so this uploads nothing and still reports success in
# ~1s (`fail_ci_if_error: false` hides it). `@vitest/coverage-v8` IS
# installed, so wiring it up is possible — but coverage instrumentation
# costs test time and nothing gates on the result today. Left in place
# pending a decision to either enable coverage or drop this step; do not
# read its green tick as "coverage was published".
- name: Upload coverage to Codecov
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5
with:
Expand All @@ -222,7 +232,11 @@ jobs:
build:
name: Build App
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-16vcpu-ubuntu-2404' || 'linux-x64-8-core' }}
timeout-minutes: 15
# Build durations crossed 15 minutes as the app grew (10m02 on Jul 29 AM,
# 14m44 after the folders/desktop/library merges, then two straight
# timeouts) — GitHub reports a job timeout as "cancelled". 25 keeps
# headroom without masking a genuine hang.
timeout-minutes: 25

steps:
- name: Checkout code
Expand Down Expand Up @@ -259,17 +273,12 @@ jobs:
key: ${{ github.repository }}-turbo-cache-build-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
path: ./.turbo

# Turbopack's persistent build cache (NEXT_TURBOPACK_BUILD_CACHE below)
# writes ~5 GB into .next/cache — a sticky disk mounts it in ~1s where an
# actions/cache round-trip would eat the warm-build win. Same event/fork
# namespacing as the other mounts; the GitHub fallback inside cache-mount
# still uses actions/cache with a run_id-suffixed key.
- name: Mount Next.js build cache
uses: ./.github/actions/cache-mount
with:
provider: ${{ vars.CI_PROVIDER }}
key: ${{ github.repository }}-nextjs-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
path: ./apps/sim/.next/cache
# No `.next/cache` mount: the Turbopack persistent build cache is off. A
# controlled A/B on one branch (PR #6078) with a byte-identical module graph
# measured compile at 113s with the cache off, 162s cold with it on, and
# 360s warm — the cache made the same build 3.2x slower, and it grew
# 5.1 GB -> 12 GB across two runs of an unchanged tree, so a disk degrades
# the more it is used. Mounting a disk nothing reads would only cost storage.

# Running out of RAM kills the whole VM and surfaces only as "the runner
# has received a shutdown signal" — no mention of memory, ~12 min in. Warn
Expand Down Expand Up @@ -300,7 +309,4 @@ jobs:
AWS_REGION: 'us-west-2'
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
TURBO_CACHE_DIR: .turbo
# Opt into Turbopack's persistent build cache (beta) for this CI
# check build only — measured 105s cold vs 22s warm locally.
NEXT_TURBOPACK_BUILD_CACHE: '1'
run: bunx turbo run build --filter=sim
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,14 @@ i18n.cache
# Python (apps/pii tests/tooling)
__pycache__/
.pytest_cache/

# Local file-upload spill directory. `UPLOAD_DIR_SERVER` is
# `join(process.cwd(), 'uploads')`, so it follows the cwd rather than a fixed
# root: under `turbo run test` the cwd is apps/sim and `apps/sim/.gitignore`'s
# `/uploads` catches it, but running vitest from the repo root drops ~40
# `uploads/execution/**/large-value-*.json` files here instead.
#
# Anchored deliberately. An unanchored `uploads/` would also match
# `apps/sim/lib/uploads/` — 61 files of tracked source — and silently ignore
# anything added there later.
/uploads
6 changes: 3 additions & 3 deletions apps/desktop/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sim Desktop (macOS)

A thin Electron shell around the hosted Sim web app. The renderer loads the configured origin (default `https://sim.ai`) as a normal top-level page in a bundled, pinned Chromium — rendering is identical to Chrome of that version on every machine. No UI is re-implemented and no server stack is bundled.
A thin Electron shell around the hosted Sim web app. The renderer loads the configured origin (default `https://www.sim.ai` — the origin the server actually serves; the apex 301s there) as a normal top-level page in a bundled, pinned Chromium — rendering is identical to Chrome of that version on every machine. No UI is re-implemented and no server stack is bundled.

## Layout

Expand Down Expand Up @@ -44,7 +44,7 @@ e2e/ # Playwright _electron smoke suite
```bash
bun install # workspace root
cd apps/desktop
bun run dev # bundle + launch against https://sim.ai
bun run dev # bundle + launch against https://www.sim.ai
SIM_DESKTOP_ORIGIN=http://localhost:3000 bun run dev # against local sim
```

Expand Down Expand Up @@ -74,7 +74,7 @@ Deviations from the original plan doc (deliberate):

## Provider matrix (U5 spike — keep current)

Host lists live in `src/main/navigation.ts` (`SYSTEM_BROWSER_IDP_HOSTS`, `IN_WINDOW_IDP_HOSTS`). Verified so far: Google + Microsoft blocked (by policy, not spike); GitHub assumed lenient. **Before GA, run the spike**: GitHub sign-in, consumer-Microsoft, a sample of integration connects (Notion, Slack, Linear, Atlassian, Box, Dropbox), SSO, and Turnstile-on-signup in a packaged build, then update the lists and this section.
The host list lives in `src/main/navigation.ts` (`SYSTEM_BROWSER_IDP_HOSTS`) and now applies to **integration connects only** — sign-in always goes through the system-browser handoff whatever the IdP, so no provider needs to be classified as embed-tolerant for login. (The old `IN_WINDOW_IDP_HOSTS` list existed to keep GitHub sign-in in-window; embedding a login is a one-way door in a chrome-less shell and splits better-auth's OAuth state across two cookie jars, so it was removed.) Verified so far: Google + Microsoft block embedding by policy. **Before GA, run the spike** for connects: a sample of integration connects (Notion, Slack, Linear, Atlassian, Box, Dropbox) plus SSO and Turnstile-on-signup in a packaged build, then update the list and this section.

## Web-app coupling contract (audit on web-app changes)

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"start": "electron .",
"package:dir": "bun run build && electron-builder --mac dir --publish never",
"package:mac": "bun run build && electron-builder --mac --publish never",
"package:share": "bun run build && electron-builder --mac --publish never -c.mac.timestamp=none",
"package:share": "bun run scripts/package-share.ts",
"install:local": "bun run scripts/install-local.ts",
"type-check": "tsc --noEmit",
"lint": "biome check --write --unsafe .",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const watch = process.argv.includes('--watch')
// non-prod environment): SIM_DESKTOP_DEFAULT_ORIGIN=https://www.dev.sim.ai.
// Baked into the bundle so it applies to fresh installs with no settings —
// unlike the SIM_DESKTOP_ORIGIN env var, which only affects terminal-launched
// processes. Official builds leave it unset (default https://sim.ai).
// processes. Official builds leave it unset (default https://www.sim.ai).
const bakedDefaultOrigin = process.env.SIM_DESKTOP_DEFAULT_ORIGIN ?? ''
if (
bakedDefaultOrigin &&
Expand Down
82 changes: 82 additions & 0 deletions apps/desktop/scripts/channels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Build-time channel identity, derived from the origin a build is baked to.
*
* Must stay in sync with APP_NAME_FOR_CHANNEL / channelForOrigin in
* src/main/config.ts. That pair is the RUNTIME source of truth — the app calls
* app.setName() from its baked origin at startup, which decides the userData
* directory and single-instance lock — and this is what the packager stamps
* into the bundle. When the two disagree, the bundle on disk and the running
* app disagree about which app they are: a dev-pointed build packaged as
* "Sim.app" with the production bundle id installs over a real production
* install while naming itself "Sim Dev" at runtime.
*/

/** Canonical no-redirect origins per environment. */
export const PROD_ORIGIN = 'https://www.sim.ai'
export const STAGING_ORIGIN = 'https://www.staging.sim.ai'
export const DEV_ORIGIN = 'https://www.dev.sim.ai'
export const LOCAL_ORIGIN = 'http://localhost:3000'

export interface ChannelIdentity {
/** Display + bundle name; also the userData directory name. */
name: string
appId: string
/** Baked default origin + persisted settings origin. */
origin: string
/**
* Artifact filename stem, and the per-channel scratch directory name.
* Space-free for the same reason electron-builder.yml's artifactName is:
* GitHub rewrites asset names containing spaces, which desyncs the
* electron-updater manifest from the uploaded files.
*/
slug: string
}

export const PROD: ChannelIdentity = {
name: 'Sim',
appId: 'ai.sim.desktop',
origin: PROD_ORIGIN,
slug: 'sim',
}
export const STAGING: ChannelIdentity = {
name: 'Sim Staging',
appId: 'ai.sim.desktop.staging',
origin: STAGING_ORIGIN,
slug: 'sim-staging',
}
export const DEV: ChannelIdentity = {
name: 'Sim Dev',
appId: 'ai.sim.desktop.dev',
origin: DEV_ORIGIN,
slug: 'sim-dev',
}
export const LOCAL: ChannelIdentity = {
name: 'Sim Local',
appId: 'ai.sim.desktop.local',
origin: LOCAL_ORIGIN,
slug: 'sim-local',
}

/** Every channel, in the order a full run builds them. */
export const ALL_CHANNELS: readonly ChannelIdentity[] = [PROD, STAGING, DEV, LOCAL]

/**
* Resolves the identity a build baked to `origin` must carry. Mirrors
* channelForOrigin in src/main/config.ts, including its fall-through to
* production for unrecognized hosts (self-hosted origins are production
* builds pointed elsewhere). An empty origin means "unset", which the app
* resolves to DEFAULT_ORIGIN — production.
*/
export function identityForOrigin(origin: string): ChannelIdentity {
if (!origin) return PROD
let host: string
try {
host = new URL(origin).hostname.toLowerCase()
} catch {
return PROD
}
if (host === 'localhost' || host === '127.0.0.1') return LOCAL
if (host === 'dev.sim.ai' || host.endsWith('.dev.sim.ai')) return DEV
if (host === 'staging.sim.ai' || host.endsWith('.staging.sim.ai')) return STAGING
return PROD
}
8 changes: 5 additions & 3 deletions apps/desktop/scripts/install-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ const CHANNEL_FLAGS: Record<string, ChannelIdentity> = {
appId: 'ai.sim.desktop.staging',
origin: 'https://www.staging.sim.ai',
},
// Bare sim.ai, matching official prod builds (config.ts) — www.sim.ai
// would land in a different cookie partition than a real install.
'--prod': { name: 'Sim', appId: 'ai.sim.desktop', origin: 'https://sim.ai' },
// Origin left unset, exactly as official prod builds do, so this install
// resolves the same DEFAULT_ORIGIN (config.ts) and therefore the same cookie
// partition as a real one. Naming an origin here re-creates the drift that
// pinned prod to the apex while the server serves www.
'--prod': { name: 'Sim', appId: 'ai.sim.desktop' },
}

const DEFAULT_IDENTITY: ChannelIdentity = { name: 'Sim', appId: 'ai.sim.desktop' }
Expand Down
124 changes: 124 additions & 0 deletions apps/desktop/scripts/package-share.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Share build: packages distributable .dmg/.zip artifacts from the current
* checkout, signed with whatever identity is on the machine (no notarization,
* no trusted timestamps) — the "send someone a build to try" loop.
*
* bun run package:share # production only
* bun run package:share --all # all four channels
* bun run package:share --staging --dev # just these two
* bun run package:share --dir # skip dmg/zip, package the .app only (fast)
* SIM_DESKTOP_DEFAULT_ORIGIN=https://sim.acme.example bun run package:share
*
* Each channel lands in release/<slug>/ with artifacts named for it — sim,
* sim-staging, sim-dev, sim-local. electron-builder.yml's artifactName is a
* single literal ("Sim-${version}-${arch}"), so without the override every
* channel writes the same filename and the last build silently wins. Only this
* path overrides it: the release workflow publishes one channel per GitHub
* release, where the flat name is what electron-updater expects.
*
* The baked origin decides the bundle identity, exactly as it decides the
* runtime one. This used to shell straight into electron-builder, which took
* productName/appId from electron-builder.yml — always the production pair — so
* a dev-pointed share packaged as "Sim.app" with the production bundle id while
* naming itself "Sim Dev" at runtime.
*
* Channels build ONE AT A TIME on purpose. scripts/build.ts writes the bundle
* to dist/ and the app icon to build/generated-icon.icns, both fixed paths, so
* concurrent channels would overwrite each other's bundle mid-package and ship
* a dmg whose baked origin belongs to a different environment — invisible until
* someone signs in. Giving each channel its own bundle directory is what would
* make concurrency safe, and the flag that redirects the app entry point
* (-c.extraMetadata.main) rewrites this package.json IN THE SOURCE TREE,
* stripping scripts and devDependencies. If parallelism is worth it later, the
* way to get it is a per-channel project directory (electron-builder --project)
* — not extraMetadata.
*/
import { spawnSync } from 'node:child_process'
import { rmSync } from 'node:fs'
import { ALL_CHANNELS, type ChannelIdentity, identityForOrigin } from './channels'

const FLAG_TO_CHANNEL: Record<string, ChannelIdentity> = Object.fromEntries(
ALL_CHANNELS.map((channel) => [
`--${channel.slug.replace(/^sim-/, '').replace(/^sim$/, 'prod')}`,
channel,
])
)

const args = process.argv.slice(2)
const dirOnly = args.includes('--dir')
const bakedOriginOverride = process.env.SIM_DESKTOP_DEFAULT_ORIGIN ?? ''

function selectedChannels(): ChannelIdentity[] {
if (args.includes('--all')) return [...ALL_CHANNELS]
const picked = args.filter((arg) => arg in FLAG_TO_CHANNEL).map((arg) => FLAG_TO_CHANNEL[arg])
if (picked.length > 0) return picked
// No channel flags: honour an explicit origin (self-hosted shares resolve to
// the production identity), otherwise plain production.
return [identityForOrigin(bakedOriginOverride)]
}

const channels = selectedChannels()
// An explicit origin only makes sense for a single-channel run; with several
// channels each one supplies its own.
const originFor = (channel: ChannelIdentity): string =>
channels.length === 1 && bakedOriginOverride ? bakedOriginOverride : channel.origin

function run(command: string, commandArgs: string[], env?: Record<string, string>): void {
const result = spawnSync(command, commandArgs, {
stdio: 'inherit',
env: env ? { ...process.env, ...env } : process.env,
})
if (result.status !== 0) {
console.error(`\n✖ ${command} ${commandArgs.join(' ')} failed`)
process.exit(result.status ?? 1)
}
}

function buildChannel(channel: ChannelIdentity): void {
const origin = originFor(channel)
console.log(`\n• ${channel.slug}: ${channel.name} (${channel.appId}) → ${origin}`)

// electron-builder only writes the output dir for the CURRENT target/arch, so
// an app left by an earlier run with different settings would survive
// alongside the new one.
for (const dir of ['mac-universal', 'mac-arm64', 'mac']) {
rmSync(`release/${channel.slug}/${dir}`, { recursive: true, force: true })
}
// electron-builder's `files: dist/**` packages whatever is in dist/, not just
// what this build wrote. Anything left there by an earlier run — another
// channel's bundle, scratch from an experiment — rides along inside the dmg,
// so a production artifact can end up carrying a dev-pointed bundle. Dead
// weight rather than a live risk (the app boots package.json's `main`), but
// not something to hand to anyone. build.ts rewrites the directory on the
// next line.
rmSync('dist', { recursive: true, force: true })

run('bun', ['run', 'scripts/build.ts'], { SIM_DESKTOP_DEFAULT_ORIGIN: origin })
run('bunx', [
'electron-builder',
'--mac',
...(dirOnly ? ['dir'] : []),
'--publish',
'never',
// Trusted timestamps make codesign do a network round trip to Apple per
// file (hundreds inside the Electron framework). Distribution builds need
// them; a share build does not, and it turns signing into a long stall.
'-c.mac.timestamp=none',
`-c.productName=${channel.name}`,
`-c.appId=${channel.appId}`,
`-c.directories.output=release/${channel.slug}`,
// The ${...} placeholders are electron-builder's own templating, expanded
// by it at packaging time — escaped here so JS leaves them alone.
`-c.artifactName=${channel.slug}-\${version}-\${arch}.\${ext}`,
])
console.log(`✔ ${channel.slug}: release/${channel.slug}/`)
}

// Shared node_modules state, and the one step every channel has in common.
run('bun', ['run', 'scripts/ensure-pty-prebuilds.ts'])
console.log(
`• Building ${channels.length} channel(s)${dirOnly ? ' (dir only)' : ''}: ${channels.map((c) => c.slug).join(', ')}`
)
for (const channel of channels) {
buildChannel(channel)
}
Loading
Loading