Skip to content

Commit 63a9c7a

Browse files
committed
Merge remote-tracking branch 'origin/staging' into chore/sync-dev-with-staging-2026-10-23
2 parents 75d5a50 + 80be6e3 commit 63a9c7a

100 files changed

Lines changed: 67126 additions & 1346 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/actions/setup/action.yml‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ runs:
6666
key: ${{ github.repository }}-${{ inputs.turbo-cache-key }}-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
6767
path: ./.turbo
6868

69+
# Bun retries a failed request itself, but a registry connection that stalls
70+
# without failing leaves `bun install` waiting until the job's own timeout
71+
# cancels it, the whole budget spent on one hung step. Healthy installs finish
72+
# in under two and a half minutes cold, so an attempt past three is stalled:
73+
# kill it and retry once, which leaves room inside the shortest caller's
74+
# 10-minute job timeout. Any other failure is real and fails at once.
6975
- name: Install dependencies
7076
shell: bash
71-
run: bun install --frozen-lockfile --ignore-scripts
77+
run: |
78+
for attempt in 1 2; do
79+
timeout --kill-after=15s 3m bun install --frozen-lockfile --ignore-scripts && exit 0
80+
status=$?
81+
if [ "$status" -ne 124 ] && [ "$status" -ne 137 ]; then exit "$status"; fi
82+
echo "::warning title=bun install stalled::attempt ${attempt} of 2 ran past 3 minutes"
83+
done
84+
echo "::error title=bun install stalled::both attempts ran past 3 minutes"
85+
exit 1

‎.github/scripts/get-ecr-image-digest.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ if [ -n "$ALLOW_MISSING" ] && [ "$ALLOW_MISSING" != '--allow-missing' ]; then
99
exit 1
1010
fi
1111
export AWS_PAGER=''
12-
aws ecr batch-get-image --repository-name "$REPOSITORY" --image-ids imageTag="$TAG" --output json |
12+
aws --cli-connect-timeout 10 --cli-read-timeout 20 ecr batch-get-image --repository-name "$REPOSITORY" --image-ids imageTag="$TAG" --output json |
1313
ALLOW_MISSING="$ALLOW_MISSING" python3 -c '
1414
import json, os, re, sys
1515
response = json.load(sys.stdin)

