Skip to content

Commit b804c24

Browse files
icecrasher321claude
andcommitted
Merge origin/staging into staging-v4
Resolved 24 conflicts. Notable decisions, all made by checking what the merged tree actually does rather than preferring a side: - MCP prompt text (process-contents.ts): took staging's wording. Staging's `defer_loading: false` in mcp-tools.ts won the (unconflicted) merge, and `load_mcp_tool` is absent from the generated tool catalog, so the branch's "load before calling" instruction described behavior that no longer exists. Fixed the same stale instruction in mothership/execute/route.ts, which had merged cleanly and so was never flagged as a conflict. - Session cookie cache (auth.ts): kept the branch's 5-minute maxAge AND staging's org security-policy `version` callback. They solve different halves of revocation latency — `version` only covers org-wide invalidation, so the short TTL remains the only bound on per-device sign-out. - E2B -> sandbox rename: adopted staging's `isDocSandboxEnabled` everywhere. - xlsx mothership-beta gate: stayed removed (the branch deleted it deliberately); dropped the now-dead `isFeatureEnabled` imports and the beta-flag arm of the test. - delete_file tool, workflow-alias machinery, and `isPrivateOrReservedIP` were all deleted by this branch while staging kept using them. Staging's new call sites merged in cleanly and referenced missing symbols; rewired them (`isPrivateOrReservedIP` -> `isPrivateIp` from @sim/security/ssrf) or removed them where the feature they guarded no longer exists. - special-tags.tsx: unioned both feature sets (branch's folder_access / browser_takeover / terminal_handoff, staging's service_account) — the already-merged dispatcher references all four. - add-resource-dropdown.tsx: dropped the branch's `isOpen` plumbing; staging's `select()` derives the same key from `existingKeys` internally. - Added the branch's `isCopilotToolPermissionsEnabled` to the shared env-flags test mock, which staging's mock migration did not know about. DB migrations were reverted to the merge-base in the preceding commit so staging's 0264-0270 chain applies cleanly; the branch's schema change is regenerated on top in the next commit. Co-Authored-By: Claude <noreply@anthropic.com>
2 parents 8f52988 + 4edc169 commit b804c24

1,674 files changed

