Skip to content

Commit 1cf37ca

Browse files
committed
docs(agents): fix contradictions and stale facts in integration skills
Verified against the code: tags live on BlockMeta, not BlockConfig; check-block-registry requires required user-only params to be filled by a subBlock of the same id, so remapping them in tools.config.params fails CI; subblock ids are unique per condition; matchEvent may return a NextResponse; createHmacVerifier needs requireSecret to fail closed; FileToolProcessor is executor-side; provider scopes live in lib/auth/connectors/providers.ts; BYOK needs PROVIDER_SECTIONS; polling crons need the matching docker/crontab line. Duplicated option-list and regenerate sections now point at one copy.
1 parent 7ca8a8b commit 1cf37ca

9 files changed

Lines changed: 117 additions & 160 deletions

File tree

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

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ export const {ServiceName}Block: BlockConfig = {
3535
docsLink: 'https://docs.sim.ai/integrations/{service}',
3636
category: 'tools', // 'tools' | 'blocks' | 'triggers'
3737
integrationType: IntegrationType.X, // Primary category (see IntegrationType enum)
38-
tags: ['oauth', 'api'], // Cross-cutting tags (see IntegrationTag type)
3938
bgColor: '#HEXCOLOR', // Brand color
4039
icon: {ServiceName}Icon,
4140

@@ -63,7 +62,7 @@ export const {ServiceName}Block: BlockConfig = {
6362
},
6463

6564
inputs: {
66-
// Optional: define expected inputs from other blocks
65+
// Required: the params the block accepts, keyed by tool param / canonical id
6766
},
6867

6968
outputs: {
@@ -74,7 +73,7 @@ export const {ServiceName}Block: BlockConfig = {
7473

7574
## SubBlock Types Reference
7675

77-
**Critical:** Every subblock `id` must be unique within the block. Duplicate IDs cause conflicts even with different conditions.
76+
**Critical:** A subblock `id` is unique per condition. The only sanctioned cross-condition reuse is the hosted-key `apiKey` pair (`add-hosted-key` skill), where both fields deliberately share one value. `blocks.test.ts` fails same-condition duplicates.
7877

7978
### Text Inputs
8079
```typescript
@@ -129,6 +128,7 @@ export const {ServiceName}Block: BlockConfig = {
129128
id: 'credential',
130129
title: 'Account',
131130
type: 'oauth-input',
131+
canonicalParamId: 'oauthCredential',
132132
serviceId: '{service}', // Must match OAuth provider service key
133133
requiredScopes: getScopesForService('{service}'), // Import from @/lib/oauth/utils
134134
placeholder: 'Select account',
@@ -370,8 +370,6 @@ Declare the **canonical** id with `type: 'json'` — the subblock ids never reac
370370
```typescript
371371
inputs: {
372372
file: { type: 'json', description: 'File to upload (UserFile or reference)' },
373-
// Legacy field for backwards compatibility
374-
fileContent: { type: 'string', description: 'Legacy: base64 encoded content' },
375373
}
376374
```
377375

@@ -500,6 +498,7 @@ Controls which UI view shows the field.
500498
- `'advanced'` - Only in advanced view
501499
- `'both'` - Both views (default if not specified)
502500
- `'trigger'` - Only in trigger configuration
501+
- `'trigger-advanced'` - The advanced side of a trigger field (a canonical pair member, or a standalone field under the block-level advanced toggle)
503502

504503
### canonicalParamId Pattern
505504

@@ -616,7 +615,7 @@ tools: {
616615
- `items` property - This is only for tool outputs with array types
617616

618617
Block outputs only support:
619-
- `type` - The data type ('string', 'number', 'boolean', 'json', 'array')
618+
- `type` - The data type ('string', 'number', 'boolean', 'json', 'array', 'file', 'file[]', 'any')
620619
- `description` - Human readable description
621620
- `condition` - Optional visibility condition
622621
- `hiddenFromDisplay` - Optional flag to hide from the output display
@@ -679,7 +678,7 @@ export const ServiceV2Block: BlockConfig = {
679678
access: ServiceBlock.tools?.access?.map(id => `${id}_v2`) || [],
680679
config: {
681680
tool: createVersionedToolSelector({
682-
baseToolSelector: (params) => (ServiceBlock.tools?.config as any)?.tool(params),
681+
baseToolSelector: (params) => ServiceBlock.tools.config?.tool(params) ?? 'service_default',
683682
suffix: '_v2',
684683
fallbackToolId: 'service_default_v2',
685684
}),
@@ -726,11 +725,23 @@ export const ServiceBlock: BlockConfig = {
726725
docsLink: 'https://docs.sim.ai/integrations/service',
727726
category: 'tools',
728727
integrationType: IntegrationType.DeveloperTools,
729-
tags: ['oauth', 'api'],
730728
bgColor: '#FF6B6B',
731729
icon: ServiceIcon,
732730
authMode: AuthMode.OAuth,
733731

732+
// Sentence rules: apps/sim/blocks/AGENTS.md → "Canvas sentences"
733+
canvasPresentation: {
734+
defaultTitle: 'Create Resource',
735+
sentences: {
736+
byOperation: {
737+
create: [{ text: 'Create resource', field: 'name', core: true }],
738+
read: [{ text: 'Read resource', field: 'resourceId', core: true }],
739+
update: [{ text: 'Update resource', field: 'resourceId', core: true }],
740+
delete: [{ text: 'Delete resource', field: 'resourceId', core: true }],
741+
},
742+
},
743+
},
744+
734745
subBlocks: [
735746
{
736747
id: 'operation',
@@ -748,6 +759,7 @@ export const ServiceBlock: BlockConfig = {
748759
id: 'credential',
749760
title: 'Service Account',
750761
type: 'oauth-input',
762+
canonicalParamId: 'oauthCredential',
751763
serviceId: 'service',
752764
requiredScopes: getScopesForService('service'),
753765
placeholder: 'Select account',
@@ -778,6 +790,13 @@ export const ServiceBlock: BlockConfig = {
778790
},
779791
},
780792

793+
inputs: {
794+
operation: { type: 'string', description: 'Operation to perform' },
795+
oauthCredential: { type: 'string', description: 'Service access token' },
796+
resourceId: { type: 'string', description: 'Resource ID' },
797+
name: { type: 'string', description: 'Resource name' },
798+
},
799+
781800
outputs: {
782801
id: { type: 'string', description: 'Resource ID' },
783802
name: { type: 'string', description: 'Resource name' },
@@ -937,30 +956,17 @@ tool IDs through `tools.access` and does not change any tool's shape.
937956

938957
But if the same change also adds, edits **or removes** a tool, run `bun run tool-metadata:generate` and commit the result, or CI fails on stale artifacts. That matters here because a block's `outputs` are authored to match its tools' outputs, and the UI reads those from the generated metadata, not the executable registry — an unregenerated tool change makes the block's outputs disagree with what the panel renders. See `.agents/skills/tool-registry-boundary/SKILL.md`.
939958

940-
A visible integration block does require the generated integration catalog and docs to be refreshed.
941-
After adding or changing one, run:
942-
943-
```bash
944-
bun run scripts/generate-docs.ts
945-
bun run deployment-config:generate
946-
bun run integration-catalog:check
947-
bun run deployment-config:check
948-
bun run docs:check
949-
```
950-
951-
The catalog check independently derives deployment metadata from the executable block registry and
952-
compares it with the committed `packages/deployment-config/src/integrations.json`. The deployment
953-
config check verifies the generated service-account facts against the canonical OAuth registry and
954-
catalog. `docs:check` re-renders every generated docs artifact in memory and fails on any committed
955-
file that differs — it runs in CI via `check:audits`, so commit the full generator output. If the
956-
generator also trues up pages an earlier PR left stale, commit that catch-up too; reverting it as
957-
"unrelated drift" makes `docs:check` fail. Review the generated diff and keep only intentional
958-
changes.
959+
A visible integration block does require the generated integration catalog and docs to be refreshed:
960+
`bun run tool-metadata:generate` (only when a tool changed), `bun run scripts/generate-docs.ts`,
961+
`bun run deployment-config:generate`, then `bun run check:audits`. Also run
962+
`bun run apps/sim/scripts/check-block-registry.ts` (CI runs it outside `check:audits`). Commit the
963+
full generator output. For what each check verifies, see the `validate-integration` skill →
964+
Regenerate Derived Artifacts.
959965

960966
## Checklist Before Finishing
961967

962968
- [ ] `integrationType` is set to the correct `IntegrationType` enum value
963-
- [ ] `tags` array includes all applicable `IntegrationTag` values
969+
- [ ] `{Service}BlockMeta.tags` lists every applicable `IntegrationTag` (tags live on the meta, not the block)
964970
- [ ] All subBlocks have `id`, `title` (except switch), and `type`
965971
- [ ] Conditions use correct syntax (field, value, not, and)
966972
- [ ] DependsOn set for fields that need other values
@@ -996,7 +1002,7 @@ Validate the block against every tool in `tools.access`:
9961002
2. **For each tool, verify the block has correct:**
9971003
- SubBlock inputs that cover all required tool params (with correct `condition` to show for that operation)
9981004
- SubBlock input types that match the tool param types (e.g., dropdown for enums, short-input for strings)
999-
- `tools.config.params` correctly maps subBlock IDs to tool param names (if they differ)
1005+
- Each subBlock (or its `canonicalParamId`) is named exactly after the tool param it fills. A required `user-only` param that is only renamed in `tools.config.params` fails `bun run apps/sim/scripts/check-block-registry.ts`; remap only optional or `user-or-llm` params
10001006
- Type coercions in `tools.config.params` for any params that need conversion (Number(), Boolean(), JSON.parse())
10011007
3. **Verify block outputs** cover the key fields returned by all tools
10021008
4. **Verify conditions** — each subBlock should only show for the operations that actually use it

‎.agents/skills/add-hosted-key/SKILL.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ In the block config (`blocks/blocks/{service}.ts`), add `hideWhenHosted: true` t
198198
},
199199
```
200200

201-
The visibility is controlled by `isSubBlockHidden()` in `lib/workflows/subblocks/visibility.ts`, which checks both the `isHosted` feature flag (`hideWhenHosted`) and optional env var conditions (`hideWhenEnvSet`).
201+
The visibility is controlled by `isSubBlockHidden()` in `lib/workflows/subblocks/visibility.ts`, which checks both `getDeploymentShape().hosted` (`hideWhenHosted`) and optional env var conditions (`hideWhenEnvSet`).
202202

203203
### Excluding Specific Operations from Hosted Key Support
204204

@@ -251,6 +251,9 @@ Add an entry to the `PROVIDERS` array in the BYOK settings component so users ca
251251
},
252252
```
253253

254+
Then add the id to exactly one section's `ids` in `PROVIDER_SECTIONS` (same file), and run
255+
`bun run check:byok-providers`.
256+
254257
## Step 6: Summarize Pricing and Throttling Comparison
255258

256259
After all code changes are complete, output a detailed summary to the user covering:
@@ -296,5 +299,6 @@ This summary helps reviewers verify that the pricing and rate limiting are well-
296299
- [ ] Cost data captured in `transformResponse` or `postProcess` if API provides it
297300
- [ ] `hideWhenHosted: true` added to the API key subblock in the block config
298301
- [ ] Provider entry added to the BYOK settings UI with icon and description
302+
- [ ] Provider id listed in exactly one `PROVIDER_SECTIONS` section's `ids`; `bun run check:byok-providers` passes
299303
- [ ] Env vars documented: `{PREFIX}_COUNT` and `{PREFIX}_1..N`
300304
- [ ] Pricing and throttling summary provided to reviewer

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

Lines changed: 23 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,8 @@ Three rules that are easy to get wrong when copying from existing blocks:
131131
add a client provider fetcher, a provider-specific query key, browser token acquisition, or a
132132
selector-only API route. The shared context builder sends only active `dependsOn` values and
133133
preserves exact `{{KEY}}` environment references for server-side resolution.
134-
- A `canonicalParamId` is a third name that neither member of a basic/advanced pair uses as its `id`
135-
(e.g. `channelSelector` + `channelId` → `canonicalParamId: 'channel'`). It is the only key that
136-
survives serialization, so `inputs` and `tools.config.params` reference the canonical id, never the
137-
subblock ids. It is unique block-wide, and every member of a group shares the same `required` value.
134+
- Basic/advanced pairs use a `canonicalParamId`; its constraints are in
135+
`.claude/rules/sim-integrations.md` and the `add-block` skill → canonicalParamId Pattern.
138136
- Every text-entry subBlock (`short-input`, `long-input`, `code`) and every selector declares a
139137
`placeholder`; an empty box tells the user nothing. Secrets read `Enter your {thing}` (e.g.
140138
`Enter your API key`), free text names what to type (`Enter branch name`), and formatted values
@@ -215,7 +213,7 @@ import {
215213
} from '@/tools/{service}'
216214

217215
// Add to tools object (alphabetically)
218-
export const tools: Record<string, ToolConfig> = {
216+
export const tools: Record<string, ExecutableToolConfig> = {
219217
// ... existing tools ...
220218
{service}_action1: {service}Action1Tool,
221219
{service}_action2: {service}Action2Tool,
@@ -304,16 +302,11 @@ a resolvable capability must fail validation.
304302

305303
## Step 8: Generate and Validate the Catalog
306304

307-
Run the documentation generator:
308-
```bash
309-
bun run scripts/generate-docs.ts
310-
bun run deployment-config:generate
311-
bun run integration-catalog:check
312-
bun run deployment-config:check
313-
bun run docs:check
314-
```
305+
Run `bun run tool-metadata:generate`, `bun run scripts/generate-docs.ts`,
306+
`bun run deployment-config:generate`, then `bun run check:audits` (see the `validate-integration`
307+
skill → Regenerate Derived Artifacts for the full list and what each check verifies).
315308

316-
This creates `apps/docs/content/docs/integrations/{service}.mdx` — one page per service carrying the block's Actions and, if it has one, its Triggers section. Never hand-edit generated pages; the only editable region is the `{/* MANUAL-CONTENT */}` block (see `scripts/README.md`).
309+
The docs generator creates `apps/docs/content/docs/integrations/{service}.mdx` — one page per service carrying the block's Actions and, if it has one, its Triggers section. Never hand-edit generated pages; the only editable region is the `{/* MANUAL-CONTENT */}` block (see `scripts/README.md`).
317310

318311
Every generated integration page carries a hand-written intro directly under `<BlockInfoCard />`. The
319312
generator preserves it across regenerations, so write it once after the first generate:
@@ -392,7 +385,7 @@ If creating V2 versions (API-aligned outputs):
392385
### Block
393386
- [ ] Created `blocks/blocks/{service}.ts`
394387
- [ ] Set `integrationType` to the correct `IntegrationType` enum value
395-
- [ ] Set `tags` array with all applicable `IntegrationTag` values
388+
- [ ] `{Service}BlockMeta.tags` lists every applicable `IntegrationTag` (tags live on the meta, not the block)
396389
- [ ] Defined operation dropdown with all operations
397390
- [ ] Added credential field with `requiredScopes: getScopesForService('{service}')`
398391
- [ ] Added conditional fields per operation
@@ -415,7 +408,7 @@ If creating V2 versions (API-aligned outputs):
415408
### OAuth Scopes (if OAuth service)
416409
- [ ] Defined scopes in `lib/oauth/oauth.ts` under `OAUTH_PROVIDERS`
417410
- [ ] Added scope descriptions in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`
418-
- [ ] Used `getCanonicalScopesForProvider()` in `auth.ts` (never hardcode)
411+
- [ ] Used `getCanonicalScopesForProvider()` in `lib/auth/connectors/providers.ts` (never hardcode)
419412
- [ ] Used `getScopesForService()` in block `requiredScopes` (never hardcode)
420413

421414
### Deployment Availability (if OAuth service)
@@ -548,51 +541,20 @@ Implement `apps/sim/lib/internal/{service}/execute-tool.ts` and keep the file/pr
548541
operations beside it. The handler validates `request.input`, derives storage authority only from
549542
trusted `request.context`, authorizes every stored file before reading bytes, forwards
550543
`request.signal`, enforces declared and actual byte caps, and returns the canonical tool response.
551-
Register `{service}_upload` in `apps/sim/lib/internal/tool-operations/registry.server.ts` and add a
552-
registry/direct-handler test. There is no HTTP fallback.
544+
Register `{service}_upload` in `apps/sim/lib/internal/tool-operations/registry.server.ts`; the
545+
existing `registry.server.test.ts` completeness test covers registration. For anything more, run the
546+
`test-audit` gate. There is no HTTP fallback.
553547

554548
### File Output Pattern (Downloads)
555549

556-
For tools that return files, use `FileToolProcessor` to store files and return `UserFile` objects.
557-
558-
#### In Tool transformResponse
559-
560-
```typescript
561-
import { FileToolProcessor } from '@/executor/utils/file-tool-processor'
562-
563-
transformResponse: async (response, context) => {
564-
const data = await response.json()
565-
566-
// Process file outputs to UserFile objects
567-
const fileProcessor = new FileToolProcessor(context)
568-
const file = await fileProcessor.processFileData({
569-
data: data.content, // base64 or buffer
570-
mimeType: data.mimeType,
571-
filename: data.filename,
572-
})
573-
574-
return {
575-
success: true,
576-
output: { file },
577-
}
578-
}
579-
```
580-
581-
#### In the operation handler (for complex file handling)
550+
Declare a `file` / `file[]` output on the tool. For a raw binary endpoint, set
551+
`request.responseType: 'binary'` and return `output.file = { name, mimeType, data: buffer, size }`
552+
from `transformResponse(response, params?, context?)`. The executor's `FileToolProcessor` stores it
553+
and replaces it with a `UserFile`; tools never call it.
582554

583-
```typescript
584-
// Return file data that FileToolProcessor can handle. No API route is involved.
585-
return Response.json({
586-
success: true,
587-
output: {
588-
file: {
589-
data: base64Content,
590-
mimeType: 'application/pdf',
591-
filename: 'document.pdf',
592-
},
593-
},
594-
})
595-
```
555+
In an operation handler, return `createInternalToolFileResult` / `createInternalToolFilesResult`
556+
from `lib/internal/tool-operations/file-result.ts` — never base64 JSON. See the `add-tools` skill →
557+
File Downloads and Generated Files.
596558

597559
### Key Helpers Reference
598560

@@ -601,7 +563,7 @@ return Response.json({
601563
| `normalizeFileInput` | `@/blocks/utils` | Normalize file params in block config |
602564
| `processFilesToUserFiles` | `@/lib/uploads/utils/file-utils` | Convert raw inputs to UserFile[] |
603565
| `downloadFileFromStorage` | `@/lib/uploads/utils/file-utils.server` | Get file Buffer from UserFile |
604-
| `FileToolProcessor` | `@/executor/utils/file-tool-processor` | Process tool output files |
566+
| `FileToolProcessor` | `@/executor/utils/file-tool-processor` | Executor-side; stores declared file outputs (not called by tools) |
605567
| `isUserFile` | `@/lib/core/utils/user-file` | Type guard for UserFile objects |
606568
| `FileInputSchema` | `@/lib/uploads/utils/file-schemas` | Zod schema for file validation |
607569

@@ -636,13 +598,13 @@ Scopes are maintained in a single source of truth and reused everywhere:
636598

637599
1. **Define scopes** in `lib/oauth/oauth.ts` under `OAUTH_PROVIDERS[provider].services[service].scopes`
638600
2. **Add descriptions** in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts` for the OAuth modal UI
639-
3. **Reference in auth.ts** using `getCanonicalScopesForProvider(providerId)` from `@/lib/oauth/utils`
601+
3. **Reference in `lib/auth/connectors/providers.ts`** (`buildConnectorProviders`) using `getCanonicalScopesForProvider(providerId)` from `@/lib/oauth/utils`
640602
4. **Reference in blocks** using `getScopesForService(serviceId)` from `@/lib/oauth/utils`
641603

642-
**Never hardcode scope arrays** in `auth.ts` or block `requiredScopes`. Always import from the centralized source.
604+
**Never hardcode scope arrays** in the Better Auth connector providers or block `requiredScopes`. Always import from the centralized source.
643605

644606
```typescript
645-
// In auth.ts (Better Auth config)
607+
// In lib/auth/connectors/providers.ts (Better Auth connector providers)
646608
scopes: getCanonicalScopesForProvider('{service}'),
647609

648610
// In block credential sub-block

0 commit comments

Comments
 (0)