‎.github/scripts/promote-app-image.sh‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,22 @@ RELEASE_TAG="${5:-}"
99
: "${GITHUB_OUTPUT:?GitHub output file required}"
1010
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
1111
PREVIOUS=$(bash "$SCRIPT_DIR/get-ecr-image-digest.sh" "$REPOSITORY" "$DEPLOY_TAG" --allow-missing)
12+
# A tag read straight after the move can still return the previous image, which
13+
# reports a moved tag as unchanged. The dry run prints the exact manifest the
14+
# move pushes (plus a trailing newline), so its hash is the digest the tag will
15+
# point at, known before the move.
16+
MANIFEST=$(docker buildx imagetools create --dry-run -t "$REGISTRY/$REPOSITORY:$DEPLOY_TAG" "$REGISTRY/$REPOSITORY:$SOURCE_TAG")
17+
DIGEST="sha256:$(printf '%s' "$MANIFEST" | sha256sum | cut -d' ' -f1)"
1218
EPOCH=$(date +%s)
1319
bash "$SCRIPT_DIR/promote-image.sh" "$REGISTRY" "$REPOSITORY" "$SOURCE_TAG" "$DEPLOY_TAG" "$RELEASE_TAG"
14-
DIGEST=$(bash "$SCRIPT_DIR/get-ecr-image-digest.sh" "$REPOSITORY" "$DEPLOY_TAG")
20+
deadline=$(( $(date +%s) + 60 ))
21+
until [ "$(bash "$SCRIPT_DIR/get-ecr-image-digest.sh" "$REPOSITORY" "$DEPLOY_TAG" --allow-missing)" = "$DIGEST" ]; do
22+
if [ "$(date +%s)" -ge "$deadline" ]; then
23+
echo "ERROR: $DEPLOY_TAG did not resolve to the pushed $DIGEST within 60s; cutover cannot be correlated" >&2
24+
exit 1
25+
fi
26+
sleep 3
27+
done
1528
CHANGED=true
1629
if [ "$DIGEST" = "$PREVIOUS" ]; then CHANGED=false; fi
1730
{

‎.github/workflows/ci.yml‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ jobs:
213213
uses: aws-actions/amazon-ecr-login@03f1aad4c6c7ffd436567f42f9384779290529bd # v2
214214

215215
- name: Login to Docker Hub
216-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
216+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
217217
with:
218218
username: ${{ secrets.DOCKERHUB_USERNAME }}
219219
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -365,14 +365,14 @@ jobs:
365365
uses: aws-actions/amazon-ecr-login@03f1aad4c6c7ffd436567f42f9384779290529bd # v2
366366

367367
- name: Login to Docker Hub
368-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
368+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
369369
with:
370370
username: ${{ secrets.DOCKERHUB_USERNAME }}
371371
password: ${{ secrets.DOCKERHUB_TOKEN }}
372372

373373
- name: Login to GHCR
374374
if: github.ref == 'refs/heads/main'
375-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
375+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
376376
with:
377377
registry: ghcr.io
378378
username: ${{ github.repository_owner }}
@@ -671,10 +671,11 @@ jobs:
671671
# push: a push that never deployed, or whose submission failed, would otherwise drop its
672672
# changed URLs for good. The check-runs lookup asks for this one check per commit, so it
673673
# stays small and counts every attempt of a re-run; a lookup that errors counts as no
674-
# success. With no success in view, the base is the oldest run in view, so URLs that no
675-
# submission ever covered are sent now. The diff needs only the two trees, so that one
676-
# commit is fetched alone; an all-zero `before` (the branch's first push) has no base,
677-
# which the script skips.
674+
# success. With no success in view, the base is the first parent of the oldest run in view,
675+
# so URLs that no submission ever covered, that run's own included, are sent now; a failed
676+
# parent lookup fails the step, so the next run retries from the same base. The diff
677+
# needs only the two trees, so that one commit is fetched alone; an all-zero `before` (the
678+
# branch's first push) has no base, which the script skips.
678679
- name: Submit changed URLs to IndexNow
679680
env:
680681
GH_TOKEN: ${{ github.token }}
@@ -691,7 +692,10 @@ jobs:
691692
fi
692693
done < <(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs?branch=main&event=push&per_page=30" \
693694
--jq ".workflow_runs[] | select(.id < $GITHUB_RUN_ID) | .head_sha")
694-
BASE_SHA="${BASE_SHA:-${OLDEST_SHA:-$PUSH_BEFORE}}"
695+
if [[ -z "$BASE_SHA" && -n "$OLDEST_SHA" ]]; then
696+
BASE_SHA="$(gh api "repos/$GITHUB_REPOSITORY/commits/$OLDEST_SHA" --jq '.parents[0].sha // empty')"
697+
fi
698+
BASE_SHA="${BASE_SHA:-$PUSH_BEFORE}"
695699
echo "Submitting URLs changed in $BASE_SHA..$GITHUB_SHA"
696700
if [[ ! "$BASE_SHA" =~ ^0+$ ]]; then
697701
git fetch --no-tags --depth=1 origin "$BASE_SHA"
@@ -750,7 +754,7 @@ jobs:
750754
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
751755

752756
- name: Login to GHCR
753-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
757+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
754758
with:
755759
registry: ghcr.io
756760
username: ${{ github.repository_owner }}
@@ -804,7 +808,7 @@ jobs:
804808

805809
steps:
806810
- name: Login to GHCR
807-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
811+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
808812
with:
809813
registry: ghcr.io
810814
username: ${{ github.repository_owner }}
@@ -888,7 +892,7 @@ jobs:
888892
count: ${{ steps.resolve.outputs.count }}
889893
steps:
890894
- name: Login to GHCR
891-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
895+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
892896
with:
893897
registry: ghcr.io
894898
username: ${{ github.repository_owner }}
@@ -1069,7 +1073,7 @@ jobs:
10691073

10701074
steps:
10711075
- name: Login to GHCR
1072-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
1076+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
10731077
with:
10741078
registry: ghcr.io
10751079
username: ${{ github.repository_owner }}

‎.github/workflows/desktop-e2e.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,11 @@ jobs:
168168
- name: Install dependencies
169169
run: bun install --frozen-lockfile
170170

171+
# The bunfig age gate would resolve `latest` to the newest week-old release; the canary
172+
# exists to test the release that just shipped. Already-locked packages stay locked.
171173
- name: Switch to electron@latest
172174
working-directory: apps/desktop
173-
run: bun add --no-save -d electron@latest
175+
run: bun add --no-save -d --minimum-release-age=0 electron@latest
174176

175177
- name: Bundle main and preload
176178
working-directory: apps/desktop

‎.github/workflows/helm.yml‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ jobs:
188188
--values helm/sim/ci/kind-overlay.yaml \
189189
--wait --timeout 15m
190190
191+
- name: Run helm test
192+
run: helm test sim --namespace sim --timeout 5m
193+
194+
# After the test, so it also explains a failed probe, not only a failed
195+
# install. A failed test pod is kept (the hook deletes it only on success),
196+
# so its log says which URL refused.
191197
- name: Diagnostics on failure
192198
if: failure()
193199
run: |
@@ -196,9 +202,8 @@ jobs:
196202
kubectl -n sim describe pods | tail -100 || true
197203
kubectl -n sim logs deploy/sim-app -c migrations --tail=50 || true
198204
kubectl -n sim logs deploy/sim-app --tail=80 || true
199-
200-
- name: Run helm test
201-
run: helm test sim --namespace sim --timeout 5m
205+
kubectl -n sim logs deploy/sim-realtime --tail=80 || true
206+
kubectl -n sim logs sim-test-connection || true
202207
203208
# Resolved once and shared, so the two publish paths cannot package the same
204209
# immutable chart version with different appVersions -- they run in parallel,
@@ -277,7 +282,7 @@ jobs:
277282
# oras also reads ~/.docker/config.json, so this one login covers both the
278283
# chart push and the Artifact Hub metadata push below.
279284
- name: Login to GHCR
280-
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
285+
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
281286
with:
282287
registry: ghcr.io
283288
username: ${{ github.repository_owner }}

‎.github/workflows/migrate.yml‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,41 @@ jobs:
3838
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
3939
# Bulk projection loads and concurrent index builds can outlast ordinary schema changes.
4040
timeout-minutes: 300
41+
# One migrate job per environment at a time, so a manual dispatch queues
42+
# behind a CI run instead of racing it for migrate.ts's advisory lock, which
43+
# gives up after 30 minutes. Job-level because a called workflow evaluates
44+
# workflow-level concurrency with the caller's github context; the group
45+
# must also never equal ci.yml's ci-<ref> group or the two deadlock.
46+
concurrency:
47+
group: migrate-${{ inputs.environment }}
48+
cancel-in-progress: false
4149

4250
steps:
51+
# An environment only migrates from its own branch, so a manual dispatch
52+
# from a feature branch cannot apply that branch's migrations to a shared
53+
# database. Under workflow_call github.ref is the caller's ref (ci.yml's
54+
# push to main/staging/dev). This fails rather than skips: ci.yml's
55+
# promote gates on migrate succeeding, and a skipped run must never read
56+
# as a migration that happened. It runs before any secret is read.
57+
- name: Require the environment's own branch
58+
env:
59+
ENVIRONMENT: ${{ inputs.environment }}
60+
REF: ${{ github.ref }}
61+
run: |
62+
case "${ENVIRONMENT}" in
63+
production) expected=refs/heads/main ;;
64+
staging) expected=refs/heads/staging ;;
65+
dev) expected=refs/heads/dev ;;
66+
*)
67+
echo "::error::Unknown migrate environment '${ENVIRONMENT}'"
68+
exit 1
69+
;;
70+
esac
71+
if [ "${REF}" != "${expected}" ]; then
72+
echo "::error::Refusing to migrate ${ENVIRONMENT} from ${REF}; ${ENVIRONMENT} migrates only from ${expected}"
73+
exit 1
74+
fi
75+
4376
- name: Checkout code
4477
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
4578
with:

