Skip to content

Commit dea6278

Browse files
authored
v0.9.53: saved credentials with keyless AWS and Databricks OIDC, read-only API keys, workspace members endpoint, trigger health and pinned versions, proxy support, search source permissions
2 parents 031ba61 + a4bbaf7 commit dea6278

3,811 files changed

Lines changed: 83059 additions & 15677 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.

‎.agents/skills/add-block/SKILL.md‎

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ export const {ServiceName}Block: BlockConfig = {
8383
// Multi-line input
8484
{ id: 'field', title: 'Label', type: 'long-input', placeholder: '...', rows: 6 }
8585

86-
// Password input
86+
// Password input: only for legacy blocks or a secret with no saved-credential fit
87+
// (model-provider keys, hosted-key pairs); a service token uses a saved credential
8788
{ id: 'apiKey', title: 'API Key', type: 'short-input', password: true }
8889
```
8990

@@ -136,6 +137,32 @@ export const {ServiceName}Block: BlockConfig = {
136137
}
137138
```
138139

140+
A service-account-only picker (a saved token or key credential) instead sets
141+
`credentialKind: 'service-account'` and `mode: 'basic'` (no scopes), and pairs with an advanced
142+
twin; `isSavedCredentialPicker` matches only this shape:
143+
```typescript
144+
{
145+
id: 'credential',
146+
title: '{Service} Account',
147+
type: 'oauth-input',
148+
serviceId: '{service}',
149+
credentialKind: 'service-account',
150+
canonicalParamId: 'oauthCredential',
151+
mode: 'basic',
152+
placeholder: 'Select {Service} credential',
153+
required: true,
154+
},
155+
{
156+
id: 'manualCredential',
157+
title: '{Service} Account',
158+
type: 'short-input',
159+
canonicalParamId: 'oauthCredential',
160+
mode: 'advanced',
161+
placeholder: 'Enter credential ID',
162+
required: true,
163+
}
164+
```
165+
139166
**Scopes:** Always use `getScopesForService(serviceId)` from `@/lib/oauth/utils` for `requiredScopes`. Never hardcode scope arrays — the single source of truth is `OAUTH_PROVIDERS` in `lib/oauth/oauth.ts`.
140167

141168
**Scope descriptions:** When adding a new OAuth provider, also add human-readable descriptions for all scopes in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`.
@@ -159,6 +186,40 @@ export const {ServiceName}Block: BlockConfig = {
159186

160187
Optional companions: `credentialLabels` (override the picker's section/connect-row copy) and `allowServiceAccounts: true` (trigger-mode only — list service accounts, which triggers otherwise exclude; set only when the trigger's polling path can resolve a service-account token). The connect modal, provider families (Google JSON key, Atlassian token, token-paste, client-credential, Slack bot), and the preview gate are all resolved from `serviceAccountProviderId` — you don't wire them per block.
161188

189+
### Credential-only successor (replacing inline secrets)
190+
191+
A block that takes a secret inline keeps running for existing workflows; the saved-credential
192+
version is a new block type that spreads it. Keep the successor an object literal with the picker
193+
written inline, because the docs generator reads the source text:
194+
195+
```typescript
196+
export const ServiceV2Block: BlockConfig = {
197+
...ServiceBlock,
198+
type: 'service_v2',
199+
authMode: AuthMode.OAuth,
200+
hideFromToolbar: false,
201+
sunset: undefined,
202+
...withSavedCredential(ServiceBlock, {
203+
id: 'credential',
204+
type: 'oauth-input',
205+
serviceId: 'service',
206+
title: 'Service Credential',
207+
}),
208+
}
209+
```
210+
211+
- The legacy block changes only by `hideFromToolbar: true` and `sunset: { status: 'legacy',
212+
replacedBy: 'service_v2' }` (an older `_v2` points at the new `_v3`). Register both types.
213+
- `withSavedCredential` (`blocks/utils.ts`) swaps exactly the fields its policy in
214+
`SAVED_CREDENTIAL_OAUTH` supplies for the picker, places it directly under the operation
215+
dropdown, inherits the replaced fields' `condition` and `required`, and leaves trigger-mode
216+
fields alone. Pass `required` when the replaced fields disagree (an optional host), and `params`
217+
only when the legacy params builder validates the secret: extract that builder so the legacy
218+
block calls it unchanged (`blocks/legacy-builders.test.ts`) and the successor skips the check.
219+
- Add the pair to `serializer/saved-credentials.test.ts` (`optionalIds` for replaced fields the
220+
legacy block never required), and grep for code that hard-codes the legacy type (`getBlock`,
221+
`integration.type ===`, availability lookups), which now resolves to the successor.
222+
162223
### OAuth deployment availability (required for integration blocks)
163224

164225
A visible tools-category block with OAuth is deployment-gated. Its `oauth-input.serviceId` is
@@ -661,7 +722,8 @@ outputs: {
661722
When creating V2 blocks (alongside legacy V1):
662723

663724
```typescript
664-
// V1 Block - mark as legacy
725+
// V1 Block - mark as legacy. A credential-only successor is the exception: the
726+
// successor keeps the legacy block's name, so neither block is renamed.
665727
export const ServiceBlock: BlockConfig = {
666728
type: 'service',
667729
name: 'Service (Legacy)',

‎.agents/skills/add-integration/SKILL.md‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ supported paths and choose the simplest supported setup for the intended user:
4343
| Customer-owned client credentials | Saved `service_account` credential with a client ID and secret | Internal-app eligibility, grant activation, scope syntax, token lifetime and revocation behavior |
4444
| API token or personal access token | Saved token service account when reusable connections are useful | Token permissions, identity verification, expiry and rotation |
4545
| Private key, certificate, or service-account JSON | Existing key-based service-account framework | Signing algorithm, audience, subject, tenant binding and key rotation |
46+
| Cloud workload identity (AWS role, Databricks/Snowflake/Entra/GCP federation) | Keyless saved credential: Sim signs a short-lived OIDC token the customer's cloud trusts. Add the provider to every table in `lib/credentials/workload-identity/audiences.ts` (`WORKLOAD_IDENTITY_PROVIDERS`, its service-account provider ID map, `WORKLOAD_IDENTITY_KEYLESS_CONFIGS`, `WORKLOAD_IDENTITY_AUDIENCES`) and to `MIXED_MODE_PROVIDERS` in `lib/credentials/application/prepare-workload-identity.ts` | Issuer and audience the provider accepts, how the trust pins the subject, token lifetime |
47+
48+
API, CLI, and MCP callers set up keyless in two steps: `POST /api/v2/credentials/workload-identity/subjects`
49+
(or `sim credentials keyless-subject <providerId> --output json`) returns the subject to trust and a
50+
`keylessConfig`; they add its `requiredConfigFields` and create the credential with it. The provider
51+
catalog advertises `keyless` only when the deployment can sign tokens.
4652

4753
Do not present customer-owned credentials as a way around a provider's approval requirements
4854
for a shared public integration. Explain the supported account/app type in the setup docs. Keep
@@ -66,6 +72,50 @@ Use explicit permission choices when the supported operations have different acc
6672
region and permission selections on reconnect unless the user changes them. Verify documented
6773
identity endpoints rather than guessing which person a client-credentials token represents.
6874

75+
### Token credentials (API key, personal access token, token + host)
76+
77+
A service authenticated by a pasted key or token takes it through a saved token credential, never an
78+
inline `password` field, so one shared, verified credential serves every block. Wire it by the
79+
service id:
80+
81+
1. `apps/sim/lib/credentials/token-service-accounts/descriptors.ts`: a provider id and descriptor.
82+
Fields are `apiToken`, plus `domain` for a host, instance URL, or region and
83+
`providerWorkspaceId` for an account or org id; every listed field is required. Set `url: true`
84+
on a `domain` that is a URL whose scheme or path matters, so the connect form sends it as typed.
85+
2. `validators/token-checks.ts`: one `tokenCheckValidator` declaration (register it in
86+
`server.ts`). Make one cheap, documented read on the tools' own host with the tools' own auth,
87+
reusing their header and URL builders. `domain` and `workspaceId` normalize to exactly the form
88+
the tools build URLs from and throw for anything that could steer the host or path
89+
(`normalizeHttpBaseUrl`, `allowedDomain`, `refuseUnsafeTypedUrl`); the normalized domain is what
90+
the credential stores. Set `egress` when the user chooses the host. Add `forbiddenMeansScoped`,
91+
`rejectsKey`, or `provesKey` only for documented replies (probe a fake key to see how a bad one
92+
answers), and `errorBodyEchoesToken` when an error body echoes the token.
93+
3. `apps/sim/lib/oauth/oauth.ts` and `types.ts`: the service entry (`authType: 'service_account'`,
94+
`serviceAccountProviderId`).
95+
4. The tools' credential policy. A NEW integration's tools take a hidden `accessToken` and declare
96+
`oauth: { required: true, provider: '<service>', credentialKind: 'service-account' }`, pinning
97+
any host or account param with `authoritativeParams` (Flow's and Snowflake's tools are the
98+
pattern). `tokenCredentialOAuth('<service>', { <toolParam>: 'accessToken' | 'domain' |
99+
'providerWorkspaceId' })` in `apps/sim/tools/shared/saved-credentials.ts`, listed in
100+
`SAVED_CREDENTIAL_OAUTH`, is for existing tools that already take the token inline (successors).
101+
Either way, the executor overwrites mapped and pinned params after authorizing the credential,
102+
and `check-block-registry` probes the real URL builders of every block with a saved-credential
103+
picker and fails on a host-steering param left unmapped or unpinned.
104+
5. A credential-only block (including a successor) uses `authMode: AuthMode.OAuth`, so the
105+
generated catalog maps the service; a token-only block that keeps `AuthMode.ApiKey`, as Coda
106+
does, registers in `tokenCredentialIntegrationTypes` instead (see below). The credential picker
107+
sits directly under the operation dropdown (`add-block` → OAuth/Credentials). A new block
108+
writes the picker inline, as Higgsfield does: `{ id: 'credential', type: 'oauth-input', serviceId,
109+
credentialKind: 'service-account', canonicalParamId: 'oauthCredential', mode: 'basic',
110+
required: true }`, plus a `manualCredential` `short-input` with the same `canonicalParamId` in
111+
`mode: 'advanced'`. An existing block that already takes the secret inline is never edited in
112+
place: add a credential-only successor (`add-block` → Credential-only successor).
113+
114+
Then run `generate-docs`, `generate:block-successors`, `generate:deployment-config`, and
115+
`generate:tool-metadata`, and `check-block-registry.ts origin/staging`. This pattern does not fit
116+
hosted-key tools, model-provider keys, two-secret or username/password logins, or a host chosen
117+
per call.
118+
69119
### Pair saved credentials with block and resource selectors
70120

71121
`oauth-input` is the shared saved-credential picker, including for service accounts that never
@@ -425,7 +475,8 @@ If creating V2 versions (API-aligned outputs):
425475

426476
1. **V2 Tools** - Add `_v2` suffix, version `2.0.0`, flat outputs
427477
2. **V2 Block** - Add `_v2` type, use `createVersionedToolSelector`
428-
3. **V1 Block** - Add `(Legacy)` to name, set `hideFromToolbar: true`, and add
478+
3. **V1 Block** - Add `(Legacy)` to name (a credential-only successor is the exception: it keeps
479+
the legacy block's name), set `hideFromToolbar: true`, and add
429480
`sunset: { status: 'legacy', replacedBy: '{service}_v2' }` — `check-block-registry`
430481
fails a legacy block with no `replacedBy`, and the amber legacy badge plus its
431482
click-to-upgrade action read from that field.

‎.agents/skills/add-tools/SKILL.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export const {serviceName}{Action}Tool: ToolConfig<
8989
provider: '{service}', // Must match the service id in OAUTH_PROVIDERS
9090
// credentialKind: 'service-account', // service-account-only integrations
9191
},
92+
// A token credential (API key, PAT, token + host) instead uses the service's shared policy from
93+
// tools/shared/saved-credentials.ts, built with tokenCredentialOAuth('{service}', { apiKey:
94+
// 'accessToken' }). Its keys are the tool's own token/host/account params, which the executor
95+
// fills after authorizing the credential.
9296

9397
params: {
9498
// Hidden params (system-injected, e.g. the OAuth accessToken)

‎.agents/skills/validate-integration/SKILL.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,24 @@ For **each tool** in `tools.access`:
218218
- True/false → `switch` (a Yes/No `dropdown` only when the tool needs a third "unset" state)
219219
- Credentials → `oauth-input` with correct `serviceId`
220220
- Every tool with a `credential`/`oauthCredential` param declares `oauth: { required: true, provider }` matching that `serviceId`, plus `credentialKind: 'service-account'` when only service accounts are supported
221+
- [ ] In a new block or credential-only successor, a pasted key, token, or token + host comes from a
222+
saved token credential (add-integration → Token credentials), not an inline `password` field
223+
(legacy blocks keep theirs); a successor changes its legacy block only by `hideFromToolbar`
224+
and `sunset`, and its picker sits under the operation
225+
- [ ] For a token-credential integration, every tool in `tools.access` declares the service's
226+
`tokenCredentialOAuth` policy, every
227+
param that decides the request host is mapped, and the stored domain is in the form the tools
228+
build URLs from (`check-block-registry.ts origin/staging` passes)
229+
- [ ] A non-successor token block's tools declare `oauth: { required: true, provider,
230+
credentialKind: 'service-account' }` and pin every host or account param in
231+
`authoritativeParams` (`check-block-registry` enforces this for every block with a
232+
saved-credential picker)
233+
- [ ] A keyless provider appears in every table in `lib/credentials/workload-identity/audiences.ts`
234+
and in `MIXED_MODE_PROVIDERS` (`prepare-workload-identity.ts`), and the provider catalog
235+
shows `keyless` only when the deployment can sign tokens
236+
- [ ] The `tokenCheckValidator` request uses the tools' host and auth, its normalizers refuse
237+
anything that could steer the host or path, and a fake key is rejected without a valid
238+
scoped key being refused
221239
- [ ] Dropdown `value: () => 'default'` is set for dropdowns with a sensible default
222240
- [ ] Every `short-input`, `long-input`, `code`, and selector subBlock has a `placeholder` — including
223241
password fields (`Enter your API key`). Formatted values show the shape

‎.github/CI.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ The `changes` job runs Turborepo 2.9.14's `affectedTasks` query before installin
44

55
A PR compares the checked-out GitHub merge commit with its first parent: the actual target revision tested by that merge. The PR event's base SHA can become stale when the target advances. Scoping requires a matching GitHub merge ref and SHA, exactly two merge parents, and a usable target parent; otherwise every check runs. This works with the existing depth-two checkout without fetching older ancestry. 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.
66

7-
Dedicated docs verification uses the same planner, comparing pushes with their previous commit and PRs with the tested merge's first parent. Site acceptance follows `docs#build` and its transitive package dependencies. The app's docs-consumer task depends on server runtime and that docs build: backend-only changes run the platform consumer against the docs app, while site changes also run corpus and browser checks. Independent SDK and unit-only edits skip those production jobs. The required `docs HTTP` gate verifies explicit site/platform selections before accepting any skip.
7+
Dedicated docs verification uses the same planner, comparing pushes with their previous commit and PRs with the tested merge's first parent. Site acceptance follows `docs#build` and its transitive package dependencies. The app's docs-consumer task depends on server runtime and that docs build: backend-only changes run the platform consumer against the docs app, while site changes also run corpus and browser checks. Standalone package and unit-only edits skip those production jobs. The required `docs HTTP` gate verifies explicit site/platform selections before accepting any skip.
88

99
Docs production builds run once and publish an artifact to isolated HTTP, corpus, Chromium, and WebKit jobs. Source checks keep their own checkout because generator verification mutates inputs. Content caches use separate lint/source/build namespaces; generation validates every restore, repairs missing declarations, and removes obsolete modules. The Next build cache keeps its supported incremental behavior. Content and Next caches have size limits, and Next cache files expire after seven days; successful content eviction happens after all consumers finish.
1010

1111
| Change | Expensive checks selected |
1212
| --- | --- |
13-
| Independent SDK | That workspace's tests and type-check; no app build, services, or browsers |
13+
| Standalone package | That workspace's tests and type-check; no app build, services, or browsers |
1414
| App unit test | App test shards and test type check |
1515
| Integration test | Both provision paths; no HTTP/browser job merely for the test edit |
1616
| Workspace UI | Database integration, mobile, SEO and live Electron, plus app tests/build |

‎.github/CONTRIBUTING.md‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,23 +140,19 @@ Using clear and consistent commit messages makes it easier for everyone to under
140140

141141
To set up your local development environment:
142142

143-
### Option 1: Using NPM Package (Simplest)
143+
### Option 1: Using the Setup Wizard (Simplest)
144144

145-
The easiest way to run Sim locally is using our NPM package:
145+
The easiest way to run Sim locally is the interactive setup wizard:
146146

147147
```bash
148-
npx simstudio
148+
npx sim-setup
149149
```
150150

151-
After running this command, open [http://localhost:3000/](http://localhost:3000/) in your browser.
152-
153-
#### Options
154-
155-
- `-p, --port <port>`: Specify the port to run Sim on (default: 3000)
156-
- `--no-pull`: Skip pulling the latest Docker images
151+
When it finishes, open [http://localhost:3000/](http://localhost:3000/) in your browser. Inside a cloned Sim repository, run `bun run sim-setup --mode dev` for the local development mode.
157152

158153
#### Requirements
159154

155+
- Node.js 20+
160156
- Docker must be installed and running on your machine
161157

162158
### Option 2: Using Docker Compose

‎.github/actions/image/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ runs:
6363
# under 300 MB, sat at 249 GB. Sticky disks bill at ~$0.51/GB-month.
6464
- name: Set up Blacksmith builder
6565
if: inputs.provider == '' || inputs.provider == 'blacksmith'
66-
uses: useblacksmith/setup-docker-builder@19215110ab936351210feebdfa5b440b4493e184 # v2
66+
uses: useblacksmith/setup-docker-builder@06dd9c83a5d541274d2e3ac67625560979275267 # v2
6767
with:
6868
cache-key: ${{ steps.cache-key.outputs.value }}
6969

‎.github/scripts/http-e2e.sh‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ case "$group" in
259259
bun --no-env-file scripts/test-assistant-actions-e2e.ts
260260
;;
261261
organization-search)
262+
GITLAB_CSV_E2E_BASE_URL="$local_app_url" \
263+
GITLAB_CSV_E2E_REPORT_PATH="$report_dir/gitlab-csv-permissions-http-report.json" \
264+
bun --no-env-file scripts/test-gitlab-csv-permissions-e2e.ts
262265
ORGANIZATION_SEARCH_E2E_BASE_URL="$local_app_url" \
263266
ORGANIZATION_SEARCH_E2E_DATABASE_URL="$DATABASE_URL" \
264267
ORGANIZATION_SEARCH_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \

0 commit comments

Comments
 (0)