Skip to content

Commit 93bfba8

Browse files
Merge remote-tracking branch 'origin/staging' into improvement/table-rename-column
# Conflicts: # packages/db/migrations/meta/0227_snapshot.json # packages/db/migrations/meta/_journal.json
2 parents 62c86c5 + 2c7b1ca commit 93bfba8

1,379 files changed

Lines changed: 71222 additions & 49475 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: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,19 +627,78 @@ export const ServiceV2Block: BlockConfig = {
627627
}
628628
```
629629

630+
## Block Metadata (BlockMeta)
631+
632+
Every integration block **must** export a `{Service}BlockMeta` object at the bottom of the block file. This metadata drives the integration catalog, tag filters, and workflow template suggestions shown to users.
633+
634+
### Structure
635+
636+
```typescript
637+
import type { BlockConfig, BlockMeta } from '@/blocks/types'
638+
639+
// ... block definition above ...
640+
641+
export const {Service}BlockMeta = {
642+
tags: ['messaging', 'automation'], // Same tags as the block's tags field
643+
templates: [ // Optional but strongly encouraged
644+
{
645+
icon: {Service}Icon,
646+
title: '{Service} use-case title',
647+
prompt: 'Build a workflow that ...',
648+
modules: ['agent', 'workflows'], // Modules the template uses
649+
category: 'productivity', // Template category
650+
tags: ['automation'], // Template-level tags
651+
alsoIntegrations: ['slack'], // Other blocks referenced in the prompt (optional)
652+
},
653+
],
654+
} as const satisfies BlockMeta
655+
```
656+
657+
### Rules
658+
659+
- **Import `BlockMeta`** from `@/blocks/types` alongside `BlockConfig`
660+
- **`tags`** must match the `tags` array on the block config exactly
661+
- **Templates are optional** but should be added for any integration that has a recognizable use case — aim for 2–4 templates per block
662+
- **Template `prompt`** should start with "Build a workflow that..." or "Create a workflow that..." and be concrete enough to generate a real workflow in Mothership
663+
- **Template `modules`** lists the Sim modules the template relies on: `'knowledge-base' | 'tables' | 'files' | 'workflows' | 'scheduled' | 'agent'`
664+
- **Template `category`** is one of: `'popular' | 'sales' | 'support' | 'engineering' | 'marketing' | 'productivity' | 'operations'`
665+
- **`alsoIntegrations`** names other block types (e.g. `'slack'`, `'linear'`) referenced in the template prompt — helps the catalog surface this template when those blocks are selected
666+
- Place the export **after** the main `{Service}Block` export, at the very bottom of the file
667+
668+
### Register in the blocksMeta object
669+
670+
After adding `{Service}BlockMeta` to the block file, register it in `apps/sim/blocks/registry.ts`:
671+
672+
```typescript
673+
// Add import (alongside the block import, alphabetically)
674+
import { ServiceBlock, ServiceBlockMeta } from '@/blocks/blocks/service'
675+
676+
// Add to blocksMeta object (alphabetically)
677+
export const blocksMeta = {
678+
// ... existing entries ...
679+
service: ServiceBlockMeta,
680+
}
681+
```
682+
630683
## Registering Blocks
631684

632685
After creating the block, remind the user to:
633-
1. Import in `apps/sim/blocks/registry.ts`
686+
1. Import `{Service}Block` and `{Service}BlockMeta` in `apps/sim/blocks/registry.ts`
634687
2. Add to the `registry` object (alphabetically):
688+
3. Add to the `blocksMeta` object (alphabetically):
635689

636690
```typescript
637-
import { ServiceBlock } from '@/blocks/blocks/service'
691+
import { ServiceBlock, ServiceBlockMeta } from '@/blocks/blocks/service'
638692