‎.github/workflows/publish-python-sdk.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
persist-credentials: false
2727

2828
- name: Setup Python
29-
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
29+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
3030
with:
3131
python-version: '3.12'
3232
cache: 'pip'

‎apps/desktop/e2e/source-connect.spec.ts‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ test('source authorization returns to its desktop screen and refreshes live', as
5353
const githubStartSessions: string[] = []
5454
const githubInventorySessions: string[] = []
5555
let nativeCredentialVisible = false
56+
let githubInventoryHold: Promise<void> | undefined
5657
let installed = false
5758
let holdSlackStart = false
5859
let canceledSlackRequests = 0
@@ -304,7 +305,7 @@ test('source authorization returns to its desktop screen and refreshes live', as
304305
}
305306
if (path === '/api/organization-credentials/oauth') {
306307
githubInventorySessions.push(session)
307-
await sleep(500)
308+
await githubInventoryHold
308309
json({
309310
credentials: nativeCredentialVisible
310311
? [
@@ -545,12 +546,21 @@ test('source authorization returns to its desktop screen and refreshes live', as
545546
await expect(page.getByLabel('GitHub error')).toContainText('not available')
546547
await expect(page.getByLabel('GitHub credential')).toHaveText('')
547548
nativeCredentialVisible = true
548-
await page.getByRole('button', { name: 'Connect GitHub' }).click()
549-
await expect.poll(async () => (await opened()).length).toBe(6)
550-
await external.goto((await opened())[5])
551-
await external.getByRole('link', { name: 'Authorize GitHub' }).click()
552-
await expect.poll(() => githubInventorySessions.length).toBe(2)
553-
await expect(page.getByLabel('GitHub pending')).toHaveText('true')
549+
let releaseGithubInventory = () => {}
550+
githubInventoryHold = new Promise<void>((resolve) => {
551+
releaseGithubInventory = resolve
552+
})
553+
try {
554+
await page.getByRole('button', { name: 'Connect GitHub' }).click()
555+
await expect.poll(async () => (await opened()).length).toBe(6)
556+
await external.goto((await opened())[5])
557+
await external.getByRole('link', { name: 'Authorize GitHub' }).click()
558+
await expect.poll(() => githubInventorySessions.length).toBe(2)
559+
await expect(page.getByLabel('GitHub pending')).toHaveText('true')
560+
} finally {
561+
githubInventoryHold = undefined
562+
releaseGithubInventory()
563+
}
554564
await expect(page.getByLabel('GitHub credential')).toHaveText('fixture-github-credential')
555565
expect(githubStartSessions).toEqual(['browser-fixture', 'browser-fixture'])
556566
expect(githubInventorySessions).toEqual(['desktop-fixture', 'desktop-fixture'])

‎apps/docs/content/docs/cli/index.mdx‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,39 @@ sim tables rows query --help
142142

143143
The [command overview](/cli/commands) lists global options and root commands. The [complete reference](/cli/reference) contains every subcommand, argument, and option on one page. Both are generated from the CLI.
144144

145+
## Agent skills
146+
147+
The [Sim skills](https://github.com/simstudioai/skills) teach coding agents such as Claude Code and Codex to build and operate Sim through the CLI. Install them with the standard Agent Skills installer:
148+
149+
```bash
150+
bunx skills add simstudioai/skills --global
151+
```
152+
153+
To install one skill for one agent without prompting:
154+
155+
```bash
156+
bunx skills add simstudioai/skills \
157+
--skill build-workflow \
158+
--agent codex \
159+
--global \
160+
--yes
161+
```
162+
163+
| Skill | What it covers |
164+
| --- | --- |
165+
| `build-workflow` | Discover blocks and author a draft graph with atomic workflow operations |
166+
| `run-workflow` | Test saved state, exercise triggers, resume from a block, and diagnose runs |
167+
| `deploy-workflow` | Publish and manage workflows as APIs, chats, or MCP tools |
168+
| `files` | Organize workspace files and folders, read and search text, and apply targeted edits |
169+
| `sync-workspaces` | Import portable workflows with bindings, fork workspaces, and preview, apply, and reconcile push/pull sync |
170+
| `table` | Design typed tables, load and query rows, import data, and run workflow groups |
171+
| `knowledge-base` | Ingest and index documents, configure connectors and tags, and verify retrieval |
172+
| `run-tool` | Call one integration tool directly with Sim-managed credentials |
173+
174+
The repository is also a native Claude Code and Codex plugin, which exposes the same skills under the `sim` namespace (`sim:build-workflow`, `sim:run-workflow`, and so on).
175+
176+
The skills assume `sim` is installed and signed in, so complete [Your first command](#your-first-command) first. They never store or print API keys. These agent skills are separate from the workspace skills you manage with [`sim skills`](/cli/skills).
177+
145178
## Related docs
146179

147180
- [Authentication](/cli/authentication)

0 commit comments

Comments
 (0)