Lines changed: 220803 additions & 26702 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: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,25 @@ export const {ServiceName}Block: BlockConfig = {
144144

145145
**Scope descriptions:** When adding a new OAuth provider, also add human-readable descriptions for all scopes in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`.
146146

147+
**Service accounts (shared, app-level credentials):** A plain `oauth-input` already lets users *select* an existing service account — those credentials fold into the picker automatically (a Google service account created for any Google service appears in every Google block's picker). You only set `credentialKind` when you want to change the *connect* action:
148+
149+
```typescript
150+
{
151+
id: 'credential',
152+
title: 'Account',
153+
type: 'oauth-input',
154+
serviceId: '{service}',
155+
requiredScopes: getScopesForService('{service}'),
156+
credentialKind: 'any', // omit | 'service-account' | 'any'
157+
}
158+
```
159+
160+
- **omit (default):** lists OAuth accounts + any existing service accounts; the only connect action is "Connect account" (OAuth). Use this for the common "let users pick a service account someone set up elsewhere, but don't offer inline setup" case — no config needed.
161+
- **`'service-account'`:** service-account credentials *only*, plus an inline setup action that opens the provider's connect modal. Use when a block accepts *only* an app credential.
162+
- **`'any'`:** merged picker — OAuth accounts *and* service accounts in one grouped dropdown, with a connect action for each. Use when a block supports both (e.g. Slack: a personal account *or* a custom bot).
163+
164+
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.
165+
147166
### Selectors (with dynamic options)
148167
```typescript
149168
// Channel selector (Slack, Discord, etc.)
@@ -238,48 +257,94 @@ When your block accepts file uploads, use the basic/advanced mode pattern with `
238257
},
239258
```
240259

260+
**Keep the pair to one logical thing.** Basic is the file upload, advanced is *only* a reference to
261+
a file from a previous block. Gmail attachments are the reference implementation
262+
(`apps/sim/blocks/blocks/gmail.ts``attachmentFiles` / `attachments`).
263+
264+
Do not overload the advanced side with alternate identifiers (a remote URL, a provider asset ID, a
265+
path). A subblock whose meaning changes based on what the string looks like is impossible to reason
266+
about, forces the params function to sniff the value, and makes the field's type meaningless. Give
267+
each alternative its own subblock outside the pair:
268+
269+
```typescript
270+
// ✓ Good — the pair is "a file"; other sources are their own fields
271+
{ id: 'mediaFile', type: 'file-upload', canonicalParamId: 'media', mode: 'basic' },
272+
{ id: 'mediaFileRef', type: 'short-input', canonicalParamId: 'media', mode: 'advanced' },
273+
{ id: 'mediaId', type: 'short-input', mode: 'advanced' }, // separate concept
274+
{ id: 'mediaLink', type: 'short-input', mode: 'advanced' }, // separate concept
275+
276+
// ✗ Bad — one field meaning three things, resolved by guessing
277+
{ id: 'mediaRef', type: 'short-input', canonicalParamId: 'media', mode: 'advanced',
278+
placeholder: 'File reference, media ID, or public URL' },
279+
```
280+
281+
When several fields are mutually exclusive alternatives, mark them all `required: false` and enforce
282+
"exactly one" at execution — a conditionally-required canonical pair rejects the workflow before the
283+
other paths ever get a chance to supply the value.
284+
241285
**Critical constraints:**
242286
- `canonicalParamId` must NOT match any subblock's `id` in the same block
243-
- Values are stored under subblock `id`, not `canonicalParamId`
287+
- A canonical group is **block-wide**, not per-operation: `buildCanonicalIndex` keys groups by
288+
`canonicalParamId` across every subblock, and a group has exactly one `basicId`. Two operations
289+
that each need a file pair need two distinct `canonicalParamId` values.
290+
- All members of a group must share the same `required` status
244291

245292
### Normalizing File Input in tools.config
246293

247-
Use `normalizeFileInput` to handle all input variants:
294+
Put the normalization in `tools.config.params`, never in `tools.config.tool``tool` runs at
295+
serialization, before variable resolution, so a `<block.output>` file reference is not yet a value
296+
there.
248297

249298
```typescript
250299
import { normalizeFileInput } from '@/blocks/utils'
251300

252301
tools: {
253302
access: ['service_upload'],
254303
config: {
255-
tool: (params) => {
256-
// Check all field IDs: uploadFile (basic), fileRef (advanced), fileContent (legacy)
257-
const normalizedFile = normalizeFileInput(
258-
params.uploadFile || params.fileRef || params.fileContent,
259-
{ single: true }
260-
)
261-
if (normalizedFile) {
262-
params.file = normalizedFile
304+
tool: (params) => `service_${params.operation}`,
305+
params: (params) => {
306+
// Read the CANONICAL id, not the subblock ids
307+
const { file: fileParam, ...rest } = params
308+
const file = normalizeFileInput(fileParam, { single: true })
309+
return {
310+
...rest,
311+
...(file ? { file } : {}),
263312
}
264-
return `service_${params.operation}`
265313
},
266314
},
267315
}
268316
```
269317

270-
**Why this pattern?**
271-
- Values come through as `params.uploadFile` or `params.fileRef` (the subblock IDs)
272-
- `canonicalParamId` only controls UI/schema mapping, not runtime values
273-
- `normalizeFileInput` handles JSON strings from advanced mode template resolution
318+
**Where the value actually lives at runtime.** The subblock `id` is where the UI *stores* the value,
319+
but it is not what the params function receives. `extractBlockParams`
320+
(`apps/sim/serializer/index.ts`) collapses each canonical group at serialization time:
321+
322+
```typescript
323+
const sourceIds = [group.basicId, ...group.advancedIds].filter(Boolean)
324+
sourceIds.forEach((id) => delete params[id]) // subblock ids are deleted
325+
if (chosen !== undefined) params[group.canonicalId] = chosen
326+
```
327+
328+
So by the time `tools.config.params(inputs)` runs (`executor/handlers/generic/generic-handler.ts`),
329+
`params.uploadFile` and `params.fileRef` are **gone** and the value is under `params.file`. Reading a
330+
subblock id there yields `undefined` and silently sends no file.
331+
332+
Only the active mode's value survives — `getCanonicalValues` returns the basic value in basic mode
333+
and the first non-empty advanced value in advanced mode, so a stale value in the dormant mode can
334+
never leak. `normalizeFileInput` then handles the JSON string that advanced-mode template resolution
335+
produces.
336+
337+
Note that `generic-handler` merges rather than replaces (`{ ...inputs, ...transformedParams }`), so
338+
omitting a key from the returned object does not strip it from what the tool receives. Tools simply
339+
ignore params they do not declare.
274340

275341
### File Input Types in `inputs`
276342

277-
Use `type: 'json'` for file inputs:
343+
Declare the **canonical** id with `type: 'json'` — the subblock ids never reach `inputs`:
278344

279345
```typescript
280346
inputs: {
281-
uploadFile: { type: 'json', description: 'Uploaded file (UserFile)' },
282-
fileRef: { type: 'json', description: 'File reference from previous block' },
347+
file: { type: 'json', description: 'File to upload (UserFile or reference)' },
283348
// Legacy field for backwards compatibility
284349
fileContent: { type: 'string', description: 'Legacy: base64 encoded content' },
285350
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export const {Service}Block: BlockConfig = {
213213
```typescript
214214
// Basic: Visual selector
215215
{
216-
id: 'channel',
216+
id: 'channelSelector',
217217
type: 'channel-selector',
218218
mode: 'basic',
219219
canonicalParamId: 'channel',
@@ -228,10 +228,19 @@ export const {Service}Block: BlockConfig = {
228228
}
229229
```
230230

231+
Note neither subblock `id` is `channel` — the canonical id is a third name that both members map
232+
onto, and it is the only one that survives serialization.
233+
231234
**Critical Canonical Param Rules:**
232235
- `canonicalParamId` must NOT match any subblock's `id` in the block
233-
- `canonicalParamId` must be unique per operation/condition context
234-
- Only use `canonicalParamId` to link basic/advanced alternatives for the same logical parameter
236+
- `canonicalParamId` must be unique **block-wide**, not per operation. `buildCanonicalIndex` keys
237+
groups by `canonicalParamId` across all subblocks and a group holds exactly one `basicId`, so two
238+
operations that each need their own pair must use two different canonical ids
239+
- Only use `canonicalParamId` to link basic/advanced alternatives for the same logical parameter.
240+
A pair carries ONE concept — for files that means upload (basic) + file reference (advanced), as
241+
in Gmail attachments (`blocks/blocks/gmail.ts`). Never overload the advanced side with alternate
242+
identifiers like a URL or a provider asset ID; give those their own subblocks, mark all the
243+
mutually exclusive sources `required: false`, and enforce "exactly one" at execution
235244
- `mode` only controls UI visibility, NOT serialization. Without `canonicalParamId`, both basic and advanced field values would be sent
236245
- Every subblock `id` must be unique within the block. Duplicate IDs cause conflicts even with different conditions
237246
- **Required consistency:** If one subblock in a canonical group has `required: true`, ALL subblocks in that group must have `required: true` (prevents bypassing validation by switching modes)

.agents/skills/add-model/SKILL.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Use a precise WebFetch prompt: *"Extract for {model_id}: exact model id string,
5252
| `reasoningEffort` | `openai/core.ts`, `azure-openai`, `anthropic/core.ts` (mapped to thinking), `gemini/core.ts` | **Dead on xai, deepseek, mistral, groq, cerebras, openrouter, fireworks, bedrock, vertex** unless their core consumes it — re-grep before assuming |
5353
| `verbosity` | `openai/core.ts`, `azure-openai/index.ts` only | Dead elsewhere |
5454
| `thinking` | `anthropic/core.ts`, `gemini/core.ts` | Dead elsewhere |
55+
| `thinking.streamed` | Docs generator + `getThinkingStreamVisibility` (`models.ts`); `anthropic/core.ts` uses `'summary'` to request `display: 'summarized'` on agent-events runs | **Mandatory on Anthropic-family thinking models** (`agent-stream-docs:check` fails without it); other families fall back to provider defaults |
5556
| `nativeStructuredOutputs` | `anthropic/core.ts`, `fireworks/index.ts`, `openrouter/index.ts` | Dead on openai, xai, google, vertex, bedrock, azure-openai, deepseek, mistral, groq, cerebras |
5657
| `maxOutputTokens` | Read by UI + executor for token estimation | Always meaningful — set if provider documents a cap |
5758
| `computerUse` | `anthropic/core.ts` | Dead elsewhere |
@@ -146,6 +147,15 @@ If anything matches, run the affected provider tests and update assertions as ne
146147

147148
The Consumption Matrix (Step 2) tells you which capability *flags* are honored by existing provider code. But if the new model needs **net-new** request handling that the provider doesn't implement yet — a new beta header (e.g. Anthropic's `anthropic-beta` structured-outputs header in `anthropic/index.ts`), a new thinking/reasoning encoding, a Responses-API quirk — you must edit `apps/sim/providers/<provider>/core.ts` / `index.ts`. Setting a flag whose behavior isn't implemented is a silent no-op. When you do edit provider code, reuse the shared helpers rather than hand-rolling: streaming responses are assembled via `createStreamingExecution` (`@/providers/streaming-execution`) and tool schemas via `adaptOpenAIChatToolSchema` / `adaptAnthropicToolSchema` (`@/providers/tool-schema-adapter`).
148149

150+
### Thinking/reasoning models: `streamed` visibility + generated docs
151+
152+
If the entry has `capabilities.thinking` or `capabilities.reasoningEffort`, it appears in the autogenerated "Streamed thinking and tool calls" table on the Agent block docs page:
153+
154+
- **Anthropic-family (`anthropic`, `azure-anthropic`) thinking models MUST declare `capabilities.thinking.streamed`** (`'full' | 'summary' | 'none'`). Verify against Anthropic's current thinking-display and streaming docs: visible thinking returned by the API is summarized, including when Sim opts models whose default display is `omitted` into `display: 'summarized'` on agent-events runs, so current Claude thinking models use `'summary'`. Use `'full'` only if future official API docs explicitly guarantee raw thinking deltas. `bun run agent-stream-docs:check` (CI) fails if the field is missing.
155+
- Other families usually omit the field and inherit the provider default in `getThinkingStreamVisibility` (Gemini/OpenAI → summaries; Bedrock/Meta → none; OpenAI-compatible vendors with documented reasoning fields → full deltas). Set it explicitly only when the model deviates from its family.
156+
- After inserting the entry, run `bun run agent-stream-docs:generate` and commit the regenerated `apps/docs/content/docs/en/workflows/blocks/agent.mdx` — CI diffs it.
157+
- Include the `streamed` value (with its source URL) in the verification report when set.
158+
149159
### Wrong family entirely?
150160

151161
- **Embedding or rerank model** → it does NOT go in the `models[]` array. Use `EMBEDDING_MODEL_PRICING` / `RERANK_MODEL_PRICING` in `models.ts` instead.
@@ -155,6 +165,7 @@ The Consumption Matrix (Step 2) tells you which capability *flags* are honored b
155165

156166
```bash
157167
bun run lint
168+
bun run agent-stream-docs:generate # only when the entry has thinking/reasoningEffort
158169
```
159170

160171
Lint must pass before reporting done. **If lint fails:** read the error, fix the syntax/typing issue in the entry you just wrote (do not delete the entry — it's the work product), re-run lint, and note the fix in a "Lint adjustments" line in the verification report. Never report done with lint failing.
@@ -201,6 +212,7 @@ Omitting a field is **not the same as verifying it**. Any field you cannot confi
201212
- ❌ Trusting a marketing email (xAI's grok-4.3 email claimed "3 reasoning efforts" but the API rejects `reasoning_effort` — verified by official docs only)
202213
- ❌ Setting `nativeStructuredOutputs: true` on xai/openai/google (dead — only anthropic/fireworks/openrouter consume it)
203214
- ❌ Setting `thinking` on non-Anthropic/non-Gemini providers
215+
- ❌ Adding an Anthropic-family thinking model without `thinking.streamed` (CI `agent-stream-docs:check` fails), or skipping `bun run agent-stream-docs:generate` after adding any thinking/reasoning model
204216
- ❌ Setting `verbosity` on anything other than OpenAI gpt-5.x
205217
- ❌ Copying `pricing.updatedAt` from a sibling instead of using today's date
206218
- ❌ Inventing a `cachedInput` price by dividing input by 4 (varies by provider — find an explicit number)

0 commit comments

Comments
 (0)