639693
export const registry: Record<string, BlockConfig> = {
640694
// ... existing blocks ...
641695
service: ServiceBlock,
642696
}
697+
698+
export const blocksMeta = {
699+
// ... existing entries ...
700+
service: ServiceBlockMeta,
701+
}
643702
```
644703

645704
## Complete Example
@@ -827,7 +886,10 @@ All tool IDs referenced in `tools.access` and returned by `tools.config.tool` MU
827886
- [ ] Tools.access lists all tool IDs (snake_case)
828887
- [ ] Tools.config.tool returns correct tool ID (snake_case)
829888
- [ ] Outputs match tool outputs
830-
- [ ] Block registered in registry.ts
889+
- [ ] Block registered in `registry.ts` blocks object (alphabetically)
890+
- [ ] `{Service}BlockMeta` exported at bottom of block file with `tags` and `templates`
891+
- [ ] `BlockMeta` imported from `@/blocks/types` alongside `BlockConfig`
892+
- [ ] Block meta registered in `registry.ts` blocksMeta object (alphabetically)
831893
- [ ] If icon missing: asked user to provide SVG
832894
- [ ] If triggers exist: `triggers` config set, trigger subBlocks spread
833895
- [ ] Optional/rarely-used fields set to `mode: 'advanced'`

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

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const {service}{Action}Tool: ToolConfig<Params, Response> = {
128128
### Block Structure
129129
```typescript
130130
import { {Service}Icon } from '@/components/icons'
131-
import type { BlockConfig } from '@/blocks/types'
131+
import type { BlockConfig, BlockMeta } from '@/blocks/types'
132132
import { AuthMode } from '@/blocks/types'
133133
import { getScopesForService } from '@/lib/oauth/utils'
134134

@@ -177,8 +177,32 @@ export const {Service}Block: BlockConfig = {
177177

178178
outputs: { /* ... */ },
179179
}
180+
181+
export const {Service}BlockMeta = {
182+
tags: ['tag1', 'tag2'], // IntegrationTag values matching the service's capabilities
183+
templates: [
184+
{
185+
icon: {Service}Icon,
186+
title: '{Service} use-case title',
187+
prompt: 'Build a workflow that ...',
188+
modules: ['agent', 'workflows'],
189+
category: 'productivity',
190+
tags: ['automation'],
191+
alsoIntegrations: ['slack'], // Optional: other blocks referenced in the prompt
192+
},
193+
],
194+
} as const satisfies BlockMeta
180195
```
181196

197+
### BlockMeta rules
198+
199+
- **Tags**: Use `IntegrationTag` values from `@/blocks/types`. Do NOT add a `tags` field to the `BlockConfig` object — tags belong only in `BlockMeta`.
200+
- **`integrationType`**: Must be a valid `IntegrationType` enum value (AI, Analytics, Commerce, Communication, Databases, DevOps, Documents, Email, HR, Marketing, Observability, Productivity, Sales, Search, Security, Support). Never invent a value that doesn't exist in the enum.
201+
- **Templates**: Aim for 2–4 templates per integration. Prompts should be concrete enough to generate a real workflow in Mothership. Start with "Build a workflow that..." or "Create a workflow that...".
202+
- **`alsoIntegrations`**: List other block type IDs referenced in the template prompt (e.g. `'slack'`, `'linear'`).
203+
- Place `{Service}BlockMeta` at the very bottom of the file, after the main block export.
204+
- Register it in both the import and the `blocksMeta` object in `registry.ts`.
205+
182206
### Key SubBlock Patterns
183207

184208
**Condition-based visibility:**
@@ -359,14 +383,20 @@ export const tools: Record<string, ToolConfig> = {
359383
### Block Registry (`apps/sim/blocks/registry.ts`)
360384

361385
```typescript
362-
// Add import (alphabetically)
363-
import { {Service}Block } from '@/blocks/blocks/{service}'
386+
// Add import (alphabetically) — include BlockMeta
387+
import { {Service}Block, {Service}BlockMeta } from '@/blocks/blocks/{service}'
364388

365-
// Add to registry (alphabetically)
389+
// Add to blocks registry (alphabetically)
366390
export const registry: Record<string, BlockConfig> = {
367391
// ... existing blocks ...
368392
{service}: {Service}Block,
369393
}
394+
395+
// Add to blocksMeta registry (alphabetically)
396+
export const blocksMeta = {
397+
// ... existing entries ...
398+
{service}: {Service}BlockMeta,
399+
}
370400
```
371401

372402
### Trigger Registry (`apps/sim/triggers/registry.ts`) - If triggers exist
@@ -433,7 +463,12 @@ If creating V2 versions (API-aligned outputs):
433463
- [ ] Configured tools.access with all tool IDs
434464
- [ ] Configured tools.config.tool selector
435465
- [ ] Defined outputs matching tool outputs
436-
- [ ] Registered block in `blocks/registry.ts`
466+
- [ ] `integrationType` uses a valid `IntegrationType` enum value (no invented values)
467+
- [ ] No `tags` field on the `BlockConfig` object (tags live only in `BlockMeta`)
468+
- [ ] `{Service}BlockMeta` exported at bottom of file with `tags` and `templates`
469+
- [ ] `BlockMeta` type imported from `@/blocks/types` alongside `BlockConfig`
470+
- [ ] Block registered in `blocks/registry.ts` blocks object (alphabetically)
471+
- [ ] Block meta registered in `blocks/registry.ts` blocksMeta object (alphabetically)
437472
- [ ] If triggers: set `triggers.enabled` and `triggers.available`
438473
- [ ] If triggers: spread trigger subBlocks with `getTrigger()`
439474

.claude/commands/add-block.md

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ Maps multiple UI fields to a single serialized parameter:
429429

430430
**Critical constraints:**
431431
- `canonicalParamId` must NOT match any other subblock's `id` in the same block (causes conflicts)
432-
- `canonicalParamId` must be unique per block (only one basic/advanced pair per canonicalParamId)
432+
- A `canonicalParamId` links exactly one basic/advanced pair for a single logical parameter. Do NOT reuse the same `canonicalParamId` for different parameters, even under mutually-exclusive conditions/operations
433433
- ONLY use `canonicalParamId` to link basic/advanced mode alternatives for the same logical parameter
434434
- Do NOT use it for any other purpose
435435

@@ -519,7 +519,10 @@ tools: {
519519
Block outputs only support:
520520
- `type` - The data type ('string', 'number', 'boolean', 'json', 'array')
521521
- `description` - Human readable description
522-
- Nested object structure (for complex types)
522+
- `condition` - Optional visibility condition
523+
- `hiddenFromDisplay` - Optional flag to hide from the output display
524+
525+
**Nested object/`properties` outputs are tool-output-only and will fail TypeScript at build time on block outputs.** For complex shapes use `type: 'json'` and describe the inner fields in the `description` string.
523526

524527
```typescript
525528
outputs: {
@@ -542,7 +545,7 @@ outputs: {
542545

543546
### Typed JSON Outputs
544547

545-
When using `type: 'json'` and you know the object shape in advance, **describe the inner fields in the description** so downstream blocks know what properties are available. For well-known, stable objects, use nested output definitions instead:
548+
When using `type: 'json'` and you know the object shape in advance, **describe the inner fields in the description** so downstream blocks know what properties are available. Block outputs have no nested `properties` form — always keep the output flat and put the shape in the `description`:
546549

547550
```typescript
548551
outputs: {
@@ -554,26 +557,10 @@ outputs: {
554557
type: 'json',
555558
description: 'Zone plan information (id, name, price, currency, frequency, is_subscribed)',
556559
},
557-
558-
// BEST: Use nested output definition when the shape is stable and well-known
559-
plan: {
560-
id: { type: 'string', description: 'Plan identifier' },
561-
name: { type: 'string', description: 'Plan name' },
562-
price: { type: 'number', description: 'Plan price' },
563-
currency: { type: 'string', description: 'Price currency' },
564-
},
565560
}
566561
```
567562

568-
Use the nested pattern when:
569-
- The object has a small, stable set of fields (< 10)
570-
- Downstream blocks will commonly access specific properties
571-
- The API response shape is well-documented and unlikely to change
572-
573-
Use `type: 'json'` with a descriptive string when:
574-
- The object has many fields or a dynamic shape
575-
- It represents a list/array of items
576-
- The shape varies by operation
563+
Nested object outputs (`plan: { id: { type: 'string' }, ... }`) are a **tool-output** feature only — `OutputFieldDefinition` for blocks does not allow them and they fail TypeScript at build time.
577564

578565
## V2 Block Pattern
579566

@@ -798,6 +785,33 @@ Use `wandConfig` for fields that are hard to fill out manually, such as timestam
798785

799786
All tool IDs referenced in `tools.access` and returned by `tools.config.tool` MUST use `snake_case` (e.g., `x_create_tweet`, `slack_send_message`). Never use camelCase or PascalCase.
800787

788+
## BlockMeta (Required)
789+
790+
Every block file must export a `{Service}BlockMeta` alongside the block — **minimum 7 templates**. Look at existing examples in `apps/sim/blocks/blocks/` (e.g. `browser_use.ts`, `google_sheets.ts`) for the pattern.
791+
792+
```typescript
793+
import type { BlockMeta } from '@/blocks/types'
794+
795+
export const {Service}BlockMeta = {
796+
tags: ['tag1', 'tag2'], // IntegrationTag[]
797+
templates: [
798+
{
799+
icon: {Service}Icon,
800+
title: '{Service} <use-case>', // 2–5 words
801+
prompt: 'Build a workflow that...', // specific use case, 1–3 sentences
802+
modules: ['agent', 'workflows'], // 'agent' | 'workflows' | 'tables' | 'files' | 'scheduled' | 'knowledge-base'
803+
category: 'operations', // 'operations' | 'marketing' | 'sales' | 'engineering' | 'productivity' | 'support' | 'popular'
804+
tags: ['automation'],
805+
alsoIntegrations: ['slack'], // optional — other block IDs referenced in the prompt
806+
featured: true, // optional
807+
},
808+
// ... at least 6 more
809+
],
810+
} as const satisfies BlockMeta
811+
```
812+
813+
Derive templates from the service's real use cases. Each prompt should name a concrete trigger, transformation, and output — not a generic description of what the service does.
814+
801815
## Checklist Before Finishing
802816

803817
- [ ] `integrationType` is set to the correct `IntegrationType` enum value
@@ -816,6 +830,7 @@ All tool IDs referenced in `tools.access` and returned by `tools.config.tool` MU
816830
- [ ] If triggers exist: `triggers` config set, trigger subBlocks spread
817831
- [ ] Optional/rarely-used fields set to `mode: 'advanced'`
818832
- [ ] Timestamps and complex inputs have `wandConfig` enabled
833+
- [ ] Exported `{Service}BlockMeta` with at least 7 templates
819834

820835
## Final Validation (Required)
821836

@@ -829,3 +844,4 @@ After creating the block, you MUST validate it against every tool it references:
829844
- Type coercions in `tools.config.params` for any params that need conversion (Number(), Boolean(), JSON.parse())
830845
3. **Verify block outputs** cover the key fields returned by all tools
831846
4. **Verify conditions** — each subBlock should only show for the operations that actually use it
847+
5. **Verify `{Service}BlockMeta` is exported** with at least 7 templates, each having `icon`, `title`, `prompt`, `modules`, `category`, and `tags`

.claude/commands/add-connector.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const {service}Connector: ConnectorConfig = {
6767

6868
configFields: [
6969
// Rendered dynamically by the add-connector modal UI
70-
// Supports 'short-input' and 'dropdown' types
70+
// Supports 'short-input', 'dropdown', and 'selector' types — see ConfigField Types below
7171
],
7272

7373
listDocuments: async (accessToken, sourceConfig, cursor) => {
@@ -408,6 +408,16 @@ Each entry has:
408408
Users can opt out of specific tags in the modal. Disabled IDs are stored in `sourceConfig.disabledTagIds`.
409409
The assigned mapping (`semantic id → slot`) is stored in `sourceConfig.tagSlotMapping`.
410410

411+
## `@/connectors/utils` Helpers
412+
413+
Reuse these instead of inlining the same logic (the validator enforces them):
414+
415+
- `htmlToPlainText(html)` — strip HTML to plain text before indexing `ExternalDocument.content`. Never index raw HTML.
416+
- `computeContentHash(content)` — stable content hash for change detection.
417+
- `parseTagDate(value)` — parse to a valid `Date` or `undefined` (guards Invalid Date). Use in `mapTags` for date fields.
418+
- `joinTagArray(value)` — validate an array and join to a comma-separated string, or `undefined`. Use in `mapTags` for array/label fields.
419+
- `parseMultiValue(value)` — normalize a value into a `string[]`.
420+
411421
## mapTags — Metadata to Semantic Keys
412422

413423
Maps source metadata to semantic tag keys. Required if `tagDefinitions` is set.
@@ -416,25 +426,27 @@ using the `tagSlotMapping` stored on the connector.
416426

417427
Return keys must match the `id` values declared in `tagDefinitions`.
418428

429+
Use the `@/connectors/utils` helpers for the common transforms — don't hand-roll date/array validation:
430+
419431
```typescript
432+
import { joinTagArray, parseTagDate } from '@/connectors/utils'
433+
420434
mapTags: (metadata: Record<string, unknown>): Record<string, unknown> => {
421435
const result: Record<string, unknown> = {}
422436

423-
// Validate arrays before casting — metadata may be malformed
424-
const labels = Array.isArray(metadata.labels) ? (metadata.labels as string[]) : []
425-
if (labels.length > 0) result.labels = labels.join(', ')
437+
// joinTagArray validates the array and joins to a comma-separated string (undefined if empty)
438+
const labels = joinTagArray(metadata.labels)
439+
if (labels) result.labels = labels
426440

427441
// Validate numbers — guard against NaN
428442
if (metadata.version != null) {
429443
const num = Number(metadata.version)
430444
if (!Number.isNaN(num)) result.version = num
431445
}
432446

433-
// Validate dates — guard against Invalid Date
434-
if (typeof metadata.lastModified === 'string') {
435-
const date = new Date(metadata.lastModified)
436-
if (!Number.isNaN(date.getTime())) result.lastModified = date
437-
}
447+
// parseTagDate returns a valid Date or undefined (guards against Invalid Date)
448+
const lastModified = parseTagDate(metadata.lastModified)
449+
if (lastModified) result.lastModified = lastModified
438450

439451
return result
440452
}

.claude/commands/add-hosted-key.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ When a tool has hosted key support, Sim provides its own API key if the user has
1111

1212
| Step | What | Where |
1313
|------|------|-------|
14-
| 1 | Register BYOK provider ID | `tools/types.ts`, `app/api/workspaces/[id]/byok-keys/route.ts` |
14+
| 1 | Register BYOK provider ID | `tools/types.ts`, `lib/api/contracts/byok-keys.ts` |
1515
| 2 | Research the API's pricing and rate limits | API docs / pricing page (before writing any code) |
1616
| 3 | Add `hosting` config to the tool | `tools/{service}/{action}.ts` |
1717
| 4 | Hide API key field when hosted | `blocks/blocks/{service}.ts` |
@@ -30,10 +30,15 @@ export type BYOKProviderId =
3030
| 'your_service'
3131
```
3232
33-
Then add it to `VALID_PROVIDERS` in `app/api/workspaces/[id]/byok-keys/route.ts`:
33+
Then add the same provider id to the `byokProviderIdSchema` enum in `lib/api/contracts/byok-keys.ts` (this is what the byok-keys route validates against):
3434
3535
```typescript
36-
const VALID_PROVIDERS = ['openai', 'anthropic', 'google', 'mistral', 'your_service'] as const
36+
export const byokProviderIdSchema = z.enum([
37+
'openai',
38+
'anthropic',
39+
// ...existing providers
40+
'your_service',
41+
])
3742
```
3843

3944
## Step 2: Research the API's Pricing Model and Rate Limits
@@ -230,7 +235,7 @@ Both subblocks share the same `id: 'apiKey'`, so the same value flows to the too
230235
To exclude multiple operations, use an array: `{ field: 'operation', value: ['op_a', 'op_b'] }`.
231236

232237
**Reference implementations:**
233-
- **Exa** (`blocks/blocks/exa.ts`): `research` operation excluded from hosting — lines 309-329
238+
- **Exa** (`blocks/blocks/exa.ts`): `exa_research` operation excluded from hosting — duplicate `apiKey` pair around lines ~348-365
234239
- **Google Maps** (`blocks/blocks/google_maps.ts`): `speed_limits` operation excluded from hosting (deprecated Roads API)
235240

236241
## Step 5: Add to the BYOK Settings UI
@@ -284,7 +289,7 @@ This summary helps reviewers verify that the pricing and rate limiting are well-
284289
## Checklist
285290

286291
- [ ] Provider added to `BYOKProviderId` in `tools/types.ts`
287-
- [ ] Provider added to `VALID_PROVIDERS` in the BYOK keys API route
292+
- [ ] Provider added to `byokProviderIdSchema` enum in `lib/api/contracts/byok-keys.ts`
288293
- [ ] API pricing docs researched — understand per-unit cost and whether the API reports cost in responses
289294
- [ ] API rate limits researched — understand RPM/TPM limits, per-key vs per-account, and plan tiers
290295
- [ ] `hosting` config added to the tool with `envKeyPrefix`, `apiKeyParam`, `byokProviderId`, `pricing`, and `rateLimit`

0 commit comments

Comments
 (0)