* fix(kb): improve error logging when connector token resolution fails
The generic "Failed to obtain access token" error hid the actual root cause.
Now logs credentialId, userId, authMode, and provider to help diagnose
token refresh failures in trigger.dev.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(kb): disable connectors after 10 consecutive sync failures
Connectors that fail 10 times in a row are set to 'disabled' status,
stopping the cron from scheduling further syncs. The UI shows an alert
triangle with a reconnect banner. Users can re-enable via the play
button or by reconnecting their account, which resets failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(kb): disable sync button for disabled connectors, use amber badge variant
Sync button should be disabled when connector is in disabled state to
guide users toward reconnecting first. Badge variant changed from red
to amber to match the warning banner styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(kb): address PR review comments for disabled connector feature
- Use `=== undefined` instead of falsy check for nextSyncAt to preserve
explicit null (manual sync only) when syncIntervalMinutes is 0
- Gate Reconnect button on serviceId/providerId so it only renders for
OAuth connectors; show appropriate copy for API key connectors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(kb): move resolveAccessToken inside try/catch for circuit-breaker coverage
Token resolution failures (e.g. revoked OAuth tokens) were thrown before
the try/catch block, bypassing consecutiveFailures tracking entirely.
Also removes dead `if (refreshed)` guards at mid-sync refresh sites since
resolveAccessToken now always returns a string or throws.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(kb): remove dead interval branch when re-enabling connector
When `updates.nextSyncAt === undefined`, syncIntervalMinutes was not in
the request, so `parsed.data.syncIntervalMinutes` is always undefined.
Simplify to just schedule an immediate sync — the sync engine sets the
proper nextSyncAt based on the connector's DB interval after completion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(kb): deferred content fetching and metadata-based hashes for connectors
* fix(kb): remove message count from outlook contentHash to prevent list/get divergence
* fix(kb): increase outlook getDocument message limit from 50 to 250
* fix(kb): skip outlook messages without conversationId to prevent broken stubs
* fix(kb): scope outlook getDocument to same folder as listDocuments to prevent hash divergence
* fix(kb): add missing connector sync cron job to Helm values
The connector sync endpoint existed but had no cron job configured to trigger it,
meaning scheduled syncs would never fire.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review comments on staging release
- Add try/catch around clipboard.writeText() in CopyCodeButton
- Add missing folder and past_chat cases in resolveResourceFromContext
- Return 400 for ZodError instead of 500 in all 8 Athena API routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): return 400 for Zod validation errors across 27 API routes
Routes using z.parse() were returning 500 for ZodError (client input
validation failures). Added instanceof z.ZodError check to return 400
before the generic 500 handler, matching the established pattern used
by 115+ other routes.
Affected services: CloudWatch (7), CloudFormation (7), DynamoDB (6),
Slack (3), Outlook (2), OneDrive (1), Google Drive (1).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): add success:false to ZodError responses for consistency
7 routes used { success: false, error: ... } in their generic error
handler but our ZodError handler only returned { error: ... }. Aligned
the ZodError response shape to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(polling): consolidate polling services into provider handler pattern
Eliminate self-POST anti-pattern and extract shared boilerplate from 4 polling
services into a clean handler registry mirroring lib/webhooks/providers/.
- Add processPolledWebhookEvent() to processor.ts for direct in-process webhook
execution, removing HTTP round-trips that caused Lambda 403/timeout errors
- Extract shared utilities (markWebhookFailed/Success, fetchActiveWebhooks,
runWithConcurrency, resolveOAuthCredential, updateWebhookProviderConfig)
- Create PollingProviderHandler interface with per-provider implementations
- Consolidate 4 identical route files into single dynamic [provider] route
- Standardize concurrency to 10 across all providers
- No infra changes needed — Helm cron paths resolve via dynamic route
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* polish(polling): extract lock TTL constant and remove unnecessary type casts
- Widen processPolledWebhookEvent body param to accept object, eliminating
`as unknown as Record<string, unknown>` double casts in all 4 handlers
- Extract LOCK_TTL_SECONDS constant in route, tying maxDuration and lock TTL
to a single value
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(polling): address PR review feedback
- Add archivedAt filters to fetchActiveWebhooks query, matching
findWebhookAndWorkflow in processor.ts to prevent polling archived
webhooks/workflows
- Move provider validation after auth check to prevent provider
enumeration by unauthenticated callers
- Fix inconsistent pollingIdempotency import path in outlook.ts to
match other handlers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(polling): use literal for maxDuration segment config
Next.js requires segment config exports to be statically analyzable
literals. Using a variable reference caused build failure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(admin): delete workspaces on ban
* Fix lint
* Wait until workspace deletion to return ban success
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(athena): add AWS Athena integration
* fix(athena): address PR review comments
- Fix variable shadowing: rename inner `data` to `rowData` in row mapper
- Fix first-page maxResults off-by-one: request maxResults+1 to compensate for header row
- Add missing runtime guard for queryString in create_named_query
- Move athena registry entries to correct alphabetical position
* fix(athena): alphabetize registry keys and add type re-exports
- Reorder athena_* registry keys to strict alphabetical order
- Add type re-exports from index.ts barrel
* fix(athena): cap maxResults at 999 to prevent overflow with header row adjustment
The +1 adjustment for the header row on first-page requests could
produce MaxResults=1001 when user requests 1000, exceeding the AWS
API hard cap of 1000.
* feat(chat): drag workflows and folders from sidebar into chat input
* fix(chat): fix effectAllowed, stale atInsertPosRef, and drag-enter overlay for resource drags
* feat(chat): add task dragging and visible drag ghost for sidebar items
* feat(sidebar): add drag ghost with icons and task icon to context chips
* refactor(types): narrow ChatMessageContext.kind to ChatContextKind union and add workflowBorderColor utility
* feat(user-input): support Tab to select resource in mention dropdown
* fix(user-input): narrow ChatContext discriminated union before accessing workflowId
* fix(colors): overload workflowBorderColor to accept string | undefined
* fix(colors): simplify workflowBorderColor to single string | undefined signature
* fix(chat): remove resource panel tab when context mention is deleted from input
* fix(chat): use resource ID for context removal identity check
* fix(chat): add folder/task cases to resource resolver, task key to existingResourceKeys, and use workflowBorderColor in drag ghost
* revert(chat): remove folder/task from resolveResourceFromContext — no panel UI for these types
* fix(chat): add chatId to stored context types and workflow.color to drag callback deps
* fix(chat): guard chatId before adding task key to existingResourceKeys
* improvement(secrets): parallelize save mutations and add admin visibility for workspace secrets
* fix(secrets): sequence workspace upsert/delete to avoid read-modify-write race
* fix(secrets): use Promise.allSettled to ensure credential invalidation after all mutations settle
* feat(slack): add subtype field and signature verification to Slack trigger
* fix(slack): guard against NaN timestamp and align null/empty-string convention
* fix(docs): resolve missing tool outputs for spread-inherited V2 tools
* fix(docs): add word boundary to baseToolRegex to prevent false matches
* fix(docs): remove unnecessary case-insensitive flag from baseToolRegex
* feat(auth): add DISABLE_GOOGLE_AUTH and DISABLE_GITHUB_AUTH env vars
* fix(auth): also disable server-side OAuth provider registration when flags are set
* lint
* fix(modals): consistent text colors, copy, and workspace delete confirmation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(modal): replace useEffect with render-time state reset
Replace useEffect anti-pattern for resetting confirmation text with
React's recommended "adjusting state during render" pattern. This
ensures stale text is never painted and avoids an extra render cycle.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): prevent navigation on context menu actions and widen tags modal
* fix(knowledge): guard onCopyId against navigation and use setTimeout for robustness
* refactor(knowledge): extract withActionGuard helper to deduplicate context menu guard
* fix(knowledge): wrap withActionGuard callback in try/finally to prevent stuck ref
* improvement(landing, blog): SEO and GEO optimization
* improvement(docs): ui/ux cleanup
* chore(blog): remove unused buildBlogJsonLd export and wordCount schema field
* fix(blog): stack related posts vertically on mobile and fill all suggestion slots
- Add flex-col sm:flex-row and matching border classes to related posts
nav for consistent mobile stacking with the main blog page
- Remove score > 0 filter in getRelatedPosts so it falls back to recent
posts when there aren't enough tag matches
- Align description text color with main page cards
The $contains filter operator builds an ILIKE pattern but does not
escape LIKE wildcard characters (%, _) in user-provided values.
This causes incorrect, over-broad query results when the search value
contains these characters. For example, filtering with
{ name: { $contains: "100%" } } matches any row where name
contains "100" followed by anything, not just the literal "100%".
Escape %, _, and \ in the value before interpolating into the ILIKE
pattern so that they match literally.
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: lawrence3699 <lawrence3699@users.noreply.github.com>
* fix(sso): default tokenEndpointAuthentication to client_secret_post
better-auth's SSO plugin does not URL-encode credentials before Base64
encoding in client_secret_basic mode (RFC 6749 §2.3.1). When the client
secret contains special characters (+, =, /), OIDC providers decode them
incorrectly, causing invalid_client errors.
Default to client_secret_post when tokenEndpointAuthentication is not
explicitly set to avoid this upstream encoding issue.
Fixes#3626
* fix(sso): use nullish coalescing and add env var for tokenEndpointAuthentication
- Use ?? instead of || for semantic correctness
- Add SSO_OIDC_TOKEN_ENDPOINT_AUTH env var so users can explicitly
set client_secret_basic when their provider requires it
* docs(sso): add SSO_OIDC_TOKEN_ENDPOINT_AUTH to script usage comment
Signed-off-by: Mini Jeong <mini.jeong@navercorp.com>
* fix(sso): validate SSO_OIDC_TOKEN_ENDPOINT_AUTH env var value
Replace unsafe `as` type cast with runtime validation to ensure only
'client_secret_post' or 'client_secret_basic' are accepted. Invalid
values (typos, empty strings) now fall back to undefined, letting the
downstream ?? fallback apply correctly.
Signed-off-by: Mini Jeong <mini.jeong@navercorp.com>
---------
Signed-off-by: Mini Jeong <mini.jeong@navercorp.com>
* refactor(triggers): consolidate v2 Linear triggers into same files as v1
Move v2 trigger exports from separate _v2.ts files into their
corresponding v1 files, matching the block v2 convention where
LinearV2Block lives alongside LinearBlock in the same file.
* updated
* fix: restore staging registry entries accidentally removed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs
* fix: restore integrations.json to staging version
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(generate-docs): extract all trigger configs from multi-export files
The buildTriggerRegistry function used a single regex exec per file,
which only captured the first TriggerConfig export. Files that export
both v1 and v2 triggers (consolidated same-file convention) had their
v2 triggers silently dropped from integrations.json.
Split each file into segments per export and parse each independently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore staging linear handler and utils with teamId support
Restores the staging version of linear provider handler and trigger
utils that were accidentally regressed. Key restorations:
- teamId sub-block and allPublicTeams fallback in createSubscription
- Timestamp skew validation in verifyAuth
- actorType renaming in formatInput (avoids TriggerOutput collision)
- url field in formatInput and all output builders
- edited field in comment outputs
- externalId validation after webhook creation
- isLinearEventMatch returns false (not true) for unknown triggers
Adds extractIdempotencyId to the linear provider handler for webhook
deduplication support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore non-Linear files accidentally modified
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove redundant extractIdempotencyId from linear handler
The idempotency service already uses the Linear-Delivery header
(which Linear always sends) as the primary dedup key. The body-based
fallback was unnecessary defensive code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* idempotency
* tets
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(secrets): restore unsaved-changes guard for settings tab navigation
- Add useSettingsDirtyStore (stores/settings/dirty) to track dirty state across the settings sidebar and section components
- Wire credentials-manager and integrations-manager to sync dirty state to the store and clean up on unmount; also reset store synchronously in handleDiscardAndNavigate
- Update settings-sidebar to check dirty state before tab switches and Back navigation, showing an Unsaved Changes dialog if needed
- Remove dead stores/settings/environment directory; move EnvironmentVariable type into lib/environment/api
* fix(teams): harden Microsoft content URL validation
- Add isMicrosoftContentUrl helper with typed allowlist covering SharePoint, OneDrive, and Teams CDN domains
- Replace loose substring checks in Teams webhook handler with parsed-hostname matching to prevent bypass via partial domain names
- Deduplicate OneDrive share-link detection into isOneDriveShareLink flag and use searchParams API instead of string splitting
* fix(env): remove type re-exports from query file, drop keepPreviousData on static key
* fix(teams): remove smba.trafficmanager.net from Microsoft content allowlist
The subdomain check for smba.trafficmanager.net was unnecessary — Azure
Traffic Manager does not support nested subdomains of existing profiles,
but the pattern still raised a valid audit concern. Teams bot-framework
attachment URLs from this host fall through to the generic fetchWithDNSPinning
branch, which provides the same protection without the ambiguity.
* fix(secrets): guard active-tab re-click, restore keepPreviousData on workspace env query
* fix(teams): add 1drv.com apex to OneDrive share-link branch
1drv.com (apex) is a short-link domain functionally equivalent to
1drv.ms and requires share-token resolution, not direct fetch.
CDN subdomains (files.1drv.com) are unaffected — the exact-match
check leaves them on the direct-fetch path.
* fix(triggers): apply webhook audit follow-ups
Align the Greenhouse webhook matcher with provider conventions and clarify the Notion webhook secret setup text after the audit review.
Made-with: Cursor
* fix(webhooks): Salesforce provider handler, Zoom CRC and block wiring
Add salesforce WebhookProviderHandler with required shared secret auth,
matchEvent filtering, formatInput aligned to trigger outputs, and
idempotency keys. Require webhook secret and document JSON-only Flow
setup; enforce objectType when configured.
Zoom: pass raw body into URL validation signature check, try all active
webhooks on a path for secret match, add extractIdempotencyId, tighten
event matching for specialized triggers. Wire Zoom triggers into the
Zoom block. Extend handleChallenge with optional rawBody.
Register Salesforce pending verification probes for pre-save URL checks.
* fix(webhooks): harden Resend and Linear triggers (idempotency, auth, outputs)
- Dedupe Resend deliveries via svix-id and Linear via Linear-Delivery in idempotency keys
- Require Resend signing secret; validate createSubscription id and signing_secret
- Single source for Resend event maps in triggers/utils; fail closed on unknown trigger IDs
- Add raw event data to Resend trigger outputs and formatInput
- Linear: remove body-based idempotency key; timestamp skew after HMAC verify; format url and actorType
- Tighten isLinearEventMatch for unknown triggers; clarify generic webhook copy; fix header examples
- Add focused tests for idempotency headers and Linear matchEvent
* fix(webhooks): harden Vercel and Greenhouse trigger handlers
Require Vercel signing secret and validate x-vercel-signature; add
matchEvent with dynamic import, delivery idempotency, strict
createSubscription trigger IDs, and formatInput aligned to string IDs.
Greenhouse: dynamic import in matchEvent, strict unknown trigger IDs,
Greenhouse-Event-ID idempotency header, body fallback keys, clearer
optional secret copy. Update generic trigger wording and add tests.
* fix(gong): JWT verification, trigger UX, alignment script
- Optional RS256 verification when Gong JWT public key is configured (webhook_url + body_sha256 per Gong docs); URL secrecy when unset.
- Document that Gong rules filter calls; payload has no event type; add eventType + callId outputs for discoverability.
- Refactor Gong triggers to buildTriggerSubBlocks + shared JWT field; setup copy matches security model.
- Add check-trigger-alignment.ts (Gong bundled; extend PROVIDER_CHECKS for others) and update add-trigger guidance paths.
Made-with: Cursor
* fix(notion): align webhook lifecycle and outputs
Handle Notion verification requests safely, expose the documented webhook fields in the trigger contract, and update setup guidance so runtime data and user-facing configuration stay aligned.
Made-with: Cursor
* fix(webhooks): tighten remaining provider hardening
Close the remaining pre-merge caveats by tightening Salesforce, Zoom, and Linear behavior, and follow through on the deferred provider and tooling cleanup for Vercel, Greenhouse, Gong, and Notion.
Made-with: Cursor
* refactor(webhooks): move subscription helpers out of providers
Move provider subscription helpers alongside the subscription lifecycle module and add targeted TSDoc so the file placement matches the responsibility boundaries in the webhook architecture.
Made-with: Cursor
* fix(zoom): resolve env-backed secrets during validation
Use the same env-aware secret resolution path for Zoom endpoint validation as regular delivery verification so URL validation works correctly when the secret token is stored via env references.
Made-with: Cursor
* fix build
* consolidate tests
* refactor(salesforce): share payload object type parsing
Remove dead code in the Salesforce provider and move shared object-type extraction into a single helper so trigger matching and input shaping stay in sync.
Made-with: Cursor
* fix(webhooks): address remaining review follow-ups
Loosen Linear's replay window to better tolerate delayed retries and make Notion event mismatches return false consistently with the rest of the hardened providers.
Made-with: Cursor
* test(webhooks): separate Zoom coverage and clean Notion output shape
Move Zoom provider coverage into its own test file and strip undeclared Notion type fields from normalized output objects so the runtime shape better matches the trigger contract.
Made-with: Cursor
* feat(triggers): enrich Vercel and Greenhouse webhook output shapes
Document and pass through Vercel links, regions, deployment.meta, and
domain.delegated; add top-level Greenhouse applicationId, candidateId,
and jobId aligned with webhook common attributes. Extend alignment checker
for greenhouse, update provider docs, and add formatInput tests.
Made-with: Cursor
* feat(webhooks): enrich Resend trigger outputs; clarify Notion output docs
- Resend: expose broadcast_id, template_id, tags, and data_created_at from
payload data (per Resend webhook docs); keep alignment with formatInput.
- Add resend entry to check-trigger-alignment and unit test for formatInput.
- Notion: tighten output descriptions for authors, entity types, parent types,
attempt_number, and accessible_by per Notion webhooks event reference.
Made-with: Cursor
* feat(webhooks): enrich Zoom and Gong trigger output schemas
- Zoom: add formatInput passthrough, fix nested TriggerOutput shape (drop invalid `properties` wrappers), document host_email, join_url, agenda, status, meeting_type on recordings, participant duration, and alignment checker entry.
- Gong: flatten topics/highlights from callData.content in formatInput, extend metaData and trigger outputs per API docs, tests and alignment keys updated.
- Docs: add English webhook trigger sections for Zoom and Gong tools pages.
* feat(triggers): enrich Salesforce and Linear webhook output schemas
Salesforce: expose simEventType alongside eventType; pass OwnerId and
SystemModstamp on record lifecycle inputs; add AccountId/OwnerId for
Opportunity and AccountId/ContactId/OwnerId for Case. Align trigger
output docs with Flow JSON payloads and formatInput.
Linear: document actor email and profile url per official webhook
payload; add Comment data.edited from Linear's sample payload.
Tests: extend Salesforce formatInput coverage for new fields.
* remove from mdx
* chore(webhooks): expand trigger alignment coverage
Extend the trigger alignment checker to cover additional webhook providers so output contracts are verified across more of the recently added trigger surface.
Made-with: Cursor
* updated skills
* updated file naming semantics
* rename file
* feat(folders): soft-delete folders and show in Recently Deleted
Folders are now soft-deleted (archived) instead of permanently removed,
matching the existing pattern for workflows, tables, and knowledge bases.
Users can restore folders from Settings > Recently Deleted.
- Add `archivedAt` column to `workflowFolder` schema with index
- Change folder deletion to set `archivedAt` instead of hard-delete
- Add folder restore endpoint (POST /api/folders/[id]/restore)
- Batch-restore all workflows inside restored folders in one transaction
- Add scope filter to GET /api/folders (active/archived)
- Add Folders tab to Recently Deleted settings page
- Update delete modal messaging for restorable items
- Change "This action cannot be undone" styling to muted text
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(testing): add FOLDER_RESTORED to audit mock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): atomic restore transaction and scope to folder-deleted workflows
Address two review findings:
- Wrap entire folder restore in a single DB transaction to prevent
partial state if any step fails
- Only restore workflows archived within 5s of the folder's archivedAt,
so individually-deleted workflows are not silently un-deleted
- Add folder_restored to PostHog event map
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(folders): simplify restore to remove hacky 5s time window
The 5-second time window for scoping which workflows to restore was
a fragile heuristic (magic number, race-prone, non-deterministic).
Restoring a folder now restores all archived workflows in it, matching
standard trash/recycle-bin behavior. Users can re-delete any workflow
they don't want after restore.
The single-transaction wrapping from the prior commit is kept — that
was a legitimate atomicity fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(db): regenerate folder soft-delete migration with drizzle-kit
Replace manually created migration with proper drizzle-kit generated
one that includes the snapshot file, fixing CI schema sync check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(db): fix migration metadata formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): scope restore to folder-deleted workflows via shared timestamp
Use a single timestamp across the entire folder deletion — folders,
workflows, schedules, webhooks, etc. all get the exact same archivedAt.
On restore, match workflows by exact archivedAt equality with the
folder's timestamp, so individually-deleted workflows are not
silently un-deleted.
- Add optional archivedAt to ArchiveWorkflowOptions (backwards-compatible)
- Pass shared timestamp through deleteFolderRecursively → archiveWorkflowsByIdsInWorkspace
- Filter restore with eq(workflow.archivedAt, folderArchivedAt) instead of isNotNull
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(workflows): clear folderId on restore when folder is archived or missing
When individually restoring a workflow from Recently Deleted, check if
its folder still exists and is active. If the folder is archived or
missing, clear folderId so the workflow appears at root instead of
being orphaned (invisible in sidebar).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): format restoreFolderRecursively call to satisfy biome
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): close remaining restore edge cases
Three issues caught by audit:
1. Child folder restore used isNotNull instead of timestamp matching,
so individually-deleted child folders would be incorrectly restored.
Now uses eq(archivedAt, folderArchivedAt) for both workflows AND
child folders — consistent and deterministic.
2. No workspace archived check — could restore a folder into an
archived workspace. Now checks getWorkspaceWithOwner, matching
the existing restoreWorkflow pattern.
3. Re-restoring an already-restored folder returned an error. Now
returns success with zero counts (idempotent).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): add archivedAt to optimistic folder creation objects
Ensures optimistic folder objects include archivedAt: null for
consistency with the database schema shape.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(folders): handle missing parent folder during restore reparenting
If the parent folder row no longer exists (not just archived), the
restored folder now correctly gets reparented to root instead of
retaining a dangling parentId reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(subflows): make edges inside subflows directly clickable
Edges inside subflows defaulted to z-index 0, causing the subflow body
area (pointer-events: auto) to intercept clicks. Derive edge z-index
from the container's depth so edges sit just above their parent container
but below canvas blocks and child blocks.
* Fix edge deletion in nested subflows
* Fix bug with multi selecting nested subblock
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(home): add folders to resource menu
* fix(home): add folder to API validation and dedup logic
* fix(home): add folder context processing and generic title dedup
* fix(home): add folder icon to mention chip overlay
* fix(home): add folder to AgentContextType and context persistence
* fix(home): add workspace scoping to folder resolver, fix folderId type and dedup
* user message
* fix(copilot): fix copilot running workflow stuck on 10mb error
* Use correct try catch
* Add const
* Strip only logs on payload too large
* Fix threshold
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* Add credential prompting for google service accounts
* Add service account credential block prompting for google service account
* Revert requiredCredentials change
* Fix lint
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(signup): show multiple signup errors at once
* Fix reset password error formatting
* Remove dead code
* Fix unit tests
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(triggers): add Linear v2 triggers with automatic webhook registration
* fix(triggers): preserve specific Linear API error messages in catch block
* fix(triggers): check response.ok before JSON parsing, replace as any with as unknown
* fix linear subscription params
* fix build
---------
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
* feat(triggers): add Zoom webhook triggers with challenge-response and signature verification
Add 6 Zoom webhook triggers (meeting started/ended, participant joined/left, recording completed, generic webhook) with full Zoom protocol support including endpoint.url_validation challenge-response handling and x-zm-signature HMAC-SHA256 verification.
* fix(triggers): use webhook.isActive instead of non-existent deletedAt column
* fix(triggers): address PR review feedback for Zoom webhooks
- Add 30s timestamp freshness check to prevent replay attacks
- Return null from handleChallenge when no secret token found instead of responding with empty-key HMAC
- Remove all `as any` casts from output builder functions
* lint
* fix(triggers): harden Zoom webhook security per PR review
- verifyAuth now fails closed (401) when secretToken is missing
- handleChallenge DB query filters by provider='zoom' to avoid cross-provider leaks
- handleChallenge verifies x-zm-signature before responding to prevent HMAC oracle
* fix(triggers): rename type to meeting_type to avoid TriggerOutput type collision
* fix(triggers): make challenge signature verification mandatory, not optional
* fix(triggers): fail closed on unknown trigger IDs and update Zoom landing page data
- isZoomEventMatch now returns false for unrecognized trigger IDs
- Update integrations.json with 6 Zoom triggers
* fix(triggers): add missing id fields to Zoom trigger entries in integrations.json
* fix(triggers): increase Zoom timestamp tolerance to 300s per Zoom docs
* feat(triggers): add Vercel webhook triggers with automatic registration
* fix(triggers): add Vercel webhook signature verification and expand generic events
* fix(triggers): validate Vercel webhook ID before storing to prevent orphaned webhooks
* fix(triggers): add triggerId validation warning and JSON parse fallback for Vercel webhooks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(triggers): add paramVisibility user-only to Vercel apiKey subblock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(triggers): add Notion webhook triggers for all event types
Add 9 Notion webhook triggers covering the full event lifecycle:
- Page events: created, properties updated, content updated, deleted
- Database events: created, schema updated, deleted
- Comment events: created
- Generic webhook trigger (all events)
Implements provider handler with HMAC SHA-256 signature verification,
event filtering via matchEvent, and structured input formatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(triggers): resolve type field collision in Notion trigger outputs
Rename nested `type` fields to `entity_type`/`parent_type` to avoid
collision with processOutputField's leaf node detection which checks
`'type' in field`. Remove spread of author outputs into `authors`
array which was overwriting `type: 'array'`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(triggers): clarify Notion webhook signing secret vs verification_token
Update placeholder and description to distinguish the signing secret
(used for HMAC-SHA256 signature verification) from the verification_token
(one-time challenge echoed during initial setup).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(webhooks): use createHmacVerifier for Notion provider handler
Replace inline verifyAuth boilerplate with createHmacVerifier utility,
consistent with Linear, Ashby, Cal.com, Circleback, Confluence, and
Fireflies providers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(triggers): add Greenhouse webhook triggers
Add 8 webhook triggers for Greenhouse ATS events:
- Candidate Hired, New Application, Stage Change, Rejected
- Offer Created, Job Created, Job Updated
- Generic Webhook (all events)
Includes event filtering via provider handler registry and output
schemas matching actual Greenhouse webhook payload structures.
* fix(triggers): address PR review feedback for Greenhouse triggers
- Fix rejection_reason.type key collision with mock payload generator
by renaming to reason_type
- Replace dynamic import with static import in matchEvent handler
- Add HMAC-SHA256 signature verification via createHmacVerifier
- Add secretKey extra field to all trigger subBlocks
- Extract shared buildJobPayload helper to deduplicate job outputs
* fix(triggers): align rejection_reason output with actual Greenhouse payload
Reverted reason_type rename — instead flattened rejection_reason to JSON
type since TriggerOutput's type?: string conflicts with nested type keys.
Also hardened processOutputField to check typeof type === 'string' before
treating an object as a leaf node, preventing this class of bug for future triggers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(triggers): add Gong webhook triggers for call events
* fix(triggers): reorder Gong trigger spread and dropdown options
* fix(triggers): resolve Biome lint errors in Gong trigger files
* json
* feat(triggers): add Resend webhook triggers with auto-registration
* fix(triggers): capture Resend signing secret and add Svix webhook verification
* fix(triggers): add paramVisibility, event-type filtering for Resend triggers
* fix(triggers): add Svix timestamp staleness check to prevent replay attacks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(triggers): use Number.parseInt and Number.isNaN for lint compliance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(integrations): add Sixtyfour AI integration
Add Sixtyfour AI integration with 4 tools: find_phone, find_email, enrich_lead, enrich_company. Includes block with operation dropdown, API key auth, conditional fields per operation, brand icon, and generated docs.
* fix(integrations): add error handling to sixtyfour tools
Wrap JSON.parse calls in try/catch for enrich_lead and enrich_company.
Add response.ok checks to all 4 tools' transformResponse.
* fix(integrations): use typed Record for leadStruct to fix spread type error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs
* airweave docslink
* turbo update
* more inp/outputs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(webhooks): extract provider-specific logic into handler registry
* fix(webhooks): address PR review feedback
- Restore original fall-through behavior for generic requireAuth with no token
- Replace `any` params with proper types in processor helper functions
- Restore array-aware initializer in processTriggerFileOutputs
* fix(webhooks): fix build error from union type indexing in processTriggerFileOutputs
Cast array initializer to Record<string, unknown> to allow string indexing
while preserving array runtime semantics for the return value.
* fix(webhooks): return 401 when requireAuth is true but no token configured
If a user explicitly sets requireAuth: true, they expect auth to be enforced.
Returning 401 when no token is configured is the correct behavior — this is
an intentional improvement over the original code which silently allowed
unauthenticated access in this case.
* refactor(webhooks): move signature validators into provider handler files
Co-locate each validate*Signature function with its provider handler,
eliminating the circular dependency where handlers imported back from
utils.server.ts. validateJiraSignature is exported from jira.ts for
shared use by confluence.ts.
* refactor(webhooks): move challenge handlers into provider files
Move handleWhatsAppVerification to providers/whatsapp.ts and
handleSlackChallenge to providers/slack.ts. Update processor.ts
imports to point to provider files.
* refactor(webhooks): move fetchAndProcessAirtablePayloads into airtable handler
Co-locate the ~400-line Airtable payload processing function with its
provider handler. Remove AirtableChange interface from utils.server.ts.
* refactor(webhooks): extract polling config functions into polling-config.ts
Move configureGmailPolling, configureOutlookPolling, configureRssPolling,
and configureImapPolling out of utils.server.ts into a dedicated module.
Update imports in deploy.ts and webhooks/route.ts.
* refactor(webhooks): decompose formatWebhookInput into per-provider formatInput methods
Move all provider-specific input formatting from the monolithic formatWebhookInput
switch statement into each provider's handler file. Delete formatWebhookInput and
all its helper functions (fetchWithDNSPinning, formatTeamsGraphNotification, Slack
file helpers, convertSquareBracketsToTwiML) from utils.server.ts. Create new handler
files for gmail, outlook, rss, imap, and calendly providers. Update webhook-execution.ts
to use handler.formatInput as the primary path with raw body passthrough as fallback.
utils.server.ts reduced from ~1600 lines to ~370 lines containing only credential-sync
functions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(webhooks): decompose provider-subscriptions into handler registry pattern
Move all provider-specific subscription create/delete logic from the monolithic
provider-subscriptions.ts into individual provider handler files via new
createSubscription/deleteSubscription methods on WebhookProviderHandler.
Replace the two massive if-else dispatch chains (11 branches each) with simple
registry lookups via getProviderHandler(). provider-subscriptions.ts reduced
from 2,337 lines to 128 lines (orchestration only).
Also migrate polling configuration (gmail, outlook, rss, imap) into provider
handlers via configurePolling() method, and challenge/verification handling
(slack, whatsapp, teams) via handleChallenge() method. Delete polling-config.ts.
Create new handler files for fathom and lemlist providers. Extract shared
subscription utilities into subscription-utils.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(webhooks): fix attio build error, restore imap field, remove demarcation comments
- Cast `body` to `Record<string, unknown>` in attio formatInput to fix
type error with extractor functions
- Restore `rejectUnauthorized` field in imap configurePolling for parity
- Remove `// ---` section demarcation comments from route.ts and airtable.ts
- Update add-trigger skill to reflect handler-based architecture
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(webhooks): remove unused imports from utils.server.ts after rebase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(webhooks): remove duplicate generic file processing from webhook-execution
The generic provider's processInputFiles handler already handles file[] field
processing via the handler.processInputFiles call. The hardcoded block from
staging was incorrectly preserved during rebase, causing double processing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(webhooks): validate auth token is set when requireAuth is enabled at deploy time
Rejects deployment with a clear error message if a generic webhook trigger
has requireAuth enabled but no authentication token configured, rather than
letting requests fail with 401 at runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(webhooks): remove unintended rejectUnauthorized field from IMAP polling config
The refactored IMAP handler added a rejectUnauthorized field that was not
present in the original configureImapPolling function. This would default
to true for all existing IMAP webhooks, potentially breaking connections
to servers with self-signed certificates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(webhooks): replace crypto.randomUUID() with generateId() in ashby handler
Per project coding standards, use generateId() from @/lib/core/utils/uuid
instead of crypto.randomUUID() directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(webhooks): standardize logger names and remove any types from providers
- Standardize logger names to WebhookProvider:X pattern across 6 providers
(fathom, gmail, imap, lemlist, outlook, rss)
- Replace all `any` types in airtable handler with proper types:
- Add AirtableTableChanges interface for API response typing
- Change function params from `any` to `Record<string, unknown>`
- Change AirtableChange fields from Record<string, any> to Record<string, unknown>
- Change all catch blocks from `error: any` to `error: unknown`
- Change input object from `any` to `Record<string, unknown>`
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(webhooks): remove remaining any types from deploy.ts
Replace 3 `catch (error: any)` with `catch (error: unknown)` and
1 `Record<string, any>` with `Record<string, unknown>`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(blocks): resolve Ollama models incorrectly requiring API key in Docker
Server-side validation failed for Ollama models like mistral:latest because
the Zustand providers store is empty on the server and getProviderFromModel
misidentified them via regex pattern matching (e.g. mistral:latest matched
Mistral AI's /^mistral/ pattern).
Replace the hardcoded CLOUD_PROVIDER_PREFIXES list with existing data sources:
- Provider store (definitive on client, checks all provider buckets)
- getBaseModelProviders() from PROVIDER_DEFINITIONS (server-side static cloud model lookup)
- Slash convention for dynamic cloud providers (fireworks/, openrouter/, etc.)
- isOllamaConfigured feature flag using existing OLLAMA_URL env var
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove getProviderFromModel regex fallback from API key validation
The fallback was the last piece of regex-based matching in the function and
only ran for self-hosted without OLLAMA_URL on the server — a path where
Ollama models cannot appear in the dropdown anyway.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix: handle vLLM models in store provider check
vLLM is a local model server like Ollama and should not require an API key.
Add vllm to the store provider check as a safety net for models that may
not have the vllm/ prefix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(core): consolidate ID generation to prevent HTTP self-hosted crashes
crypto.randomUUID() requires a secure context (HTTPS) in browsers,
causing white-screen crashes on self-hosted HTTP deployments. This
replaces all direct usage of crypto.randomUUID(), nanoid, and the uuid
package with a central utility that falls back to crypto.getRandomValues()
which works in all contexts.
- Add generateId(), generateShortId(), isValidUuid() in @/lib/core/utils/uuid
- Replace crypto.randomUUID() imports across ~220 server + client files
- Replace nanoid imports with generateShortId()
- Replace uuid package validate with isValidUuid()
- Remove nanoid dependency from apps/sim and packages/testing
- Remove browser polyfill script from layout.tsx
- Update test mocks to target @/lib/core/utils/uuid
- Update CLAUDE.md, AGENTS.md, cursor rules, claude rules
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update bunlock
* fix(core): remove UUID_REGEX shim, use isValidUuid directly
* fix(core): remove deprecated uuid mock helpers that use vi.doMock
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(files): expand file editor to support more formats, add docx/xlsx preview
* lint
* fix(files): narrow fileData type for closure in docx/xlsx preview effects
* fix(files): address PR review — fix xlsx type, simplify error helper, tighten iframe sandbox
* add mothership read externsions
* fix(files): update upload test — js is now a supported extension
* fix(files): deduplicate code extensions, handle dotless filenames
* fix(files): lower xlsx preview row cap to 1k and type workbookRef properly
Reduces XLSX_MAX_ROWS from 10,000 to 1,000 to prevent browser sluggishness
on large spreadsheets. Types workbookRef with the proper xlsx.WorkBook
interface instead of unknown, removing the unsafe cast.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(files): extract shared DataTable, isolate client-safe constants
- Move SUPPORTED_CODE_EXTENSIONS to validation-constants.ts so client
components no longer transitively import Node's `path` module
- Extract shared DataTable component used by both CsvPreview and
XlsxPreview, eliminating duplicated table markup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(validation): remove Node path import, use plain string extraction
Replace `import path from 'path'` with a simple `extractExtension` helper
that does `fileName.slice(fileName.lastIndexOf('.') + 1)`. This removes
the only Node module dependency from validation.ts, making it safe to
import from client components without pulling in a Node polyfill.
Deletes the unnecessary validation-constants.ts that was introduced as
a workaround — the constants now live back in validation.ts where they
belong.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(kb): fix Linear connector GraphQL type errors and tag slot reuse
* fix(kb): simplify tag slot reuse, revert Linear GraphQL types to String
Clean up newTagSlotMapping into direct assignment, remove unnecessary
comment, and revert ID! back to String! to match Linear SDK types.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(kb): use ID! type for Linear GraphQL filter variables
* fix(kb): verify field type when reusing existing tag slots
Add fieldType check to the tag slot reuse logic so a connector with
a matching displayName but different fieldType falls through to fresh
slot allocation instead of silently reusing an incompatible slot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(kb): enable search on connector selector dropdowns
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(analytics): posthog audit — remove noise, add 10 new events
Remove task_marked_read (fires automatically on every task view).
Add workspace_id to task_message_sent for group analytics.
New events:
- search_result_selected: block/tool/trigger/workflow/table/file/
knowledge_base/workspace/task/page/docs with query_length
- workflow_imported: count + format (json/zip)
- workflow_exported: count + format (json/zip)
- folder_created / folder_deleted
- logs_filter_applied: status/workflow/folder/trigger/time
- knowledge_base_document_deleted
- scheduled_task_created / scheduled_task_deleted
* fix(analytics): use usePostHog + captureEvent in hooks, track custom date range
* fix(analytics): always fire scheduled_task_deleted regardless of workspaceId
* fix(analytics): correct format field logic and add missing useCallback deps
* feat(knowledge): add Live sync option to KB connector modal for Max/Enterprise users
Adds a "Live" (every 5 min) sync frequency option gated to Max and Enterprise plan users.
Includes client-side badge + disabled state, shared sync intervals constant, and server-side
plan validation on both POST and PATCH connector routes.
* fix(knowledge): record embedding usage cost for KB document processing
Adds billing tracking to the KB embedding pipeline, which was previously
generating OpenAI API calls with no cost recorded. Token counts are now
captured from the actual API response and recorded via recordUsage after
successful embedding insertion. BYOK workspaces are excluded from billing.
Applies to all execution paths: direct, BullMQ, and Trigger.dev.
* fix(knowledge): simplify embedding billing — use calculateCost, return modelName
- Use calculateCost() from @/providers/utils instead of inline formula, consistent
with how LLM billing works throughout the platform
- Return modelName from GenerateEmbeddingsResult so billing uses the actual model
(handles custom Azure deployments) instead of a hardcoded fallback string
- Fix docs-chunker.ts empty-path fallback to satisfy full GenerateEmbeddingsResult type
* fix(knowledge): remove dev bypass from hasLiveSyncAccess
* chore(knowledge): rename sync-intervals to consts, fix stale TSDoc comment
* improvement(knowledge): extract MaxBadge component, capture billing config once per document
* fix(knowledge): add knowledge-base to usage_log_source enum, fix docs-chunker type
* fix(knowledge): generate migration for knowledge-base usage_log_source enum value
* fix(knowledge): add knowledge-base to usage_log_source enum via drizzle-kit
* fix(knowledge): fix search embedding test mocks, parallelize billing lookups
* fix(knowledge): warn when embedding model has no pricing entry
* fix(knowledge): call checkAndBillOverageThreshold after embedding usage
* fix(envvars): restore workflowUserId fallback for scheduled execution env var resolution
* test(envvars): add coverage for env var user resolution branches
* fix(modals): center modals in visible content area accounting for sidebar and panel
* fix(modals): address pr feedback — comment clarity and document panel assumption
* fix(modals): remove open/close animation from modal content
* fix(modals): center modals in visible content area accounting for sidebar and panel
* fix(modals): address pr feedback — comment clarity and document panel assumption
* refactor(stores): consolidate variables stores into stores/variables/
Move variable data store from stores/panel/variables/ to stores/variables/
since the panel variables tab no longer exists. Rename the modal UI store
to useVariablesModalStore to eliminate naming collision with the data store.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unused workflowId variable in deleteVariable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(blocks): add Credential block
* fix(blocks): explicit workspaceId guard in credential handler, clarify hasOAuthSelection
* feat(credential): add list operation with type/provider filters
* feat(credential): restrict to OAuth only, remove env vars and service accounts
* docs(credential): update screenshots
* fix(credential): remove stale isServiceAccount dep from overlayContent memo
* fix(credential): filter to oauth-only in handleComboboxChange matchedCred lookup
* feat(email): send plain personal email on abandoned checkout
* feat(email): lower free tier warning to 80% and add credits exhausted email
* feat(email): use wordmark in email header instead of icon-only logo
* fix(email): restore accidentally deleted social icons in email footer
* fix(email): prevent double email for free users at 80%, fix subject line
* improvement(emails): extract shared plain email styles and proFeatures constant, fix double email on 100% usage
* fix(email): filter subscription-mode checkout, skip already-subscribed users, fix preview text
* fix(email): use notifications type for onboarding followup to respect unsubscribe preferences
* fix(email): use limit instead of currentUsage in credits exhausted email body
* fix(email): use notifications type for abandoned checkout, clarify crosses80 comment
* chore(email): rename _constants.ts to constants.ts
* fix(email): use isProPlan to catch org-level subscriptions in abandoned checkout guard
* fix(email): align onboarding followup delay to 5 days for email/password users
* Directly query db for custom tool id
* Switch back to inline imports
* Fix lint
* Fix test
* Fix greptile comments
* Fix lint
* Make userId and workspaceId required
* Add back nullable userId and workspaceId fields
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(email): send onboarding followup email 3 days after signup
* fix(email): add trigger guard, idempotency key, and shared task ID constant
* fix(email): increase onboarding followup delay from 3 to 5 days
* feat(rootly): expand Rootly integration from 14 to 27 tools
Add 13 new tools: delete_incident, get_alert, update_alert,
acknowledge_alert, resolve_alert, create_action_item, list_action_items,
list_users, list_on_calls, list_schedules, list_escalation_policies,
list_causes, list_playbooks. Includes tool files, types, registry,
block definition with subBlocks/conditions/params, and docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rootly): handle 204 No Content response for delete_incident
DELETE /v1/incidents/{id} returns 204 with empty body. Avoid calling
response.json() on success — return success/message instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rootly): remove non-TSDoc comments, add empty body to acknowledge_alert
Remove all inline section comments from block definition per CLAUDE.md
guidelines. Add explicit empty JSON:API body to acknowledge_alert POST
to prevent potential 400 from servers expecting a body with Content-Type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rootly): send empty body on resolve_alert, guard assignedToUserId parse
resolve_alert now sends { data: {} } instead of undefined when no
optional params are provided, matching the acknowledge_alert fix.
create_action_item now validates assignedToUserId is numeric before
parseInt to avoid silent NaN coercion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rootly): extract on-call relationships from JSON:API relationships/included
On-call user, schedule, and escalation policy are exposed as JSON:API
relationships, not flat attributes. Now extracts IDs from
item.relationships and looks up names from the included array.
Adds ?include=user,schedule,escalation_policy to the request URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rootly): remove last non-TSDoc comment from block definition
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(agentmail): add AgentMail integration with 21 tools
* fix(agentmail): clear stale to field when switching to reply_message operation
* fix(agentmail): guard messageId and label remappings with operation checks
* fix(agentmail): clean up subBlock titles
* fix(agentmail): guard replyTo and thread label remappings with operation checks
* fix(agentmail): guard inboxIdParam remapping with operation check
* fix(agentmail): guard permanent, replyAll, and draftInReplyTo with operation checks
* feat(rootly): add Rootly incident management integration with 14 tools
* fix(rootly): address PR review feedback - PATCH method, totalCount, environmentIds
- Changed update_incident HTTP method from PUT to PATCH per Rootly API spec
- Fixed totalCount in all 9 list tools to use data.meta?.total_count from API response
- Added missing updateEnvironmentIds subBlock and params mapping for update_incident
* fix(rootly): add id to PATCH body and unchanged option to update status dropdown
- Include incident id in JSON:API PATCH body per spec requirement
- Add 'Unchanged' empty option to updateStatus dropdown to avoid accidental overwrites
* icon update
* improvement(rootly): complete block-tool alignment and fix validation gaps
- Add missing get_incident output fields (private, shortUrl, closedAt)
- Add missing block subBlocks: createPrivate, alertStatus, alertExternalId, listAlertsServices
- Add pageNumber subBlocks for all 9 list operations
- Add teams/environments filter subBlocks for list_incidents and list_alerts
- Add environmentIds subBlock for create_alert
- Add empty default options to all optional dropdowns (createStatus, createKind, listIncidentsSort, eventVisibility)
- Wire all new subBlocks in tools.config.params and inputs
- Regenerate docs
* fix(rootly): align tools with OpenAPI spec
- list_incident_types: use filter[name] instead of unsupported filter[search]
- list_severities: add missing search param (filter[search])
- create_incident: title is optional per API (auto-generated if null)
- update_incident: add kind, private, labels, incidentTypeIds,
functionalityIds, cancellationMessage params
- create/update/list incidents: add scheduled, in_progress, completed
status values
- create_alert: fix status description (only open/triggered on create)
- add_incident_event: add updatedAt to response
- block: add matching subBlocks and params for all new tool fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rootly): final validation fixes from OpenAPI spec audit
- update_incident: change PATCH to PUT per OpenAPI spec
- index.ts: add types re-export
- types.ts: fix id fields to string | null (matches ?? null runtime)
- block: add value initializers to 4 dropdowns missing them
- registry: fix alphabetical order (incident_types before incidents)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* reorg
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(rippling): expand Rippling integration from 16 to 86 tools
* fix(rippling): add required constraints on name and data subBlocks for create operations
* fix(rippling): add subblock ID migrations for removed legacy fields
* fix(docs): add MANUAL-CONTENT markers to tailscale docs and regenerate
* fix(rippling): add missing response fields to tool transforms
Add fields found missing by validation agents:
- list_companies: physical_address
- list/get_supergroups: sub_group_type, read_only, parent, mutually_exclusive_key, cumulatively_exhaustive_default, include_terminated
- list/get/create/update_custom_object: native_category_id, managed_package_install_id, owner_id
- list/get/create/update_custom_app: icon, pages
- list/get/create/update_custom_object_field: managed_package_install_id
* fix(rippling): add missing block outputs and required data conditions
- Add 17 missing collection output keys (titles, workLocations, supergroups, etc.)
- Add delete/bulk/report output keys (deleted, results, report_id, etc.)
- Mark data subBlock required for create_business_partner, create_custom_app,
and create_custom_object_field (all have required params via data JSON spread)
- Add optional: true to get_current_user work_email and company_id outputs
* fix(rippling): add missing supergroup fields and fix validation issues
- Add 5 missing supergroup fields (allow_non_employees, can_override_role_states, priority, is_invisible, ignore_prov_group_matching) to types, list, and get tools
- Fix ok fallback from true to false in supergroup inclusion/exclusion member update tools
- Fix truthy check to null check for description param in create_custom_object_field
* fix(rippling): add missing custom page fields and structured custom setting responses
- Add 5 missing CustomPage fields (components, actions, canvas_actions, variables, media) to types and all page tools
- Replace opaque data blob with structured field mapping in create/update custom setting transforms
- Fix secret_value type cast consistency in list_custom_settings
* fix(rippling): add missing response fields, fix truthy checks, and improve UX
- Add 9 missing Worker fields (location, gender, date_of_birth, race, ethnicity, citizenship, termination_details, custom_fields, country_fields)
- Add 5 missing User fields (name, emails, phone_numbers, addresses, photos)
- Add worker expandable field to GroupMember types and all 3 member list tools
- Add 5 optional params to trigger_report_run (includeObjectIds, includeTotalRows, formatDateFields, formatCurrencyFields, outputType)
- Fix truthy checks to null checks in create_department, create/update_work_location
- Fix customObjectId subBlock label to say "API Name" instead of "ID"
* update docs
* fix(rippling): fix truthy checks, add missing fields, and regenerate docs
- Replace all `if (params.x)` with `if (params.x != null)` across 30+ tool files to prevent empty string/false/zero suppression
- Add expandable `parent` and `department_hierarchy` fields to department tools
- Add expandable `parent` field to team tools
- Add `company` expandable field to get_current_user
- Add `addressType` param to create/update work location tools
- Fix `secret_value` output type from 'json' to 'string' in list_custom_settings
- Regenerate docs for all 86 tools from current definitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): add all remaining spec fields and regenerate docs
- Add 6 advanced params to create_custom_object_field: required, rqlDefinition,
formulaAttrMetas, section, derivedFieldFormula, derivedAggregatedField
- Add 6 advanced params to update_custom_object_field: required, rqlDefinition,
formulaAttrMetas, section, derivedFieldFormula, nameFieldDetails
- Add 4 record output fields to all custom object record tools: created_by,
last_modified_by, owner_role, system_updated_at
- Add cursor param to get_current_user
- Add __meta response field to get_report_run
- Regenerate docs for all 86 tools
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): align all tools with OpenAPI spec
- Add __meta to 14 GET-by-ID tools (MetaResponse pattern)
- Fix supergroup tools: add filter to list_supergroups, remove invalid
cursor from 4 list endpoints, revert update members to PATCH with
Operations body
- Fix query_custom_object_records: use query/limit/cursor body params,
return cursor instead of nextLink
- Fix bulk_create: use rows_to_write per spec
- Fix create/update record body wrappers with externalId support
- Update types.ts param interfaces and block config mappings
- Add limit param mapping with Number() conversion in block config
- Regenerate docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): address PR review comments — add dedicated subBlocks, fix data duplication, expand externalId condition
- Add dedicated apiName, businessPartnerGroupId, workerId, dataType subBlocks so required params are no longer hidden behind opaque data JSON
- Narrow `data: item` in custom object record tools to only include dynamic fields, avoiding duplication of enumerated fields
- Expand externalId subBlock condition to include create/update custom object record operations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): remove data JSON required for ops with dedicated subBlocks
create_business_partner, create_custom_app, and create_custom_object_field
now have dedicated subBlocks for their required params, so the data JSON
field is supplementary (not required) for those operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): use rest-destructuring for all custom object record data output
The spec uses additionalProperties for custom fields at the top level,
not a nested `data` sub-object. Use the same rest-destructuring pattern
across all 6 custom object record tools so `data` only contains dynamic
fields, not duplicates of enumerated standard fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): make update_custom_object_record data param optional in type
Matches the tool's `required: false` — users may update only external_id
without changing data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): add dedicated streetAddress subBlock for create_work_location
streetAddress is required by the tool but had no dedicated subBlock —
users had to include it in the data JSON. Now has its own required
subBlock matching the pattern used by all other required params.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): add allOrNothing subBlock for bulk operations
The bulk create/update/delete tools accept an optional allOrNothing
boolean param, but it had no subBlock and no way to be passed through
the block UI. Added as an advanced-mode dropdown with boolean coercion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): derive spreadOps from DATA_OPS to prevent divergence
Replace the hardcoded spreadOps array with a derivation from the
file-level DATA_OPS constant minus non-spread operations. This ensures
new create/update operations added to DATA_OPS automatically get
spread behavior without needing a second manual update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* updated
* fix(rippling): replace generic JSON outputs with specific fields per API spec
- Extract file_url, expires_at, output_type from report run result blob
- Rename bulk create/update outputs to createdRecords/updatedRecords
- Fix list_custom_settings output key mismatch (settings → customSettings)
- Make data optional for update_custom_object_record in block
- Update block outputs to match new tool output fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix landing
* restore FF
* fix(rippling): add wandConfig, clean titles, and migrate legacy operation values
- Remove "(JSON)" suffix from all subBlock titles
- Add wandConfig with AI prompts for filter, expand, orderBy, query, data, records, and dataType fields
- Add OPERATION_VALUE_MIGRATIONS to migrate old operation values (list_employees → list_workers, etc.) preventing runtime errors on saved workflows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(rippling): fix grammar typos and revert unnecessary migration
- Fix "a object" → "an object" in update/delete object category descriptions
- Revert OPERATION_VALUE_MIGRATIONS (unnecessary for low-usage integration)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(landing): add interactive workspace preview tabs
Adds Tables, Files, Knowledge Base, Logs, and Scheduled Tasks preview
components to the landing hero, with sidebar nav items that switch to each view.
* test updates
* refactor(landing): clean up code quality issues in preview components
- Replace widthMultiplier with explicit width on PreviewColumn
- Replace key={i} with key={Icon.name} in connectorIcons
- Scope --c-active CSS variable to sidebar container, eliminating hardcoded #363636 duplication
- Replace '- - -' fallback with em dash
- Type onSelectNav as (id: SidebarView) removing the unsafe cast
* fix(landing): use stable index key in connectorIcons to avoid minification breakage
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(auth): allow google service account
* Add gmail support for google services
* Refresh creds on typing in impersonated email
* Switch to adding subblock impersonateUserEmail conditionally
* Directly pass subblock for impersonateUserEmail
* Fix lint
* Update documentation for google service accounts
* Fix lint
* Address comments
* Remove hardcoded scopes, remove orphaned migration script
* Simplify subblocks for google service account
* Fix lint
* Fix build error
* Fix documentation scopes listed for google service accounts
* Fix issue with credential selector, remove bigquery and ad support
* create credentialCondition
* Shift conditional render out of subblock
* Simplify sublock values
* Fix security message
* Handle tool service accounts
* Address bugbot
* Fix lint
* Fix manual credential input not showing impersonate
* Fix tests
* Allow watching param id and subblock ids
* Fix bad test
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement(providers): audit and update all provider model definitions
* fix(providers): add maxOutputTokens to azure/o3 and azure/o4-mini
* fix(providers): move maxOutputTokens inside capabilities for azure models
* improvement(workflow): seed start block on server side
* add creating state machine for optimistic switch
* fix worksapce switch
* address comments
* address error handling at correct level
* fix: allow Bedrock provider to use AWS SDK default credential chain
Remove hard requirement for explicit AWS credentials in Bedrock provider.
When access key and secret key are not provided, the AWS SDK automatically
falls back to its default credential chain (env vars, instance profile,
ECS task role, EKS IRSA, SSO).
Closes#3694
Signed-off-by: majiayu000 <1835304752@qq.com>
* fix: add partial credential guard for Bedrock provider
Reject configurations where only one of bedrockAccessKeyId or
bedrockSecretKey is provided, preventing silent fallback to the
default credential chain with a potentially different identity.
Add tests covering all credential configuration scenarios.
Signed-off-by: majiayu000 <1835304752@qq.com>
* fix: clean up bedrock test lint and dead code
Remove unused config parameter and dead _lastConfig assignment
from mock factory. Break long mockReturnValue chain to satisfy
biome line-length rule.
Signed-off-by: majiayu000 <1835304752@qq.com>
* fix: address greptile review feedback on PR #3708
Use BedrockRuntimeClientConfig from SDK instead of inline type.
Add default return value for prepareToolsWithUsageControl mock.
Signed-off-by: majiayu000 <1835304752@qq.com>
* feat(providers): server-side credential hiding for Azure and Bedrock
* fix(providers): revert Bedrock credential fields to required with original placeholders
* fix(blocks): add hideWhenEnvSet to getProviderCredentialSubBlocks for Azure and Bedrock
* fix(agent): use getProviderCredentialSubBlocks() instead of duplicating credential subblocks
* fix(blocks): consolidate Vertex credential into shared factory with basic/advanced mode
* fix(types): resolve pre-existing TypeScript errors across auth, secrets, and copilot
* lint
* improvement(blocks): make Vertex AI project ID a password field
* fix(blocks): preserve vertexCredential subblock ID for backwards compatibility
* fix(blocks): follow canonicalParamId pattern correctly for vertex credential subblocks
* fix(blocks): keep vertexCredential subblock ID stable to preserve saved workflow state
* fix(blocks): add canonicalParamId to vertexCredential basic subblock to complete the swap pair
* fix types
* more types
---------
Signed-off-by: majiayu000 <1835304752@qq.com>
Co-authored-by: majiayu000 <1835304752@qq.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
* fix: specify authTagLength in AES-GCM decipheriv calls
Fixes missing authTagLength parameter in createDecipheriv calls using
AES-256-GCM mode. Without explicit tag length specification, the
application may be tricked into accepting shorter authentication tags,
potentially allowing ciphertext spoofing.
CWE-310: Cryptographic Issues (gcm-no-tag-length)
* fix: specify authTagLength on createCipheriv calls for AES-GCM consistency
Complements #3881 by adding explicit authTagLength: 16 to the encrypt
side as well, ensuring both cipher and decipher specify the tag length.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: clean up crypto modules
- Fix error: any → error: unknown with proper type guard in encryption.ts
- Eliminate duplicate iv.toString('hex') calls in both encrypt functions
- Remove redundant string split in decryptApiKey (was splitting twice)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* new turborepo version
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: NLmejiro <kuroda.k1021@gmail.com>
* improvement(triggers): add tags to all trigger.dev task invocations
* fix(triggers): prefix unused type param in buildTags
* fix(triggers): remove unused type param from buildTags
* feat(providers): add Fireworks AI provider integration
* fix(providers): remove unused logger and dead modelInfo from fireworks
* lint
* feat(providers): add Fireworks BYOK support and official icon
* fix(providers): add workspace membership check and remove shared fetch cache for fireworks models
* improvement(attio): validate integration, fix event bug, add missing tool and triggers
* fix(attio): wire new trigger extractors into dispatcher, trim targetUrl
Add extractAttioListData and extractAttioWorkspaceMemberData dispatch
branches in utils.server.ts so the four new triggers return correct
outputs instead of falling through to generic extraction.
Also add missing .trim() on targetUrl in update_webhook.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(workflows): replace Zustand workflow sync with React Query as single source of truth
* fix(workflows): address PR review feedback — sandbox execution, hydration deadlock, test mock, copy casing
* lint
* improvement(workflows): adopt skipToken over enabled+as-string for type-safe conditional queries
* improvement(workflows): remove dead complexity, fix mutation edge cases
- Throw on state PUT failure in useCreateWorkflow instead of swallowing
- Use Map for O(1) lookups in duplicate/export loops (3 hooks)
- Broaden invalidation scope in update/delete mutations to lists()
- Switch workflow-block to useWorkflowMap for direct ID lookup
- Consolidate use-workflow-operations to single useWorkflowMap hook
- Remove workspace transition guard (sync body, unreachable timeout)
- Make switchToWorkspace synchronous (remove async/try-catch/finally)
* fix(workflows): resolve cold-start deadlock on direct URL navigation
loadWorkflowState used hydration.workspaceId (null on cold start) to
look up the RQ cache, causing "Workflow not found" even when the
workflow exists in the DB. Now falls back to getWorkspaceIdFromUrl()
and skips the cache guard when the cache is empty (letting the API
fetch proceed).
Also removes the redundant isRegistryReady guard in workflow.tsx that
blocked setActiveWorkflow when hydration.workspaceId was null.
* fix(ui): prevent flash of empty state while workflows query is pending
Dashboard and EmbeddedWorkflow checked workflow list length before
the RQ query resolved, briefly showing "No workflows" or "Workflow
not found" on initial load. Now gates on isPending first.
* fix(workflows): address PR review — await description update, revert state PUT throw
- api-info-modal: use mutateAsync for description update so errors
are caught by the surrounding try/catch instead of silently swallowed
- useCreateWorkflow: revert state PUT to log-only — the workflow is
already created in the DB, throwing rolls back the optimistic entry
and makes it appear the creation failed when it actually succeeded
* move folders over to react query native, restructure passage of data
* pass signal correctly
* fix types
* fix workspace id
* address comment
* soft deletion accuring
---------
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
* feat(infra): add dev environment support
* fix(ci): push :dev ECR tag when building from dev branch
* fix(feature-flags): simplify isHosted subdomain check
* fix(ci,feature-flags): guard URL parse, fix dev AWS creds in images.yml
* improvement(ui): fix nav loading flash, skeleton mismatches, and React anti-patterns across resource pages
- Convert knowledge, files, tables, scheduled-tasks, and home page.tsx files from async server components to simple client re-exports, eliminating the loading.tsx flash on every navigation
- Add client-side permission redirects (usePermissionConfig) to knowledge, files, and tables components to replace server-side checks
- Fix knowledge loading.tsx skeleton column count (6→7) and tables loading.tsx (remove phantom checkbox column)
- Fix connector document live updates: use isConnectorSyncingOrPending instead of status === 'syncing' so polling activates immediately after connector creation
- Remove dead chunk-switch useEffect in ChunkEditor (redundant with key prop remount)
- Replace useState+useEffect debounce with useDebounce hook in document.tsx
- Replace useRef+useEffect URL init with lazy useState initializers in document.tsx and logs.tsx
- Make handleToggleEnabled optimistic in document.tsx (cache first, onError rollback)
- Replace mutate+new Promise wrapper with mutateAsync+try/catch in base.tsx
- Fix schedule-modal.tsx: replace 15-setter useEffect with useState lazy initializers + key prop remount; wrap parseCronToScheduleType in useMemo
- Fix logs search: eliminate mount-only useEffect with eslint-disable by passing initialQuery to useSearchState; parse query once via shared initialParsed state
- Add useWorkspaceFileRecord hook to workspace-files.ts; refactor FileViewer to self-fetch
- Fix value: any → value: string in useTagSelection and collaborativeSetTagSelection
- Fix knowledge-tag-filters.tsx: pass '' instead of null when filters are cleared (type safety)
* fix(kb): use active scope in useWorkspaceFileRecord to share cache with useWorkspaceFiles
* fix(logs,kb,tasks): lazy-init useRef for URL param, add cold-path docs to useWorkspaceFileRecord, document key remount requirement in ScheduleModal
* fix(files): redirect to files list when file record not found in viewer
* revert(files): remove useEffect redirect from file-viewer, keep simple null return
* fix(scheduled-tasks): correct useMemo dep from schedule?.cronExpression to schedule
* feat(logs): add copy link and deep link support for log entries
* fix(logs): move Link icon to emcn and handle clipboard rejections
* feat(notifications): use executionId deep-link for View Log URLs
Switch buildLogUrl from ?search= to ?executionId= so email and Slack
'View Log' buttons open the logs page with the specific execution
auto-selected and the details panel expanded.
* fix(knowledge): fix document processing stuck in processing state
* fix(knowledge): use Promise.allSettled for document dispatch and fix Copilot OAuth context
- Change Promise.all to Promise.allSettled in processDocumentsWithQueue so
one failed dispatch doesn't abort the entire batch
- Add writeOAuthReturnContext before showing LazyOAuthRequiredModal from
Copilot tools so useOAuthReturnForWorkflow can handle the return
- Add consumeOAuthReturnContext on modal close to clean up stale context
* fix(knowledge): fix type error in useCredentialRefreshTriggers call
Pass empty string instead of undefined for connectorProviderId fallback
to match the hook's string parameter type.
* upgrade turbo
* fix(knowledge): fix type error in connectors-section useCredentialRefreshTriggers call
Same string narrowing fix as add-connector-modal — pass empty string
fallback for providerId.
* feat(logs): add copy link and deep link support for log entries
* fix(logs): fetch next page when deep linked log is beyond initial page
* fix(logs): move Link icon to emcn and handle clipboard rejections
* fix(logs): track isFetching reactively and drop empty-list early-return
- Remove guard that prevented clearing the
pending ref when filters return no results
- Use directly in the condition and add it to
the effect deps so the effect re-triggers after a background refetch
* fix(logs): guard deep-link ref clear until query has succeeded
Only clear pendingExecutionIdRef when the query status is 'success',
preventing premature clearing before the initial fetch completes.
On mount, the query is disabled (isInitialized.current starts false),
so hasNextPage is false but no data has loaded yet — the ref was being
cleared in the same effect pass that set it.
* fix(logs): guard fetchNextPage call until query has succeeded
Add logsQuery.status === 'success' to the fetchNextPage branch so it
mirrors the clear branch. On mount the query is disabled (isFetching is
false, status is pending), causing the effect to call fetchNextPage()
before the query is initialized — now both branches require success.
* feat(profound): add Profound AI visibility and analytics integration
* fix(profound): fix import ordering and JSON formatting for CI lint
* fix(profound): gate metrics mapping on current operation to prevent stale overrides
* fix(profound): guard JSON.parse on filters, fix offset=0 falsy check, remove duplicate prompt_answers in FILTER_OPS
* lint
* fix(docs): fix import ordering and trailing newline for docs lint
* fix(scripts): sort generated imports to match Biome's organizeImports order
* fix(profound): use != null checks for limit param across all tools
* fix(profound): flatten block output type to 'json' to pass block validation test
* fix(profound): remove invalid 'required' field from block inputs (not part of ParamConfig)
* fix(profound): rename tool files from kebab-case to snake_case for docs generator compatibility
* lint
* fix(docs): let biome auto-fix import order, revert custom sort in generator
* fix(landing): fix import order in sim icon-mapping via biome
* fix(scripts): match Biome's exact import sort order in docs generator
* fix(generate-docs): produce Biome-compatible JSON output
The generator wrote multi-line arrays for short string arrays (like tags)
and omitted trailing newlines, causing Biome format check failures in CI.
Post-process integrations.json to collapse short arrays onto single lines
and add trailing newlines to both integrations.json and meta.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(logs): add additional metadata for workflow execution logs
* Revert "Feat(logs) upgrade mothership chat messages to error (#3772)"
This reverts commit 9d1b9763c5.
* Fix lint, address greptile comments
* improvement(sidebar): expand sidebar by hovering and clicking the edge (#3830)
* improvement(sidebar): expand sidebar by hovering and clicking the edge
* improvement(sidebar): add keyboard shortcuts for new workflow/task, center search modal, fix edge ARIA
* improvement(sidebar): use Tooltip.Shortcut for inline shortcut display
* fix(sidebar): change new workflow shortcut from Mod+Shift+W to Mod+Shift+P to avoid browser close-window conflict
* fix(hotkeys): fall back to event.code for international keyboard layout compatibility
* fix(sidebar): guard add-workflow shortcut with canEdit and isCreatingWorkflow checks
* feat(ui): handle image paste (#3826)
* feat(ui): handle image paste
* Fix lint
* Fix type error
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(files): interactive markdown checkbox toggling in preview (#3829)
* feat(files): interactive markdown checkbox toggling in preview
* fix(files): handle ordered-list checkboxes and fix index drift
* lint
* fix(files): remove counter offset that prevented checkbox toggling
* fix(files): apply task-list styling to ordered lists too
* fix(files): render single pass when interactive to avoid index drift
* fix(files): move useMemo above conditional return to fix Rules of Hooks
* fix(files): pass content directly to preview when not streaming to avoid stale frame
* improvement(home): position @ mention popup at caret and fix icon consistency (#3831)
* improvement(home): position @ mention popup at caret and fix icon consistency
* fix(home): pin mirror div to document origin and guard button anchor
* chore(auth): restore hybrid.ts to staging
* improvement(ui): sidebar (#3832)
* Fix logger tests
* Add metadata to mothership logs
---------
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(sidebar): cmd+click opens in new tab, shift+click for range select
* comment cleanup
* fix(sidebar): drop stale metaKey param from workflow and task selection hooks
* feat(file-viewer): add pan and zoom to image preview
* fix(viewer): fix sort key mapping, disable load-more on sort, hide status dots when menu open
* fix(file-viewer): prevent scroll bleed and zoom button micro-pans
* fix(file-viewer): use exponential zoom formula to prevent zero/negative multiplier
* improvement(tables): improve table filtering UX
- Replace popover filter with persistent inline panel below toolbar
- Add AND/OR toggle between filter rules (shown in Where label slot)
- Sync filter panel state from applied filter on open
- Show filter button active state when filter is applied or panel is open
- Use readable operator labels matching dropdown options
- Add Clear filters button (shown only when filter is active)
- Close filter panel when last rule is removed via X
- Fix empty gap rows appearing in filtered results by skipping position gap rendering when filter is active
- Add toggle mode to ResourceOptionsBar for inline panel pattern
- Memoize FilterRuleRow for perf, fix filterTags key collision, remove dead filterActiveCount prop
* fix(table-filter): use ref to stabilize handleRemove/handleApply callbacks
Reading rules via ref instead of closure eliminates rules from useCallback
dependency arrays, keeping callbacks stable across rule edits and preserving
the memo() benefit on FilterRuleRow.
* improvement(tables,kb): remove hacky patterns, fix KB filter popover width
- Remove non-TSDoc comment from table-filter (rulesRef pattern is self-evident)
- Simplify SearchSection: remove setState-during-render anti-pattern; controlled
input binds directly to search.value/onChange (simpler and correct)
- Reduce KB filter popover from w-[320px] to w-[200px]; tag filter uses vertical
layout so narrow width works; Status-only case is now appropriately compact
* feat(knowledge): add sort and filter to KB list page
Sort dropdown: name, documents, tokens, created, last updated — pre-sorted
externally before passing rows to Resource. Active sort highlights the Sort
button; clear resets to default (created desc).
Filter popover: filter by connector status (All / With connectors /
Without connectors). Active filter shown as a removable tag in the toolbar.
* feat(files): add sort and filter to files list page
* feat(scheduled-tasks): add sort and filter to scheduled tasks page
* fix(table-filter): use explicit close handler instead of toggle
* improvement(files,knowledge): replace manual debounce with useDebounce hook and use type guards for file filtering
* fix(resource): prevent popover from inheriting anchor min-width
* feat(tables): add sort to tables list page
* feat(knowledge): add content and owner filters to KB list
* feat(scheduled-tasks): add status and health filters
* feat(files): add size and uploaded-by filters to files list
* feat(tables): add row count, owner, and column type filters
* improvement(scheduled-tasks): use combobox filter panel matching logs UI style
* improvement(knowledge): use combobox filter panel matching logs UI style
* improvement(files): use combobox filter panel matching logs UI style
Replaces button-list filters with Combobox-based multi-select sections for file type, size, and uploaded-by filters, aligning the panel with the logs page filter UI.
* improvement(tables): use combobox filter panel matching logs UI style
* feat(settings): add sort to recently deleted page
Add a sort dropdown next to the search bar allowing users to sort by deletion date (default, newest first), name (A–Z), or type (A–Z).
* feat(logs): add sort to logs page
* improvement(knowledge): upgrade document list filter to combobox style
* fix(resources): fix missing imports, memoization, and stale refs across resource pages
* improvement(tables): remove column type filter
* fix(resources): fix filter/sort correctness issues from audit
* fix(chunks): add server-side sort to document chunks API
Chunk sort was previously done client-side on a single page of
server-paginated data, which only reordered the current page.
Now sort params (sortBy, sortOrder) flow through the full stack:
types → service → API route → query hook → useDocumentChunks → document.tsx.
* perf(resources): memoize filterContent JSX across all resource pages
Resource is wrapped in React.memo, so an unstable filterContent reference
on every parent re-render defeats the memo. Wrap filterContent in useMemo
with correct deps in all 6 pages (files, tables, scheduled-tasks, knowledge,
base, document).
* fix(resources): add missing sort options for all visible columns
Every column visible in a resource table should be sortable. Three pages
had visible columns with no sort support:
- files.tsx: add 'owner' sort (member name lookup)
- scheduled-tasks.tsx: add 'schedule' sort (localeCompare on description)
- knowledge.tsx: add 'connectors' (count) and 'owner' (member name) sorts
Also add 'members' to processedKBs deps in knowledge.tsx since owner
sort now reads member names inside the memo.
* whitelabeling updates, sidebar fixes, files bug
* increased type safety
* pr fixes
* improvement(home): position @ mention popup at caret and fix icon consistency
* fix(home): pin mirror div to document origin and guard button anchor
* chore(auth): restore hybrid.ts to staging
* feat(files): interactive markdown checkbox toggling in preview
* fix(files): handle ordered-list checkboxes and fix index drift
* lint
* fix(files): remove counter offset that prevented checkbox toggling
* fix(files): apply task-list styling to ordered lists too
* fix(files): render single pass when interactive to avoid index drift
* fix(files): move useMemo above conditional return to fix Rules of Hooks
* fix(files): pass content directly to preview when not streaming to avoid stale frame
* improvement(sidebar): expand sidebar by hovering and clicking the edge
* improvement(sidebar): add keyboard shortcuts for new workflow/task, center search modal, fix edge ARIA
* improvement(sidebar): use Tooltip.Shortcut for inline shortcut display
* fix(sidebar): change new workflow shortcut from Mod+Shift+W to Mod+Shift+P to avoid browser close-window conflict
* fix(hotkeys): fall back to event.code for international keyboard layout compatibility
* fix(sidebar): guard add-workflow shortcut with canEdit and isCreatingWorkflow checks
* fix(import): dedup workflow name (#3813)
* feat(concurrency): bullmq based concurrency control system (#3605)
* feat(concurrency): bullmq based queueing system
* fix bun lock
* remove manual execs off queues
* address comments
* fix legacy team limits
* cleanup enterprise typing code
* inline child triggers
* fix status check
* address more comments
* optimize reconciler scan
* remove dead code
* add to landing page
* Add load testing framework
* update bullmq
* fix
* fix headless path
---------
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
* fix(linear): add default null for after cursor (#3814)
* fix(knowledge): reject non-alphanumeric file extensions from document names (#3816)
* fix(knowledge): reject non-alphanumeric file extensions from document names
* fix(knowledge): improve error message when extension is non-alphanumeric
* fix(security): SSRF, access control, and info disclosure (#3815)
* fix(security): scope copilot feedback GET endpoint to authenticated user
Add WHERE clause to filter feedback records by the authenticated user's
ID, preventing any authenticated user from reading all users' copilot
interactions, queries, and workflow YAML (IDOR / CWE-639).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): add SSRF validation and genericize network error messages
Prevent SSRF via user-controlled smtpHost by validating with
validateDatabaseHost before creating the nodemailer transporter.
Collapse distinct network error messages (ECONNREFUSED, ECONNRESET,
ETIMEDOUT) into a single generic message to prevent port-state leakage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): add SSRF validation to SFTP/SSH and access control to workspace invitations
Add `validateDatabaseHost` checks to SFTP and SSH connection utilities to
block connections to private/reserved IPs and localhost, matching the
existing pattern used by all database tools. Add authorization check to
the workspace invitation GET endpoint so only the invitee or a workspace
admin can view invitation details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): restore SMTP response code handling for post-connection errors
SMTP 4xx/5xx response codes are application-level errors (invalid
recipient, mailbox full, server error) unrelated to the SSRF hardening
goal. Restore response code differentiation and logging to preserve
actionable user-facing error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): use session email directly instead of extra DB query
Addresses PR review feedback — align with the workspace invitation
route pattern by using session.user.email instead of re-fetching
from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix that broke hasExternalApiCredentials return type
Biome auto-fixed `return auth !== null && auth.startsWith(...)` to
`return auth?.startsWith(...)` which returns `boolean | undefined`,
not `boolean`, causing a TypeScript build failure.
* fix(smtp): pin resolved IP to prevent DNS rebinding (TOCTOU)
Use the pre-resolved IP from validateDatabaseHost instead of the
original hostname when creating the nodemailer transporter. Set
servername to the original hostname to preserve TLS SNI validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(security): extract createPinnedLookup helper for DNS rebinding prevention
Extract reusable createPinnedLookup from secureFetchWithPinnedIP so
non-HTTP transports (SSH, SFTP, IMAP) can pin resolved IPs at the
socket level. SMTP route uses host+servername pinning instead since
nodemailer doesn't reliably pass lookup to both secure/plaintext paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): pin IMAP connections to validated resolved IP
Pass the resolved IP from validateDatabaseHost to ImapFlow as host,
with the original hostname as servername for TLS SNI verification.
Closes the DNS TOCTOU rebinding window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix on hasExternalApiCredentials return type
Also pin SFTP/SSH connections to validated resolved IP to prevent DNS rebinding.
* fix(security): short-circuit admin check when caller is invitee
Skip the hasWorkspaceAdminAccess DB query when the caller is already
the invitee, avoiding an unnecessary round-trip. Aligns with the org
invitation route pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(worker): dockerfile + helm updates (#3818)
* fix(worker): dockerfile + helm updates
* address comments
* update dockerfile (#3819)
* fix dockerfile
* fix(security): pentest remediation — condition escaping, SSRF hardening, ReDoS protection (#3820)
* fix(executor): escape newline characters in condition expression strings
Unescaped newline/carriage-return characters in resolved string values
cause unterminated string literals in generated JS, crashing condition
evaluation with a SyntaxError.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): prevent ReDoS in guardrails regex validation
Add safe-regex2 to reject catastrophic backtracking patterns before
execution and cap input length at 10k characters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): SSRF localhost hardening and regex DoS protection
Block localhost/loopback URLs in hosted environments using isHosted flag
instead of allowHttp. Add safe-regex2 validation and input length limits
to regex guardrails to prevent catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): validate regex syntax before safety check
Move new RegExp() before safe() so invalid patterns get a proper syntax
error instead of a misleading "catastrophic backtracking" message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): address PR review feedback
- Hoist isLocalhost && isHosted guard to single early-return before
protocol checks, removing redundant duplicate block
- Move regex syntax validation (new RegExp) before safe-regex2 check
so invalid patterns get proper syntax error instead of misleading
"catastrophic backtracking" message
* fix(security): remove input length cap from regex validation
The 10k character cap would block legitimate guardrail checks on long
LLM outputs. Input length doesn't affect ReDoS risk — the safe-regex2
pattern check already prevents catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tests): mock isHosted in input-validation and function-execute tests
Tests that assert self-hosted localhost behavior need isHosted=false,
which is not guaranteed in CI where NEXT_PUBLIC_APP_URL is set to the
hosted domain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(worker): configuration defaults (#3821)
* improvement(worker): configuration defaults
* update readmes
* realtime curl import
* improvement(tour): remove auto-start, only trigger on explicit user action (#3823)
* fix(mcp): use correct modal for creating workflow MCP servers in deploy (#3822)
* fix(mcp): use correct modal for creating workflow MCP servers in deploy
* fix(mcp): show workflows field during loading and when empty
* mock course
* fix(db): use bigint for token counter columns in user_stats (#3755)
* mock course
* updates
* updated X handle for emir
* cleanup: audit and clean academy implementation
* fix(academy): add label to ValidationRule, fix quiz gating, simplify getRuleMessage
* cleanup: remove unnecessary comments across academy files
* refactor(academy): simplify abstractions and fix perf issues
* perf(academy): convert course detail page to server component with client island
* fix(academy): null-safe canAdvance, render exercise instructions, remove stale comments
* fix(academy): remove orphaned migration, fix getCourseById, clean up comments
- Delete 0181_academy_certificate.sql (orphaned duplicate not in journal)
- Add getCourseById() to content/index.ts; use it in certificates API
(was using getCourse which searches by slug, not stable id)
- Remove JSX comments from catalog page
- Remove redundant `passed` recomputation in LessonQuiz
* chore(db): regenerate academy_certificate migration with drizzle-kit
* chore: include blog mdx and components changes
* fix(blog): correct cn import path
* fix(academy): constrain progress bar to max-w-3xl with proper padding
* feat(academy): show back-to-course button on first lesson
* fix(academy): force dark theme on all /academy routes
* content(academy): rewrite sim-foundations course with full 6-module curriculum
* fix(academy): correct edge handles, quiz explanation, and starter mock outputs
- Fix Exercise 2 initial edge handles: 'starter-1-source'/'agent-1-target' → 'source'/'target' (React Flow actual IDs)
- Fix M1-L4 Q4 quiz explanation: remove non-existent Ctrl/Cmd+D and Alt+drag shortcuts
- Add starter mock output to all exercises so run animation shows feedback on the first block
* refine(academy): fix inaccurate content and improve exercise clarity
- Fix Exercise 3: replace hardcoded <agent-1.content> (invalid UUID-based ref) with reference picker instructions
- Fix M4 Quiz Q5: Loop block (subflow container) is correct answer, not the Workflow block
- Fix M4 Quiz Q4: clarify fan-out vs Parallel block distinction in explanation
- Fix M4-L2 video description: accurately describe Loop and Parallel subflow blocks
- Fix M2 Quiz Q3: make response format question conceptual rather than syntax-specific
- Improve Exercise 4 branching instructions: clarify top=true / bottom=false output handles
- Improve Final Project instructions: step-by-step numbered flow
* fix(academy): remove double border on quiz question cards
* fix(academy): single scroll container on lesson pages — remove nested flex scroll
* fix(academy): remove min-h-screen from root layout — fixes double scrollbar on lesson pages
* fix(academy): use fixed inset-0 on lesson page to eliminate document-level scrollbar
* fix(academy): replace sr-only radio/checkbox inputs with buttons to prevent scroll-on-focus; restore layout min-h-screen
* improvement(academy): polish, security hardening, and certificate claim UI
- Replace raw localStorage with BrowserStorage utility in local-progress
- Pre-compute slug/id Maps in content/index for O(1) course lookups
- Move blockMap construction into edge_exists branch only in validation
- Extract navBtnClass constant and MetaRow/formatDate helpers in UI
- Add rate limiting, server-side completion verification, audit logging, and nanoid cert numbers to certificate issuance endpoint
- Add useIssueCertificate mutation hook with completedLessonIds
- Wire certificate claim UI into CourseProgress: sign-in prompt, claim button with loading state, and post-issuance view with link to certificate page
- Fix lesson page scroll container and quiz scroll-on-focus bug
* fix(academy): validate condition branch handles in edge_exists rules
- Add sourceHandle field to edge_exists ValidationRule type
- Check sourceHandle in validation.ts when specified
- Require both condition-if and condition-else branches to be connected in the branching and final project exercises
* fix(academy): address PR review — isHosted regression, stuck isExecuting, revoked cert 500, certificate SSR
- Restore env-var-based isHosted check (was hardcoded true, breaking self-hosted deployments)
- Fix isExecuting stuck at true when mock run fails validation — set isMockRunningRef immediately and reset both flags on early exit
- Fix revoked/expired certificate causing 500 — any existing record (not just active) now returns 409 instead of falling through to INSERT
- Convert certificate verification page from client component to server component — direct DB fetch, notFound() on missing cert, generateMetadata for SEO/social previews
* fix(auth): restore hybrid.ts from staging to fix CI type error
* fix(academy): mark video lessons complete on visit and fix sign-in path
* fix(academy): replace useEffect+setState with lazy useState initializer in CourseProgress
* fix(academy): reset exerciseComplete on lesson navigation, remove unused useAcademyCertificate hook
* fix(academy): useState for slug-change reset, cache() for cert page, handleMockRunRef for stale closure
* fix(academy): replace shadcn theme vars with explicit hex in LessonVideo fallback
* fix(academy): reset completedRef on exercise change, conditional verified badge, multi-select empty guard
* fix(academy): type safety fixes — null metadata fallbacks, returning() guard, exhaustive union, empty catch
* fix(academy): reset ExerciseView completed banner on nav; fix CourseProgress hydration mismatch
* fix(lightbox): guard effect body with isOpen to prevent spurious overflow reset
* fix(academy): reset LessonQuiz state on lesson change to prevent stale answers persisting
* fix(academy): course not-found metadata title; try-finally guard in mock run loop
* fix(academy): type safety, cert persistence, regex guard, mixed-lesson video, shorts support
- Derive AcademyCertificate from db $inferSelect to prevent schema drift
- Add useCourseCertificate query hook; GET /api/academy/certificates now accepts courseId for authenticated lookup
- Use useCourseCertificate in CourseProgress so certificate state survives page refresh
- Guard new RegExp(valuePattern) in validation.ts with try/catch; log warn on invalid pattern
- Add logger.warn for custom validation rules so content authors are alerted
- Add YouTube Shorts URL support to LessonVideo (youtube.com/shorts/VIDEO_ID)
- Fix mixed-lesson video gap: render videoUrl above quiz when mixed has quiz but no exercise
- Add academy-scoped not-found.tsx with link back to /academy
* fix(academy): reset hintIndex when exercise changes
* chore: remove ban-spam-accounts script (wrong branch)
* fix(academy): enforce availableBlocks in toolbar; fix mixed exercise+quiz rendering
- Add useSandboxBlockConstraints context; SandboxCanvasProvider provides exerciseConfig.availableBlocks so the toolbar only shows permitted block types. Empty array hides all blocks (configure-only exercises); non-null array restricts to listed types; triggers always hidden in sandbox.
- Fix mixed lesson with both exerciseConfig and quizConfig: exercise renders first, quiz reveals after exercise completes (sequential pedagogy). canAdvance now requires both exerciseComplete && quizComplete when both are present.
* chore(academy): remove extraneous inline comments
* fix(academy): blank mixed lesson, quiz canAdvance flag, empty-array valueNotEmpty
* prep for merge
* chore(db): regenerate academy certificate migration after staging merge
* fix(academy): disable auto-connect in sandbox mode
* fix(academy): render video in mixed lesson with no exercise or quiz
* fix(academy): mark mixed video-only lessons complete; handle cert insert race
* fix(canvas): add sandbox and embedded to nodes useMemo deps
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
* fix(knowledge): give users choice to keep or delete documents when removing connector
* refactor(knowledge): clean up connector delete and extract shared extension validator
- Extract `isAlphanumericExtension` helper to deduplicate regex across parser-extension.ts and validation.ts
- Extract `closeDeleteModal` callback to eliminate 4x scattered state resets
- Add archivedAt/deletedAt filters to UPDATE query in keep-docs delete path
- Parallelize storage file cleanup and tag definition cleanup with Promise.all
- Deduplicate URL construction in delete connector hook
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(knowledge): remove duplicate extension list from parser-extension
Use SUPPORTED_DOCUMENT_EXTENSIONS and isSupportedExtension from
validation.ts instead of maintaining a separate identical list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(db): change document.connectorId FK from cascade to set null
The cascade behavior meant deleting a connector would always delete
its documents, contradicting the "keep documents" option. With set null,
the database automatically nullifies connectorId when a connector is
removed, and we only need explicit deletion when the user opts in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(db): add migration metadata for connectorId FK change
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): fix connector delete test and use URL-safe searchParams
Use `new URL(request.url).searchParams` instead of `request.nextUrl.searchParams`
for compatibility with test mocks. Add missing `connectorType` to test fixture.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* spacing
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(executor): escape newline characters in condition expression strings
Unescaped newline/carriage-return characters in resolved string values
cause unterminated string literals in generated JS, crashing condition
evaluation with a SyntaxError.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): prevent ReDoS in guardrails regex validation
Add safe-regex2 to reject catastrophic backtracking patterns before
execution and cap input length at 10k characters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): SSRF localhost hardening and regex DoS protection
Block localhost/loopback URLs in hosted environments using isHosted flag
instead of allowHttp. Add safe-regex2 validation and input length limits
to regex guardrails to prevent catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): validate regex syntax before safety check
Move new RegExp() before safe() so invalid patterns get a proper syntax
error instead of a misleading "catastrophic backtracking" message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): address PR review feedback
- Hoist isLocalhost && isHosted guard to single early-return before
protocol checks, removing redundant duplicate block
- Move regex syntax validation (new RegExp) before safe-regex2 check
so invalid patterns get proper syntax error instead of misleading
"catastrophic backtracking" message
* fix(security): remove input length cap from regex validation
The 10k character cap would block legitimate guardrail checks on long
LLM outputs. Input length doesn't affect ReDoS risk — the safe-regex2
pattern check already prevents catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tests): mock isHosted in input-validation and function-execute tests
Tests that assert self-hosted localhost behavior need isHosted=false,
which is not guaranteed in CI where NEXT_PUBLIC_APP_URL is set to the
hosted domain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): scope copilot feedback GET endpoint to authenticated user
Add WHERE clause to filter feedback records by the authenticated user's
ID, preventing any authenticated user from reading all users' copilot
interactions, queries, and workflow YAML (IDOR / CWE-639).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): add SSRF validation and genericize network error messages
Prevent SSRF via user-controlled smtpHost by validating with
validateDatabaseHost before creating the nodemailer transporter.
Collapse distinct network error messages (ECONNREFUSED, ECONNRESET,
ETIMEDOUT) into a single generic message to prevent port-state leakage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): add SSRF validation to SFTP/SSH and access control to workspace invitations
Add `validateDatabaseHost` checks to SFTP and SSH connection utilities to
block connections to private/reserved IPs and localhost, matching the
existing pattern used by all database tools. Add authorization check to
the workspace invitation GET endpoint so only the invitee or a workspace
admin can view invitation details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): restore SMTP response code handling for post-connection errors
SMTP 4xx/5xx response codes are application-level errors (invalid
recipient, mailbox full, server error) unrelated to the SSRF hardening
goal. Restore response code differentiation and logging to preserve
actionable user-facing error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): use session email directly instead of extra DB query
Addresses PR review feedback — align with the workspace invitation
route pattern by using session.user.email instead of re-fetching
from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix that broke hasExternalApiCredentials return type
Biome auto-fixed `return auth !== null && auth.startsWith(...)` to
`return auth?.startsWith(...)` which returns `boolean | undefined`,
not `boolean`, causing a TypeScript build failure.
* fix(smtp): pin resolved IP to prevent DNS rebinding (TOCTOU)
Use the pre-resolved IP from validateDatabaseHost instead of the
original hostname when creating the nodemailer transporter. Set
servername to the original hostname to preserve TLS SNI validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(security): extract createPinnedLookup helper for DNS rebinding prevention
Extract reusable createPinnedLookup from secureFetchWithPinnedIP so
non-HTTP transports (SSH, SFTP, IMAP) can pin resolved IPs at the
socket level. SMTP route uses host+servername pinning instead since
nodemailer doesn't reliably pass lookup to both secure/plaintext paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): pin IMAP connections to validated resolved IP
Pass the resolved IP from validateDatabaseHost to ImapFlow as host,
with the original hostname as servername for TLS SNI verification.
Closes the DNS TOCTOU rebinding window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix on hasExternalApiCredentials return type
Also pin SFTP/SSH connections to validated resolved IP to prevent DNS rebinding.
* fix(security): short-circuit admin check when caller is invitee
Skip the hasWorkspaceAdminAccess DB query when the caller is already
the invitee, avoiding an unnecessary round-trip. Aligns with the org
invitation route pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): scope sync/update state per-connector to prevent race conditions
* feat(knowledge): add connectors column to knowledge base list
* refactor(knowledge): extract set helpers, handleTogglePause, and filter-before-map
* refactor(knowledge): use onSettled for syncingIds cleanup, consistent with updatingIds
* feat(generic): add generic resource tab, refactor home structure, and UI polish
* reverted hardcoded ff
* fix build
* styling consistency
* styling
* fix(auth): extract shared auth button class and align SSO primary style
- Extract AUTH_SUBMIT_BTN constant to (auth)/components/auth-button-classes.ts,
replacing 10 copy-pasted identical className strings across 7 files
- Update SSOLoginButton primary variant to use AUTH_SUBMIT_BTN instead of
hardcoded purple gradient, making it consistent with all other auth form
submit buttons
- Fix missing isEphemeralResource import in lib/copilot/resources.ts
(was re-exported but not available in local scope)
* fix(auth): replace inline button class in chat auth components with AUTH_SUBMIT_BTN
* fix send button hover state
* feat(search): add tables, files, knowledge bases, and jobs to cmd-k search
* fix(search): address PR feedback — drop files/jobs, add onSelect to memo
* fix(search): add files back with per-file deep links, keep jobs out
* fix(search): remove onSelect from memo comparator to match existing pattern
* fix(knowledge): enqueue connector docs per-batch to survive sync timeouts
* fix(connectors): convert all connectors to contentDeferred pattern and fix validation issues
All 10 connectors now use contentDeferred: true in listDocuments, returning
lightweight metadata stubs instead of downloading content during listing.
Content is fetched lazily via getDocument only for new/changed documents,
preventing Trigger.dev task timeouts on large syncs.
Connector-specific fixes from validation audit:
- Google Drive: metadata-based contentHash, orderBy for deterministic pagination,
precise maxFiles, byte-length size check with truncation warning
- OneDrive: metadata-based contentHash, orderBy for deterministic pagination
- SharePoint: metadata-based contentHash, byte-length size check
- Dropbox: metadata-based contentHash using content_hash field
- Notion: code/equation block extraction, empty page fallback to title,
reduced CHILD_PAGE_CONCURRENCY to 5, syncContext parameter
- Confluence: syncContext caching for cloudId, reduced label concurrency to 5
- Gmail: use joinTagArray for label tags
- Obsidian: syncRunId-based stub hash for forced re-fetch, mtime-based hash
in getDocument, .trim() on vaultUrl, lightweight validateConfig
- Evernote: retryOptions threaded through apiFindNotesMetadata and apiGetNote
- GitHub: added contentDeferred: false to getDocument, syncContext parameter
Infrastructure:
- sync-engine: added syncRunId to syncContext for Obsidian change detection
- confluence/utils: replaced raw fetch with fetchWithRetry, added retryOptions
- oauth: added supportsRefreshTokenRotation: false for Dropbox
- Updated add-connector and validate-connector skills with contentDeferred docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(connectors): address PR review comments - metadata merge, retryOptions, UTF-8 safety
- Sync engine: merge metadata from getDocument during deferred hydration,
so Gmail/Obsidian/Confluence tags and metadata survive the stub→full transition
- Evernote: pass retryOptions {retries:3, backoff:500} from listDocuments and
getDocument callers into apiFindNotesMetadata and apiGetNote
- Google Drive + SharePoint: safe UTF-8 truncation that walks back to the last
complete character boundary instead of splitting multi-byte chars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(evernote): use correct RetryOptions property names
maxRetries/initialDelayMs instead of retries/backoff to match the
RetryOptions interface from lib/knowledge/documents/utils.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sync-engine): merge title from getDocument and skip unchanged docs after hydration
- Merge title from getDocument during deferred hydration so Gmail
documents get the email Subject header instead of the snippet text
- After hydration, compare the hydrated contentHash against the stored
DB hash — if they match, skip the update. This prevents Obsidian
(and any connector with a force-refresh stub hash) from re-uploading
and re-processing unchanged documents every sync
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sync-engine): dedup externalIds, enable deletion reconciliation, merge sourceUrl
Three sync engine gaps identified during audit:
1. Duplicate externalId guard: if a connector returns the same externalId
across pages (pagination overlap), skip the second occurrence to prevent
unique constraint violations on add and double-uploads on update.
2. Deletion reconciliation: previously required explicit fullSync or
syncMode='full', meaning docs deleted from the source accumulated in
the KB forever. Now runs on all non-incremental syncs (which return
ALL docs). Includes a safety threshold: if >50% of existing docs
(and >5 docs) would be deleted, skip and warn — protects against
partial listing failures. Explicit fullSync bypasses the threshold.
3. sourceUrl merge: hydration now picks up sourceUrl from getDocument,
falling back to the stub's sourceUrl if getDocument doesn't set one.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(connectors): confluence version metadata fallback and google drive maxFiles guard
- Confluence: use `version?.number` directly (undefined) in metadata instead
of `?? ''` (empty string) to prevent Number('') = 0 passing NaN check in
mapTags. Hash still uses `?? ''` for string interpolation.
- Google Drive: add early return when previouslyFetched >= maxFiles to prevent
effectivePageSize <= 0 which violates the API's pageSize requirement (1-1000).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(connectors): blogpost labels and capped listing deletion reconciliation
- Confluence: fetchLabelsForPages now tries both /pages/{id}/labels and
/blogposts/{id}/labels, preventing label loss when getDocument hydrates
blogpost content (previously returned empty labels on 404).
- Sync engine: skip deletion reconciliation when listing was capped
(maxFiles/maxThreads). Connectors signal this via syncContext.listingCapped.
Prevents incorrect deletion of docs beyond the cap that still exist in source.
fullSync override still forces deletion for explicit cleanup.
- Google Drive & Gmail: set syncContext.listingCapped = true when cap is hit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(connectors): set syncContext.listingCapped in all connectors with caps
OneDrive, Dropbox, SharePoint, Confluence (v2 + CQL), and Notion (3 listing
functions) now set syncContext.listingCapped = true when their respective
maxFiles/maxPages limit is hit. Without this, the sync engine's deletion
reconciliation would run against an incomplete listing and incorrectly
hard-delete documents that exist in the source but fell outside the cap window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(evernote): thread retryOptions through apiListTags and apiListNotebooks
All calls to apiListTags and apiListNotebooks in both listDocuments and
getDocument now pass retryOptions for consistent retry protection across
all Thrift RPC calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent auth bypass via user-controlled context query param in file serve
The /api/files/serve endpoint trusted a user-supplied `context` query
parameter to skip authentication. An attacker could append
`?context=profile-pictures` to any file URL and download files without
auth. Now the public access gate checks the key prefix instead of the
query param, and `og-images/` is added to `inferContextFromKey`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use randomized heredoc delimiter in SSH execute-script route
Prevents accidental heredoc termination if script content contains
the delimiter string on its own line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: escape workingDirectory in SSH execute-command route
Use escapeShellArg() with single quotes for the workingDirectory
parameter, consistent with all other SSH routes (execute-script,
create-directory, delete-file, move-rename).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden chat/form deployment auth (OTP brute-force, CSPRNG, HMAC tokens)
- Add brute-force protection to OTP verification with attempt tracking (CWE-307)
- Replace Math.random() with crypto.randomInt() for OTP generation (CWE-338)
- Replace unsigned Base64 auth tokens with HMAC-SHA256 signed tokens (CWE-327)
- Use shared isEmailAllowed utility in OTP route instead of inline duplicate
- Simplify Redis OTP update to single KEEPTTL call
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden SSRF protections and input validation across API routes
Add DNS-based SSRF validation for MCP server URLs, secure OIDC discovery
with IP-pinned fetch, strengthen OTP/chat/form input validation, sanitize
1Password vault parameters, and tighten deployment security checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(file-serve): remove user-controlled context param from authenticated path
The `?context` query param was still being passed to `handleCloudProxy`
in the authenticated code path, allowing any logged-in user to spoof
context as `profile-pictures` and bypass ownership checks in
`verifyFileAccess`. Now always use `inferContextFromKey` from the
server-controlled key prefix.
* fix: handle legacy OTP format in decodeOTPValue for deploy-time compat
Add guard for OTP values without colon separator (pre-deploy format)
to avoid misparse that would lock out users with in-flight OTPs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mcp): distinguish DNS resolution failures from SSRF policy blocks
DNS lookup failures now throw McpDnsResolutionError (502) instead of
McpSsrfError (403), so transient DNS hiccups surface as retryable
upstream errors rather than confusing permission rejections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make OTP attempt counting atomic to prevent TOCTOU race
Redis path: use Lua script for atomic read-increment-conditional-delete.
DB path: use optimistic locking (UPDATE WHERE value = currentValue) with
re-read fallback on conflict. Prevents concurrent wrong guesses from
each counting as a single attempt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: check attempt count before OTP comparison to prevent bypass
Reject OTPs that have already reached max failed attempts before
comparing the code, closing a race window where a correct guess
could bypass brute-force protection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate OIDC discovered endpoints against SSRF
The discovery URL itself was SSRF-validated, but endpoint URLs returned
in the discovery document (tokenEndpoint, userInfoEndpoint, jwksEndpoint)
were stored without validation. A malicious OIDC issuer on a public IP
could return internal network URLs in the discovery response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove duplicate OIDC endpoint SSRF validation block
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate OIDC discovered endpoints and pin DNS for 1Password Connect
- SSRF-validate all endpoint URLs returned by OIDC discovery documents
before storing them (authorization, token, userinfo, jwks endpoints)
- Pin DNS resolution in 1Password Connect requests using
secureFetchWithPinnedIP to prevent TOCTOU DNS rebinding attacks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix: replace KEEPTTL with TTL+EX for Redis <6.0 compat, add DB retry loop
- Lua script now reads TTL and uses SET...EX instead of KEEPTTL
- DB optimistic locking now retries up to 3 times on conflict
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review feedback on OTP atomicity and 1Password fetch
- Replace Redis KEEPTTL with TTL+SET EX for Redis <6.0 compatibility
- Add retry loop to DB optimistic lock path so concurrent OTP attempts
are actually counted instead of silently dropped
- Remove unreachable fallback fetch in 1Password Connect; make
validateConnectServerUrl return non-nullable string
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: treat Lua nil return as locked when OTP key is missing
When the Redis key is deleted/expired between getOTP and
incrementOTPAttempts, the Lua script returns nil. Handle this
as 'locked' instead of silently treating it as 'incremented'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle Lua nil as locked OTP and add SSRF check to MCP env resolution
- Treat Redis Lua nil return (expired/deleted key) as 'locked' instead
of silently treating it as a successful increment
- Add validateMcpServerSsrf to MCP service resolveConfigEnvVars so
env-var URLs are SSRF-validated after resolution at execution time
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: narrow resolvedIP type guard instead of non-null assertion
Replace urlValidation.resolvedIP! with proper type narrowing by adding
!urlValidation.resolvedIP to the guard clause, so TypeScript can infer
the string type without a fragile assertion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bind auth tokens to deployment password for immediate revocation
Include a SHA-256 hash of the encrypted password in the HMAC-signed
token payload. Changing the deployment password now immediately
invalidates all existing auth cookies, restoring the pre-HMAC behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bind auth tokens to deployment password and remove resolvedIP non-null assertion
- Include SHA-256 hash of encryptedPassword in HMAC token payload so
changing a deployment's password immediately invalidates all sessions
- Pass encryptedPassword through setChatAuthCookie/setFormAuthCookie
and validateAuthToken at all call sites
- Replace non-null assertion on resolvedIP with proper narrowing guard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update test assertions for new encryptedPassword parameter
Tests now expect the encryptedPassword arg passed to validateAuthToken
and setDeploymentAuthCookie after the password-binding change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: format long lines in chat/form test assertions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pass encryptedPassword through OTP route cookie generation
Select chat.password in PUT handler DB query and pass it to
setChatAuthCookie so OTP-issued tokens include the correct
password slot for subsequent validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(copilot): expand tool metadata, fix thinking text rendering, clean up display logic
* fix(copilot): guard null reasoning data, use ensureTextBlock for thinking end
* fix(copilot): restore displayTitle precedence so cancelled tools show 'Stopped by user'
* feat: skills import, MCP modal updates, wordmark icon, tool-input improvements
- Add skills import functionality (route + components + utils)
- Update MCP deploy modal
- Add Wordmark emcn icon + logo SVG assets
- Improve tool-input component
- Update README branding to new wordmark
- Add ban-spam-accounts admin script
* fix: resolve build error and audit findings from simplify review
- Add BUILT_IN_TOOL_TYPES export to blocks/utils.ts (was removed from
tool-input.tsx but never added to the new import target — caused build
error "Export BUILT_IN_TOOL_TYPES doesn't exist in target module")
- Export Wordmark from emcn icons barrel (index.ts)
- Derive isDragging from dragCounter in skill-import.tsx instead of
maintaining redundant state that could desync
- Replace manual AbortController/setTimeout with AbortSignal.timeout()
in skills import API route (Node 17.3+ supported, cleaner no-cleanup)
- Use useId() for SVG gradient ID in wordmark.tsx to prevent duplicate
ID collisions if rendered multiple times on the same page
* fix(scripts): fix docs mismatch and N+1 query in ban-spam-accounts
- Fix comment: default pattern is @vapu.xyz, not @sharebot.net
- Replace per-user stats loop with a single aggregated JOIN query
* feat: wire wordmark into sidebar, fix credential selector modal dispatch
- Show Wordmark (icon + text) in the expanded sidebar instead of the
bare Sim icon; collapsed state keeps the small Sim icon unchanged
- Untrack scripts/ban-spam-accounts.ts (gitignored; one-off script)
- Credential selector: open OAuthRequiredModal inline instead of
navigating to Settings → Integrations (matches MCP/tool-input pattern)
- Credential selector: update billing import from getSubscriptionAccessState
to getSubscriptionStatus; drop writePendingCredentialCreateRequest and
useSettingsNavigation dependencies
* feat(misc): misc UX/UI improvements
* more random fixes
* more random fixes
* fix: address PR review findings from cursor bugbot
- settings-sidebar: use getSubscriptionAccessState instead of getSubscriptionStatus
so billingBlocked and status validity are checked; add requiresMax gating so
max-plan-only nav items (inbox) are hidden for lower-tier users
- credential-selector: same getSubscriptionAccessState migration for credential sets
visibility check
- mothership chats PATCH: change else if to if for isUnread so both title and
isUnread can be updated in a single request
- skills import: check Content-Length header before reading response body to avoid
loading oversized files into memory
* fix(skills): add ZIP file size guard before extraction
Checks file.size > 5 MB before calling extractSkillFromZip to prevent
zip bombs from exhausting browser memory at the client-side upload path.
* feat(settings-sidebar): show locked upsell items with plan badge
Sim Mailer (requiresMax) and Email Polling (requiresTeam) now always
appear in the settings sidebar when billing is enabled and the
deployment is hosted. If the user lacks the required plan they see a
small MAX / TEAM badge next to the label and are taken to the page
which already contains the upgrade prompt.
Enterprise (Access Control, SSO) and Team management stay hard-hidden
for lower tiers. Admin/superuser items stay truly hidden.
* fix(settings-sidebar): remove flex-1 from label span to fix text centering
* feat(settings-sidebar): remove team gate from email polling, keep only mailer max gate
* feat(subscription): billing details layout and Enterprise card improvements
- Move Enterprise plan card into the plan grid (auto-fit columns) instead
of a separate standalone section below billing details
- Refactor billing details section: remove outer border/background,
separate each row with top border + padding for cleaner separation
- Update button variants: Add Credits → active, Invoices → active
* fix(mothership): prevent lastSeenAt conflict when both title and isUnread are patched together
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sidebar): prevent double-save race in flyout inline rename on Enter+blur
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(skills): normalize CRLF line endings before parsing SKILL.md frontmatter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(log): enable info logs in staging and prod
* Upgrade info logs to error for message route
* Add to orchestrator, remove helm shennanigans
* Fix lint
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(ui): add request a demo modal
* Remove dead code
* Remove footer modal
* Address greptile comments
* Sanatize CRLF characters from emails
* extract shared email header safety regex
Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>
* Use pricing CTA action for demo modal
Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>
* fix demo request import ordering
Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>
* merge staging and fix hubspot list formatting
Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>
* fix(generate-docs): fix tool description extraction and simplify script
- Fix endsWith over-matching: basename === 'index.ts'/'types.ts' instead
of endsWith(), which was silently skipping valid tool files like
list_leave_types.ts, delete_index.ts, etc.
- Add extractSwitchCaseToolMapping() to resolve op ID → tool ID mismatches
where block switch statements map differently (e.g. HubSpot get_carts →
hubspot_list_carts)
- Fix double fs.readFileSync in writeIntegrationsJson — reuse existing
fileContent variable instead of re-reading the file
- Remove 5 dead functions superseded by *FromContent variants
- Simplify extractToolsAccessFromContent to use matchAll
- fix(upstash): replace template literal tool ID with explicit switch cases
* fix(generate-docs): restore extractIconName by aliasing to extractIconNameFromContent
* restore
* fix(demo-modal): reset form on open to prevent stale success state on reopen
* undo hardcoded ff
* fix(upstash): throw on unknown operation instead of silently falling back to get
---------
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>
Co-authored-by: waleed <walif6@gmail.com>
* feat(hubspot): add 27 CRM tools and fix OAuth scope mismatch
* lint
* fix(hubspot): switch marketing events to CRM Objects API and add HubSpotCrmObject base type
* chore(docs): fix import ordering and formatting lint errors
* feat(hubspot): wire all 27 new tools into block definition
* fix(hubspot): address review comments - schema mismatch, pagination, trim, descriptions
- Switch marketing event outputs to CRM envelope structure (id, properties, createdAt, updatedAt, archived) matching CRM Objects API
- Fix list_lists pagination: add offset param, map offset-based response to paging structure
- Add .trim() to contactId/companyId in pre-existing get/update tools
- Fix default limit descriptions (100 → 10) in list_contacts/list_companies
- Fix operator examples (CONTAINS → CONTAINS_TOKEN) in search_contacts/search_companies
- Remove unused params arg in get_users transformResponse
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hubspot): revert to Marketing Events API and fix Lists pagination per API docs
Marketing Events:
- Revert from /crm/v3/objects/marketing_events back to /marketing/v3/marketing-events
- The Marketing Events API does NOT require appId for GET /marketing-events/{objectId}
- appId is only needed for the /events/{externalEventId} endpoint (which we don't use)
- Restore flat response schema (objectId, eventName, etc. at top level, not CRM envelope)
Lists:
- POST /crm/v3/lists/search uses offset-based pagination (not cursor-based)
- Response shape: { lists, hasMore, offset, total } — not { results, paging }
- Map offset → paging.next.after for consistent block interface
- Fix default count: 20 (not 25), max 500
- GET /crm/v3/lists/{listId} wraps response in { list: { ... } }
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hubspot): final audit fixes verified against API docs
- Revert list_contacts/list_companies default limit back to 100 (confirmed by API docs)
- Add idProperty param to get_appointment.ts (was missing, inconsistent with update_appointment)
- Remove get_carts from idProperty block condition (carts don't support idProperty)
- Add get_lists to after block condition (pagination was inaccessible from UI)
- Add after pagination param to get_users.ts (was missing, users beyond first page unreachable)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hubspot): return paging in get_users and add to block after condition
- Add paging output to get_users transformResponse and outputs
- Add get_users to block after subBlock condition so cursor is accessible from UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hubspot): align total fallback with type definitions in search tools
Use `?? 0` instead of `?? null` for search tools where the type declares
`total: number`. Also declare `total` in list_lists metadata output schema.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(rippling): add Rippling HR integration with 19 tools
* fix(rippling): address PR review feedback
- Fix lint:check import ordering in icon-mapping.ts
- Build clean params object instead of spreading all UI fields to API
- Add try/catch around JSON.parse for users field
- Use != null guard for limit/offset to not drop 0 values
- Add missing tags to block config and integrations.json
* fix(rippling): guard startDate by operation and clarify totalCount descriptions
- Guard startDate/endDate with operation check to prevent candidateStartDate
from clobbering date filters on leave/activity operations
- Update totalCount output descriptions on paginated tools to clarify it
reflects page size, not total record count
* fix(rippling): use null-safe guard for groupVersion param
* fix(rippling): remove operation field from tool params payload
* fix(rippling): add input validation for action param and empty group update body
* fix(ui): fix kb id extraction logic for resource, sync tags
* Pass knowledge base id back on edit tag
---------
Co-authored-by: Theodore Li <theo@sim.ai>
- Add max-w-[260px] to Tooltip.Content so video previews don't blow out the tooltip size
- Replace cursor-help with cursor-default on info icons in settings
* improvement(tour): fix tour auto-start logic and standardize selectors
* fix(tour): address PR review comments
- Move autoStartAttempted.add() inside timer callback to prevent
blocking auto-start when tour first mounts while disabled
- Memoize setJoyrideRef with useCallback to prevent ref churn
- Remove unused joyrideRef
* feat(home): auth-aware landing page navigation
- Redirect authenticated users from / to /workspace via middleware (?home param bypasses)
- Show "Go to App" instead of "Log in / Get started" in navbar for authenticated users
- Logo links to /?home for authenticated users to stay in marketing context
- Settings "Home Page" button opens /?home
- Handle isPending session state to prevent CTA button flash
* lint
* fix(home): remove stale ?from=nav params in landing nav
* fix(home): preserve ?home param in nav links during session pending state
* lint
* feat: add product tour
* chore: updated modals
* chore: fix the tour
* chore: Tour Updates
* chore: fix review changes
* chore: fix review changes
* chore: fix review changes
* chore: fix review changes
* chore: fix review changes
* minor improvements
* chore(tour): address PR review comments
- Extract shared TourState, TourStateContext, mapPlacement, and TourTooltipAdapter
into tour-shared.tsx, eliminating ~100 lines of duplication between product-tour.tsx
and workflow-tour.tsx
- Fix stale closure in handleStartTour — add isOnWorkflowPage to useCallback deps
so Take a tour dispatches the correct event after navigation
* chore(tour): address remaining PR review comments
- Remove unused logger import and instance in product-tour.tsx
- Remove unused tour-tooltip-fade animation from tailwind config
- Remove unnecessary overflow-hidden wrapper around WorkflowTour
- Add border stroke to arrow SVG in tour-tooltip for visual consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(tour): address second round of PR review comments
- Remove unnecessary 'use client' from workflow layout (children are already client components)
- Fix ref guard timing issue in TourTooltipAdapter that could prevent Joyride from tracking tooltip on subsequent steps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(tour): extract shared Joyride config, fix popover arrow overflow
- Extract duplicated Joyride floaterProps/styles into getSharedJoyrideProps()
in tour-shared.tsx, parameterized by spotlightBorderRadius
- Fix showArrow disabling content scrolling in PopoverContent by wrapping
children in a scrollable div when arrow is visible
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(tour): stop running tour when disabled becomes true
Prevents nav and workflow tours from overlapping. When a user navigates
to a workflow page while the nav tour is running, the disabled flag
now stops the nav tour instead of just suppressing auto-start.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tour): move auto-start flag into timer, fix truncate selector conflict
- Move hasAutoStarted flag inside setTimeout callback so it's only set
when the timer fires, allowing retry if disabled changes during delay
- Add data-popover-scroll attribute to showArrow scroll wrapper and
exclude it from the flex-1 truncate selector to prevent overflow
conflict
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tour): remove duplicate overlay on center-placed tour steps
Joyride's spotlight already renders a full-screen overlay via boxShadow.
The centered TourTooltip was adding its own bg-black/55 overlay on top,
causing double-darkened backgrounds. Removed the redundant overlay div.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move docs link from settings to help dropdown
The Docs link (https://docs.sim.ai) was buried in settings navigation.
Moved it to the Help dropdown in the sidebar for better discoverability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Adithya Krishna <aadithya794@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(table): column drag-and-drop reorder
* fix(table): remove duplicate onDragEnd call from handleDrop
* fix(table): persist columnOrder on rename/delete and defer delete to onSuccess
* fix(table): prevent stale refs during column drag operations
Fix two bugs in column drag-and-drop:
1. Stale columnWidths ref during rename - compute updated widths inline
before passing to updateMetadata
2. Escape-cancelled drag still reorders - update dropTargetColumnNameRef
directly in handleColumnDragLeave to prevent handleColumnDragEnd from
reading stale ref value
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(table): insert column at correct side when anchor is unordered
When the anchor column isn't in columnOrder, add it first then insert
the new column relative to it, so 'right' insertions appear after the
anchor as expected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(home): voice input text persistence bugs
* fix(home): gate setIsListening on startRecognition success
* fix(home): handle startRecognition failure in restartRecognition
* fix(home): reset speech prefix on submit while mic is active
* fix(oauth): decode ID token instead of calling Graph API for Microsoft providers
* fix(oauth): fix type error in getMicrosoftUserInfoFromIdToken parameter
* fix(oauth): address review comments - try-catch JSON.parse, fix email fallback order, guard undefined email
* style(oauth): format email fallback chain to single line
* feat(slack): add conversations.create and conversations.invite tools
* fix(slack): address PR review comments on conversation tools
* feat(slack): wire create/invite conversation tools into Slack block
* lint
* fix(slack): rename channel output to channelInfo to avoid type collision
The block outputs already declare `channel` as type string (channel ID from
send operation). Rename the object output to `channelInfo` to match the
pattern used by get_channel_info and avoid [object Object] rendering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(slack): update output key in docs to match channelInfo rename
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(docs): fix lint errors in auto-generated docs files
Sort imports in icon-mapping.ts and add trailing newline to meta.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
h-0 w-0 overflow-hidden was clipping the iframe, preventing
Turnstile from executing. absolute takes it out of flow without
clipping, fixing both the layout gap and the captcha failure.
* v0
* Fix ppt load
* Fixes
* Fixes
* Fix lint
* Fix wid
* Download image
* Update tools
* Fix lint
* Fix error msg
* Tool fixes
* Reenable subagent stream
* Subagent stream
* Fix edit workflow hydration
* Throw func execute error on error
* Sandbox PPTX generation in subprocess with vm.createContext
AI-generated PptxGenJS code was executed via new Function() in both
the server (full Node.js access) and browser (XSS risk). Replace with
a dedicated Node.js subprocess (pptx-worker.cjs) that runs user code
inside vm.createContext with a null-prototype sandbox — no access to
process, require, Buffer, or any Node.js globals. Process-level
isolation ensures a vm escape cannot reach the main process or DB.
File access is brokered via IPC so the subprocess never touches the
database directly, mirroring the isolated-vm worker pattern. Compilation
happens lazily at serve time (compilePptxIfNeeded) rather than on write,
matching industry practice for source-stored PPTX pipelines.
- Add pptx-worker.cjs: sandboxed subprocess worker
- Add pptx-vm.ts: orchestration, IPC bridge, file brokering
- Add /api/workspaces/[id]/pptx/preview: REST-correct preview endpoint
- Update serve route: compile pptxgenjs source to binary on demand
- Update workspace-file.ts: remove unsafe new Function(), store source only
- Update next.config.ts: include pptxgenjs in outputFileTracingIncludes
- Update trigger.config.ts: add pptx-worker.cjs and pptxgenjs to build
* upgrade deps, file viewer
* Fix auth bypass, SSRF, and wrong size limit comment
- Add 'patch' to workspace_file WRITE_ACTIONS — patch operation was
missing, letting read-only users modify file content
- Add download_to_workspace_file to WRITE_ACTIONS with '*' wildcard —
tool was completely ungated, letting read-only users write workspace files
- Update isActionAllowed to handle '*' (always-write tools) and undefined
action (tools with no operation/action field)
- Block private/internal URLs in download_to_workspace_file to prevent
SSRF against RFC 1918 ranges, loopback, and cloud metadata endpoints
- Fix file-reader.ts image size limit comment and error message (was 20MB,
actual constant is 5MB)
* Fix Buffer not assignable to BodyInit in preview route
Wrap Buffer in Uint8Array for NextResponse body — Buffer is not
directly assignable to BodyInit in strict TypeScript mode.
* Fix SSRF bypass, IPv6 coverage, download size cap, and missing deps
- Validate post-redirect URL to block SSRF via open redirectors
- Expand IPv6 private range blocking: fe80::/10, fc00::/7, ::ffff: mapped
- Add 50 MB download cap (Content-Length pre-check + post-buffer check)
- Add refetchOnWindowFocus: 'always' to useWorkspaceFileBinary
- Add workspaceId to PptxPreview useEffect dependency array
* Replace hand-rolled SSRF guard with secureFetchWithValidation
The previous implementation hand-rolled private-IP detection with regex,
missing edge cases (octal IPs, hex IPs, full IPv6 coverage). The codebase
already has secureFetchWithValidation which uses ipaddr.js, handles DNS
rebinding via IP pinning, validates each redirect target, and enforces a
streaming size cap — removing the need for isPrivateUrl, isPrivateIPv4,
the manual pre/post-redirect checks, and the Content-Length + post-buffer
size checks.
* Fix streaming preview cache ordering and patch ambiguity
- PptxPreview: move streaming content check before cache check so live
AI-generated previews are never blocked by a warm cache from a prior
file view
- workspace_file patch: reject edits where the search string matches
more than one location, preventing silent wrong-location patches
- workspace_file patch: remove redundant Record<string, unknown> cast;
args is already Zod-validated with the correct field types
* Fix subprocess env leak, unbounded preview spawning, and dead code
- pptx-vm: pass minimal env to worker subprocess so it cannot inherit
DB URLs, API keys, or other secrets from the Next.js process on a
vm.createContext escape
- PptxPreview: add AbortController so in-flight preview fetch is
cancelled when the effect re-runs (e.g. next SSE update), preventing
unbounded concurrent subprocesses; add 500ms debounce on streaming
renders to reduce subprocess churn during rapid AI generation
- file-reader: remove dead code — the `if (!isReadableType)` guard on
line 110 was always true (all readable types returned earlier at
line 76), making the subsequent `return null` unreachable
* Wire abort signal through to subprocess and correct security comment
- generatePptxFromCode now accepts an optional AbortSignal; when the
signal fires (e.g. client disconnects mid-stream), done() is called
which clears timers and kills the subprocess immediately rather than
waiting for the 60s timeout
- preview route passes req.signal so client-side AbortController.abort()
(from the streaming debounce cleanup) propagates all the way to the
worker process
- Correct misleading comment in pptx-worker.cjs and pptx-vm.ts:
vm.createContext is NOT a sandbox when non-primitives are in scope;
the real security boundary is the subprocess + minimal env
* Remove implementation-specific comments from pptx worker files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix pre-aborted signal, pptx-worker tracing, and binary fetch cache
* Lazy worker path resolution, code size cap, unused param prefix
* Add cache-busting timestamp to binary file fetch
* Fix PPTX cache key stability and attribute-order-independent dimension parsing
* ran lint
---------
Co-authored-by: waleed <walif6@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(sidebar): add right-click context menu to settings nav item
* fix(sidebar): revert settings active highlight
* fix(sidebar): allow modifier-key clicks to open in new tab, make InfisicalIcon black
* update icons
* fix(kb): store filename with .txt extension for connector documents
Connector documents (e.g. Fireflies transcripts) have titles without
file extensions. The DB stored the raw title as filename, but the
processing pipeline extracts file extension from filename to determine
the parser. On retry/reprocess, this caused "Unsupported file type"
errors with the document title treated as the extension.
Now stores processingFilename (which includes .txt) instead of the
raw title, consistent with what was actually uploaded to storage.
* fix(kb): guard stuck document retry against filenames without extension
Existing DB rows may have connector document filenames stored without
a .txt extension (raw meeting titles). The stuck-doc retry path reads
filename from DB and passes it to parseHttpFile, which extracts the
extension via split('.'). When there's no dot, the entire title
becomes the "extension", causing "Unsupported file type" errors.
Falls back to 'document.txt' when the stored filename has no extension.
* fix(kb): fix race condition in stuck document retry during sync
The stuck document retry at the end of each sync was querying for all
documents with processingStatus 'pending' or 'failed'. This included
documents added in the CURRENT sync that were still processing
asynchronously, causing duplicate concurrent processing attempts.
The race between the original (correct) processing and the retry
(which reads the raw title from DB as filename) produced
nondeterministic failures — some documents would succeed while
others would fail with "Unsupported file type: <meeting title>".
Fixes:
- Filter stuck doc query by uploadedAt < syncStartedAt to exclude
documents from the current sync
- Pass mimeType through to parseHttpFile so text/plain content can
be decoded directly without requiring a file extension in the
filename (matches parseDataURI which already handles this)
- Restore filename as extDoc.title in DB (the display name, not
the processing filename)
* fix(kb): fix race condition in stuck document retry during sync
The stuck document retry at the end of each sync was querying for all
documents with processingStatus 'pending' or 'failed'. This included
documents added in the CURRENT sync that were still processing
asynchronously, causing duplicate concurrent processing attempts.
The race between the original (correct) processing and the retry
(which reads the raw title from DB as filename) produced
nondeterministic failures — some documents would succeed while
others would fail with "Unsupported file type: <meeting title>".
Fixes:
- Filter stuck doc query by uploadedAt < syncStartedAt to exclude
documents from the current sync
- Pass mimeType through to parseHttpFile and use existing
getExtensionFromMimeType utility as fallback when filename has
no extension (e.g. Fireflies meeting titles)
- Apply same mimeType fallback in parseDataURI for consistency
* lint
* fix(kb): handle empty extension edge case in parseDataURI
When filename ends with a dot (e.g. "file."), split('.').pop() returns
an empty string. Fall through to mimeType-based extension lookup
instead of passing empty string to parseBuffer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(turnstile): conditionally added CF turnstile to signup
* feat(auth): add execute-on-submit Turnstile, conditional harmony, and feature flag
- Switch Turnstile to execution: 'execute' mode so challenge runs on
form submit (fresh token every time, no expiry issues)
- Make emailHarmony conditional via SIGNUP_EMAIL_VALIDATION_ENABLED
feature flag so self-hosted users can opt out
- Add isSignupEmailValidationEnabled to feature-flags.ts following
existing pattern
- Add better-auth-harmony to Next.js transpilePackages (required for
validator.js ESM compatibility)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(validation): remove dead validateEmail and checkMXRecord
Server-side disposable email blocking is now handled by
better-auth-harmony. The async validateEmail (with MX check) had no
remaining callers. Only quickValidateEmail remains for client-side
form feedback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(auth): add 15s timeout to Turnstile captcha promise
Prevents form from hanging indefinitely if Turnstile never fires
onSuccess/onError (e.g. script fails to load, network drop).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(helm): add Turnstile and harmony env vars to values.yaml
Adds TURNSTILE_SECRET_KEY, NEXT_PUBLIC_TURNSTILE_SITE_KEY, and
SIGNUP_EMAIL_VALIDATION_ENABLED to the helm chart so self-hosted
deployments can configure captcha and disposable email blocking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(auth): reject captcha promise on token expiry
onExpire now rejects the pending promise so the form doesn't hang
if the Turnstile token expires mid-challenge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(login): replace useEffect keydown listener with form onSubmit
The forgot-password modal used a global window keydown listener in a
useEffect to handle Enter key — a "you might not need an effect"
anti-pattern with a stale closure risk. Replaced with a native
<form onSubmit> wrapper which handles Enter natively, eliminating
the useEffect, the global listener, and the stale closure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(auth): clear dangling timeout after captcha promise settles
Use .finally(() => clearTimeout(timeoutId)) to clean up the 15s
timeout timer when the captcha resolves before the deadline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(auth): use getResponsePromise() for Turnstile token retrieval
Replace the manual Promise + refs + timeout pattern with the
documented getResponsePromise(timeout) API from @marsidev/react-turnstile.
This eliminates captchaToken state, captchaResolveRef, captchaRejectRef,
and all callback wiring on the Turnstile component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(auth): show captcha errors as form-level message, not password error
Captcha failures were misleadingly displayed under the password field.
Added a dedicated formError state that renders above the submit button,
making it clear the issue is with verification, not the password.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(templates): disable templates page and related UI
* chore(templates): remove unused imports from disabled template code
* fix(config): restore noNestedComponentDefinitions rule in biome config
* chore(templates): comment out remaining dead template code
Comment out handleTemplateFormSubmit, handleTemplateDelete,
TemplateStatusBadge component, and TemplateProfile dynamic import
that were left over after disabling the templates feature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(templates): clean up dead code from review feedback
- Remove unused usePathname/pathnameRef in use-workspace-management.ts
- Comment out stale 'template' from TabView union type
- Remove unused params from TemplateLayoutProps interface
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(preview): show actual nested workflow name in log snapshots
* fix(preview): ensure metadata.name in non-deployed child workflow path
* style(preview): fix line formatting
* fix(landing): update broken links, change colors
* update integration pages
* update icons
* link to tag
* fix(landing): resolve build errors and address PR review comments
- Extract useEffect redirect into ExternalRedirect client component to fix
fs/promises bundling error in privacy/terms server pages
- Fix InfisicalIcon fill='black' → fill='currentColor' for theme compatibility
- Add target="_blank" + rel="noopener noreferrer" to enterprise Typeform link
- Install @types/micromatch to fix missing type declarations build error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(icons): fix InfisicalIcon fill='black' → fill='currentColor' in docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* remove hardcoded ff
* fix(generate-docs): fix tool description extraction for two-step and name-mismatch patterns
Replace the fragile first-id/first-description heuristic with a per-id
window search: for each id: 'tool_id' match, scan the next 600 chars
(stopping before any params: block) for description: and name: fields.
This correctly handles the two-step pattern used by Intercom and others
where the ToolConfig export comes after a separate base object whose
params: would have cut off the old approach.
Add an exact-name fallback that checks tools.access for a tool whose
name matches the operation label — handles cases where block op IDs are
short aliases (e.g. Slack 'send') while the tool ID is more descriptive
('slack_message') but the tool name 'Slack Message' still differs.
Remove the word-overlap scoring fallback which was producing incorrect
descriptions (Intercom all saying 'Intercom API access token', Reddit
Save/Unsave inverted, etc.).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(okta): add complete Okta identity management integration
Add 18 Okta Management API tools covering user lifecycle (list, get,
create, update, activate, deactivate, suspend, unsuspend, reset password,
delete) and group management (list, get, create, update, delete, add/remove
members, list members). Includes block with conditional UI, icon, registry
entries, and generated docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(okta): add manual description section to generated docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(okta): address PR review — SSRF prevention, safe response parsing, consistent sendEmail
- Add validateOktaDomain() to prevent SSRF via user-supplied domain param
- Fix 9 tools to check response.ok before calling response.json()
- Make sendEmail query param explicit in deactivate_user and delete_user
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(okta): only forward boolean switches when explicitly true
Switch subBlocks default to OFF (false), which was being forwarded to
tools and overriding their default-true behavior for sendEmail and
activate params. Now only forward these when explicitly toggled ON.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(okta): use nullish coalescing for boolean switch defaults
Block now forwards sendEmail/activate values as-is (including false).
Tools use ?? operator so: explicit true/false from switches are respected,
undefined (programmatic calls) still defaults to true.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(okta): prevent silent data loss in update operations
- update_group: always include description in PUT body (defaults to '')
since PUT replaces the full profile object
- update_user: use !== undefined checks so empty strings can clear fields
via Okta's POST partial update
- block: allow empty strings through passthrough loop and use !== undefined
for groupDescription mapping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(okta): move validateOktaDomain to centralized input-validation
- Moved validateOktaDomain from tools/okta/types.ts to
lib/core/security/input-validation.ts alongside other validation utils
- Added .trim() to handle copy-paste whitespace in domain input
- Updated all 18 tool files to import from the new location
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(microsoft-ad): add Azure AD (Entra ID) integration
Add complete Azure AD integration with 13 tools for managing users
and groups via Microsoft Graph API v1.0. Includes OAuth config with
PKCE, block definition with conditional subBlocks, and generated docs.
Tools: list/get/create/update/delete users, list/get/create/update/delete
groups, list/add/remove group members.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(microsoft-ad): add $search/$filter guard, $count=true, and memberId validation
- Prevent using $search and $filter together (Graph API rejects this)
- Add $count=true when $search is used (required with ConsistencyLevel: eventual)
- Validate and trim memberId in add_group_member body before use
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(microsoft-ad): fix docsLink underscore and accountEnabled update safety
- Change docsLink from microsoft-ad to microsoft_ad to match docs routing
- Split accountEnabled dropdown into separate create/update subBlocks
- Update operation shows "No Change" default (empty string) to prevent
silently re-enabling disabled accounts when updating other fields
- Create operation keeps "Yes" default as before
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(microsoft-ad): prevent visibility from always being sent on group update
Split visibility dropdown into separate create/update subBlocks with
"No Change" default for update_group, preventing silent overwrite of
group visibility when updating other fields like description.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(microsoft-ad): prevent empty values leaking into PATCH requests
- Use operation-aware checks for accountEnabled and visibility in block
params to prevent create defaults bleeding into update operations
- Change tool body guards from `!== undefined` to truthy checks so
empty-string inputs from unfilled subBlocks are omitted from PATCH
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(react): replace unnecessary useEffect patterns with better React primitives
* fix(react): revert unsafe render-time side effects to useEffect
* fix(react): restore useEffect for modals, scroll, and env sync
- Modals (create-workspace, rename-document, edit-knowledge-base): restore
useEffect watching `open` prop for form reset on programmatic open, since
Radix onOpenChange doesn't fire for parent-driven prop changes
- Popover: add useEffect watching `open` for programmatic close reset
- Chat scroll: restore useEffect watching `isStreamingResponse` so the 1s
suppression timer starts when streaming begins, not before the fetch
- Credentials manager: revert render-time pattern to useEffect for initial
sync from cached React Query data (useRef captures initial value, making
the !== check always false on mount)
* fix(react): restore useEffect for help/invite modals, combobox index reset
- Help modal: restore useEffect watching `open` for form reset on
programmatic open (same Radix onOpenChange pattern as other modals)
- Invite modal: restore useEffect watching `open` to clear error on
programmatic open
- Combobox: restore useEffect to reset highlightedIndex when filtered
options shrink (prevents stale index from reappearing when options grow)
- Remove no-op handleOpenChange wrappers in rename-document and
edit-knowledge-base modals (now pure pass-throughs after useEffect fix)
* fix(context-menu): use requestAnimationFrame for ColorGrid focus, remove no-op wrapper in create-workspace-modal
- ColorGrid: replaced setTimeout with requestAnimationFrame for initial
focus to wait for submenu paint completion
- create-workspace-modal: removed handleOpenChange pass-through wrapper,
use onOpenChange directly
* fix(files): restore filesRef pattern to prevent preview mode reset on refetch
The useEffect that sets previewMode should only run when selectedFileId
changes, not when files array reference changes from React Query refetch.
Restores the filesRef pattern to read latest files without triggering
the effect — prevents overriding user's manual mode selection.
* fix(add-documents-modal, combobox): restore useEffect for modal reset, fix combobox dep array
- add-documents-modal: handleOpenChange(true) is dead code in Radix
controlled mode — restored useEffect watching open for reset-on-open
- combobox: depend on filteredOptions array (not .length) so highlight
resets when items change even with same count
Providers like Box don't return a scope field in their token response,
leaving the account.scope column empty. The credentials API now falls
back to the provider's configured scopes when the stored scope is
empty, preventing false "Additional permissions required" banners.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(box): add Box and Box Sign integrations
Add complete Box integration with file management (upload, download, get info, list folders, create/delete folders, copy, search, update metadata) and Box Sign e-signature support (create/get/list/cancel/resend sign requests). Includes OAuth provider setup, internal upload API route following the Dropbox pattern, block configurations, icon, and generated docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): address PR review comments
- Fix docsLink for Box Sign: use underscore (box_sign) to match docs URL
- Move normalizeFileInput from tool() to params() in Box block config to match Dropbox pattern
- Throw error on invalid additionalSigners JSON instead of silently dropping signers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): remove unsupported reason param from cancel sign request
The Box Sign cancel endpoint (POST /sign_requests/{id}/cancel) does not
accept a request body per the API specification. Remove the misleading
reason parameter from the tool, types, block config, and docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): use canonical param ID for file normalization in params()
The params function must reference canonical IDs (params.file), not raw
subBlock IDs (uploadFile/fileRef) which are deleted after canonical
transformation. Matches the Dropbox block pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): use generic output descriptions for shared file properties
Rename "Uploaded file ID/name" to "File ID/name" in
UPLOAD_FILE_OUTPUT_PROPERTIES since the constant is shared by both
upload and copy operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): rename items output to entries for list_folder_items
Rename the output field from "items" to "entries" to match Box API
naming and avoid collision with JSON schema "items" keyword that
prevented the docs generator from rendering the nested array structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): filter empty file IDs from sourceFileIds input
Add .filter(Boolean) after splitting sourceFileIds to prevent empty
strings from trailing/double commas being sent as invalid file IDs
to the Box Sign API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(box): merge Box Sign into single Box block
Combine Box and Box Sign into one unified block with all 15 operations
accessible via a single dropdown, removing the separate box_sign block.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): filter empty strings from tags array in update_file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(docs): apply lint formatting to icon-mapping and meta.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(box): format chained method calls per linter rules
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(box,docusign): set block bgColor to white and regenerate docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(docs): apply lint formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): populate OAuth scopes for Box since token response omits them
Box's OAuth2 token endpoint does not return a scope field in the
response, so Better Auth stores nothing in the DB. This causes the
credential selector to always show "Additional permissions required".
Fix by populating the scope from the requested scopes in the
account.create.before hook.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(box): add sign_requests.readwrite scope for Box Sign operations
Box Sign API requires the sign_requests.readwrite scope in addition
to root_readwrite. Without it, sign requests fail with "The request
requires higher privileges than provided by the access token."
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update docs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ashby): add 15 new tools and fix existing tool accuracy
* fix(ashby): fix response field mappings for changeStage and createNote
* fix(ashby): fix websiteUrl field name in candidate.update request
* fix(ashby): revert body field names to candidateId and jobId for info endpoints
* fix(ashby): add subblock ID migrations for removed emailType and phoneType
* fix(ashby): map removed emailType/phoneType to dummy keys to avoid data corruption
* feat(docusign): add DocuSign e-signature integration
* fix(docusign): add base_uri null check and move file normalization to params
* fix(docusign): use canonical param documentFile instead of raw subBlock IDs
* fix(docusign): validate document file is present before sending envelope
* fix(docusign): rename tool files from kebab-case to snake_case for docs generation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): infer MIME type from file extension in create/upsert tools
Both create_document and upsert_document forced .txt extension and
text/plain MIME type regardless of the document name. Now the tools
infer the correct MIME type from the file extension (html, md, csv,
json, yaml, xml) and only default to .txt when no extension is given.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(knowledge): reuse existing getMimeTypeFromExtension from uploads
Replace duplicate EXTENSION_MIME_MAP and getMimeTypeFromExtension with
the existing, more comprehensive version from lib/uploads/utils/file-utils.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): fix btoa stack overflow and duplicate encoding in create_document
Same fixes as upsert_document: use loop-based String.fromCharCode
instead of spread, consolidate duplicate TextEncoder calls, and
check byte length instead of character length for 1MB limit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): allowlist text-compatible MIME types in inferDocumentFileInfo
Use an explicit allowlist instead of only checking for octet-stream,
preventing binary MIME types (image/jpeg, audio/mpeg, etc.) from
leaking through when a user names a document with a binary extension.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): remove pdf/rtf from allowlist, normalize unrecognized extensions
- Remove application/pdf and application/rtf from TEXT_COMPATIBLE_MIME_TYPES
since these tools pass plain text content, not binary
- Normalize unrecognized extensions (e.g. report.v2) to .txt instead of
preserving the original extension with text/plain MIME type
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): handle dotfile names to avoid empty base in filename
Dotfiles like .env would produce an empty base, resulting in '.txt'.
Now falls back to the original name so .env becomes .env.txt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore(blog): add v0.6 blog post and email broadcast scaffolding
* mothership blog
* turned on mothership blog
* small change
---------
Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
* feat(home): resizable chat/resource panel divider
* fix(home): address PR review comments
- Remove aria-hidden from resize handle outer div so separator role is visible to AT
- Add viewport-resize re-clamping in useMothershipResize to prevent panel exceeding max % after browser window narrows
- Change default MothershipView width from 60% to 50%
* refactor(home): eradicate useEffect anti-patterns per you-might-not-need-an-effect
- use-chat: remove messageQueue→ref sync Effect; inline assignment like other refs
- use-chat: replace activeResourceId selection Effect with useMemo (derived value, avoids
extra re-render cycle; activeResourceIdRef now tracks effective value for API payloads)
- use-chat: replace 3x useLayoutEffect ref-sync (processSSEStream, finalize, sendMessage)
with direct render-phase assignment — consistent with existing resourcesRef pattern
- user-input: fold onEditValueConsumed callback into existing render-phase guard; remove Effect
- home: move isResourceAnimatingIn 400ms timer into expandResource/handleResourceEvent event
handlers where setIsResourceAnimatingIn(true) is called; remove reactive Effect watcher
* fix(home): revert default width to 60%, reduce max resize to 63%
* improvement(react): replace useEffect anti-patterns with better React primitives
* improvement(react): replace useEffect anti-patterns with better React primitives
* improvement(home): use pointer events for resize handle (touch + mouse support)
* fix(autosave): store idle-reset timer ref to prevent status corruption on rapid saves
* fix(home): move onEditValueConsumed call out of render phase into useEffect
* fix(home): add pointercancel handler; fix(settings): sync name on profile refetch
* fix(home): restore cleanupRef assignment dropped during AbortController refactor
* improvement(landing): added enterprise section
* make components interactive
* added more things to pricing sheet
* remove debug log
* fix(landing): remove dead DotGrid component and fix enterprise CTA to use Link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(knowledge): add upsert document operation to Knowledge block
Add a "Create or Update" (upsert) document capability that finds an
existing document by ID or filename, deletes it if found, then creates
a new document and queues re-processing. Includes new tool, API route,
block wiring, and typed interfaces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): address review comments on upsert document
- Reorder create-then-delete to prevent data loss if creation fails
- Move Zod validation before workflow authorization for validated input
- Fix btoa stack overflow for large content using loop-based encoding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): guard against empty createDocumentRecords result
Add safety check before accessing firstDocument to prevent TypeError
and data loss if createDocumentRecords unexpectedly returns empty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): prevent documentId fallthrough and use byte-count limit
- Use if/else so filename lookup only runs when no documentId is provided,
preventing stale IDs from silently replacing unrelated documents
- Check utf8 byte length instead of character count for 1MB size limit,
correctly handling multi-byte characters (CJK, emoji)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): rollback on delete failure, deduplicate sub-block IDs
- Add compensating rollback: if deleteDocument throws after create
succeeds, clean up the new record to prevent orphaned pending docs
- Merge duplicate name/content sub-blocks into single entries with
array conditions, matching the documentTags pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* lint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(admin): add user search by email and ID, remove table border
- Replace Load Users button with a live search input; query fires on any input
- Email search uses listUsers with contains operator
- User ID search (UUID format) uses admin.getUser directly for exact lookup
- Remove outer border on user table that rendered white in dark mode
- Reset pagination to page 0 on new search
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(admin): replace live search with explicit search button
- Split searchInput (controlled input) from searchQuery (committed value)
so the hook only fires on Search click or Enter, not every keystroke
- Gate table render on searchQuery.length > 0 to prevent stale results
showing after input is cleared
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(logs): persist execution diagnostics markers
Store last-started and last-completed block markers with finalization metadata so later read surfaces can explain how a run ended without reconstructing executor state.
* fix(executor): preserve durable diagnostics ordering
Await only the persistence needed to keep diagnostics durable before terminal completion while keeping callback failures from changing execution behavior.
* fix(logs): preserve fallback diagnostics semantics
Keep successful fallback output and accumulated cost intact while tightening progress-write draining and deduplicating trace span counting for diagnostics helpers.
* fix(api): restore async execute route test mock
Add the missing AuthType export to the hybrid auth mock so the async execution route test exercises the 202 queueing path instead of crashing with a 500 in CI.
* fix(executor): align async block error handling
* fix(logs): tighten marker ordering scope
Allow same-millisecond marker writes to replace prior markers and drop the unused diagnostics read helper so this PR stays focused on persistence rather than unread foundation code.
* fix(logs): remove unused finalization type guard
Drop the unused helper so this PR only ships the persistence-side status types it actually uses.
* fix(executor): await subflow diagnostics callbacks
Ensure empty-subflow and subflow-error lifecycle callbacks participate in progress-write draining before terminal finalization while still swallowing callback failures.
---------
Co-authored-by: test <test@example.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
* feat(csp): allow chat UI to be embedded in iframes
Mirror the existing form embed CSP pattern for chat pages: add
getChatEmbedCSPPolicy() with frame-ancestors *, configure /chat/:path*
headers in next.config.ts without X-Frame-Options, and early-return in
proxy.ts so chat routes skip the strict runtime CSP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(csp): extract shared getEmbedCSPPolicy helper
Deduplicate getChatEmbedCSPPolicy and getFormEmbedCSPPolicy into a
shared private helper to prevent future divergence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(auth): migrate to better-auth admin plugin
* feat(settings): add unified Admin tab with user management
Consolidate superuser features into a single Admin settings tab:
- Super admin mode toggle (moved from General)
- Workflow import (moved from Debug)
- User management via better-auth admin (list, set role, ban/unban)
Replace Debug tab with Admin tab gated by requiresAdminRole.
Add React Query hooks for admin user operations.
* fix(db): backfill existing super users to admin role in migration
Add UPDATE statement to promote is_super_user=true rows to role='admin'
before dropping the is_super_user column, preventing silent demotion.
* fix(admin): resolve type errors in admin tab
- Fix cn import path to @/lib/core/utils/cn
- Use valid Badge variants (blue/gray/red/green instead of secondary/destructive)
- Type setRole param as 'user' | 'admin' union
* improvement(auth): remove /api/user/super-user route, use session role
Include user.role in customSession so it's available client-side.
Replace all useSuperUserStatus() calls with session.user.role === 'admin'.
Delete the now-redundant /api/user/super-user endpoint.
* chore(auth): remove redundant role override in customSession
The admin plugin already includes role on the user object.
No need to manually spread it in customSession.
* improvement(queries): clean up admin-users hooks per React Query best practices
- Remove unsafe unknown/Record casting, use better-auth typed response
- Add placeholderData: keepPreviousData for paginated variable-key query
- Remove nullable types where defaults are always applied
* fix(admin): address review feedback on admin tab
- Fix superUserModeEnabled default to false (matches sidebar behavior)
- Reset banReason when switching ban target to prevent state bleed
- Guard admin section render with session role check for direct URL access
* fix(settings): align superUserModeEnabled default to false everywhere
Three places defaulted to true while admin tab and sidebar used false.
Align all to false so new admins see consistent behavior.
* fix(admin): fix stale pendingUserId, add isPending guard and error feedback
- Only read mutation.variables when mutation isPending (prevents stale ID)
- Add isPending guard to super user mode toggle (prevents concurrent mutations)
- Show inline error message when setRole/ban/unban mutations fail
* fix(admin): concurrent pending users Set, session loading guard, domain blocking
- Replace pendingUserId scalar with pendingUserIds Set (useMemo) so concurrent
mutations across different users each disable their own row correctly
- Add sessionLoading guard to admin section redirect to prevent flash on direct
/settings/admin navigation before session resolves
- Add BLOCKED_SIGNUP_DOMAINS env var and before-hook for email domain denylist,
parsed once at module init as a Set for O(1) per-request lookups
- Add trailing newline to migration file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(admin): close OAuth domain bypass, fix stale errors, deduplicate icon
- Add databaseHooks.user.create.before to enforce BLOCKED_SIGNUP_DOMAINS at
the model level, covering all signup vectors (email, OAuth, social) not just
/sign-up paths
- Call .reset() on each mutation before firing to clear stale error state from
previous operations
- Change Admin nav icon from ShieldCheck to Lock to avoid duplicate with
Access Control tab
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* improvement(mothership): tool display titles, html sanitization, and ui fixes
- Use TOOL_UI_METADATA as fallback for tool display titles (fast_edit shows "Editing workflow" instead of "Fast Edit")
- Harden HTML-to-text extraction with replaceUntilStable to prevent nested tag injection
- Decode HTML entities in a single pass to avoid double-unescaping
- Fix Google Drive/Docs query escaping for backslashes in folder IDs
- Replace regex with indexOf for email sender/display name parsing
- Update embedded workflow run tooltip to "Run workflow"
* fix(security): decode entities before tag stripping and cap loop iterations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Include notification view in embedded workflow view
* fix(ui) fix workflow not showing up when mothership calls run
* Wire up fix in mothership
* Refresh events after workflow run
* Fix so run workflow switches tabs as well
---------
Co-authored-by: Theodore Li <theo@sim.ai>
Tools with requiresConfirmation (e.g. user_table) blocked indefinitely
in mothership because the frontend has no approval UI. Added a new
promptForToolApproval orchestrator option so mothership auto-executes
these tools while copilot continues to prompt for user approval.
Made-with: Cursor
Co-authored-by: Theodore Li <theo@sim.ai>
* Fix deploy subagent
* fix(stream) handle task switching (#3609)
* Fix task switching causing stream to abort
* Process all task streams all the time
* Process task streams that are in the background
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* Always return isDeployed for undeploy chat
* Fix lint
---------
Co-authored-by: Siddharth Ganesan <siddharthganesan@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(deploy): consolidate deployment detection into single source of truth
* fix(deploy): address PR review feedback
- Remove redundant isLoading check (subset of isPending in RQ v5)
- Make deployedState prop nullable to avoid non-null assertion
- Destructure mutateAsync to eliminate ESLint suppression
- Guard debounced invalidation against stale workflowId on switch
* fix(deploy): clean up self-explanatory comments and fix unstable mutation dep
- Remove self-explanatory comments in deploy.tsx, tool-input.tsx, use-child-workflow.ts
- Tighten non-obvious comments in use-change-detection.ts
- Destructure mutate/isPending in WorkflowToolDeployBadge to avoid unstable
mutation object in useCallback deps (TanStack Query no-unstable-deps pattern)
* lint
* fix(deploy): skip expensive state merge when deployedState is null
Avoid running mergeSubblockStateWithValues on every render for
non-deployed workflows where changeDetected always returns false.
* fix(deploy): add missing workflow table import in deploy route
Pre-existing type error — workflow table was used but not imported.
* fix(deploy): forward AbortSignal in fetchDeployedWorkflowState
Match the pattern used by all other fetch helpers in the file so
in-flight requests are cancelled on component unmount or query re-trigger.
* perf(deploy): parallelize all DB queries in checkNeedsRedeployment
All three queries (active version, normalized data, workflow variables)
now run concurrently via Promise.all, saving one DB round trip on the
common path.
* fix(deploy): use sequential-then-parallel pattern in checkNeedsRedeployment
* fix(deploy): use client-side comparison for editor header, remove server polling
The lastSaved-based server polling was triggering API calls on every
local store mutation (before socket persistence), wasting requests and
checking stale DB state. Revert the editor header to pure client-side
hasWorkflowChanged comparison — zero network during editing, instant
badge updates. Child workflow badges still use server-side
useDeploymentInfo (they don't have Zustand state).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(deploy): suppress transient Update flash during deployed state refetch
Guard change detection on isFetching (not just isLoading) so the
comparison is suppressed during background refetches after mutations,
preventing a brief Update→Live badge flicker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(connectors): audit and harden all 30 knowledge base connectors
* fix(oauth): update Notion test to match Basic Auth + JSON body config
* fix(connectors): address PR review comments for hubspot, jira, salesforce
- HubSpot: revert to Search API (POST /search) to restore lastmodifieddate DESCENDING sorting
- Salesforce: restore ArticleBody field and add it to HTML_FIELDS for proper stripping
- Jira: add zero-remaining guard to prevent requesting 0 maxResults
* fix(salesforce): revert ArticleBody — not a standard KnowledgeArticleVersion field
ArticleBody is not a standard field on KnowledgeArticleVersion per Salesforce
API docs. Article body content lives in custom fields on org-specific __kav
objects. Including ArticleBody in the SOQL query would cause runtime errors.
* fix(connectors): address second round of PR review comments
- OneDrive: use Buffer.subarray for byte-accurate truncation instead of
character-count slice
- Reddit: deduplicate comment extraction — fetchPostComments now calls
extractComments instead of duplicating the logic
- Webflow: replace crude value.includes('<') with regex /<[a-z][^>]*>/i
to avoid false positives on plain text containing '<'
- Jira: add response.ok check in getJiraCloudId before parsing JSON to
surface real HTTP errors instead of misleading "No Jira resources found"
* fix(jira,outlook): replace raw fetch in downloadJiraAttachments, fix Outlook URL encoding
- Jira: replace bare fetch() with fetchWithRetry in downloadJiraAttachments
for retry logic on transient errors and rate limits
- Outlook: use URLSearchParams in validateConfig $search URL construction
to match buildInitialUrl and produce RFC 3986 compliant encoding
* fix(tools): support stringified HTTP request tables
Accept stored header and query tables after they are reloaded from UI JSON so HTTP requests keep their query strings and URL-encoded body handling intact.
* test: mock AuthType in async execute route
* test(tools): cover invalid stringified HTTP inputs
---------
Co-authored-by: test <test@example.com>
* fix(cancel): report cancellation durability truthfully
Return explicit durability results for execution cancellation so success only reflects persisted cancellation state instead of best-effort Redis availability.
* fix: hoist cancellation test mocks
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sim): harden execution cancel durability
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sim): fallback manual cancel without redis
Abort active manual SSE executions locally when Redis cannot durably record the cancellation marker so the run still finalizes as cancelled instead of completing normally.
* test: mock AuthType in async execute route
Keep the rebased async execute route test aligned with the current hybrid auth module exports so it exercises the queueing path instead of failing at import time.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: test <test@example.com>
* fix(ashby): add secretToken to webhook creation and fix trigger UX
* fix(toast): restore useMemo for context value to prevent unnecessary re-renders
* fix(notifications): track exit animation timeout so pauseAll can cancel it
* fix(notifications): use isPausedRef to guard exit timeout instead of synthetic timer keys
* fix(notifications): clear pending timers when notification stack empties
* improvement(mothership): message queueing for home chat
* fix(mothership): address PR review — move FileAttachmentForApi to types, defer onEditValueConsumed to effect, await sendMessage in sendNow
* fix(mothership): replace updater side-effect with useEffect ref sync, move sendMessageRef to useLayoutEffect
* fix(mothership): clear message queue on chat switch while sending
* fix(mothership): remove stale isSending from handleKeyDown deps
* fix(mothership): guard sendNow against double-click duplicate sends
* fix(mothership): simplify queue callbacks — drop redundant deps and guard ref
- Remove `setMessageQueue` from useCallback deps (stable setter, never changes)
- Replace `sendNowProcessingRef` double-click guard with eager `messageQueueRef` update
- Simplify `editQueuedMessage` with same eager-ref pattern for consistency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(mothership): clear edit value on nav, stop queue drain on send failure
- Reset editingInputValue when chatId changes so stale edit text
doesn't leak into the next chat
- Pass error flag to finalize so queue is cleared (not drained) when
sendMessage fails — prevents cascading failures on auth expiry or
rate limiting from silently consuming every queued message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(mothership): eagerly update messageQueueRef in removeFromQueue
Match the pattern used by sendNow and editQueuedMessage — update the
ref synchronously so finalize's microtask cannot read a stale queue
and drain a message the user just removed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(mothership): mark onSendNow as explicit fire-and-forget
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(connectors): align connector scopes with oauth config and fix kb modal UX
* fix(connectors): restore onCheckedChange for keyboard accessibility
* feat(connectors): add dynamic selectors to knowledge base connector config
Replace manual ID text inputs with dynamic selector dropdowns that fetch
options from the existing selector registry. Users can toggle between
selector and manual input via canonical pairs (basic/advanced mode).
Adds selector support to 12 connectors: Airtable (cascading base→table),
Slack, Gmail, Google Calendar, Linear (cascading team→project), Jira,
Confluence, MS Teams (cascading team→channel), Notion, Asana, Webflow,
and Outlook. Dependency clearing propagates across canonical siblings to
prevent stale cross-mode data on submit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* updated animated blocks UI
* fix(connectors): clear canonical siblings of dependents and resolve active mode values
Fixes three issues from PR review:
- Dependency clearing now includes canonical siblings of dependent fields
(e.g., changing base clears both tableSelector AND tableIdOrName)
- Selector context and depsResolved now resolve dependency values through
the active canonical mode, not just the raw depFieldId
- Tooltip text changed from "Switch to manual ID" to "Switch to manual input"
to correctly describe dropdown fallbacks (e.g., Outlook folder)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: linter class ordering fixes and docs link update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(connectors): reset apiKeyFocused on connector re-selection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix lint
* improvement(sidebar): loading
* fix(sidebar): use client-generated UUIDs for stable optimistic updates (#3439)
* fix(sidebar): use client-generated UUIDs for stable optimistic updates
* fix(folders): use zod schema validation for folder create API
Replace inline UUID regex with zod schema validation for consistency
with other API routes. Update test expectations accordingly.
* fix(sidebar): add client UUID to single workflow duplicate hook
The useDuplicateWorkflow hook was missing newId: crypto.randomUUID(),
causing the same temp-ID-swap issue for single workflow duplication
from the context menu.
* fix(folders): avoid unnecessary Set re-creation in replaceOptimisticEntry
Only create new expandedFolders/selectedFolders Sets when tempId
differs from data.id. In the common happy path (client-generated UUIDs),
this avoids unnecessary Zustand state reference changes and re-renders.
* Mothership block logs
* Fix mothership block logs
* improvement(knowledge): make connector-synced document chunks readonly (#3440)
* improvement(knowledge): make connector-synced document chunks readonly
* fix(knowledge): enforce connector chunk readonly on server side
* fix(knowledge): disable toggle and delete actions for connector-synced chunks
* Job exeuction logs
* Job logs
* fix(connectors): remove unverifiable requiredScopes for Linear connector
* fix(connectors): remove legacy requiredScopes from Jira and Confluence connectors
Jira and Confluence OAuth tokens don't return legacy scope names like
read:jira-work or read:confluence-content.all, causing the 'Update access'
banner to always appear. Set requiredScopes to empty array like Linear.
* feat(tasks): add rename to task context menu (#3442)
* Revert "fix(connectors): remove legacy requiredScopes from Jira and Confluence connectors"
This reverts commit a0be3ff414.
* fix(connectors): restore Linear connector requiredScopes
Linear OAuth does return scopes in the token response. The previous
fix of emptying requiredScopes was based on an incorrect assumption.
Restoring requiredScopes: ['read'] as it should work correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): pass workspaceId to useOAuthCredentials in connector card
The ConnectorCard was calling useOAuthCredentials(providerId) without
a workspaceId, causing the credentials API to return an empty array.
This meant the credential lookup always failed, getMissingRequiredScopes
received undefined, and the "Update access" banner always appeared.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix oauth link callback from mothership task
* feat(connectors): add Fireflies connector and API key auth support (#3448)
* feat(connectors): add Fireflies connector and API key auth support
Extend the connector system to support both OAuth and API key authentication
via a discriminated union (`ConnectorAuthConfig`). Add Fireflies as the first
API key connector, syncing meeting transcripts via the Fireflies GraphQL API.
Schema changes:
- Make `credentialId` nullable (null for API key connectors)
- Add `encryptedApiKey` column (AES-256-GCM encrypted, null for OAuth)
This eliminates the `'_apikey_'` sentinel and inline `sourceConfig._encryptedApiKey`
patterns, giving each auth mode its own clean column.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(fireflies): allow 0 for maxTranscripts (means unlimited)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add context
* fix(fireflies): correct types from live API validation (#3450)
* fix(fireflies): correct types from live API validation
- speakers.id is number, not string (API returns 0, 1, 2...)
- summary.action_items is a single string, not string[]
- Update formatTranscriptContent to handle action_items as string
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(fireflies): correct tool types from live API validation
- FirefliesSpeaker.id: string -> number
- FirefliesSentence.speaker_id: string -> number
- FirefliesSpeakerAnalytics.speaker_id: string -> number
- FirefliesSummary.action_items: string[] -> string
- FirefliesSummary.outline: string[] -> string
- FirefliesSummary.shorthand_bullet: string[] -> string
- FirefliesSummary.bullet_gist: string[] -> string
- FirefliesSummary.topics_discussed: string[] -> string
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(knowledge): add connector tools and expand document metadata (#3452)
* feat(knowledge): add connector tools and expand document metadata
* fix(knowledge): address PR review feedback on new tools
* fix(knowledge): remove unused params from get_document transform
* refactor, improvement
* fix: correct knowledge block canonical pair pattern and subblock migration
- Rename manualDocumentId to documentId (advanced subblock ID should match
canonicalParamId, consistent with airtable/gmail patterns)
- Fix documentSelector.dependsOn to reference knowledgeBaseSelector (basic
depends on basic, not advanced)
- Remove unnecessary documentId migration (ID unchanged from main)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix: resolve post-merge test and lint failures
- airtable: sync tableSelector condition with tableId (add getSchema)
- backfillCanonicalModes test: add documentId mode to prevent false backfill
- schedule PUT test: use invalid action string now that disable is valid
- schedule execute tests: add ne mock, sourceType field, use
mockReturnValueOnce for two db.update calls
- knowledge tools: fix biome formatting (single-line arrow functions)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fixes
* Fixes
* Clean vfs
* Fix
* Fix lint
* fix(connectors): add rate limiting, concurrency controls, and bug fixes (#3457)
* fix(connectors): add rate limiting, concurrency controls, and bug fixes across knowledge connectors
- Add Retry-After header support to fetchWithRetry for all 18 connectors
- Batch concurrent API calls (concurrency 5) in Dropbox, Google Docs, Google Drive, OneDrive, SharePoint
- Batch concurrent API calls (concurrency 3) in Notion to match 3 req/s limit
- Cache GitHub tree in syncContext to avoid re-fetching on every pagination page
- Batch GitHub blob fetches with concurrency 5
- Fix GitHub base64 decoding: atob() → Buffer.from() for UTF-8 safety
- Fix HubSpot OAuth scope: 'tickets' → 'crm.objects.tickets.read' (v3 API)
- Fix HubSpot syncContext key: totalFetched → totalDocsFetched for consistency
- Add jitter to nextSyncAt (10% of interval, capped at 5min) to prevent thundering herd
- Fix Date consistency in connector DELETE route
* fix(connectors): address PR review feedback on retry and SharePoint batching
- Remove 120s cap on Retry-After — pass all values through to retry loop
- Add maxDelayMs guard: if Retry-After exceeds maxDelayMs, throw immediately
instead of hammering with shorter intervals (addresses validate timeout concern)
- Add early exit in SharePoint batch loop when maxFiles limit is reached
to avoid unnecessary API calls
* fix(connectors): cap Retry-After at maxDelayMs instead of aborting
Match Google Cloud SDK behavior: when Retry-After exceeds maxDelayMs,
cap the wait to maxDelayMs and log a warning, rather than throwing
immediately. This ensures retries are bounded in duration while still
respecting server guidance within the configured limit.
* fix(connectors): add early-exit guard to Dropbox, Google Docs, OneDrive batch loops
Match the SharePoint fix — skip remaining batches once maxFiles limit
is reached to avoid unnecessary API calls.
* improvement(turbo): align turborepo config with best practices (#3458)
* improvement(turbo): align turborepo config with best practices
* fix(turbo): address PR review feedback
* fix(turbo): add lint:check task for read-only lint+format CI checks
lint:check previously delegated to format:check which only checked
formatting. Now it runs biome check (no --write) which enforces both
lint rules and formatting without mutating files.
* upgrade turbo
* improvement(perf): apply react and js performance optimizations across codebase (#3459)
* improvement(perf): apply react and js performance optimizations across codebase
- Parallelize independent DB queries with Promise.all in API routes
- Defer PostHog and OneDollarStats via dynamic import() to reduce bundle size
- Use functional setState in countdown timers to prevent stale closures
- Replace O(n*m) .filter().find() with Set-based O(n) lookups in undo-redo
- Use .toSorted() instead of .sort() for immutable state operations
- Use lazy initializers for useState(new Set()) across 20 components
- Remove useMemo wrapping trivially cheap expressions (typeof, ternary, template strings)
- Add passive: true to scroll event listener
* fix(perf): address PR review feedback
- Extract IIFE Set patterns to named consts for readability in use-undo-redo
- Hoist Set construction above loops in BATCH_UPDATE_PARENT cases
- Add .catch() error handler to PostHog dynamic import
- Convert session-provider posthog import to dynamic import() to complete bundle split
* fix(analytics): add .catch() to onedollarstats dynamic import
* improvement(resource): tables, files
* improvement(resources): all outer page structure complete
* refactor(queries): comprehensive TanStack Query best practices audit (#3460)
* refactor: comprehensive TanStack Query best practices audit and migration
- Add AbortSignal forwarding to all 41 queryFn implementations for proper request cancellation
- Migrate manual fetch patterns to useMutation hooks (useResetPassword, useRedeemReferralCode, usePurchaseCredits, useImportWorkflow, useOpenBillingPortal, useAllowedMcpDomains)
- Migrate standalone hooks to TanStack Query (use-next-available-slot, use-mcp-server-test, use-webhook-management, use-referral-attribution)
- Fix query key factories: add missing `all` keys, replace inline keys with factory methods
- Fix optimistic mutations: use onSettled instead of onSuccess for cache reconciliation
- Replace overly broad cache invalidations with targeted key invalidation
- Remove keepPreviousData from static-key queries where it provides no benefit
- Add staleTime to queries missing explicit cache duration
- Fix `any` type in UpdateSettingParams with proper GeneralSettings typing
- Remove dead code: loadingWebhooks/checkedWebhooks from subblock store, unused helper functions
- Update settings components (general, debug, referral-code, credit-balance, subscription, mcp) to use mutation state instead of manual useState for loading/error/success
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unstable mutation object from useCallback deps
openBillingPortal mutation object is not referentially stable,
but .mutate() is stable in TanStack Query v5. Remove from deps
to prevent unnecessary handleBadgeClick recreations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add missing byWorkflows invalidation to useUpdateTemplate
The onSettled handler was missing the byWorkflows() invalidation
that was dropped during the onSuccess→onSettled migration. Without
this, the deploy modal (useTemplateByWorkflow) would show stale data
after a template update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add TanStack Query best practices to CLAUDE.md and cursor rules
Add comprehensive React Query best practices covering:
- Hierarchical query key factories with intermediate plural keys
- AbortSignal forwarding in all queryFn implementations
- Targeted cache invalidation over broad .all invalidation
- onSettled for optimistic mutation cache reconciliation
- keepPreviousData only on variable-key queries
- No manual fetch in components rule
- Stable mutation references in useCallback deps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review feedback
- Fix syncedRef regression in use-webhook-management: only set
syncedRef.current=true when webhook is found, so re-sync works
after webhook creation (e.g., post-deploy)
- Remove redundant detail(id) invalidation from useUpdateTemplate
onSettled since onSuccess already populates cache via setQueryData
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address second round of PR review feedback
- Reset syncedRef when blockId changes in use-webhook-management so
component reuse with a different block syncs the new webhook
- Add response.ok check in postAttribution so non-2xx responses
throw and trigger TanStack Query retry logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use lists() prefix invalidation in useCreateWorkspaceCredential
Use workspaceCredentialKeys.lists() instead of .list(workspaceId) so
filtered list queries are also invalidated on credential creation,
matching the pattern used by update and delete mutations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address third round of PR review feedback
- Add nullish coalescing fallback for bonusAmount in referral-code
to prevent rendering "undefined" when server omits the field
- Reset syncedRef when queryEnabled becomes false so webhook data
re-syncs when the query is re-enabled without component remount
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address fourth round of PR review feedback
- Add AbortSignal to testMcpServerConnection for consistency
- Wrap handleTestConnection in try/catch for mutateAsync error handling
- Replace broad subscriptionKeys.all with targeted users()/usage() invalidation
- Add intermediate users() key to subscription key factory for prefix matching
- Add comment documenting syncedRef null-webhook behavior
- Fix api-keys.ts silent error swallowing on non-ok responses
- Move deployments.ts cache invalidation from onSuccess to onSettled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: achieve full TanStack Query best practices compliance
- Add intermediate plural keys to api-keys, deployments, and schedules
key factories for prefix-based invalidation support
- Change copilot-keys from refetchQueries to invalidateQueries
- Add signal parameter to organization.ts fetch functions (better-auth
client does not support AbortSignal, documented accordingly)
- Move useCreateMcpServer invalidation from onSuccess to onSettled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* ran lint
* Fix tables row count
* Update mothership to match copilot in logs
* improvement(resource): layout
* fix(knowledge): compute KB tokenCount from documents instead of stale column (#3463)
The knowledge_base.token_count column was initialized to 0 and never
updated. Replace with COALESCE(SUM(document.token_count), 0) in all
read queries, which already JOIN on documents with GROUP BY.
* improvement(resources): layout and items
* feat(knowledge): add v1 knowledge base API, Obsidian/Evernote connectors, and docs (#3465)
* feat(knowledge): add v1 knowledge base API, Obsidian/Evernote connectors, and docs
- Add v1 REST API for knowledge bases (CRUD, document management, vector search)
- Add Obsidian and Evernote knowledge base connectors
- Add file type validation to v1 file and document upload endpoints
- Update OpenAPI spec with knowledge base endpoints and schemas
- Add connectors documentation page
- Apply query hook formatting improvements
* fix(knowledge): address PR review feedback
- Remove validateFileType from v1/files route (general file upload, not document-only)
- Reject tag filters when searching multiple KBs (tag defs are KB-specific)
- Cache tag definitions to avoid duplicate getDocumentTagDefinitions call
- Fix Obsidian connector silent empty results when syncContext is undefined
* improvement(connectors): add syncContext to getDocument, clean up caching
- Update docs to say 20+ connectors
- Add syncContext param to ConnectorConfig.getDocument interface
- Use syncContext in Evernote getDocument to cache tag/notebook maps
- Replace index-based cache check with Map keyed by KB ID in search route
* fix(knowledge): address second round of PR review feedback
- Fix Zod .default('text') overriding tag definition's actual fieldType
- Fix encodeURIComponent breaking multi-level folder paths in Obsidian
- Use 413 instead of 400 for file-too-large in document upload
- Add knowledge-bases to API reference docs navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): prevent cross-workspace KB access in search
Filter accessible KBs by matching workspaceId from the request,
preventing users from querying KBs in other workspaces they have
access to but didn't specify.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): audit resourceId, SSRF protection, recursion depth limit
- Fix recordAudit using knowledgeBaseId instead of newDocument.id
- Add SSRF validation to Obsidian connector (reject private/loopback URLs)
- Add max recursion depth (20) to listVaultFiles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(obsidian): remove SSRF check that blocks localhost usage
The Obsidian connector is designed to connect to the Local REST API
plugin running on localhost (127.0.0.1:27124). The SSRF check was
incorrectly blocking this primary use case.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(resources): segmented API
* fix(execution): ensure background tasks await post-execution DB status updates (#3466)
The fire-and-forget IIFE in execution-core.ts for post-execution logging could be abandoned when trigger.dev tasks exit, leaving executions permanently stuck in "running" status. Store the promise on LoggingSession so background tasks can optionally await it before returning.
* improvement(resource): sorting and icons
* fix(resource): sorting
* improvement(settings): fix mcp modal, add option to edit JSON and add Sim as an MCP client (#3467)
* improvement(settings): fix mcp modal, add option to edit JSON and add Sim as an MCP client
* added docs link in sidebar
* ack comments
* ack comments
* fixed error msg
* feat(mothership): billing (#3464)
* Billing update
* more billing improvements
* credits UI
* credit purchase safety
* progress
* ui improvements
* fix cancel sub
* fix types
* fix daily refresh for teams
* make max features differentiated
* address bugbot comments
* address greptile comments
* revert isHosted
* address more comments
* fix org refresh bar
* fix ui rounding
* fix minor rounding
* fix upgrade issue for legacy plans
* fix formatPlanName
* fix email dispay names
* fix legacy team reference bugs
* referral bonus in credits
* fix org upgrade bug
* improve logs
* respect toggle for paid users
* fix landing page pro features and usage limit checks
* fixed query and usage
* add unit test
* address more comments
* enterprise guard
* fix limits bug
* pass period start/end for overage
* fix(sidebar): restore drag-and-drop for workflows and folders (#3470)
* fix(sidebar): restore drag-and-drop for workflows and folders
Made-with: Cursor
* update docs, unrelated
* improvement(tables): consolidation
* feat(schedules): add schedule creator modal for standalone jobs
Add modal to create standalone scheduled jobs from the Schedules page.
Includes POST API endpoint, useCreateSchedule mutation hook, and full
modal with schedule type selection, timezone, lifecycle, and live preview.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(schedules): add edit support with context menu for standalone jobs
* style(schedules): apply linter formatting
* improvement: tables, favicon
* feat(files): inline file viewer with text editing (#3475)
* feat(files): add inline file viewer with text editing and create file modal
Add file preview/edit functionality to the workspace files page. Text files
(md, json, txt, yaml, etc.) open in an editable textarea with Cmd/Ctrl+S save.
PDFs render in an iframe. New file button creates empty .md files via a modal.
Uses ResourceHeader breadcrumbs and ResourceOptionsBar for save/download/delete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(files): add UX polish, PR review fixes, and context menu
- Add unsaved changes guard modal (matching credentials manager pattern)
- Add delete confirmation modal for both viewer and context menu
- Add save status feedback (Save → Saving... → Saved)
- Add right-click context menu with Open, Download, Delete actions
- Add 50MB file size limit on content update API
- Add storage quota check before content updates
- Add response.ok guard on download to prevent corrupt files
- Add skeleton loading for pending file selection (prevents flicker)
- Fix updateContent in handleSave dependency array
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): propagate save errors and remove redundant sizeDiff
- Remove try/catch in TextEditor.handleSave so errors propagate to
parent, which correctly shows save failure status
- Remove redundant inner sizeDiff declaration that shadowed outer scope
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): remove unused textareaRef
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): move Cmd+S to parent, add save error feedback, hide save for non-text files
- Move Cmd+S keyboard handler from TextEditor to Files so it goes
through the parent handleSave with proper status management
- Add 'error' save status with red "Save failed" label that auto-resets
- Only show Save button for text-editable file types (md, txt, json, etc.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(files): add save tooltip, deduplicate text-editable extensions
- Add Tooltip on Save button showing Cmd+S / Ctrl+S shortcut
- Export TEXT_EDITABLE_EXTENSIONS from file-viewer and reuse in files.tsx
instead of duplicating the list inline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract isMacPlatform to shared utility
Move isMacPlatform() from global-commands-provider.tsx to
lib/core/utils/platform.ts so it can be reused by files.tsx tooltip
without duplication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(files): deduplicate delete modal, use shared formatFileSize
- Extract DeleteConfirmModal component to eliminate duplicate modal
markup between viewer and list modes
- Replace local formatFileSize with shared utility from file-utils.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): fix a11y label lint error and remove mutation object from useCallback deps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): add isDirty guard on handleSave, return proper HTTP status codes
Prevents "Saving → Saved" flash when pressing Cmd+S with no changes.
Returns 404 for file-not-found and 402 for quota-exceeded instead of 500.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): reset isDirty/saveStatus on delete and discard, remove deprecated navigator.platform
- Clear isDirty and saveStatus when deleting the currently-viewed file to
prevent spurious beforeunload prompts
- Reset saveStatus on discard to prevent stale "Save failed" when opening
another file
- Remove deprecated navigator.platform, userAgent fallback covers all cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): prevent concurrent saves on rapid Cmd+S, add YAML MIME types
- Add saveStatus === 'saving' guard to handleSave to prevent duplicate
concurrent PUT requests from rapid keyboard shortcuts
- Add yaml/yml MIME type mappings to getMimeTypeFromExtension
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(files): reuse shared extension constants, parallelize cancelQueries
- Replace hand-rolled SUPPORTED_EXTENSIONS with composition from existing
SUPPORTED_DOCUMENT/AUDIO/VIDEO_EXTENSIONS in validation.ts
- Parallelize sequential cancelQueries calls in delete mutation onMutate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): guard handleCreate against duplicate calls while pending
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): show upload progress on the Upload button, not New file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(files): use ref-based guard for create pending state to avoid stale closure
The uploadFile.isPending check was stale because the mutation object
is excluded from useCallback deps (per codebase convention). Using a
ref ensures the guard works correctly across rapid Enter key presses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* cleanup(files): use shared icon import, remove no-op props, wrap handler in useCallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement: tables, dropdown
* improvement(docs): align sidebar method badges and polish API reference styling (#3484)
* improvement(docs): align sidebar method badges and polish API reference styling
* fix(docs): revert className prop on DocsPage for CI compatibility
* fix(docs): restore oneOf schema for delete rows and use rem units in CSS
* fix(docs): replace :has() selectors with direct className for reliable prod layout
The API docs layout was intermittently narrow in production because CSS
:has(.api-page-header) selectors are unreliable in Tailwind v4 production
builds. Apply className="openapi-page" directly to DocsPage and replace
all 64 :has() selectors with .openapi-page class targeting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(docs): bypass TypeScript check for className prop on DocsPage
Use spread with type assertion to pass className to DocsPage, working
around a CI type resolution issue where the prop exists at runtime but
is not recognized by TypeScript in the Vercel build environment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(docs): use inline style tag for grid layout, revert CSS to :has() selectors
The className prop on DocsPage doesn't exist in the fumadocs-ui version
resolved on Vercel, so .openapi-page was never applied and all 64 CSS
rules broke. Revert to :has(.api-page-header) selectors for styling and
use an inline <style> tag for the critical grid-column layout override,
which is SSR'd and doesn't depend on any CSS selector matching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(docs): add pill styling to footer navigation method badges
The footer nav badges (POST, GET, etc.) had color from data-method rules
but lacked the structural pill styling (padding, border-radius, font-size).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(docs): use named grid lines instead of numeric column indices (#3487)
Root cause: the fumadocs grid template has 3 columns in production but
5 columns in local dev. Our CSS used `grid-column: 3 / span 2` which
targeted the wrong column in the 3-column grid, placing content in
the near-zero-width TOC column instead of the main content column.
Fix: use `grid-column: main-start / toc-end` which uses CSS named grid
lines from grid-template-areas, working regardless of column count.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(resource): layout
* improvement: icon, resource header options
* improvement: icons
* fix(files): icon
* feat(tables): column operations, row ordering, V1 API (#3488)
* feat(tables): add column operations, row ordering, V1 columns API, and OpenAPI spec
Adds column rename/delete/type change/constraint updates to the tables module,
row ordering via position column, UI metadata schema, V1 public API for column
operations with rate limiting and audit logging, and OpenAPI documentation.
Key changes:
- Service-layer column operations with validation (name pattern, type compatibility, unique/required constraints)
- Position column on user_table_rows with composite index for efficient ordering
- V1 /api/v1/tables/{tableId}/columns endpoint (POST/PATCH/DELETE) with rate limiting and audit
- Shared Zod schemas extracted to table/utils.ts using COLUMN_TYPES constant
- Targeted React Query invalidation (row vs schema mutations) with consistent onSettled usage
- OpenAPI 3.1.0 spec for columns endpoint with code samples
- Position field added to all row response mappings for consistency
- Sort fallback to position ordering when buildSortClause returns null
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tables): use specific error prefixes instead of broad "Cannot" match
Prevents internal TypeErrors (e.g. "Cannot read properties of undefined")
from leaking as 400 responses. Now matches only domain-specific errors:
"Cannot delete the last column" and "Cannot set column".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tables): reject Infinity and NaN in number type compatibility check
Number.isFinite rejects Infinity, -Infinity, and NaN, preventing
non-finite values from passing column type validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tables): invalidate table list on row create/delete for stale rowCount
Row create and delete mutations now invalidate the table list cache since
it includes a computed rowCount. Row updates (which don't change count)
continue to only invalidate row queries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tables): add column name length check, deduplicate name gen, reset pagination on clear
- Add MAX_COLUMN_NAME_LENGTH validation to addTableColumn (was missing,
renameColumn already had it)
- Extract generateColumnName helper to eliminate triplicated logic across
handleAddColumn, handleInsertColumnLeft, handleInsertColumnRight
- Reset pagination to page 0 when clearing sort/filter to prevent showing
empty pages after narrowing filters are removed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: hoist tableId above try block in V1 columns route, add detail invalidation to invalidateRowCount
- V1 columns route: `tableId` was declared inside `try` but referenced in
`catch` logger.error, causing undefined in error logs. Hoisted `await params`
above try in all three handlers (POST, PATCH, DELETE).
- invalidateRowCount: added `tableKeys.detail(tableId)` invalidation since the
single-table GET response includes `rowCount`, which becomes stale after
row create/delete without this.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add position to all row mutation responses, remove dead filter code
- Add `position` field to POST (single + batch) and PATCH row responses
across both internal and V1 routes, matching GET responses and OpenAPI spec.
- Remove unused `filterConfig`, `handleFilterToggle`, `handleFilterClear`,
and `activeFilters` — dead code left over from merge conflict resolution.
`handleFilterApply` (the one actually wired to JSX) is preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: invalidateTableSchema now also invalidates table list cache
Column add/rename/delete/update mutations now invalidate tableKeys.list()
since the list endpoint returns schema.columns for each table. Without this,
the sidebar table list would show stale column schemas until staleTime expires.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace window.prompt/confirm with emcn Modal dialogs
Replace non-standard browser dialogs with proper emcn Modal components
to match the existing codebase pattern (e.g. delete table confirmation).
- Column rename: Modal with Input field + Enter key support
- Column delete: Modal with destructive confirmation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* update schedule creation ui and run lint
* improvement: logs
* improvement(tables): multi-select and efficiencies
* Table tools
* improvement(folder-selection): folder deselection + selection order should match visual
* fix(selections): more nested folder inaccuracies
* Tool updates
* Store tool call results
* fix(landing): wire agent input to mothership
* feat(mothership): resource viewer
* fix tests
* fix(streaming): smoother streaming with throttled rendering, ResizeObserver scroll, and batched updates (#3471)
* fix(streaming): smoother streaming with throttled rendering, ResizeObserver scroll, and batched updates
- Add useThrottledValue hook (100ms trailing-edge throttle) to gate DOM re-renders during streaming across all chat surfaces
- Replace 100ms setInterval scroll polling with ResizeObserver-based auto-scroll, programmatic scroll timestamp tracking, and nested [data-scrollable] region handling
- Extract processContentBuffer from inline content handler for cleaner code organization in copilot SSE handlers
- Add RAF-based update batching (50ms max interval) to floating chat and home chat streaming paths
- Add useProgressiveList hook for progressive rendering of long conversation histories via requestAnimationFrame
Made-with: Cursor
* ack PR comments
* fix search modal
* more comments
* ack comments
* count
* ack comments
* ack comment
* improvement(mothership): worklfow resource
* Fix tool call persistence in chat
* Tool results
* Fix error status
* File uploads to mothership
* feat(templates): landing page templates workflow states
* improvement(mothership): chat stability
* improvement(mothership): chat history and stability
* improvement(tables): click-to-select navigation, inline rename, column resize (#3496)
* improvement(tables): click-to-select navigation, inline rename, column resize
* fix(tables): address PR review comments
- Add doneRef guard to useInlineRename preventing Enter+blur double-fire
- Fix PATCH error handler: return 500 for non-validation errors, fix unreachable logger.error
- Stop click propagation on breadcrumb rename input
* fix(tables): add rows-affected check in renameTable service
Prevents silent no-op when tableId doesn't match any record.
* fix(tables): useMemo deps + placeholder memo initialCharacter check
- Use primitive editingId/editValue in useMemo deps instead of whole
useInlineRename object (which creates a new ref every render)
- Add initialCharacter comparison to placeholderPropsAreEqual, matching
the existing pattern in dataRowPropsAreEqual
* fix(tables): address round 2 review comments
- Mirror name validation (regex + max length) in PatchTableSchema so
validateTableName failures return 400 instead of 500
- Add .returning() + rows-affected check to renameWorkspaceFile,
matching the renameTable pattern
- Check response.ok before parsing JSON in useRenameWorkspaceFile,
matching the useRenameTable pattern
* refactor(tables): reuse InlineRenameInput in BreadcrumbSegment
Replace duplicated inline input markup with the shared component.
Eliminates redundant useRef, useEffect, and input boilerplate.
* fix(tables): set doneRef in cancelRename to prevent blur-triggered save
Escape → cancelRename → input unmounts → blur → submitRename would
save instead of canceling. Now cancelRename sets doneRef like
submitRename does, blocking the subsequent blur handler.
* fix(tables): pointercancel cleanup + typed FileConflictError
- Add pointercancel handler to column resize to prevent listener leaks
when system interrupts the pointer (touch-action override, etc.)
- Replace stringly-typed error.message.includes('already exists') with
FileConflictError class for refactor-safe 409 status detection
* fix(tables): stable useCallback dep + rename shadowed variable
- Use listRename.startRename (stable ref) instead of whole listRename
object in handleContextMenuRename deps
- Rename inner 'target' to 'origin' in arrow-key handler to avoid
shadowing the outer HTMLElement 'target'
* fix(tables): move class below imports, stable submitRename, clear editingCell
- Move FileConflictError below import statements (import-first convention)
- Make submitRename a stable useCallback([]) by reading editingId and
editValue through refs (matches existing onSaveRef pattern)
- Add setEditingCell(null) to handleEmptyRowClick for symmetry with
handleCellClick
* feat(tables): persist column widths in table metadata
Column widths now survive navigation and page reloads. On resize-end,
widths are debounced (500ms) and saved to the table's metadata field
via a new PUT /api/table/[tableId]/metadata endpoint. On load, widths
are seeded from the server once via React Query.
* fix type checking for file viewer
* fix(tables): address review feedback — 4 fixes
1. headerRename.onSave now uses the fileId parameter directly instead
of the selectedFile closure, preventing rename-wrong-file race
2. updateMetadataMutation uses ref pattern matching mutateRef/createRef
3. Type-to-enter filters non-numeric chars for number columns, non-date
chars for date columns
4. renameValue only passed to actively-renaming ColumnHeaderMenu,
preserving React.memo for other columns
* fix(tables): position-based gap rows, insert above/below, consistency fixes
- Fix gap row insert shifting: only shift rows when target position is
occupied, preventing unnecessary displacement of rows below
- Switch to position-based indexing throughout (positionMap, maxPosition)
instead of array-index for correct sparse position handling
- Add insert row above/below to context menu
- Use CellContent for pending values in PositionGapRows (matching PlaceholderRows)
- Add belowHeader selection overlay logic to PositionGapRows
- Remove unnecessary 500ms debounce on column width persistence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix cells nav w keyboard
* added preview panel for html, markdown rendering, completed table
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tables): one small tables ting (#3497)
* feat(exa-hosted-key): Restore exa hosted key (#3499)
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement(ui): consistent styling
* styling alignment
* improvements(tables): styling improvements
* improve resizer for file preview for html files
* updated document icon
* fix(credentials): exclude regular login methods from credential sync
* update docs
* upgrade turbo
* improvement: tables, chat
* Fix table column delete
* small table rename bug, files updates not persisting
* Table batch ops
* fix(credentials): block usage at execution layer without perms + fix invites
* feat(hosted-key-services) Add hosted key for multiple services (#3461)
* feat(hosted keys): Implement serper hosted key
* Handle required fields correctly for hosted keys
* Add rate limiting (3 tries, exponential backoff)
* Add custom pricing, switch to exa as first hosted key
* Add telemetry
* Consolidate byok type definitions
* Add warning comment if default calculation is used
* Record usage to user stats table
* Fix unit tests, use cost property
* Include more metadata in cost output
* Fix disabled tests
* Fix spacing
* Fix lint
* Move knowledge cost restructuring away from generic block handler
* Migrate knowledge unit tests
* Lint
* Fix broken tests
* Add user based hosted key throttling
* Refactor hosted key handling. Add optimistic handling of throttling for custom throttle rules.
* Remove research as hosted key. Recommend BYOK if throtttling occurs
* Make adding api keys adjustable via env vars
* Remove vestigial fields from research
* Make billing actor id required for throttling
* Switch to round robin for api key distribution
* Add helper method for adding hosted key cost
* Strip leading double underscores to avoid breaking change
* Lint fix
* Remove falsy check in favor for explicit null check
* Add more detailed metrics for different throttling types
* Fix _costDollars field
* Handle hosted agent tool calls
* Fail loudly if cost field isn't found
* Remove any type
* Fix type error
* Fix lint
* Fix usage log double logging data
* Fix test
* Add browseruse hosted key
* Add firecrawl and serper hosted keys
* feat(hosted key): Add exa hosted key (#3221)
* feat(hosted keys): Implement serper hosted key
* Handle required fields correctly for hosted keys
* Add rate limiting (3 tries, exponential backoff)
* Add custom pricing, switch to exa as first hosted key
* Add telemetry
* Consolidate byok type definitions
* Add warning comment if default calculation is used
* Record usage to user stats table
* Fix unit tests, use cost property
* Include more metadata in cost output
* Fix disabled tests
* Fix spacing
* Fix lint
* Move knowledge cost restructuring away from generic block handler
* Migrate knowledge unit tests
* Lint
* Fix broken tests
* Add user based hosted key throttling
* Refactor hosted key handling. Add optimistic handling of throttling for custom throttle rules.
* Remove research as hosted key. Recommend BYOK if throtttling occurs
* Make adding api keys adjustable via env vars
* Remove vestigial fields from research
* Make billing actor id required for throttling
* Switch to round robin for api key distribution
* Add helper method for adding hosted key cost
* Strip leading double underscores to avoid breaking change
* Lint fix
* Remove falsy check in favor for explicit null check
* Add more detailed metrics for different throttling types
* Fix _costDollars field
* Handle hosted agent tool calls
* Fail loudly if cost field isn't found
* Remove any type
* Fix type error
* Fix lint
* Fix usage log double logging data
* Fix test
---------
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
* Fail fast on cost data not being found
* Add hosted key for google services
* Add hosting configuration and pricing logic for ElevenLabs TTS tools
* Add linkup hosted key
* Add jina hosted key
* Add hugging face hosted key
* Add perplexity hosting
* Add broader metrics for throttling
* Add skill for adding hosted key
* Lint, remove vestigial hosted keys not implemented
* Revert agent changes
* fail fast
* Fix build issue
* Fix build issues
* Fix type error
* Remove byok types that aren't implemented
* Address feedback
* Use default model when model id isn't provided
* Fix cost default issues
* Remove firecrawl error suppression
* Restore original behavior for hugging face
* Add mistral hosted key
* Remove hugging face hosted key
* Fix pricing mismatch is mistral and perplexity
* Add hosted keys for parallel and brand fetch
* Add brandfetch hosted key
* Update types
* Change byok name to parallel_ai
* Add telemetry on unknown models
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement(settings): SSR prefetch, code splitting, dedicated skeletons
* fix: bust browser cache for workspace file downloads
The downloadFile function was using a plain fetch() that honored the
aggressive cache headers, causing newly created files to download empty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(settings): use emcn Skeleton in extracted skeleton files
* fix(settings): extract shared response mappers to prevent server/client shape drift
Addresses PR review feedback — prefetch.ts duplicated response mapping logic from client hooks. Extracted mapGeneralSettingsResponse and mapUserProfileResponse as shared functions used by both client fetch and server prefetch.
* update byok page
* fix(settings): include theme sync in client-side prefetch queryFn
Hover-based prefetchGeneralSettings now calls syncThemeToNextThemes, matching the useGeneralSettings hook behavior so theme updates aren't missed when prefetch refreshes stale cache.
* fix(byok): use EMCN Input for search field instead of ui Input
Replace @/components/ui Input with the already-imported EmcnInput for design-system consistency.
* fix(byok): use ui Input for search bar to match other settings pages
* fix(settings): use emcn Input for file input in general settings
* improvement(settings): add search bar to skeleton loading states
Skeletons now include the search bar (and action button where applicable) so the layout matches the final component 1:1. Eliminates layout shift when the dynamic chunk loads — search bar area is already reserved by the skeleton.
* fix(settings): align skeleton layouts with actual component structures
- Fix list item gap from 12px to 8px across all skeletons (API keys, custom tools, credentials, MCP)
- Add OAuth icon placeholder to credential skeleton
- Fix credential button group gap from 8px to 4px
- Remove incorrect gap-[4px] from credential-sets text column
- Rebuild debug skeleton to match real layout (description + input/button row)
- Add scrollable wrapper to BYOK skeleton with more representative item count
* chore: lint fixes
* improvement(sidebar): match workspace switcher popover width to sidebar
Use Radix UI's built-in --radix-popover-trigger-width CSS variable
instead of hardcoded 160px so the popover matches the trigger width
and responds to sidebar resizing.
* revert hardcoded ff
* fix: copilot, improvement: tables, mothership
* feat: inline chunk editor and table batch ops with undo/redo (#3504)
* feat: inline chunk editor and table batch operations with undo/redo
Replace modal-based chunk editing/creation with inline editor following
the files tab pattern (state-based view toggle with ResourceHeader).
Add batch update API endpoint, undo/redo support, and Popover-based
context menus for tables.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove icons from table context menu PopoverItems
Icons were incorrectly carried over from the DropdownMenu migration.
PopoverItems in this codebase use text-only labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore DropdownMenu for table context menu
The table-level context menu was incorrectly migrated to Popover during
conflict resolution. Only the row-level context menu uses Popover; the
table context menu should remain DropdownMenu with icons, matching the
base branch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bound cross-page chunk navigation polling to max 50 retries
Prevent indefinite polling if page data never loads during
chunk navigation across page boundaries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: navigate to last page after chunk creation for multi-page documents
After creating a chunk, navigate to the last page (where new chunks
append) before selecting it. This prevents the editor from showing
"Loading chunk..." when the new chunk is not on the current page.
The loading state breadcrumb remains as an escape hatch for edge cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add duplicate rowId validation to BatchUpdateByIdsSchema
Adds a .refine() check to reject duplicate rowIds in batch update
requests, consistent with the positions uniqueness check on batch insert.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review comments
- Fix disableEdit logic: use || instead of && so connector doc chunks
cannot be edited from context menu (row click still opens viewer)
- Add uniqueness validation for rowIds in BatchUpdateByIdsSchema
- Fix inconsistent bg token: bg-background → bg-[var(--bg)] in Pagination
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove duplicate rowId uniqueness refine on BatchUpdateByIdsSchema
The refine was applied both on the inner updates array and the outer
object. Keep only the inner array refine which is cleaner.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address additional PR review comments
- Fix stale rowId after create-row redo: patch undo stack with new row
ID using patchUndoRowId so subsequent undo targets the correct row
- Fix text color tokens in Pagination: use CSS variable references
(text-[var(--text-body)], text-[var(--text-secondary)]) instead of
Tailwind semantic tokens for consistency with the rest of the file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove dead code and fix type errors in table context menu
Remove unused `onAddData` prop and `isEmptyCell` variable from row context
menu (introduced in PR but never wired to JSX). Fix type errors in
optimistic update spreads by removing unnecessary `as Record<string, unknown>`
casts that lost the RowData type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent false "Saved" status on invalid content and mark fire-and-forget goToPage calls
ChunkEditor.handleSave now throws on empty/oversized content instead of
silently returning, so the parent's catch block correctly sets saveStatus
to 'error'. Also added explicit `void` to unawaited goToPage(1) calls
in filter handlers to signal intentional fire-and-forget.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle stale totalPages in handleChunkCreated for new-page edge case
When creating a chunk that spills onto a new page, totalPages in the
closure is stale. Now polls displayChunksRef for the new chunk, and if
not found, checks totalPagesRef for an updated page count and navigates
to the new last page before continuing to poll.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Streaming fix -- need to test more
* Make mothership block use long input instead of prompt input
* improvement(billing): isAnnual metadata + docs updates (#3506)
* improvement(billing): on demand toggling and infinite limits
* store stripe metadata to distinguish annual vs monthly
* udpate docs
* address bugbot
* Add piping
* feat(clean-hosted-keys) Remove eleven labs, browseruse. Tweak firecrawl and mistral key impl (#3503)
* Remove eleven labs, browseruse, and firecrawl
* Remove creditsUsed output
* Add back mistral hosting for mistral blocks
* Add back firecrawl since they queue up concurrent requests
* Fix price calculation, remove agent since its super long running and will clog up queue
* Define hosting per tool
* Remove redundant token finding
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* Update vfs to handle hosted keys
* improvement(tables): fix cell editing flash, batch API docs, and UI polish (#3507)
* fix: show text cursor in chunk editor and ensure textarea fills container
Add cursor-text to the editor wrapper so the whole area shows a text
cursor. Click on empty space focuses the textarea. Changed textarea from
h-full/w-full to flex-1/min-h-0 so it properly fills the flex container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(tables): fix cell editing flash, add batch API docs, and UI polish
Fix stale-data flash when saving inline cell edits by using TanStack Query's
isPending+variables pattern instead of manual cache writes. Also adds OpenAPI
docs for batch table endpoints, DatePicker support in row modal, duplicate row
in context menu, and styling improvements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove dead resolveColumnFromEvent callback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: unify paste undo into single create-rows action
Batch-created rows from paste now push one `create-rows` undo entry
instead of N individual `create-row` entries, so a single Ctrl+Z
reverses the entire paste operation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate dates in inline editor and displayToStorage
InlineDateEditor now validates computed values via Date.parse before
saving, preventing invalid strings like "hello" from being sent to the
server. displayToStorage now rejects out-of-range month/day values
(e.g. 13/32) instead of producing invalid YYYY-MM-DD strings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: accept ISO date format in inline date editor
Fall back to raw draft input when displayToStorage returns null, so
valid ISO dates like "2024-03-15" pasted or typed directly are
accepted instead of silently discarded. Date.parse still validates
the final value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add ISO date support to displayToStorage and fix picker Escape
displayToStorage now recognizes YYYY-MM-DD input directly, so ISO
dates typed or pasted work correctly for both saving and picker sync.
DatePicker Escape now refocuses the input instead of saving, so the
user can press Escape again to cancel or Enter to confirm — matching
the expected cancel behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove dead paste boundary check
The totalR guard in handlePaste could never trigger since totalR
included pasteRows.length, making targetRow always < totalR.
Remove the unused variable and simplify the selection focus calc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update openapi
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix dysfunctional unique operation in tables
* feat(autosave): files and chunk editor autosave with debounce + refetch (#3508)
* feat(files): debounced autosave while editing
* address review comments
* more comments
* fix: unique constraint check crash and copilot table initial rows
- Fix TypeError in updateColumnConstraints: db.execute() returns a
plain array with postgres-js, not { rows: [...] }. The .rows.length
access always crashed, making "Set unique" completely broken.
- Add initialRowCount: 20 to copilot table creation so tables created
via chat have the same empty rows as tables created from the UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix signaling
* revert: remove initialRowCount from copilot table creation
Copilot populates its own data after creating a table, so pre-creating
20 empty rows causes data to start at position 21 with empty rows above.
initialRowCount only makes sense for the manual UI creation flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* improvement: chat, workspace header
* chat metadata
* Fix schema mismatch (#3510)
Co-authored-by: Theodore Li <theo@sim.ai>
* Fixes
* fix: manual table creation starts with 1 row, 1 column
Manual tables now create with a single 'name' column and 1 row instead
of 2 columns and 20 rows. Copilot tables remain at 0 rows, 0 columns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: horizontal scroll in embedded table by replacing overflow-hidden with overflow-clip
Cell content spans used Tailwind's `truncate` (overflow: hidden), creating
scroll containers that consumed trackpad wheel events on macOS without
propagating to the actual scroll ancestor. Replaced with overflow-clip
which clips identically but doesn't create a scroll container. Also moved
focus target from outer container to the scroll div for correctness.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix tool call ordering
* Fix tests
* feat: add task multi-select, context menu, and subscription UI updates
Add shift-click range selection, cmd/ctrl-click toggle, and right-click
context menu for tasks in sidebar matching workflow/folder patterns.
Update subscription settings tab UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(credentials): autosync behaviour cross workspace (#3511)
* fix(credentials): autosync behaviour cross workspace
* address comments
* fix(api-key-reminder) Add reminder on hosted keys that api key isnt needed (#3512)
* Add reminder on hosted keys that api key isnt needed
* Fix test case
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement: sidebar, chat
* Usage limit
* Plan prompt
* fix(sidebar): workspace header collapse
* fix(sidebar): task navigation
* Subagent tool call persistence
* Don't drop suabgent text
* improvement(ux): streaming
* improvement: thinking
* fix(random): optimized kb connector sync engine, rerenders in tables, files, editors, chat (#3513)
* optimized kb connector sync engine, rerenders in tables, files, editors, chat
* refactor(sidebar): rename onTaskClick to onMultiSelectClick for clarity
Made-with: Cursor
* ack comments, add docsFailed
* feat(email-footer) Add "sent with sim ai" for free users (#3515)
* Add "sent with sim ai" for free users
* Only add prompt injection on free tier
* Add try catch around billing info fetch
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement: modals
* ran migrations
* fix(mothership): fix hardcoded workflow color, tables drag line overflowing
* feat(mothership): file attachment indicators, persistence, and chat input improvements
- Show image thumbnails and file-icon cards above user messages in mothership chat
- Persist file attachment metadata (key, filename, media_type, size) in DB with user messages
- Restore attachments from history via /api/files/serve/ URLs so they survive refresh/navigation
- Unify all chat file inputs to use shared CHAT_ACCEPT_ATTRIBUTE constant
- Fix file thumbnail overflow: use flex-wrap instead of hidden horizontal scroll
- Compact attachment cards in floating workflow chat messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* improvement: search modal
* improvement(usage): free plan to 1000 credits (#3516)
* improvement(billing): free plan to five dollars
* fix comment
* remove per month terminology from marketing
* generate migration
* remove migration
* add migration back
* feat(workspace): add workspace color changing, consolidate update hooks, fix popover dismiss
- Add workspace color change via context menu, reusing workflow ColorGrid UI
- Consolidate useUpdateWorkspaceName + useUpdateWorkspaceColor into useUpdateWorkspace
- Fix popover hover submenu dismiss by using DismissableLayerBranch with pointerEvents
- Remove passthrough wrapper for export, reuse Workspace type for capturedWorkspaceRef
- Reorder log columns: workflow first, merge date+time into single column
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update oauth cred tool
* fix(diff-controls): fixed positioning for copilot diff controls
* fix(font): added back old font for emcn code editor
* improvement: panel, special tags
* improvement: chat
* improvement: loading and file dropping
* feat(templates): create home templates
* fix(uploads): resolve .md file upload rejection and deduplicate file type utilities
Browsers report empty or application/octet-stream MIME types for .md files,
causing copilot uploads to be rejected. Added resolveFileType() utility that
falls back to extension-based MIME resolution at both client and server
boundaries. Consolidated duplicate MIME mappings into module-level constants,
removed duplicate isImageFileType from copilot module, and replaced hardcoded
ALLOWED_EXTENSIONS with composition from shared validation constants. Also
switched file attachment previews to use shared getDocumentIcon utility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(home): prevent initial view from being scrollable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* autofill fixes
* added back integrations page, reverted secrets page back to old UI
* Fix workspace dropdown getting cut off when sidebar is collapsed
* fix(mothership): lint (#3517)
* fix(mothership): lint
* fix typing
* fix tests
* fix stale query
* fix plan display name
* Feat/add mothership manual workflow runs (#3520)
* Add run and open workflow buttons in workflow preview
* Send log request message after manual workflow run
* Make edges in embedded workflow non-editable
* Change chat to pass in log as additional context
* Revert "Change chat to pass in log as additional context"
This reverts commit e957dffb2f.
* Revert "Send log request message after manual workflow run"
This reverts commit 0fb92751f0.
* Move run and workflow icons to tab bar
* Simplify boolean condition
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(resource-tab-scroll): Allow vertical scrolling to scroll resource tab
* fix(remove-speed-hosted-key) Remove maps speed limit hosted key, it's deprecated (#3521)
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement: home, sidebar
* fix(download-file): render correct file download link for mothership (#3522)
* fix(download-file): render correct file download link for mothership
* Fix uunecessary call
* Use simple strip instead of db lookup and moving behavior
* Make regex strip more strict
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement: schedules, auto-scroll
* fix(settings): navigate back to origin page instead of always going home
Use sessionStorage to store the return URL when entering settings, and
use router.replace for tab switches so history doesn't accumulate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(schedules): release lastQueuedAt lock on all exit paths to prevent stuck schedules
Multiple error/early-return paths in executeScheduleJob and executeJobInline
were exiting without clearing lastQueuedAt, causing the dueFilter to permanently
skip those schedules — resulting in stale "X hours ago" display for nextRunAt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(mothership): inline rename for resource tabs + workspace_file rename tool
- Add double-click inline rename on file and table resource tabs
- Wire useInlineRename + useRenameWorkspaceFile/useRenameTable mutations
- Add rename operation to workspace_file copilot tool (schema, server, router)
- Add knowledge base resource support (type, extraction, rendering, actions)
- Accept optional className on InlineRenameInput for context-specific sizing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* revert: remove inline rename UI from resource tabs
Keep the workspace_file rename tool for the mothership agent.
Only the UI-side inline rename (double-click tabs) is removed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(mothership): knowledge base resource extraction + Resource/ResourceTable refactor
- Extract KB resources from knowledge subagent respond format (knowledge_bases array)
- Add knowledge_base tool to RESOURCE_TOOL_NAMES and TOOL_UI_METADATA
- Extract ResourceTable as independently composable memoized component
- Move contentOverride/overlay to Resource shell level (not table primitive)
- Remove redundant disableHeaderSort and loadingRows props
- Rename internal sort state for clarity (sort → internalSort, sortOverride → externalSort)
- Export ResourceTable and ResourceTableProps from barrel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(logs) Run workflows client side in mothership to transmit logs (#3529)
* Run workflows client side in mothership to transmit logs
* Initialize set as constant, prevent duplicate execution
* Fix lint
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(import) fix missing file
* fix(resource): Hide resources that have been deleted (#3528)
* Hide resources that have been deleted
* Handle table, workflow not found
* Add animation to prevent flash when previous resource was deleted
* Fix animation playing on every switch
* Run workflows client side in mothership to transmit logs
* Fix race condition for animation
* Use shared workflow tool util file
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* fix: chat scrollbar on sidebar collapse/open
* edit existing workflow should bring up artifact
* fix(agent) subagent and main agent text being merged without spacing
* feat(mothership): remove resource-level delete tools from copilot
Remove delete operations for workflows, folders, tables, and files
from the mothership copilot to prevent destructive actions via AI.
Row-level and column-level deletes are preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: stop sidebar from auto-collapsing when resource panel appears (#3540)
The sidebar was forcibly collapsed whenever a resource (e.g. workflow)
first appeared in the resource panel during a task. This was disruptive
on larger screens where users want to keep both the sidebar and resource
panel visible simultaneously.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mothership): insert copilot-created workflows at top of list (#3537)
* feat(mothership): remove resource-level delete tools from copilot
Remove delete operations for workflows, folders, tables, and files
from the mothership copilot to prevent destructive actions via AI.
Row-level and column-level deletes are preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mothership): insert copilot-created workflows at top of list
* fix(mothership): server-side top-insertion sort order and deduplicate registry logic
* fix(mothership): include folder sort orders when computing top-insertion position
* fix(mothership): use getNextWorkflowColor instead of hardcoded color
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(stop) Add stop of motehership ran workflows, persist stop messages (#3538)
* Connect play stop workflow in embedded view to workflow
* Fix stop not actually stoping workflow
* Fix ui not showing stopped by user
* Lint fix
* Plumb cancellation through system
* Stopping mothership chat stops workflow
* Remove extra fluff
* Persist blocks on cancellation
* Add root level stopped by user
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(autolayout): targetted autolayout heuristic restored (#3536)
* fix(autolayout): targetted autolayout heuristic restored
* fix autolayout boundary cases
* more fixes
* address comments
* on conflict updates
* address more comments
* fix relative position scope
* fix tye omission
* address bugbot comment
* Credential tags
* Credential id field
* feat(mothership): server-persisted unread task indicators via SSE (#3549)
* feat(mothership): server-persisted unread task indicators via SSE
Replace fragile client-side polling + timer-based green flash with
server-persisted lastSeenAt semantics, real-time SSE push via Redis
pub/sub, and dot overlay UI on the Blimp icon.
- Add lastSeenAt column to copilotChats for server-persisted read state
- Add Redis/local pub/sub singleton for task status events (started,
completed, created, deleted, renamed)
- Add SSE endpoint (GET /api/mothership/events) with heartbeat and
workspace-scoped filtering
- Add mark-read endpoint (POST /api/mothership/chats/read)
- Publish SSE events from chat, rename, delete, and auto-title handlers
- Add useTaskEvents hook for client-side SSE subscription
- Add useMarkTaskRead mutation with optimistic update
- Replace timer logic in sidebar with TaskStatus state machine
(running/unread/idle) and dot overlay using brand color variables
- Mark tasks read on mount and stream completion in home page
- Fix security: add userId check to delete WHERE clause
- Fix: bump updatedAt on stream completion
- Fix: set lastSeenAt on rename to prevent false-positive unread
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review feedback
- Return 404 when delete finds no matching chat (was silent no-op)
- Move log after ownership check so it only fires on actual deletion
- Publish completed SSE event from stop route so sidebar dot clears on abort
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: backfill last_seen_at in migration to prevent false unread dots
Existing rows would have last_seen_at = NULL after migration, causing
all past completed tasks to show as unread. Backfill sets last_seen_at
to updated_at for all existing rows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: timestamp mismatch on task creation + wasSendingRef leak across navigation
- Pass updatedAt explicitly alongside lastSeenAt on chat creation so
both use the same JS timestamp (DB defaultNow() ran later, causing
updatedAt > lastSeenAt → false unread)
- Reset wasSendingRef when chatId changes to prevent a stale true
from task A triggering a redundant markRead on task B
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: mark-read fires for inline-created chats + encode workspaceId in SSE URL
Expose resolvedChatId from useChat so home.tsx can mark-read even when
chatId prop stays undefined after replaceState URL update. Also
URL-encode workspaceId in EventSource URL as a defensive measure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: auto-focus home input on initial view + fix sidebar task click handling
Auto-focus the textarea when the initial home view renders. Also fix
sidebar task click to always call onMultiSelectClick so selection state
stays consistent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: auto-title sets lastSeenAt + move started event inside DB guard
Auto-title now sets both updatedAt and lastSeenAt (matching the rename
route pattern) to prevent false-positive unread dots. Also move the
'started' SSE event inside the if(updated) guard so it only fires when
the DB update actually matched a row.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* modified tasks multi select to be just like workflows
* fix
* refactor: extract generic pub/sub and SSE factories + fixes
- Extract createPubSubChannel factory (lib/events/pubsub.ts) to eliminate
duplicated Redis/EventEmitter boilerplate between task and MCP pub/sub
- Extract createWorkspaceSSE factory (lib/events/sse-endpoint.ts) to share
auth, heartbeat, and cleanup logic across SSE endpoints
- Fix auto-title race suppressing unread status by removing updatedAt/lastSeenAt
from title-only DB update
- Fix wheel event listener leak in ResourceTabs (RefCallback cleanup was silently
discarded)
- Fix getFullSelection() missing taskIds (inconsistent with hasAnySelection)
- Deduplicate SSE_RESPONSE_HEADERS to spread from shared SSE_HEADERS
- Hoist isSttAvailable to module-level constant to avoid per-render IIFE
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(logs): add workflow trigger type for sub-workflow executions (#3554)
* feat(logs): add workflow trigger type for sub-workflow executions
* fix(logs): align workflow filter color with blue-secondary badge variant
* feat(tab) allow user to control resource tabs
* Make resources persist to backend
* Use colored squares for workflows
* Add click and drag functionality to resource
* Fix expanding panel logic
* Reduce duplication, reading resource also opens up resource panel
* Move resource dropdown to own file
* Handle renamed resources
* Clicking already open tab should just switch to tab
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* Fix new resource tab button not appearing on tasks
* improvement(ui): dropdown menus, icons, globals
* improvement: notifications, terminal, globals
* reverted task logic
* feat(context) pass resource tab as context (#3555)
* feat(context) add currenttly open resource file to context for agent
* Simplify resource resolution
* Skip initialize vfs
* Restore ff
* Add back try catch
* Remove redundant code
* Remove json serialization/deserialization loop
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* Feat(references) add at to reference sim resources(#3560)
* feat(chat) add at sign
* Address bugbot issues
* Remove extra chatcontext defs
* Add table and file to schema
* Add icon to chip for files
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* improvement(refactor): move to soft deletion of resources + reliability improvements (#3561)
* improvement(deletion): migrate to soft deletion of resources
* progress
* scoping fixes
* round of fixes
* deduplicated name on workflow import
* fix tests
* add migration
* cleanup dead code
* address bugbot comments
* optimize query
* feat(sim-mailer): email inbox for mothership with chat history and plan gating (#3558)
* feat(sim-mailer): email inbox for mothership with chat history and plan gating
* revert hardcoded ff
* fix(inbox): address PR review comments - plan enforcement, idempotency, webhook auth
- Enforce Max plan at API layer: hasInboxAccess() now checks subscription tier (>= 25k credits or enterprise)
- Add idempotency guard to executeInboxTask() to prevent duplicate emails on Trigger.dev retries
- Add AGENTMAIL_WEBHOOK_SECRET env var for webhook signature verification (Bearer token)
* improvement(inbox): harden security and efficiency from code audit
- Use crypto.timingSafeEqual for webhook secret comparison (prevents timing attacks)
- Atomic claim in executor: WHERE status='received' prevents duplicate processing on retries
- Parallelize hasInboxAccess + getUserEntityPermissions in all API routes (reduces latency)
- Truncate email body at webhook insertion (50k char limit, prevents unbounded DB storage)
- Harden escapeAttr with angle bracket and single quote escaping
- Rename use-inbox.ts to inbox.ts (matches hooks/queries/ naming convention)
* fix(inbox): replace Bearer token auth with proper Svix HMAC-SHA256 webhook verification
- Use per-workspace webhook secret from DB instead of global env var
- Verify AgentMail/Svix signatures: HMAC-SHA256 over svix-id.timestamp.body
- Timing-safe comparison via crypto.timingSafeEqual
- Replay protection via timestamp tolerance (5 min window)
- Join mothershipInboxWebhook in workspace lookup (zero additional DB calls)
- Remove dead AGENTMAIL_WEBHOOK_SECRET env var
- Select only needed workspace columns in webhook handler
* fix(inbox): require webhook secret — reject requests when secret is missing
Previously, if the webhook secret was missing from the DB (corrupted state),
the handler would skip verification entirely and process the request
unauthenticated. Now all three conditions are hard requirements: secret must
exist in DB, Svix headers must be present, and signature must verify.
* fix(inbox): address second round of PR review comments
- Exclude rejected tasks from rate limit count to prevent DoS via spam
- Strip raw HTML from LLM output before marked.parse to prevent XSS in emails
- Track responseSent flag to prevent duplicate emails when DB update fails after send
* fix(inbox): address third round of PR review comments
- Use dynamic isHosted from feature-flags instead of hardcoded true
- Atomic JSON append for chat message persistence (eliminates read-modify-write race)
- Handle cutIndex === 0 in stripQuotedReply (body starts with quote)
- Clean up orphan mothershipInboxWebhook row on enableInbox rollback
- Validate status query parameter against enum in tasks API
* fix(inbox): validate cursor param, preserve code blocks in HTML stripping
- Validate cursor date before using in query (return 400 for invalid)
- Split on fenced code blocks before stripping HTML tags to preserve
code examples in email responses
* fix(inbox): return 500 on webhook server errors to enable Svix retries
* fix(inbox): remove isHosted guard from hasInboxAccess — feature flag is sufficient
* fix(inbox): prevent double-enable from deleting webhook secret row
* fix(inbox): null-safe stripThinkingTags, encode URL params, surface remove-sender errors
- Guard against null result.content in stripThinkingTags
- Use encodeURIComponent on all AgentMail API path parameters
- Surface handleRemoveSender errors to the user instead of swallowing
* improvement(inbox): remove unused types, narrow SELECT queries, fix optimistic ID collision
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(inbox): add keyboard accessibility to clickable task rows
* fix(inbox): use Svix library for webhook verification, fix responseSent flag, prevent inbox enumeration
- Replace manual HMAC-SHA256 verification with official Svix library per AgentMail docs
- Fix responseSent flag: only set true when email delivery actually succeeds
- Return consistent 401 for unknown inbox and bad signature to prevent enumeration
- Make AgentMailInbox.organization_id optional to match API docs
* chore(db): rebase inbox migration onto feat/mothership-copilot (0172 → 0173)
Sync schema with target branch and regenerate migration as 0173
to avoid conflicts with 0172_silky_magma on feat/mothership-copilot.
* fix(db): rebase inbox migration to 0173 after feat/mothership-copilot divergence
Target branch added 0172_silky_magma, so our inbox migration is now 0173_youthful_stryfe.
* fix(db): regenerate inbox migration after rebase on feat/mothership-copilot
* fix(inbox): case-insensitive email match and sanitize javascript: URIs in email HTML
- Use lower() in isSenderAllowed SQL to match workspace members regardless
of email case stored by auth provider
- Strip javascript:, vbscript:, and data: URIs from marked HTML output to
prevent XSS in outbound email responses
* fix(inbox): case-insensitive email match in resolveUserId
Consistent with the isSenderAllowed fix — uses lower() so mixed-case
stored emails match correctly, preventing silent fallback to workspace owner.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Kb args
* refactor(resource): remove logs-specific escape hatches from Resource abstraction
Logs now composes ResourceHeader + ResourceOptionsBar + ResourceTable directly
instead of using Resource with contentOverride/overlay escape hatches. Removes
contentOverride, onLoadMore, hasMore, isLoadingMore from ResourceProps. Adds
ColumnOption to barrel export and fixes table.tsx internal import.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(sim-mailer): download email attachments and pass to LLM as multimodal content
Attachments were only passed as metadata text in the email body. Now downloads
actual file bytes from AgentMail, converts via createFileContent (same path as
interactive chat), and sends as fileAttachments to the orchestrator. Also
parallelizes attachment fetching with workspace context loading, and downloads
multiple attachments concurrently via Promise.allSettled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(connector): add Gmail knowledge base connector with thread-based sync and filtering
Syncs email threads from Gmail into knowledge bases with configurable filters:
label scoping, date range presets, promotions/social exclusion, Gmail search
syntax support, and max thread caps to keep KB size manageable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(connector): add Outlook knowledge base connector with conversation grouping and filtering
Syncs email conversations from Outlook/Office 365 via Microsoft Graph API.
Groups messages by conversationId into single documents. Configurable filters:
folder selection, date range presets, Focused Inbox, KQL search syntax, and
max conversation caps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* cleanup resource definition
* feat(connectors): add 8 knowledge base connectors — Zendesk, Intercom, ServiceNow, Google Sheets, Microsoft Teams, Discord, Google Calendar, Reddit
Each connector syncs documents into knowledge bases with configurable filtering:
- Zendesk: Help Center articles + support tickets with status/locale filters
- Intercom: Articles + conversations with state filtering
- ServiceNow: KB articles + incidents with state/priority/category filters
- Google Sheets: Spreadsheet tabs as LLM-friendly row-by-row documents
- Microsoft Teams: Channel messages (Slack-like pattern) via Graph API
- Discord: Channel messages with bot token auth
- Google Calendar: Events with date range presets and attendee metadata
- Reddit: Subreddit posts with top comments, sort/time filters
All connectors validated against official API docs with bug fixes applied.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(inbox): fetch real attachment binary from presigned URL and persist for chat display
The AgentMail attachment endpoint returns JSON metadata with a download_url,
not raw binary. We were base64-encoding the JSON text and sending it to the
LLM, causing provider rejection. Now we parse the metadata, fetch the actual
file from the presigned URL, upload it to copilot storage, and persist it on
the chat message so images render inline with previews.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* added agentmail domain for mailer
* added docs for sim mailer
* fix(resource) handle resource deletion deletion (#3568)
* Add handle dragging tab to input chat
* Add back delete tools
* Handle deletions properly with resources view
* Fix lint
* Add permisssions checking
* Skip resource_added event when resource is deleted
* Pass workflow id as context
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* update docs styling, add delete confirmation on inbox
* Fix fast edit route
* updated docs styling, added FAQs, updated content
* upgrade turbo
* fix(knowledge) use consistent empty state for documents page
Replace the centered "No documents yet" text with the standard Resource
table empty state (column headers + create row), matching all other
resource pages. Move "Upload documents" from header action to table
create row as "New documents".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(notifications): polish modal styling, credential display, and trigger filters (#3571)
* fix(notifications): polish modal styling, credential display, and trigger filters
- Show credential display name instead of raw account ID in Slack account selector
- Fix label styling to use default Label component (text-primary) for consistency
- Fix modal body spacing with proper top padding after tab bar
- Replace list-card skeleton with form-field skeleton matching actual layout
- Replace custom "Select a Slack account first" box with disabled Combobox (dependsOn pattern)
- Use proper Label component in WorkflowSelector with consistent gap spacing
- Add overflow badge pattern (slice + +N) to level and trigger filter badges
- Use dynamic trigger options from getTriggerOptions() instead of hardcoded CORE_TRIGGER_TYPES
- Relax API validation to accept integration trigger types (z.string instead of z.enum)
- Deduplicate account rows from credential leftJoin in accounts API
- Extract getTriggerOptions() to module-level constants to avoid per-render calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(notifications): address PR review feedback
- Restore accountId in displayName fallback chain (credentialDisplayName || accountId || providerId)
- Add .default([]) to triggerFilter in create schema to preserve backward compatibility
- Treat empty triggerFilter as "match all" in notification matching logic
- Remove unreachable overflow badge for levelFilter (only 2 possible values)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(settings): add spacing to Sim Keys toggle and replace Sim Mailer icon with Send
Add 24px top margin to the "Allow personal Sim keys" toggle so it doesn't
sit right below the empty state. Replace the Mail envelope icon for Sim
Mailer with a new Send (paper plane) icon matching the emcn icon style.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* standardize back buttons in settings
* feat(restore) Add restore endpoints and ui (#3570)
* Add restore endpoints and ui
* Derive toast from notification
* Auth user if workspaceid not found
* Fix recently deleted ui
* Add restore error toast
* Fix deleted at timestamp mismatch
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* fix type errors
* Lint
* improvements: ui/ux around mothership
* reactquery best practices, UI alignment in restore
* clamp logs panel
* subagent thinking text
* fix build, speedup tests by up to 40%
* Fix fast edit
* Add download file shortcut on mothership file view
* fix: SVG file support in mothership chat and file serving
- Send SVGs as document/text-xml to Claude instead of unsupported
image/svg+xml, so the mothership can actually read SVG content
- Serve SVGs inline with proper content type and CSP sandbox so
chat previews render correctly
- Add SVG preview support in file viewer (sandboxed iframe)
- Derive IMAGE_MIME_TYPES from MIME_TYPE_MAPPING to reduce duplication
- Add missing webp to contentTypeMap, SAFE_INLINE_TYPES, binaryExtensions
- Consolidate PREVIEWABLE_EXTENSIONS into preview-panel exports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace image/* wildcard with explicit supported types in file picker
The image/* accept attribute allowed users to select BMP, TIFF, HEIC,
and other image types that are rejected server-side. Replace with the
exact set of supported image MIME types and extensions to match the
copilot upload validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Context tags
* Fix lint
* improvement: chat and terminal
---------
Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(execution): finalize runs before wrapper recovery
* fix(async): preserve execution correlation across queued runs
* fix(webhooks): pass correlation into preprocessing
* style(webhooks): normalize webhook executor formatting
* fix(async): avoid pre-starting queued execution logs
Let executeWorkflowCore own normal-path logging start so queued workflow and schedule executions persist the richer deployment and environment metadata instead of an earlier placeholder start record.
* fix(async): harden execution finalization guards
Prevent leaked core finalization markers from accumulating while keeping outer recovery paths idempotent. Preserve best-effort logging completion by reusing settled completion promises instead of reopening duplicate terminal writes.
* fix(async): preserve outcomes during cleanup
Keep execution finalization cleanup best-effort so cancellation cleanup failures do not overwrite successful or failed outcomes. Restore webhook processor formatting to the repository Biome style to avoid noisy formatter churn.
* fix(async): keep execution finalization state consistent
Retry minimal logging for early failures, only mark core finalization after a log row actually completes, and let paused completions fall back cleanly.
* fix(async): clean stale finalization guards
Scan all finalized execution ids during TTL cleanup so refreshed keys cannot keep expired guards alive, and cover the reused-id ordering regression.
* fix(async): retry failed error finalization
Allow error finalization to retry after a non-error completion and fallback both fail, and always persist failed/error semantics for completeWithError.
* fix(webhooks): reuse preprocessing execution ids
Thread preprocessing execution identity into queued webhook execution so both phases share the same correlation and logs.
---------
Co-authored-by: test <test@example.com>
* fix(grain): update to stable version of API
* fix prewebhook lookup
* update pending webhook verification infra
* add generic webhook test event verification subblock
* feat(google-ads): add google ads integration for campaign and ad performance queries
* fix(google-ads): add input validation for GAQL query parameters
* fix(google-ads): remove deprecated pageSize param, fix searchSettings nesting, add missing date ranges
* fix(google-ads): validate managerCustomerId before use in login-customer-id header
* chore(docs): regenerate docs after google ads integration
* fix(google-ads): use centralized scope utilities and add type re-export
- Replace hardcoded scopes in auth.ts with getCanonicalScopesForProvider('google-ads')
- Replace hardcoded requiredScopes in block with getScopesForService('google-ads')
- Add type re-export from index.ts barrel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(google-ads): add userinfo scopes to oauth provider config
Align google-ads with all other Google services by including
userinfo.email and userinfo.profile scopes in the centralized
OAUTH_PROVIDERS config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(executor): skip Response block formatting for internal JWT callers
The workflow executor tool received `{error: true}` despite successful child
workflow execution when the child had a Response block. This happened because
`createHttpResponseFromBlock()` hijacked the response with raw user-defined
data, and the executor's `transformResponse` expected the standard
`{success, executionId, output, metadata}` wrapper.
Fix: skip Response block formatting when `authType === INTERNAL_JWT` since
Response blocks are designed for external API consumers, not internal
workflow-to-workflow calls. Also extract `AuthType` constants from magic
strings across all auth type comparisons in the codebase.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(executor): add route-level tests for Response block auth gating
Verify that internal JWT callers receive standard format while external
callers (API key, session) get Response block formatting. Tests the
server-side condition directly using workflowHasResponseBlock and
createHttpResponseFromBlock with AuthType constants.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(testing): add AuthType to all hybrid auth test mocks
Route code now imports AuthType from @/lib/auth/hybrid, so test mocks
must export it too. Added AuthTypeMock to @sim/testing and included it
in all 15 test files that mock the hybrid auth module.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Issue keys are self-sufficient identifiers in Jira (e.g., PROJ-123).
The manualIssueKey field is a text input where users type the key directly,
so it should not depend on projectId/manualProjectId. This dependency
caused the field to clear unnecessarily when the project selection changed.
* feat(slack): add email field to get user and list users tools
* fix(slack): use empty string fallback for email and make type non-optional
* fix(slack): comment out users:read.email scope pending app review
Use explicit hyphen separator instead of relying on slice offset to
implicitly include the hyphen in the suffix, making the intent clearer.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The GET /rest/api/3/search/jql endpoint requires an explicit `fields`
parameter to return issue data. Without it, only the issue `id` is
returned with all other fields empty. This adds `fields=*all` as the
default when the user doesn't specify custom fields.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(fathom): add Fathom AI Notetaker integration
* fix(fathom): address PR review feedback
- Add response.ok checks to all 5 tool transformResponse functions
- Fix include_summary default to respect explicit false (check undefined)
- Add externalId validation before URL interpolation in webhook deletion
* fix(fathom): address second round PR review feedback
- Remove redundant 204 status check in deleteFathomWebhook (204 is ok)
- Use consistent undefined-guard pattern for all include flags
- Add .catch() fallback on webhook creation JSON parse
- Change recording_id default from 0 to null to avoid misleading sentinel
* fix(fathom): add missing crm_matches to list_meetings transform and fix action_items type
- Add crm_matches pass-through in list_meetings transform (was silently dropped)
- Fix action_items type to match API schema (description, user_generated, completed, etc.)
- Add crm_matches type with contacts, companies, deals, error fields
* fix(fathom): guard against undefined webhook id on creation success
* fix(fathom): add type to nested trigger outputs and fix boolean coercion
- Add type: 'object' to recorded_by and default_summary trigger outputs
- Use val === true || val === 'true' pattern for include flag coercion
to safely handle both boolean and string values from providerConfig
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
* fix(traces): prevent condition blocks from rendering source agent's timeSegments
Condition blocks spread their source block's entire output into their own
output. When the source is an agent, this leaked providerTiming/timeSegments
into the condition's output, causing buildTraceSpans to create "Initial
response" as a child of the condition span instead of the agent span.
Two fixes:
- Skip timeSegment child creation for condition block types in buildTraceSpans
- Filter execution metadata (providerTiming, tokens, toolCalls, model, cost)
from condition handler's filterSourceOutput
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(traces): guard condition blocks from leaked metadata on old persisted logs
Extend isConditionBlockType guards to also skip setting span.providerTiming,
span.cost, span.tokens, and span.model for condition blocks. This ensures
old persisted logs (recorded before the filterSourceOutput fix) don't display
misleading execution metadata on condition spans.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(traces): guard toolCalls fallback path for condition blocks on old logs
The else branch that extracts toolCalls from log.output also needs a
condition block guard, otherwise old persisted logs with leaked toolCalls
from the source agent would render on the condition span.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(traces): extract isCondition to local variable for readability
Cache isConditionBlockType(log.blockType) in a local const at the top
of the forEach loop instead of calling it 6 times per iteration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(blocks): remap condition/router IDs when duplicating blocks
Condition and router blocks embed IDs in the format `{blockId}-{suffix}`
inside their subBlock values and edge sourceHandles. When blocks were
duplicated, these IDs were not updated to reference the new block ID,
causing duplicate handle IDs and broken edge routing.
Fixes all four duplication paths: single block duplicate, copy/paste,
workflow duplication (server-side), and workflow import.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(blocks): deep-clone subBlocks before mutating condition IDs
Shallow copy of subBlocks meant remapConditionIds could mutate the
source data (clipboard on repeated paste, or input workflowState on
import). Deep-clone subBlocks in both regenerateBlockIds and
regenerateWorkflowIds to prevent this.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(blocks): remap condition IDs in regenerateWorkflowStateIds (template use)
The template use code path was missing condition/router ID remapping,
causing broken condition blocks when creating workflows from templates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(gmail): RFC 2047 encode subject headers for non-ASCII characters
* Fix RFC 2047 encoded word length limit
Split long email subjects into multiple RFC 2047 encoded words to comply with the 75-character limit per RFC 2047 Section 2. Each encoded word now contains at most 45 bytes of UTF-8 content (producing max 60 chars of base64 + 12 chars overhead = 72 total). Multiple encoded words are separated by CRLF + space (folding whitespace).
Applied via @cursor push command
* fix(gmail): split RFC 2047 encoded words on character boundaries
* fix(gmail): simplify RFC 2047 encoding to match Google's own sample
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(webhooks): eliminate redundant DB queries from webhook execution path
* chore(webhooks): remove implementation-detail comments
* fix(webhooks): restore auth-first ordering and add credential resolution warning
- Revert parallel auth+preprocessing to sequential auth→preprocessing
to prevent rate-limit exhaustion via unauthenticated requests
- Add warning log when credential account resolution fails in background job
* fix(webhooks): restore auth-before-reachability ordering and remove dead credentialAccountUserId field
- Move reachability test back after auth to prevent path enumeration
- Remove dead credentialAccountUserId from WebhookExecutionPayload
- Simplify credential resolution condition in background job
* fix(security): add SSRF protection to database tools and webhook delivery
* fix(security): address review comments on SSRF PR
- Remove Promise.race timeout pattern to avoid unhandled rejections
(http.request timeout is sufficient for webhook delivery)
- Use safeCompare in verifyCronAuth instead of inline HMAC logic
- Strip IPv6 brackets before validateDatabaseHost in Redis route
* fix(security): allow HTTP webhooks and fix misleading MCP error docs
- Add allowHttp option to validateExternalUrl, validateUrlWithDNS,
and secureFetchWithValidation to support HTTP webhook URLs
- Pass allowHttp: true for webhook delivery and test endpoints
- Fix misleading JSDoc on createMcpErrorResponse (doesn't log errors)
- Mark unused error param with underscore prefix
* fix(security): forward allowHttp option through redirect validation
Pass allowHttp to validateUrlWithDNS in the redirect handler of
secureFetchWithPinnedIP so HTTP-to-HTTP redirects work when allowHttp
is enabled for webhook delivery.
* fix(security): block localhost when allowHttp is enabled
When allowHttp is true (user-supplied webhook URLs), explicitly block
localhost/loopback in both validateExternalUrl and validateUrlWithDNS
to prevent SSRF against internal services.
* fix(security): always strip multi-line content in sanitizeConnectionError
Take the first line of the error message regardless of length to
prevent leaking sensitive data from multi-line error messages.
* fix(parallel): align integration with Parallel AI API docs
* fix(parallel): keep processor subBlock ID for backwards compatibility
* fix(parallel): move error field to top level per ToolResponse interface
* fix(parallel): guard research_input and prevent domain leakage across operations
* fix(parallel): make url/title nullable in types to match transformResponse
* fix(parallel): revert search_queries param type to string for backwards compatibility
* feat(hosted keys): Implement serper hosted key
* Handle required fields correctly for hosted keys
* Add rate limiting (3 tries, exponential backoff)
* Add custom pricing, switch to exa as first hosted key
* Add telemetry
* Consolidate byok type definitions
* Add warning comment if default calculation is used
* Record usage to user stats table
* Fix unit tests, use cost property
* Include more metadata in cost output
* Fix disabled tests
* Fix spacing
* Fix lint
* Move knowledge cost restructuring away from generic block handler
* Migrate knowledge unit tests
* Lint
* Fix broken tests
* Add user based hosted key throttling
* Refactor hosted key handling. Add optimistic handling of throttling for custom throttle rules.
* Remove research as hosted key. Recommend BYOK if throtttling occurs
* Make adding api keys adjustable via env vars
* Remove vestigial fields from research
* Make billing actor id required for throttling
* Switch to round robin for api key distribution
* Add helper method for adding hosted key cost
* Strip leading double underscores to avoid breaking change
* Lint fix
* Remove falsy check in favor for explicit null check
* Add more detailed metrics for different throttling types
* Fix _costDollars field
* Handle hosted agent tool calls
* Fail loudly if cost field isn't found
* Remove any type
* Fix type error
* Fix lint
* Fix usage log double logging data
* Fix test
---------
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
* feat(evernote): add Evernote integration with 11 tools
* fix(evernote): fix signed integer mismatch in Thrift version check
* fix(evernote): fix exception field mapping and add sandbox support
* fix(evernote): address PR review feedback
* fix(evernote): clamp maxNotes to Evernote's 250 limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(oauth): centralize scopes and remove dead scope evaluation code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(oauth): fix stale scope-descriptions.ts references and add test coverage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): resolve env var references at design time for selector context
Selectors now resolve {{ENV_VAR}} references before building context and
returning dependency values to consumers, enabling env-var-based credentials
(e.g. {{SLACK_BOT_TOKEN}}) to work with selector dropdowns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): prevent unresolved env var templates from leaking into context
- Fall back to undefined instead of raw template string when env var is
missing from store, so the null-check in the context loop discards it
- Use resolvedDetailId in query cache key so React Query refetches when
the underlying env var value changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): use || for consistent empty-string env var handling
Align use-selector-setup.ts with use-selector-query.ts by using || instead
of ?? so empty-string env var values are treated as unset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(selectors): add dropdown selectors for 14 integrations
* fix(selectors): secure OAuth tokens in JSM and Confluence selector routes
Convert JSM selector-servicedesks, selector-requesttypes, and Confluence
selector-spaces routes from GET (with access token in URL query params) to
POST with authorizeCredentialUse + refreshAccessTokenIfNeeded pattern. Also
adds missing ensureCredential guard to microsoft.planner.plans registry entry.
* fix(selectors): use sanitized serviceDeskId and encode SharePoint siteId
Use serviceDeskIdValidation.sanitized instead of raw serviceDeskId in JSM
request types URL. Add encodeURIComponent to SharePoint siteId to prevent
URL path injection.
* lint
* fix(selectors): revert encodeURIComponent on SharePoint siteId
SharePoint site IDs use the format "hostname,guid,guid" with commas that
must remain unencoded for the Microsoft Graph API. The encodeURIComponent
call would convert commas to %2C and break the API call.
* fix(selectors): use sanitized cloudId in Confluence and JSM route URLs
Use cloudIdValidation.sanitized instead of raw cloudId in URL construction
for consistency with the validation pattern, even though the current
validator returns the input unchanged.
* fix(selectors): add missing context fields to resolution, ensureCredential to sharepoint.lists, and siteId validation
- Add baseId, datasetId, serviceDeskId to SelectorResolutionArgs,
ExtendedSelectorContext, extractExtendedContext, useSelectorDisplayName,
and resolveSelectorForSubBlock so cascading selectors resolve correctly
through the resolution path.
- Add ensureCredential guard to sharepoint.lists registry entry.
- Add regex validation for SharePoint siteId format (hostname,GUID,GUID).
* fix(selectors): rename advanced subBlock IDs to avoid canonicalParamId clashes
Rename all advanced-mode subBlock IDs that matched their canonicalParamId
to use a `manual*` prefix, following the established convention
(e.g., manualSiteId, manualCredential). This prevents ambiguity between
subBlock IDs and canonical parameter names in the serialization layer.
25 renames across 14 blocks: baseId→manualBaseId, tableId→manualTableId,
workspace→manualWorkspace, objectType→manualObjectType, etc.
* Revert "fix(selectors): rename advanced subBlock IDs to avoid canonicalParamId clashes"
This reverts commit 4e30161c68.
* fix(selectors): rename canonicalParamIds to avoid subBlock ID clashes
Prefix all clashing canonicalParamId values with `selected_` so they
don't match any subBlock ID. Update each block's `inputs` section and
`tools.config.params` function to destructure the new canonical names
and remap them to the original tool param names. SubBlock IDs and tool
definitions remain unchanged for backwards compatibility.
Affected: 25 canonical params across 14 blocks (airtable, asana, attio,
calcom, confluence, google_bigquery, google_tasks, jsm, microsoft_planner,
notion, pipedrive, sharepoint, trello, zoom).
* fix(selectors): rename pre-existing driveId and files canonicalParamIds in SharePoint
Apply the same selected_ prefix convention to the pre-existing SharePoint
driveId and files canonical params that clashed with their subBlock IDs.
* style: format long lines in calcom, pipedrive, and sharepoint blocks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): resolve cascading context for selected_ canonical params and normalize Asana response
Strip `selected_` prefix from canonical param IDs when mapping to
SelectorContext fields so cascading selectors (Airtable base→table,
BigQuery dataset→table, JSM serviceDesk→requestType) correctly
propagate parent values.
Normalize Asana workspaces route to return `{ id, name }` instead of
`{ gid, name }` for consistency with all other selector routes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): replace hacky prefix stripping with explicit CANONICAL_TO_CONTEXT mapping
Replace CONTEXT_FIELD_SET (Record<string, true>) with CANONICAL_TO_CONTEXT
(Record<string, keyof SelectorContext>) that explicitly maps canonical
param IDs to their SelectorContext field names.
This properly handles the selected_ prefix aliases (e.g. selected_baseId
→ baseId) without string manipulation, and removes the unsafe
Record<string, unknown> cast.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(selectors): remove unnecessary selected_ prefix from canonicalParamIds
The selected_ prefix was added to avoid a perceived clash between
canonicalParamId and subBlock id values, but this clash does not
actually cause any issues — pre-existing blocks on main (Google Sheets,
Webflow, SharePoint) already use matching values successfully.
Remove the prefix from all 14 blocks, revert use-selector-setup.ts to
the simple CONTEXT_FIELD_SET pattern, and simplify tools.config.params
functions that were only remapping the prefix back.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): add spaceId selector pair to Confluence V2 block
The V2 block was missing the spaceSelector basic-mode selector that the
V1 (Legacy) block already had.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(selectors): revert V1 block changes, add selectors to Notion V1 for V2 inheritance
Confluence V1: reverted to main state (V2 has its own subBlocks).
Notion V1: added selector pairs per-operation since V2 inherits
subBlocks, inputs, and params from V1.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): audit fixes for auth patterns, registry gaps, and display name resolution
- Convert Microsoft Planner plans/tasks routes from GET+getSession to POST+authorizeCredentialUse
- Add fetchById to microsoft.planner (tasks) and sharepoint.sites registry entries
- Add ensureCredential to sharepoint.sites and microsoft.planner registry fetchList
- Update microsoft.planner.plans registry to use POST method
- Add siteId, collectionId, spreadsheetId, fileId to SelectorDisplayNameArgs and caller
- Add fileId to SelectorResolutionArgs and resolution context
- Fix Zoom topicUpdate visibility in basic mode (remove mode:'advanced')
- Change Zoom meetings selector to fetch upcoming_meetings instead of only scheduled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: lint formatting fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): consolidate Notion canonical param pairs into array conditions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): add missing selectorKey to Confluence V1 page selector
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): use sanitized IDs in URLs, convert SharePoint routes to POST+authorizeCredentialUse
- Use planIdValidation.sanitized in MS Planner tasks fetch URL
- Convert sharepoint/lists and sharepoint/sites from GET+getSession to POST+authorizeCredentialUse
- Update registry entries to match POST pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): revert Zoom meetings type to scheduled for broader compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): add SharePoint site ID validator, fix cascading selector display name fallbacks
- Add validateSharePointSiteId to input-validation.ts
- Use validation util in SharePoint lists route instead of inline regex
- Add || fallback to selector IDs in workflow-block.tsx so cascading
display names resolve in basic mode (baseSelector, planSelector, etc.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): hoist requestId before try block in all selector routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): hoist requestId before try block in Trello boards route
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): guard selector queries against unresolved variable references
Skip fetchById and context population when values are design-time
placeholders (<Block.output> or {{ENV_VAR}}) rather than real IDs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(selectors): replace hardcoded display name fallbacks with canonical-aware resolution
Use resolveDependencyValue to resolve context values for
useSelectorDisplayName, eliminating manual || getStringValue('*Selector')
fallbacks that required updating for each new selector pair.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): tighten SharePoint site ID validation to exclude underscores
SharePoint composite site IDs use hostname,guid,guid format where only
alphanumerics, periods, hyphens, and commas are valid characters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): ensure string IDs in Pipedrive/Cal.com routes, fix Trello closed board filter
Pipedrive pipelines and Cal.com event-types/schedules routes now
consistently return string IDs via String() conversion.
Trello boards route no longer filters out closed boards, preserving
them for fetchById lookups. The closed filter is applied only in the
registry's fetchList so archived boards don't appear in dropdowns
but can still be resolved by ID for display names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): convert Zoom meeting IDs to strings for consistency
Zoom API returns numeric meeting IDs. Convert with String() to match
the string ID convention used by all other selector routes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): align registry types with route string ID returns
Routes already convert numeric IDs to strings via String(), so update
the registry types (CalcomEventType, CalcomSchedule, PipedrivePipeline,
ZoomMeeting) from id: number to id: string and remove the now-redundant
String() coercions in fetchList/fetchById.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(reddit): add 5 new tools, fix bugs, and audit all endpoints against API docs
* fix(reddit): add optional chaining, pagination wiring, and trim safety
- Add optional chaining on children?.[0] in get_posts, get_controversial,
search, and get_comments to prevent TypeError on unexpected API responses
- Wire after/before pagination params to get_messages block operation
- Use ?? instead of || for get_comments limit to handle 0 correctly
- Add .trim() on postId in get_comments URL path
* chore(reddit): remove unused output property constants from types.ts
* fix(reddit): add HTTP error handling to GET tools
Add !response.ok guards to get_me, get_user, get_subreddit_info,
and get_messages to return success: false on non-2xx responses
instead of silently returning empty data with success: true.
* fix(reddit): add input validation and HTTP error guards
- Add validateEnum/validatePathSegment to prevent URL path traversal
- Add !response.ok guards to send_message and reply tools
- Centralize subreddit validation in normalizeSubreddit
* feat(slack): add new tools and user selectors
* fix(slack): fix download fileName param and canvas error handling
* fix(slack): use markdown format for canvas rename title_content
* fix(slack): rename channel output to channelInfo and document presence API limitation
* lint
* fix(chat): use explicit trigger type check instead of heuristic for chat guard (#3419)
* fix(chat): use explicit trigger type check instead of heuristic for chat guard
* fix(chat): remove heuristic fallback from isExecutingFromChat
Use only overrideTriggerType === 'chat' instead of also checking
for 'input' in workflowInput, which can false-positive on manual
executions with workflow input.
* fix(chat): use isExecutingFromChat variable consistently in callbacks
Replace inline overrideTriggerType !== 'chat' checks with
!isExecutingFromChat to stay consistent with the rest of the function.
* fix(slack): add missing fields to SlackChannel interface
* fix(slack): fix canvas transformResponse type mismatch
Provide required output fields on error path to match SlackCanvasResponse type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(slack): move error field to top level in canvas transformResponse
The error field belongs on ToolResponse, not inside the output object.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(chat): use explicit trigger type check instead of heuristic for chat guard
* fix(chat): remove heuristic fallback from isExecutingFromChat
Use only overrideTriggerType === 'chat' instead of also checking
for 'input' in workflowInput, which can false-positive on manual
executions with workflow input.
* fix(chat): use isExecutingFromChat variable consistently in callbacks
Replace inline overrideTriggerType !== 'chat' checks with
!isExecutingFromChat to stay consistent with the rest of the function.
* fix(memory): add Bun.gc, stream cancellation, and unconsumed fetch drains
* fix(memory): await reader.cancel() and use non-blocking Bun.gc
* fix(memory): update Bun.gc comment to match non-blocking call
* fix(memory): use response.body.cancel() instead of response.text() for drains
* fix(executor): flush TextDecoder after streaming loop for multi-byte chars
* fix(memory): use text() drain for SecureFetchResponse which lacks body property
* fix(chat): prevent premature isExecuting=false from killing chat stream
The onExecutionCompleted/Error/Cancelled callbacks were setting
isExecuting=false as soon as the server-side SSE stream completed.
For chat executions, this triggered a useEffect in chat.tsx that
cancelled the client-side stream reader before it finished consuming
buffered data — causing empty or partial chat responses.
Skip the isExecuting=false in these callbacks for chat executions
since the chat's own finally block handles cleanup after the stream
is fully consumed.
* fix(chat): remove useEffect anti-pattern that killed chat stream on state change
The effect reacted to isExecuting becoming false to clean up streams,
but this is an anti-pattern per React guidelines — using state changes
as a proxy for events. All cleanup cases are already handled by proper
event paths: stream done (processStreamingResponse), user cancel
(handleStopStreaming), component unmount (cleanup effect), and
abort/error (catch block).
* fix(servicenow): remove invalid string comparison on numeric offset param
* upgrade turborepo
* feat(servicenow): add offset and display value params to read records
* fix(servicenow): address greptile review feedback for offset and displayValue
* fix(servicenow): handle offset=0 correctly in pagination
* fix(servicenow): guard offset against empty string in URL builder
* fix(subflows): recurse into all descendants for lock, enable, and protection checks
* fix(subflows): prevent container resize on initial render and clean up code
- Add canvasReadyRef to skip container dimension recalculation during
ReactFlow init — position changes from extent clamping fired before
block heights are measured, causing containers to resize on page load
- Resolve globals.css merge conflict, remove global z-index overrides
(handled via ReactFlow zIndex prop instead)
- Clean up subflow-node: hoist static helpers to module scope, remove
unused ref, fix nested ternary readability, rename outlineColor→ringColor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(subflows): use full ancestor-chain protection for descendant enable-toggle
The enable-toggle for descendants was checking only direct `locked` status
instead of walking the full ancestor chain via `isBlockProtected`. This meant
a block nested 2+ levels inside a locked subflow could still be toggled.
Also added TSDoc clarifying why boxShadow works for subflow ring indicators.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* revert(subflows): remove canvasReadyRef height-gating approach
The canvasReadyRef gating in onNodesChange didn't fully fix the
container resize-on-load issue. Reverting to address properly later.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unintentional edge-interaction CSS from globals
Leftover from merge conflict resolution — not part of this PR's changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(editor): correct isAncestorLocked when block and ancestor both locked, restore fade-in transition
isAncestorLocked was derived from isBlockProtected which short-circuits
on block.locked, so a self-locked block inside a locked ancestor showed
"Unlock block" instead of "Ancestor container is locked". Now walks the
ancestor chain independently.
Also restores the accidentally removed transition-opacity duration-150
class on the ReactFlow container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(subflows): use full ancestor-chain protection for top-level enable-toggle, restore edge-label z-index
The top-level block check in batchToggleEnabled used block.locked (self
only) while descendants used isBlockProtected (full ancestor chain). A
block inside a locked ancestor but not itself locked would bypass the
check. Now all three layers (store, collaborative hook, DB operations)
consistently use isBlockProtected/isDbBlockProtected at both levels.
Also restores the accidentally removed edge-labels z-index rule, bumped
from 60 to 1001 so labels render above child nodes (zIndex: 1000).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(subflows): extract isAncestorProtected utility, add cycle detection to all traversals
- Extract isAncestorProtected from utils.ts so editor.tsx doesn't
duplicate the ancestor-chain walk. isBlockProtected now delegates to it.
- Add visited-set cycle detection to all ancestor walks
(isBlockProtected, isAncestorProtected, isDbBlockProtected) and
descendant searches (findAllDescendantNodes, findDbDescendants) to
guard against corrupt parentId references.
- Document why click-catching div has no event bubbling concern
(ReactFlow renders children as viewport siblings, not DOM children).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
description: Create or update a Sim integration block with correct subBlocks, conditions, dependsOn, modes, canonicalParamId usage, outputs, and tool wiring. Use when working on `apps/sim/blocks/blocks/{service}.ts` or aligning a block with its tools.
---
# Add Block Skill
You are an expert at creating block configurations for Sim. You understand the serializer, subBlock types, conditions, dependsOn, modes, and all UI patterns.
## Your Task
When the user asks you to create a block:
1. Create the block file in `apps/sim/blocks/blocks/{service}.ts`
2. Configure all subBlocks with proper types, conditions, and dependencies
serviceId:'{service}',// Must match OAuth provider service key
requiredScopes: getScopesForService('{service}'),// Import from @/lib/oauth/utils
placeholder:'Select account',
required: true,
}
```
**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`.
**Scope descriptions:** When adding a new OAuth provider, also add human-readable descriptions for all scopes in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`.
-`'json-object'` - Raw JSON (adds "no markdown" instruction)
-`'json-schema'` - JSON Schema definitions
-`'sql-query'` - SQL statements
-`'timestamp'` - Adds current date/time context
## Tools Configuration
**Important:**`tools.config.tool` runs during serialization before variable resolution. Put `Number()` and other type coercions in `tools.config.params` instead, which runs at execution time after variables are resolved.
**Preferred:** Use tool names directly as dropdown option IDs to avoid switch cases:
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:
```typescript
outputs:{
// BAD: Opaque json with no info about what's inside
plan:{type:'json',description:'Zone plan information'},
// GOOD: Describe the known fields in the description
plan:{
type:'json',
description:'Zone plan information (id, name, price, currency, frequency, is_subscribed)',
},
// BEST: Use nested output definition when the shape is stable and well-known
See the `/add-trigger` skill for creating triggers.
## Icon Requirement
If the icon doesn't already exist in `@/components/icons.tsx`, **do NOT search for it yourself**. After completing the block, ask the user to provide the SVG:
```
The block is complete, but I need an icon for {Service}.
Please provide the SVG and I'll convert it to a React component.
You can usually find this in the service's brand/press kit page, or copy it from their website.
```
## Advanced Mode for Optional Fields
Optional fields that are rarely used should be set to `mode: 'advanced'` so they don't clutter the basic UI. This includes:
mode:'advanced',// Rarely used, hide from basic view
}
```
## WandConfig for Complex Inputs
Use `wandConfig` for fields that are hard to fill out manually, such as timestamps, comma-separated lists, and complex query strings. This gives users an AI-assisted input experience.
```typescript
// Timestamps - use generationType: 'timestamp' to inject current date context
{
id:'startTime',
title:'Start Time',
type:'short-input',
mode:'advanced',
wandConfig:{
enabled: true,
prompt:'Generate an ISO 8601 timestamp based on the user description. Return ONLY the timestamp string.',
generationType:'timestamp',
},
}
// Comma-separated lists - simple prompt without generationType
{
id:'mediaIds',
title:'Media IDs',
type:'short-input',
mode:'advanced',
wandConfig:{
enabled: true,
prompt:'Generate a comma-separated list of media IDs. Return ONLY the comma-separated values.',
},
}
```
## Naming Convention
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.
## Checklist Before Finishing
- [ ]`integrationType` is set to the correct `IntegrationType` enum value
- [ ]`tags` array includes all applicable `IntegrationTag` values
- [ ] All subBlocks have `id`, `title` (except switch), and `type`
description: Add or update a Sim knowledge base connector for syncing documents from an external source, including auth mode, config fields, pagination, document mapping, tags, and registry wiring. Use when working in `apps/sim/connectors/{service}/` or adding a new external document source.
---
# Add Connector Skill
You are an expert at adding knowledge base connectors to Sim. A connector syncs documents from an external source (Confluence, Google Drive, Notion, etc.) into a knowledge base.
## Your Task
When the user asks you to create a connector:
1. Use Context7 or WebFetch to read the service's API documentation
2. Determine the auth mode: **OAuth** (if Sim already has an OAuth provider for the service) or **API key** (if the service uses API key / Bearer token auth)
3. Create the connector directory and config
4. Register it in the connector registry
## Directory Structure
Create files in `apps/sim/connectors/{service}/`:
```
connectors/{service}/
├── index.ts # Barrel export
└── {service}.ts # ConnectorConfig definition
```
## Authentication
Connectors use a discriminated union for auth config (`ConnectorAuthConfig` in `connectors/types.ts`):
For services with existing OAuth providers in `apps/sim/lib/oauth/types.ts`. The `provider` must match an `OAuthService`. The modal shows a credential picker and handles token refresh automatically.
### API key mode
For services that use API key / Bearer token auth. The modal shows a password input with the configured `label` and `placeholder`. The API key is encrypted at rest using AES-256-GCM and stored in a dedicated `encryptedApiKey` column on the connector record. The sync engine decrypts it automatically — connectors receive the raw access token in `listDocuments`, `getDocument`, and `validateConfig`.
The add-connector modal renders these automatically — no custom UI needed.
Three field types are supported: `short-input`, `dropdown`, and `selector`.
```typescript
// Text input
{
id:'domain',
title:'Domain',
type:'short-input',
placeholder:'yoursite.example.com',
required: true,
}
// Dropdown (static options)
{
id:'contentType',
title:'Content Type',
type:'dropdown',
required: false,
options:[
{label:'Pages only',id:'page'},
{label:'Blog posts only',id:'blogpost'},
{label:'All content',id:'all'},
],
}
```
## Dynamic Selectors (Canonical Pairs)
Use `type: 'selector'` to fetch options dynamically from the existing selector registry (`hooks/selectors/registry.ts`). Selectors are always paired with a manual fallback input using the **canonical pair** pattern — a `selector` field (basic mode) and a `short-input` field (advanced mode) linked by `canonicalParamId`.
The user sees a toggle button (ArrowLeftRight) to switch between the selector dropdown and manual text input. On submit, the modal resolves each canonical pair to the active mode's value, keyed by `canonicalParamId`.
### Rules
1.**Every selector field MUST have a canonical pair** — a corresponding `short-input` (or `dropdown`) field with the same `canonicalParamId` and `mode: 'advanced'`.
2.**`required` must be set identically on both fields** in a pair. If the selector is required, the manual input must also be required.
3.**`canonicalParamId` must match the key the connector expects in `sourceConfig`** (e.g. `baseId`, `channel`, `teamId`). The advanced field's `id` should typically match `canonicalParamId`.
4.**`dependsOn` references the selector field's `id`**, not the `canonicalParamId`. The modal propagates dependency clearing across canonical siblings automatically — changing either field in a parent pair clears dependent children.
### Selector canonical pair example (Airtable base → table cascade)
```typescript
configFields:[
// Base: selector (basic) + manual (advanced)
{
id:'baseSelector',
title:'Base',
type:'selector',
selectorKey:'airtable.bases',// Must exist in hooks/selectors/registry.ts
canonicalParamId:'baseId',
mode:'basic',
placeholder:'Select a base',
required: true,
},
{
id:'baseId',
title:'Base ID',
type:'short-input',
canonicalParamId:'baseId',
mode:'advanced',
placeholder:'e.g. appXXXXXXXXXXXXXX',
required: true,
},
// Table: selector depends on base (basic) + manual (advanced)
{
id:'tableSelector',
title:'Table',
type:'selector',
selectorKey:'airtable.tables',
canonicalParamId:'tableIdOrName',
mode:'basic',
dependsOn:['baseSelector'],// References the selector field ID
### Selector with domain dependency (Jira/Confluence pattern)
When a selector depends on a plain `short-input` field (no canonical pair), `dependsOn` references that field's `id` directly. The `domain` field's value maps to `SelectorContext.domain` automatically via `SELECTOR_CONTEXT_FIELDS`.
```typescript
configFields:[
{
id:'domain',
title:'Jira Domain',
type:'short-input',
placeholder:'yoursite.atlassian.net',
required: true,
},
{
id:'projectSelector',
title:'Project',
type:'selector',
selectorKey:'jira.projects',
canonicalParamId:'projectKey',
mode:'basic',
dependsOn:['domain'],
placeholder:'Select a project',
required: true,
},
{
id:'projectKey',
title:'Project Key',
type:'short-input',
canonicalParamId:'projectKey',
mode:'advanced',
placeholder:'e.g. ENG, PROJ',
required: true,
},
]
```
### How `dependsOn` maps to `SelectorContext`
The connector selector field builds a `SelectorContext` from dependency values. For the mapping to work, each dependency's `canonicalParamId` (or field `id` for non-canonical fields) must exist in `SELECTOR_CONTEXT_FIELDS` (`lib/workflows/subblocks/context.ts`):
| `confluence.spaces` | credential, `domain` | Space key + name |
| `notion.databases` | credential | Database ID + name |
| `asana.workspaces` | credential | Workspace GID + name |
| `microsoft.teams` | credential | Team ID + name |
| `microsoft.channels` | credential, `teamId` | Channel ID + name |
| `webflow.sites` | credential | Site ID + name |
| `outlook.folders` | credential | Folder ID + name |
## ExternalDocument Shape
Every document returned from `listDocuments`/`getDocument` must include:
```typescript
{
externalId: string// Source-specific unique ID
title: string// Document title
content: string// Extracted plain text (or '' if contentDeferred)
contentDeferred?: boolean// true = content will be fetched via getDocument
mimeType:'text/plain'// Always text/plain (content is extracted)
contentHash: string// Metadata-based hash for change detection
sourceUrl?: string// Link back to original (stored on document record)
metadata?: Record<string,unknown>// Source-specific data (fed to mapTags)
}
```
## Content Deferral (Required for file/content-download connectors)
**All connectors that require per-document API calls to fetch content MUST use `contentDeferred: true`.** This is the standard pattern — `listDocuments` returns lightweight metadata stubs, and content is fetched lazily by the sync engine via `getDocument` only for new/changed documents.
This pattern is critical for reliability: the sync engine processes documents in batches and enqueues each batch for processing immediately. If a sync times out, all previously-batched documents are already queued. Without deferral, content downloads during listing can exhaust the sync task's time budget before any documents are saved.
### When to use `contentDeferred: true`
- The service's list API does NOT return document content (only metadata)
- Content requires a separate download/export API call per document
- The list API already returns the full content inline (e.g., Slack messages, Reddit posts, HubSpot notes)
- No per-document API call is needed to get content
### Content Hash Strategy
Use a **metadata-based**`contentHash` — never a content-based hash. The hash must be derivable from the list response metadata alone, so the sync engine can detect changes without downloading content.
Good metadata hash sources:
-`modifiedTime` / `lastModifiedDateTime` — changes when file is edited
**Critical invariant:** The `contentHash` MUST be identical whether produced by `listDocuments` (stub) or `getDocument` (full doc). Both should use the same stub function to guarantee this.
All external API calls must use `fetchWithRetry` from `@/lib/knowledge/documents/utils` instead of raw `fetch()`. This provides exponential backoff with retries on 429/502/503/504 errors. It returns a standard `Response` — all `.ok`, `.json()`, `.text()` checks work unchanged.
For `validateConfig` (user-facing, called on save), pass `VALIDATE_RETRY_OPTIONS` to cap wait time at ~7s. Background operations (`listDocuments`, `getDocument`) use the built-in defaults (5 retries, ~31s max).
If `ExternalDocument.sourceUrl` is set, the sync engine stores it on the document record. Always construct the full URL (not a relative path).
## Sync Engine Behavior (Do Not Modify)
The sync engine (`lib/knowledge/connectors/sync-engine.ts`) is connector-agnostic. It:
1. Calls `listDocuments` with pagination until `hasMore` is false
2. Compares `contentHash` to detect new/changed/unchanged documents
3. Stores `sourceUrl` and calls `mapTags` on insert/update automatically
4. Handles soft-delete of removed documents
5. Resolves access tokens automatically — OAuth tokens are refreshed, API keys are decrypted from the `encryptedApiKey` column
You never need to modify the sync engine when adding a connector.
## Icon
The `icon` field on `ConnectorConfig` is used throughout the UI — in the connector list, the add-connector modal, and as the document icon in the knowledge base table (replacing the generic file type icon for connector-sourced documents). The icon is read from `CONNECTOR_REGISTRY[connectorType].icon` at runtime — no separate icon map to maintain.
If the service already has an icon in `apps/sim/components/icons.tsx` (from a tool integration), reuse it. Otherwise, ask the user to provide the SVG.
## Registering
Add one line to `apps/sim/connectors/registry.ts`:
description: Add a complete Sim integration from API docs, covering tools, block, icon, optional triggers, registrations, and integration conventions. Use when introducing a new service under `apps/sim/tools`, `apps/sim/blocks`, and `apps/sim/triggers`.
---
# Add Integration Skill
You are an expert at adding complete integrations to Sim. This skill orchestrates the full process of adding a new service integration.
## Overview
Adding an integration involves these steps in order:
1.**Research** - Read the service's API documentation
2.**Create Tools** - Build tool configurations for each API operation
3.**Create Block** - Build the block UI configuration
4.**Add Icon** - Add the service's brand icon
5.**Create Triggers** (optional) - If the service supports webhooks
6.**Register** - Register tools, block, and triggers in their registries
7.**Generate Docs** - Run the docs generation script
## Step 1: Research the API
Before writing any code:
1. Use Context7 to find official documentation: `mcp__plugin_context7_context7__resolve-library-id`
-`visibility: 'user-only'` for API keys and user credentials
-`visibility: 'user-or-llm'` for operation parameters
- Always use `?? null` for nullable API response fields
- Always use `?? []` for optional array fields
- Set `optional: true` for outputs that may not exist
- Never output raw JSON dumps - extract meaningful fields
- When using `type: 'json'` and you know the object shape, define `properties` with the inner fields so downstream consumers know the structure. Only use bare `type: 'json'` when the shape is truly dynamic
-`canonicalParamId` must NOT match any subblock's `id` in the block
-`canonicalParamId` must be unique per operation/condition context
- Only use `canonicalParamId` to link basic/advanced alternatives for the same logical parameter
-`mode` only controls UI visibility, NOT serialization. Without `canonicalParamId`, both basic and advanced field values would be sent
- Every subblock `id` must be unique within the block. Duplicate IDs cause conflicts even with different conditions
- **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)
- **Inputs section:** Must list canonical param IDs (e.g., `fileId`), NOT raw subblock IDs (e.g., `fileSelector`, `manualFileId`)
- **Params function:** Must use canonical param IDs, NOT raw subblock IDs (raw IDs are deleted after canonical transformation)
- [ ] Secondary triggers do NOT have `includeDropdown`
- [ ] All triggers use `buildTriggerSubBlocks` helper
- [ ] Created `index.ts` barrel export
- [ ] Registered all triggers in `triggers/registry.ts`
### Docs
- [ ] Ran `bun run scripts/generate-docs.ts`
- [ ] Verified docs file created
### Final Validation (Required)
- [ ] Read every tool file and cross-referenced inputs/outputs against the API docs
- [ ] Verified block subBlocks cover all required tool params with correct conditions
- [ ] Verified block outputs match what the tools actually return
- [ ] Verified `tools.config.params` correctly maps and coerces all param types
## Example Command
When the user asks to add an integration:
```
User: Add a Stripe integration
You: I'll add the Stripe integration. Let me:
1. First, research the Stripe API using Context7
2. Create the tools for key operations (payments, subscriptions, etc.)
3. Create the block with operation dropdown
4. Register everything
5. Generate docs
6. Ask you for the Stripe icon SVG
[Proceed with implementation...]
[After completing steps 1-5...]
I've completed the Stripe integration. Before I can add the icon, please provide the SVG for Stripe.
You can usually find this in the service's brand/press kit page, or copy it from their website.
Paste the SVG code here and I'll convert it to a React component.
```
## File Handling
When your integration handles file uploads or downloads, follow these patterns to work with `UserFile` objects consistently.
### What is a UserFile?
A `UserFile` is the standard file representation in Sim:
```typescript
interfaceUserFile{
id: string// Unique identifier
name: string// Original filename
url: string// Presigned URL for download
size: number// File size in bytes
type:string// MIME type (e.g., 'application/pdf')
base64?: string// Optional base64 content (if small file)
key?: string// Internal storage key
context?: object// Storage context metadata
}
```
### File Input Pattern (Uploads)
For tools that accept file uploads, **always route through an internal API endpoint** rather than calling external APIs directly. This ensures proper file content retrieval.
Optional fields that are rarely used should be set to `mode: 'advanced'` so they don't clutter the basic UI. Examples: pagination tokens, time range filters, sort order, max results, reply settings.
### WandConfig for Complex Inputs
Use `wandConfig` for fields that are hard to fill out manually:
- **Timestamps**: Use `generationType: 'timestamp'` to inject current date context into the AI prompt
- **JSON arrays**: Use `generationType: 'json-object'` for structured data
- **Complex queries**: Use a descriptive prompt explaining the expected format
```typescript
{
id:'startTime',
title:'Start Time',
type:'short-input',
mode:'advanced',
wandConfig:{
enabled: true,
prompt:'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
generationType:'timestamp',
},
}
```
### OAuth Scopes (Centralized System)
Scopes are maintained in a single source of truth and reused everywhere:
1.**Define scopes** in `lib/oauth/oauth.ts` under `OAUTH_PROVIDERS[provider].services[service].scopes`
2.**Add descriptions** in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts` for the OAuth modal UI
3.**Reference in auth.ts** using `getCanonicalScopesForProvider(providerId)` from `@/lib/oauth/utils`
4.**Reference in blocks** using `getScopesForService(serviceId)` from `@/lib/oauth/utils`
**Never hardcode scope arrays** in `auth.ts` or block `requiredScopes`. Always import from the centralized source.
1.**OAuth serviceId must match** - The `serviceId` in oauth-input must match the OAuth provider configuration
2.**All tool IDs MUST be snake_case** - `stripe_create_payment`, not `stripeCreatePayment`. This applies to tool `id` fields, registry keys, `tools.access` arrays, and `tools.config.tool` return values
3.**Block type is snake_case** - `type: 'stripe'`, not `type: 'Stripe'`
4.**Alphabetical ordering** - Keep imports and registry entries alphabetically sorted
5.**Required can be conditional** - Use `required: { field: 'op', value: 'create' }` instead of always true
6.**DependsOn clears options** - When a dependency changes, selector options are refetched
7.**Never pass Buffer directly to fetch** - Convert to `new Uint8Array(buffer)` for TypeScript compatibility
description: Create or update Sim tool configurations from service API docs, including typed params, request mapping, response transforms, outputs, and registry entries. Use when working in `apps/sim/tools/{service}/` or fixing tool definitions for an integration.
---
# Add Tools Skill
You are an expert at creating tool configurations for Sim integrations. Your job is to read API documentation and create properly structured tool files.
## Your Task
When the user asks you to create tools for a service:
1. Use Context7 or WebFetch to read the service's API documentation
2. Create the tools directory structure
3. Generate properly typed tool configurations
## Directory Structure
Create files in `apps/sim/tools/{service}/`:
```
tools/{service}/
├── index.ts # Barrel export
├── types.ts # Parameter & response types
└── {action}.ts # Individual tool files (one per operation)
// Trim ID fields to prevent copy-paste whitespace errors:
// userId: params.userId?.trim(),
}),
},
transformResponse: async(response: Response)=>{
constdata=awaitresponse.json()
return{
success: true,
output:{
// Map API response to output
// Use ?? null for nullable fields
// Use ?? [] for optional arrays
},
}
},
outputs:{
// Define each output field
},
}
```
## Critical Rules for Parameters
### Visibility Options
-`'hidden'` - System-injected (OAuth tokens, internal params). User never sees.
-`'user-only'` - User must provide (credentials, api keys, account-specific IDs)
-`'user-or-llm'` - User provides OR LLM can compute (search queries, content, filters, most fall into this category)
### Parameter Types
-`'string'` - Text values
-`'number'` - Numeric values
-`'boolean'` - True/false
-`'json'` - Complex objects (NOT 'object', use 'json')
-`'file'` - Single file
-`'file[]'` - Multiple files
### Required vs Optional
- Always explicitly set `required: true` or `required: false`
- Optional params should have `required: false`
## Critical Rules for Outputs
### Output Types
-`'string'`, `'number'`, `'boolean'` - Primitives
-`'json'` - Complex objects (use this, NOT 'object')
-`'array'` - Arrays with `items` property
-`'object'` - Objects with `properties` property
### Optional Outputs
Add `optional: true` for fields that may not exist in the response:
```typescript
closedAt:{
type:'string',
description:'When the issue was closed',
optional: true,
},
```
### Typed JSON Outputs
When using `type: 'json'` and you know the object shape in advance, **always define the inner structure** using `properties` so downstream consumers know what fields are available:
```typescript
// BAD: Opaque json with no info about what's inside
2. Add to the `tools` object with snake_case keys (alphabetically):
```typescript
import{serviceActionTool}from'@/tools/{service}'
exportconsttools={
// ... existing tools ...
{service}_{action}:serviceActionTool,
}
```
## Wiring Tools into the Block (Required)
After registering in `tools/registry.ts`, you MUST also update the block definition at `apps/sim/blocks/blocks/{service}.ts`. This is not optional — tools are only usable from the UI if they are wired into the block.
### 1. Add to `tools.access`
```typescript
tools:{
access:[
// existing tools...
'service_new_action',// Add every new tool ID here
],
config:{...}
}
```
### 2. Add operation dropdown options
If the block uses an operation dropdown, add an option for each new tool:
```typescript
{
id:'operation',
type:'dropdown',
options:[
// existing options...
{label:'New Action',id:'new_action'},// id maps to what tools.config.tool returns
],
}
```
### 3. Add subBlocks for new tool params
For each new tool, add subBlocks covering all its required params (and optional ones where useful). Apply `condition` to show them only for the right operation, and mark required params with `required`:
```typescript
// Required param for new_action
{
id:'someParam',
title:'Some Param',
type:'short-input',
placeholder:'e.g., value',
condition:{field:'operation',value:'new_action'},
required:{field:'operation',value:'new_action'},
},
// Optional param — put in advanced mode
{
id:'optionalParam',
title:'Optional Param',
type:'short-input',
condition:{field:'operation',value:'new_action'},
mode:'advanced',
},
```
### 4. Update `tools.config.tool`
Ensure the tool selector returns the correct tool ID for every new operation. The simplest pattern:
```typescript
tool:(params)=>`service_${params.operation}`,
// If operation dropdown IDs already match tool IDs, this requires no change.
```
If the dropdown IDs differ from tool IDs, add explicit mappings:
- [ ] Operation dropdown has an option for each new tool
- [ ] SubBlocks cover all required params for each new tool
- [ ] SubBlocks have correct `condition` (only show for the right operation)
- [ ] Optional/rarely-used params set to `mode: 'advanced'`
- [ ]`tools.config.tool` returns correct ID for every new operation
- [ ]`tools.config.params` handles any ID remapping or type coercions
- [ ] New outputs added to block `outputs`
- [ ] New params added to block `inputs`
## V2 Tool Pattern
If creating V2 tools (API-aligned outputs), use `_v2` suffix:
- Tool ID: `{service}_{action}_v2`
- Variable name: `{action}V2Tool`
- Version: `'2.0.0'`
- Outputs: Flat, API-aligned (no content/metadata wrapper)
## Naming Convention
All tool IDs MUST use `snake_case`: `{service}_{action}` (e.g., `x_create_tweet`, `slack_send_message`). Never use camelCase or PascalCase for tool IDs.
## Checklist Before Finishing
- [ ] All tool IDs use snake_case
- [ ] All params have explicit `required: true` or `required: false`
- [ ] All params have appropriate `visibility`
- [ ] All nullable response fields use `?? null`
- [ ] All optional outputs have `optional: true`
- [ ] No raw JSON dumps in outputs
- [ ] Types file has all interfaces
- [ ] Index.ts exports all tools and re-exports types (`export * from './types'`)
description: Create or update Sim webhook triggers using the generic trigger builder, service-specific setup instructions, outputs, and registry wiring. Use when working in `apps/sim/triggers/{service}/` or adding webhook support to an integration.
---
# Add Trigger
You are an expert at creating webhook triggers for Sim. You understand the trigger system, the generic `buildTriggerSubBlocks` helper, and how triggers connect to blocks.
## Your Task
1. Research what webhook events the service supports
2. Create the trigger files using the generic builder
3. Create a provider handler if custom auth, formatting, or subscriptions are needed
4. Register triggers and connect them to the block
## Directory Structure
```
apps/sim/triggers/{service}/
├── index.ts # Barrel exports
├── utils.ts # Service-specific helpers (options, instructions, extra fields, outputs)
If the service API supports programmatic webhook creation, implement `createSubscription` and `deleteSubscription` on the handler. The orchestration layer calls these automatically — **no code touches `route.ts`, `provider-subscriptions.ts`, or `deploy.ts`**.
description: Audit an existing Sim knowledge base connector against the service API docs and repository conventions, then report and fix issues in auth, config fields, pagination, document mapping, tags, and registry entries. Use when validating or repairing code in `apps/sim/connectors/{service}/`.
---
# Validate Connector Skill
You are an expert auditor for Sim knowledge base connectors. Your job is to thoroughly validate that an existing connector is correct, complete, and follows all conventions.
## Your Task
When the user asks you to validate a connector:
1. Read the service's API documentation (via Context7 or WebFetch)
2. Read the connector implementation, OAuth config, and registry entries
3. Cross-reference everything against the API docs and Sim conventions
4. Report all issues found, grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the connector — do not skip any:
Fetch the official API docs for the service. This is the **source of truth** for:
- Endpoint URLs, HTTP methods, and auth headers
- Required vs optional parameters
- Parameter types and allowed values
- Response shapes and field names
- Pagination patterns (cursor, offset, next token)
- Rate limits and error formats
- OAuth scopes and their meanings
Use Context7 (resolve-library-id → query-docs) or WebFetch to retrieve documentation. If both fail, note which claims are based on training knowledge vs verified docs.
## Step 3: Validate API Endpoints
For **every** API call in the connector (`listDocuments`, `getDocument`, `validateConfig`, and any helper functions), verify against the API docs:
### URLs and Methods
- [ ] Base URL is correct for the service's API version
- [ ] Endpoint paths match the API docs exactly
- [ ] HTTP method is correct (GET, POST, PUT, PATCH, DELETE)
- [ ] Path parameters are correctly interpolated and URI-encoded where needed
- [ ] Query parameters use correct names and formats per the API docs
### Headers
- [ ] Authorization header uses the correct format:
- OAuth: `Authorization: Bearer ${accessToken}`
- API Key: correct header name per the service's docs
- [ ]`Content-Type` is set for POST/PUT/PATCH requests
- [ ] Any service-specific headers are present (e.g., `Notion-Version`, `Dropbox-API-Arg`)
- [ ] No headers are sent that the API doesn't support or silently ignores
### Request Bodies
- [ ] POST/PUT body fields match API parameter names exactly
- [ ] Required fields are always sent
- [ ] Optional fields are conditionally included (not sent as `null` or empty unless the API expects that)
- [ ] Field value types match API expectations (string vs number vs boolean)
### Input Sanitization
- [ ] User-controlled values interpolated into query strings are properly escaped:
- OData `$filter`: single quotes escaped with `''` (e.g., `externalId.replace(/'/g, "''")`)
- SOQL: single quotes escaped with `\'`
- GraphQL variables: passed as variables, not interpolated into query strings
Scopes must be correctly declared and sufficient for all API calls the connector makes.
### Connector requiredScopes
- [ ]`requiredScopes` in the connector's `auth` config lists all scopes needed by the connector
- [ ] Each scope in `requiredScopes` is a real, valid scope recognized by the service's API
- [ ] No invalid, deprecated, or made-up scopes are listed
- [ ] No unnecessary excess scopes beyond what the connector actually needs
### Scope Subset Validation (CRITICAL)
- [ ] Every scope in `requiredScopes` exists in the OAuth provider's `scopes` array in `lib/oauth/oauth.ts`
- [ ] Find the provider in `OAUTH_PROVIDERS[providerGroup].services[serviceId].scopes`
- [ ] Verify: `requiredScopes` ⊆ `OAUTH_PROVIDERS scopes` (every required scope is present in the provider config)
- [ ] If a required scope is NOT in the provider config, flag as **critical** — the connector will fail at runtime
### Scope Sufficiency
For each API endpoint the connector calls:
- [ ] Identify which scopes are required per the API docs
- [ ] Verify those scopes are included in the connector's `requiredScopes`
- [ ] If the connector calls endpoints requiring scopes not in `requiredScopes`, flag as **warning**
### Token Refresh Config
- [ ] Check the `getOAuthTokenRefreshConfig` function in `lib/oauth/oauth.ts` for this provider
- [ ]`useBasicAuth` matches the service's token exchange requirements
- [ ]`supportsRefreshTokenRotation` matches whether the service issues rotating refresh tokens
- [ ] Token endpoint URL is correct
## Step 5: Validate Pagination
### listDocuments Pagination
- [ ] Cursor/pagination parameter name matches the API docs
- [ ] Response pagination field is correctly extracted (e.g., `next_cursor`, `nextPageToken`, `@odata.nextLink`, `offset`)
- [ ]`hasMore` is correctly determined from the response
- [ ]`nextCursor` is correctly passed back for the next page
- [ ]`maxItems` / `maxRecords` cap is correctly applied across pages using `syncContext.totalDocsFetched`
- [ ] Page size is within the API's allowed range (not exceeding max page size)
- [ ] Last page precision: when a `maxItems` cap exists, the final page request uses `Math.min(PAGE_SIZE, remaining)` to avoid fetching more records than needed
- [ ] No off-by-one errors in pagination tracking
- [ ] The connector does NOT hit known API pagination limits silently (e.g., HubSpot search 10k cap)
### Pagination State Across Pages
- [ ]`syncContext` is used to cache state across pages (user names, field maps, instance URLs, portal IDs, etc.)
- [ ] Cached state in `syncContext` is correctly initialized on first page and reused on subsequent pages
## Step 6: Validate Data Transformation
### Content Deferral (CRITICAL)
Connectors that require per-document API calls to fetch content (file download, export, blocks fetch) MUST use `contentDeferred: true`. This is the standard pattern for reliability — without it, content downloads during listing can exhaust the sync task's time budget before any documents are saved.
- [ ] If the connector downloads content per-doc during `listDocuments`, it MUST use `contentDeferred: true` instead
- [ ]`listDocuments` returns lightweight stubs with `content: ''` and `contentDeferred: true`
- [ ]`getDocument` fetches actual content and returns the full document with `contentDeferred: false`
- [ ] A shared stub function (e.g., `fileToStub`) is used by both `listDocuments` and `getDocument` to guarantee `contentHash` consistency
- [ ]`contentHash` is metadata-based (e.g., `service:{id}:{modifiedTime}`), NOT content-based — it must be derivable from list metadata alone
- [ ] The `contentHash` is identical whether produced by `listDocuments` or `getDocument`
Connectors where the list API already returns content inline (e.g., Slack messages, Reddit posts) do NOT need `contentDeferred`.
### ExternalDocument Construction
- [ ]`externalId` is a stable, unique identifier from the source API
- [ ]`title` is extracted from the correct field and has a sensible fallback (e.g., `'Untitled'`)
- [ ]`content` is plain text — HTML content is stripped using `htmlToPlainText` from `@/connectors/utils`
- [ ]`mimeType` is `'text/plain'`
- [ ]`contentHash` uses a metadata-based format (e.g., `service:{id}:{modifiedTime}`) for connectors with `contentDeferred: true`, or `computeContentHash` from `@/connectors/utils` for inline-content connectors
- [ ]`sourceUrl` is a valid, complete URL back to the original resource (not relative)
- [ ]`metadata` contains all fields referenced by `mapTags` and `tagDefinitions`
### Content Extraction
- [ ] Rich text / HTML fields are converted to plain text before indexing
- [ ] Important content is not silently dropped (e.g., nested blocks, table cells, code blocks)
- [ ] Content is not silently truncated without logging a warning
- [ ] Empty/blank documents are properly filtered out
- [ ] Size checks use `Buffer.byteLength(text, 'utf8')` not `text.length` when comparing against byte-based limits (e.g., `MAX_FILE_SIZE` in bytes)
## Step 7: Validate Tag Definitions and mapTags
### tagDefinitions
- [ ] Each `tagDefinition` has an `id`, `displayName`, and `fieldType`
- [ ]`fieldType` matches the actual data type: `'text'` for strings, `'number'` for numbers, `'date'` for dates, `'boolean'` for booleans
- [ ] Every `id` in `tagDefinitions` is returned by `mapTags`
- [ ] No `tagDefinition` references a field that `mapTags` never produces
### mapTags
- [ ] Return keys match `tagDefinition``id` values exactly
- [ ] Date values are properly parsed using `parseTagDate` from `@/connectors/utils`
- [ ] Array values are properly joined using `joinTagArray` from `@/connectors/utils`
- [ ] Number values are validated (not `NaN`)
- [ ] Metadata field names accessed in `mapTags` match what `listDocuments`/`getDocument` store in `metadata`
## Step 8: Validate Config Fields and Validation
### configFields
- [ ] Every field has `id`, `title`, `type`
- [ ]`required` is set explicitly (not omitted)
- [ ] Dropdown fields have `options` with `label` and `id` for each option
- [ ] Selector fields follow the canonical pair pattern:
- A `type: 'selector'` field with `selectorKey`, `canonicalParamId`, `mode: 'basic'`
- A `type: 'short-input'` field with the same `canonicalParamId`, `mode: 'advanced'`
-`required` is identical on both fields in the pair
- [ ]`selectorKey` values exist in the selector registry
- [ ]`dependsOn` references selector field `id` values, not `canonicalParamId`
### validateConfig
- [ ] Validates all required fields are present before making API calls
- [ ] Catches exceptions and returns user-friendly error messages
- [ ] Does NOT make expensive calls (full data listing, large queries)
## Step 9: Validate getDocument
- [ ] Fetches a single document by `externalId`
- [ ] Returns `null` for 404 / not found (does not throw)
- [ ] Returns the same `ExternalDocument` shape as `listDocuments`
- [ ] If `listDocuments` uses `contentDeferred: true`, `getDocument` MUST fetch actual content and return `contentDeferred: false`
- [ ] If `listDocuments` uses `contentDeferred: true`, `getDocument` MUST use the same stub function to ensure `contentHash` is identical
- [ ] Handles all content types that `listDocuments` can produce (e.g., if `listDocuments` returns both pages and blogposts, `getDocument` must handle both — not hardcode one endpoint)
- [ ] Forwards `syncContext` if it needs cached state (user names, field maps, etc.)
- [ ] Error handling is graceful (catches, logs, returns null or throws with context)
- [ ] Does not redundantly re-fetch data already included in the initial API response (e.g., if comments come back with the post, don't fetch them again separately)
## Step 10: Validate General Quality
### fetchWithRetry Usage
- [ ] All external API calls use `fetchWithRetry` from `@/lib/knowledge/documents/utils`
- [ ] No raw `fetch()` calls to external APIs
- [ ]`VALIDATE_RETRY_OPTIONS` used in `validateConfig`
- [ ] If `validateConfig` calls a shared helper (e.g., `linearGraphQL`, `resolveId`), that helper must accept and forward `retryOptions` to `fetchWithRetry`
- [ ] Default retry options used in `listDocuments`/`getDocument`
### API Efficiency
- [ ] APIs that support field selection (e.g., `$select`, `sysparm_fields`, `fields`) should request only the fields the connector needs — in both `listDocuments` AND `getDocument`
- [ ] No redundant API calls: if a helper already fetches data (e.g., site metadata), callers should reuse the result instead of making a second call for the same information
- [ ] Sequential per-item API calls (fetching details for each document in a loop) should be batched with `Promise.all` and a concurrency limit of 3-5
### Error Handling
- [ ] Individual document failures are caught and logged without aborting the sync
- [ ] API error responses include status codes in error messages
- [ ] No unhandled promise rejections in concurrent operations
### Concurrency
- [ ] Concurrent API calls use reasonable batch sizes (3-5 is typical)
- [ ] No unbounded `Promise.all` over large arrays
### Logging
- [ ] Uses `createLogger` from `@sim/logger` (not `console.log`)
- [ ] Logs sync progress at `info` level
- [ ] Logs errors at `warn` or `error` level with context
### Registry
- [ ] Connector is exported from `connectors/{service}/index.ts`
- [ ] Connector is registered in `connectors/registry.ts`
- [ ] Registry key matches the connector's `id` field
## Step 11: Report and Fix
### Report Format
Group findings by severity:
**Critical** (will cause runtime errors, data loss, or auth failures):
- Wrong API endpoint URL or HTTP method
- Invalid or missing OAuth scopes (not in provider config)
- Incorrect response field mapping (accessing wrong path)
- SOQL/query fields that don't exist on the target object
- Pagination that silently hits undocumented API limits
- Missing error handling that would crash the sync
-`requiredScopes` not a subset of OAuth provider scopes
- Query/filter injection: user-controlled values interpolated into OData `$filter`, SOQL, or query strings without escaping
- Per-document content download in `listDocuments` without `contentDeferred: true` — causes sync timeouts for large document sets
-`contentHash` mismatch between `listDocuments` stub and `getDocument` return — causes unnecessary re-processing every sync
**Warning** (incorrect behavior, data quality issues, or convention violations):
- HTML content not stripped via `htmlToPlainText`
-`getDocument` not forwarding `syncContext`
-`getDocument` hardcoded to one content type when `listDocuments` returns multiple (e.g., only pages but not blogposts)
- Missing `tagDefinition` for metadata fields returned by `mapTags`
- Incorrect `useBasicAuth` or `supportsRefreshTokenRotation` in token refresh config
- Invalid scope names that the API doesn't recognize (even if silently ignored)
- Private resources excluded from name-based lookup despite scopes being available
- Silent data truncation without logging
- Size checks using `text.length` (character count) instead of `Buffer.byteLength` (byte count) for byte-based limits
- URL-type config fields not normalized (protocol prefix, trailing slashes cause API failures)
-`VALIDATE_RETRY_OPTIONS` not threaded through helper functions called by `validateConfig`
**Suggestion** (minor improvements):
- Missing incremental sync support despite API supporting it
- Overly broad scopes that could be narrowed (not wrong, but could be tighter)
- Source URL format could be more specific
- Missing `orderBy` for deterministic pagination
- Redundant API calls that could be cached in `syncContext`
- Sequential per-item API calls that could be batched with `Promise.all` (concurrency 3-5)
- API supports field selection but connector fetches all fields (e.g., missing `$select`, `sysparm_fields`, `fields`)
-`getDocument` re-fetches data already included in the initial API response (e.g., comments returned with post)
- Last page of pagination requests full `PAGE_SIZE` when fewer records remain (`Math.min(PAGE_SIZE, remaining)`)
### Fix All Issues
After reporting, fix every **critical** and **warning** issue. Apply **suggestions** where they don't add unnecessary complexity.
### Validation Output
After fixing, confirm:
1.`bun run lint` passes
2. TypeScript compiles clean
3. Re-read all modified files to verify fixes are correct
description: Audit an existing Sim integration against the service API docs and repository conventions, then report and fix issues across tools, blocks, outputs, OAuth scopes, triggers, and registry entries. Use when validating or repairing a service integration under `apps/sim/tools`, `apps/sim/blocks`, or `apps/sim/triggers`.
---
# Validate Integration Skill
You are an expert auditor for Sim integrations. Your job is to thoroughly validate that an existing integration is correct, complete, and follows all conventions.
## Your Task
When the user asks you to validate an integration:
1. Read the service's API documentation (via WebFetch or Context7)
2. Read every tool, the block, and registry entries
3. Cross-reference everything against the API docs and Sim conventions
4. Report all issues found, grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the integration — do not skip any:
```
apps/sim/tools/{service}/ # All tool files, types.ts, index.ts
- Credentials → `oauth-input` with correct `serviceId`
- [ ] Dropdown `value: () => 'default'` is set for dropdowns with a sensible default
### Advanced Mode
- [ ] Optional, rarely-used fields are set to `mode: 'advanced'`:
- Pagination tokens / next tokens
- Time range filters (start/end time)
- Sort order / direction options
- Max results / per page limits
- Reply settings / threading options
- Rarely used IDs (reply-to, quote-tweet, etc.)
- Exclude filters
- [ ]**Required** fields are NEVER set to `mode: 'advanced'`
- [ ] Fields that users fill in most of the time are NOT set to `mode: 'advanced'`
### WandConfig
- [ ] Timestamp fields have `wandConfig` with `generationType: 'timestamp'`
- [ ] Comma-separated list fields have `wandConfig` with a descriptive prompt
- [ ] Complex filter/query fields have `wandConfig` with format examples in the prompt
- [ ] All `wandConfig` prompts end with "Return ONLY the [format] - no explanations, no extra text."
- [ ]`wandConfig.placeholder` describes what to type in natural language
### Tools Config
- [ ]`tools.access` lists **every** tool ID the block can use — none missing
- [ ]`tools.config.tool` returns the correct tool ID for each operation
- [ ] Type coercions are in `tools.config.params` (runs at execution time), NOT in `tools.config.tool` (runs at serialization time before variable resolution)
- [ ]`tools.config.params` handles:
-`Number()` conversion for numeric params that come as strings from inputs
-`Boolean` / string-to-boolean conversion for toggle params
- Empty string → `undefined` conversion for optional dropdown values
- Any subBlock ID → tool param name remapping
- [ ] No `Number()`, `JSON.parse()`, or other coercions in `tools.config.tool` — these would destroy dynamic references like `<Block.output>`
### Block Outputs
- [ ] Outputs cover the key fields returned by ALL tools (not just one operation)
description: Audit an existing Sim webhook trigger against the service's webhook API docs and repository conventions, then report and fix issues across trigger definitions, provider handler, output alignment, registration, and security. Use when validating or repairing a trigger under `apps/sim/triggers/{service}/` or `apps/sim/lib/webhooks/providers/{service}.ts`.
---
# Validate Trigger
You are an expert auditor for Sim webhook triggers. Your job is to validate that an existing trigger implementation is correct, complete, secure, and aligned across all layers.
## Your Task
1. Read the service's webhook/API documentation (via WebFetch)
2. Read every trigger file, provider handler, and registry entry
3. Cross-reference against the API docs and Sim conventions
4. Report all issues grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the trigger — do not skip any:
```
apps/sim/triggers/{service}/ # All trigger files, utils.ts, index.ts
serviceId:'{service}',// Must match OAuth provider
serviceId:'{service}',// Must match OAuth provider service key
requiredScopes: getScopesForService('{service}'),// Import from @/lib/oauth/utils
placeholder:'Select account',
required: true,
}
```
**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`.
**Scope descriptions:** When adding a new OAuth provider, also add human-readable descriptions for all scopes in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`.
description: Add a knowledge base connector for syncing documents from an external source
argument-hint: <service-name> [api-docs-url]
---
# Add Connector Skill
You are an expert at adding knowledge base connectors to Sim. A connector syncs documents from an external source (Confluence, Google Drive, Notion, etc.) into a knowledge base.
## Your Task
When the user asks you to create a connector:
1. Use Context7 or WebFetch to read the service's API documentation
2. Determine the auth mode: **OAuth** (if Sim already has an OAuth provider for the service) or **API key** (if the service uses API key / Bearer token auth)
3. Create the connector directory and config
4. Register it in the connector registry
## Directory Structure
Create files in `apps/sim/connectors/{service}/`:
```
connectors/{service}/
├── index.ts # Barrel export
└── {service}.ts # ConnectorConfig definition
```
## Authentication
Connectors use a discriminated union for auth config (`ConnectorAuthConfig` in `connectors/types.ts`):
For services with existing OAuth providers in `apps/sim/lib/oauth/types.ts`. The `provider` must match an `OAuthService`. The modal shows a credential picker and handles token refresh automatically.
### API key mode
For services that use API key / Bearer token auth. The modal shows a password input with the configured `label` and `placeholder`. The API key is encrypted at rest using AES-256-GCM and stored in a dedicated `encryptedApiKey` column on the connector record. The sync engine decrypts it automatically — connectors receive the raw access token in `listDocuments`, `getDocument`, and `validateConfig`.
The add-connector modal renders these automatically — no custom UI needed.
Three field types are supported: `short-input`, `dropdown`, and `selector`.
```typescript
// Text input
{
id:'domain',
title:'Domain',
type:'short-input',
placeholder:'yoursite.example.com',
required: true,
}
// Dropdown (static options)
{
id:'contentType',
title:'Content Type',
type:'dropdown',
required: false,
options:[
{label:'Pages only',id:'page'},
{label:'Blog posts only',id:'blogpost'},
{label:'All content',id:'all'},
],
}
```
## Dynamic Selectors (Canonical Pairs)
Use `type: 'selector'` to fetch options dynamically from the existing selector registry (`hooks/selectors/registry.ts`). Selectors are always paired with a manual fallback input using the **canonical pair** pattern — a `selector` field (basic mode) and a `short-input` field (advanced mode) linked by `canonicalParamId`.
The user sees a toggle button (ArrowLeftRight) to switch between the selector dropdown and manual text input. On submit, the modal resolves each canonical pair to the active mode's value, keyed by `canonicalParamId`.
### Rules
1.**Every selector field MUST have a canonical pair** — a corresponding `short-input` (or `dropdown`) field with the same `canonicalParamId` and `mode: 'advanced'`.
2.**`required` must be set identically on both fields** in a pair. If the selector is required, the manual input must also be required.
3.**`canonicalParamId` must match the key the connector expects in `sourceConfig`** (e.g. `baseId`, `channel`, `teamId`). The advanced field's `id` should typically match `canonicalParamId`.
4.**`dependsOn` references the selector field's `id`**, not the `canonicalParamId`. The modal propagates dependency clearing across canonical siblings automatically — changing either field in a parent pair clears dependent children.
### Selector canonical pair example (Airtable base → table cascade)
```typescript
configFields:[
// Base: selector (basic) + manual (advanced)
{
id:'baseSelector',
title:'Base',
type:'selector',
selectorKey:'airtable.bases',// Must exist in hooks/selectors/registry.ts
canonicalParamId:'baseId',
mode:'basic',
placeholder:'Select a base',
required: true,
},
{
id:'baseId',
title:'Base ID',
type:'short-input',
canonicalParamId:'baseId',
mode:'advanced',
placeholder:'e.g. appXXXXXXXXXXXXXX',
required: true,
},
// Table: selector depends on base (basic) + manual (advanced)
{
id:'tableSelector',
title:'Table',
type:'selector',
selectorKey:'airtable.tables',
canonicalParamId:'tableIdOrName',
mode:'basic',
dependsOn:['baseSelector'],// References the selector field ID
### Selector with domain dependency (Jira/Confluence pattern)
When a selector depends on a plain `short-input` field (no canonical pair), `dependsOn` references that field's `id` directly. The `domain` field's value maps to `SelectorContext.domain` automatically via `SELECTOR_CONTEXT_FIELDS`.
```typescript
configFields:[
{
id:'domain',
title:'Jira Domain',
type:'short-input',
placeholder:'yoursite.atlassian.net',
required: true,
},
{
id:'projectSelector',
title:'Project',
type:'selector',
selectorKey:'jira.projects',
canonicalParamId:'projectKey',
mode:'basic',
dependsOn:['domain'],
placeholder:'Select a project',
required: true,
},
{
id:'projectKey',
title:'Project Key',
type:'short-input',
canonicalParamId:'projectKey',
mode:'advanced',
placeholder:'e.g. ENG, PROJ',
required: true,
},
]
```
### How `dependsOn` maps to `SelectorContext`
The connector selector field builds a `SelectorContext` from dependency values. For the mapping to work, each dependency's `canonicalParamId` (or field `id` for non-canonical fields) must exist in `SELECTOR_CONTEXT_FIELDS` (`lib/workflows/subblocks/context.ts`):
| `confluence.spaces` | credential, `domain` | Space key + name |
| `notion.databases` | credential | Database ID + name |
| `asana.workspaces` | credential | Workspace GID + name |
| `microsoft.teams` | credential | Team ID + name |
| `microsoft.channels` | credential, `teamId` | Channel ID + name |
| `webflow.sites` | credential | Site ID + name |
| `outlook.folders` | credential | Folder ID + name |
## ExternalDocument Shape
Every document returned from `listDocuments`/`getDocument` must include:
```typescript
{
externalId: string// Source-specific unique ID
title: string// Document title
content: string// Extracted plain text (or '' if contentDeferred)
contentDeferred?: boolean// true = content will be fetched via getDocument
mimeType:'text/plain'// Always text/plain (content is extracted)
contentHash: string// Metadata-based hash for change detection
sourceUrl?: string// Link back to original (stored on document record)
metadata?: Record<string,unknown>// Source-specific data (fed to mapTags)
}
```
## Content Deferral (Required for file/content-download connectors)
**All connectors that require per-document API calls to fetch content MUST use `contentDeferred: true`.** This is the standard pattern — `listDocuments` returns lightweight metadata stubs, and content is fetched lazily by the sync engine via `getDocument` only for new/changed documents.
This pattern is critical for reliability: the sync engine processes documents in batches and enqueues each batch for processing immediately. If a sync times out, all previously-batched documents are already queued. Without deferral, content downloads during listing can exhaust the sync task's time budget before any documents are saved.
### When to use `contentDeferred: true`
- The service's list API does NOT return document content (only metadata)
- Content requires a separate download/export API call per document
- The list API already returns the full content inline (e.g., Slack messages, Reddit posts, HubSpot notes)
- No per-document API call is needed to get content
### Content Hash Strategy
Use a **metadata-based**`contentHash` — never a content-based hash. The hash must be derivable from the list response metadata alone, so the sync engine can detect changes without downloading content.
Good metadata hash sources:
-`modifiedTime` / `lastModifiedDateTime` — changes when file is edited
**Critical invariant:** The `contentHash` MUST be identical whether produced by `listDocuments` (stub) or `getDocument` (full doc). Both should use the same stub function to guarantee this.
All external API calls must use `fetchWithRetry` from `@/lib/knowledge/documents/utils` instead of raw `fetch()`. This provides exponential backoff with retries on 429/502/503/504 errors. It returns a standard `Response` — all `.ok`, `.json()`, `.text()` checks work unchanged.
For `validateConfig` (user-facing, called on save), pass `VALIDATE_RETRY_OPTIONS` to cap wait time at ~7s. Background operations (`listDocuments`, `getDocument`) use the built-in defaults (5 retries, ~31s max).
If `ExternalDocument.sourceUrl` is set, the sync engine stores it on the document record. Always construct the full URL (not a relative path).
## Sync Engine Behavior (Do Not Modify)
The sync engine (`lib/knowledge/connectors/sync-engine.ts`) is connector-agnostic. It:
1. Calls `listDocuments` with pagination until `hasMore` is false
2. Compares `contentHash` to detect new/changed/unchanged documents
3. Stores `sourceUrl` and calls `mapTags` on insert/update automatically
4. Handles soft-delete of removed documents
5. Resolves access tokens automatically — OAuth tokens are refreshed, API keys are decrypted from the `encryptedApiKey` column
You never need to modify the sync engine when adding a connector.
## Icon
The `icon` field on `ConnectorConfig` is used throughout the UI — in the connector list, the add-connector modal, and as the document icon in the knowledge base table (replacing the generic file type icon for connector-sourced documents). The icon is read from `CONNECTOR_REGISTRY[connectorType].icon` at runtime — no separate icon map to maintain.
If the service already has an icon in `apps/sim/components/icons.tsx` (from a tool integration), reuse it. Otherwise, ask the user to provide the SVG.
## Registering
Add one line to `apps/sim/connectors/registry.ts`:
description: Add hosted API key support to a tool so Sim provides the key when users don't bring their own
argument-hint: <service-name>
---
# Adding Hosted Key Support to a Tool
When a tool has hosted key support, Sim provides its own API key if the user hasn't configured one (via BYOK or env var). Usage is metered and billed to the workspace.
## Step 2: Research the API's Pricing Model and Rate Limits
**Before writing any `getCost` or `rateLimit` code**, look up the service's official documentation for both pricing and rate limits. You need to understand:
### Pricing
1.**How the API charges** — per request, per credit, per token, per step, per minute, etc.
2.**Whether the API reports cost in its response** — look for fields like `creditsUsed`, `costDollars`, `tokensUsed`, or similar in the response body or headers
3.**Whether cost varies by endpoint/options** — some APIs charge more for certain features (e.g., Firecrawl charges 1 credit/page base but +4 for JSON format, +4 for enhanced mode)
4.**The dollar-per-unit rate** — what each credit/token/unit costs in dollars on our plan
### Rate Limits
1.**What rate limits the API enforces** — requests per minute/second, tokens per minute, concurrent requests, etc.
2.**Whether limits vary by plan tier** — free vs paid vs enterprise often have different ceilings
3.**Whether limits are per-key or per-account** — determines whether adding more hosted keys actually increases total throughput
4.**What the API returns when rate limited** — HTTP 429, `Retry-After` header, error body format, etc.
5.**Whether there are multiple dimensions** — some APIs limit both requests/min AND tokens/min independently
Search the API's docs/pricing page (use WebSearch/WebFetch). Capture the pricing model as a comment in `getCost` so future maintainers know the source of truth.
### Setting Our Rate Limits
Our rate limiter (`lib/core/rate-limiter/hosted-key/`) uses a token-bucket algorithm applied **per billing actor** (workspace). It supports two modes:
- **`per_request`** — simple; just `requestsPerMinute`. Good when the API charges flat per-request or cost doesn't vary much.
- **`custom`** — `requestsPerMinute` plus additional `dimensions` (e.g., `tokens`, `search_units`). Each dimension has its own `limitPerMinute` and an `extractUsage` function that reads actual usage from the response. Use when the API charges on a variable metric (tokens, credits) and you want to cap that metric too.
When choosing values for `requestsPerMinute` and any dimension limits:
- **Stay well below the API's per-key limit** — our keys are shared across all workspaces. If the API allows 60 RPM per key and we have 3 keys, the global ceiling is ~180 RPM. Set the per-workspace limit low enough (e.g., 20-60 RPM) that many workspaces can coexist without collectively hitting the API's ceiling.
- **Account for key pooling** — our round-robin distributes requests across `N` hosted keys, so the effective API-side rate per key is `(total requests) / N`. But per-workspace limits are enforced *before* key selection, so they apply regardless of key count.
- **Prefer conservative defaults** — it's easy to raise limits later but hard to claw back after users depend on high throughput.
## Step 3: Add `hosting` Config to the Tool
Add a `hosting` object to the tool's `ToolConfig`. This tells the execution layer how to acquire hosted keys, calculate cost, and rate-limit.
Keys use a numbered naming pattern driven by a count env var:
```
YOUR_SERVICE_API_KEY_COUNT=3
YOUR_SERVICE_API_KEY_1=sk-...
YOUR_SERVICE_API_KEY_2=sk-...
YOUR_SERVICE_API_KEY_3=sk-...
```
The `envKeyPrefix` value (`YOUR_SERVICE_API_KEY`) determines which env vars are read at runtime. Adding more keys only requires bumping the count and adding the new env var.
### Pricing: Prefer API-Reported Cost
Always prefer using cost data returned by the API (e.g., `creditsUsed`, `costDollars`). This is the most accurate because it accounts for variable pricing tiers, feature modifiers, and plan-level discounts.
**When the API reports cost** — use it directly and throw if missing:
// Serper: 1 credit for <=10 results, 2 credits for >10 — from https://serper.dev/pricing
constcredits=Number(params.num)>10?2 : 1
return{cost: credits*0.001,metadata:{credits}}
},
},
```
**`getCost` must always throw** if it cannot determine cost. Never silently fall back to a default — this would hide billing inaccuracies.
### Capturing Cost Data from the API
If the API returns cost info, capture it in `transformResponse` so `getCost` can read it from the output:
```typescript
transformResponse: async(response: Response)=>{
constdata=awaitresponse.json()
return{
success: true,
output:{
results: data.results,
creditsUsed: data.creditsUsed,// pass through for getCost
},
}
},
```
For async/polling tools, capture it in `postProcess` when the job completes:
```typescript
if(jobData.status==='completed'){
result.output={
data: jobData.data,
creditsUsed: jobData.creditsUsed,
}
}
```
## Step 4: Hide the API Key Field When Hosted
In the block config (`blocks/blocks/{service}.ts`), add `hideWhenHosted: true` to the API key subblock. This hides the field on hosted Sim since the platform provides the key:
```typescript
{
id:'apiKey',
title:'API Key',
type:'short-input',
placeholder:'Enter your API key',
password: true,
required: true,
hideWhenHosted: true,
},
```
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`).
### Excluding Specific Operations from Hosted Key Support
When a block has multiple operations but some operations should **not** use a hosted key (e.g., the underlying API is deprecated, unsupported, or too expensive), use the **duplicate apiKey subblock** pattern. This is the same pattern Exa uses for its `research` operation:
1.**Remove the `hosting` config** from the tool definition for that operation — it must not have a `hosting` object at all.
2.**Duplicate the `apiKey` subblock** in the block config with opposing conditions:
```typescript
// API Key — hidden when hosted for operations with hosted key support
Both subblocks share the same `id: 'apiKey'`, so the same value flows to the tool. The conditions ensure only one is visible at a time. The first has `hideWhenHosted: true` and shows for all hosted operations; the second has no `hideWhenHosted` and shows only for the excluded operation — meaning users must always provide their own key for that operation.
To exclude multiple operations, use an array: `{ field: 'operation', value: ['op_a', 'op_b'] }`.
Add an entry to the `PROVIDERS` array in the BYOK settings component so users can bring their own key. You need the service icon from `components/icons.tsx`:
```typescript
{
id:'your_service',
name:'Your Service',
icon: YourServiceIcon,
description:'What this service does',
placeholder:'Enter your API key',
},
```
## Step 6: Summarize Pricing and Throttling Comparison
After all code changes are complete, output a detailed summary to the user covering:
### What to include
1.**API's pricing model** — how the service charges (per token, per credit, per request, etc.), the specific rates found in docs, and whether the API reports cost in responses.
2.**Our `getCost` approach** — how we calculate cost, what fields we depend on, and any assumptions or estimates (especially when the API doesn't report exact dollar cost).
3.**API's rate limits** — the documented limits (RPM, TPM, concurrent, etc.), which plan tier they apply to, and whether they're per-key or per-account.
4.**Our `rateLimit` config** — what we set for `requestsPerMinute` (and dimensions if custom mode), why we chose those values, and how they compare to the API's limits.
5.**Key pooling impact** — how many hosted keys we expect, and how round-robin distribution affects the effective per-key rate at the API.
6.**Gaps or risks** — anything the API charges for that we don't meter, rate limit dimensions we chose not to enforce, or pricing that may be inaccurate due to variable model/tier costs.
### Format
Present this as a structured summary with clear headings. Example:
```
### Pricing
- **API charges**: $X per 1M tokens (input), $Y per 1M tokens (output) — varies by model
- **Response reports cost?**: No — only token counts in `usage` field
- **Our getCost**: Estimates cost at $Z per 1M total tokens based on median model pricing
- **Risk**: Actual cost varies by model; our estimate may over/undercharge for cheap/expensive models
@@ -3,63 +3,57 @@ description: Create webhook triggers for a Sim integration using the generic tri
argument-hint: <service-name>
---
# Add Trigger Skill
# Add Trigger
You are an expert at creating webhook triggers for Sim. You understand the trigger system, the generic `buildTriggerSubBlocks` helper, and how triggers connect to blocks.
## Your Task
When the user asks you to create triggers for a service:
1. Research what webhook events the service supports
2. Create the trigger files using the generic builder
3.Register triggers and connect them to the block
3.Create a provider handler if custom auth, formatting, or subscriptions are needed
4. Register triggers and connect them to the block
If the service's API supports programmatic webhook creation, implement automatic webhook registration instead of requiring users to manually configure webhooks. This provides a much better user experience.
All provider-specific webhook logic lives in a single handler file: `apps/sim/lib/webhooks/providers/{service}.ts`.
### When to Use Automatic Registration
### When to Create a Handler
Check the service's API documentation for endpoints like:
-`POST /webhooks` or `POST /hooks` - Create webhook
If the service API supports programmatic webhook creation, implement `createSubscription` and `deleteSubscription` on the handler. The orchestration layer calls these automatically — **no code touches `route.ts`, `provider-subscriptions.ts`, or `deploy.ts`**.
1.**Dropdown** (only if `includeDropdown: true`) - Trigger type selector
2.**Webhook URL** - Read-only field with copy button
3.**Extra Fields** - Your service-specific fields (filters, options, etc.)
4.**Save Button** - Activates the trigger
5.**Instructions** - Setup guide for users
All fields automatically have:
-`mode: 'trigger'` - Only shown in trigger mode
-`condition: { field: 'selectedTriggerId', value: triggerId }` - Only shown when this trigger is selected
## Trigger Outputs & Webhook Input Formatting
### Important: Two Sources of Truth
There are two related but separate concerns:
1.**Trigger `outputs`** - Schema/contract defining what fields SHOULD be available. Used by UI for tag dropdown.
2.**`formatWebhookInput`** - Implementation that transforms raw webhook payload into actual data. Located in `apps/sim/lib/webhooks/utils.server.ts`.
**These MUST be aligned.** The fields returned by `formatWebhookInput` should match what's defined in trigger `outputs`. If they differ:
- Tag dropdown shows fields that don't exist (broken variable resolution)
- Or actual data has fields not shown in dropdown (users can't discover them)
### When to Add a formatWebhookInput Handler
- **Simple providers**: If the raw webhook payload structure already matches your outputs, you don't need a handler. The generic fallback returns `body` directly.
- **Complex providers**: If you need to transform, flatten, extract nested data, compute fields, or handle conditional logic, add a handler.
### Adding a Handler
In `apps/sim/lib/webhooks/utils.server.ts`, add a handler block:
```typescript
if(foundWebhook.provider==='{service}'){
// Transform raw webhook body to match trigger outputs
return{
eventType: body.type,
resourceId: body.data?.id||'',
timestamp: body.created_at,
resource: body.data,
}
}
```
**Key rules:**
- Return fields that match your trigger `outputs` definition exactly
- No wrapper objects like `webhook: { data: ... }` or `{service}: { ... }`
- No duplication (don't spread body AND add individual fields)
- Use `null` for missing optional data, not empty objects with empty strings
### Verify Alignment
Run the alignment checker:
```bash
bunx scripts/check-trigger-alignment.ts {service}
```
## Trigger Outputs
## Trigger Outputs Schema
Trigger outputs use the same schema as block outputs (NOT tool outputs).
**Supported:**
-`type` and `description` for simple fields
- Nested object structure for complex data
**NOT Supported:**
-`optional: true` (tool outputs only)
-`items` property (tool outputs only)
**Supported:**`type` + `description` for leaf fields, nested objects for complex data.
**NOT supported:**`optional: true`, `items` (those are tool-output-only features).
description: Spawn task agents to explore a given area of interest in the codebase
argument-hint: <area-of-interest>
---
Based on the given area of interest, please:
1. Dig around the codebase in terms of that given area of interest, gather general information such as keywords and architecture overview.
2. Spawn off n=10 (unless specified otherwise) task agents to dig deeper into the codebase in terms of that given area of interest, some of them should be out of the box for variance.
3. Once the task agents are done, use the information to do what the user wants.
If user is in plan mode, use the information to create the plan.
description: Validate an existing knowledge base connector against its service's API docs
argument-hint: <service-name> [api-docs-url]
---
# Validate Connector Skill
You are an expert auditor for Sim knowledge base connectors. Your job is to thoroughly validate that an existing connector is correct, complete, and follows all conventions.
## Your Task
When the user asks you to validate a connector:
1. Read the service's API documentation (via Context7 or WebFetch)
2. Read the connector implementation, OAuth config, and registry entries
3. Cross-reference everything against the API docs and Sim conventions
4. Report all issues found, grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the connector — do not skip any:
Fetch the official API docs for the service. This is the **source of truth** for:
- Endpoint URLs, HTTP methods, and auth headers
- Required vs optional parameters
- Parameter types and allowed values
- Response shapes and field names
- Pagination patterns (cursor, offset, next token)
- Rate limits and error formats
- OAuth scopes and their meanings
Use Context7 (resolve-library-id → query-docs) or WebFetch to retrieve documentation. If both fail, note which claims are based on training knowledge vs verified docs.
## Step 3: Validate API Endpoints
For **every** API call in the connector (`listDocuments`, `getDocument`, `validateConfig`, and any helper functions), verify against the API docs:
### URLs and Methods
- [ ] Base URL is correct for the service's API version
- [ ] Endpoint paths match the API docs exactly
- [ ] HTTP method is correct (GET, POST, PUT, PATCH, DELETE)
- [ ] Path parameters are correctly interpolated and URI-encoded where needed
- [ ] Query parameters use correct names and formats per the API docs
### Headers
- [ ] Authorization header uses the correct format:
- OAuth: `Authorization: Bearer ${accessToken}`
- API Key: correct header name per the service's docs
- [ ]`Content-Type` is set for POST/PUT/PATCH requests
- [ ] Any service-specific headers are present (e.g., `Notion-Version`, `Dropbox-API-Arg`)
- [ ] No headers are sent that the API doesn't support or silently ignores
### Request Bodies
- [ ] POST/PUT body fields match API parameter names exactly
- [ ] Required fields are always sent
- [ ] Optional fields are conditionally included (not sent as `null` or empty unless the API expects that)
- [ ] Field value types match API expectations (string vs number vs boolean)
### Input Sanitization
- [ ] User-controlled values interpolated into query strings are properly escaped:
- OData `$filter`: single quotes escaped with `''` (e.g., `externalId.replace(/'/g, "''")`)
- SOQL: single quotes escaped with `\'`
- GraphQL variables: passed as variables, not interpolated into query strings
Scopes must be correctly declared and sufficient for all API calls the connector makes.
### Connector requiredScopes
- [ ]`requiredScopes` in the connector's `auth` config lists all scopes needed by the connector
- [ ] Each scope in `requiredScopes` is a real, valid scope recognized by the service's API
- [ ] No invalid, deprecated, or made-up scopes are listed
- [ ] No unnecessary excess scopes beyond what the connector actually needs
### Scope Subset Validation (CRITICAL)
- [ ] Every scope in `requiredScopes` exists in the OAuth provider's `scopes` array in `lib/oauth/oauth.ts`
- [ ] Find the provider in `OAUTH_PROVIDERS[providerGroup].services[serviceId].scopes`
- [ ] Verify: `requiredScopes` ⊆ `OAUTH_PROVIDERS scopes` (every required scope is present in the provider config)
- [ ] If a required scope is NOT in the provider config, flag as **critical** — the connector will fail at runtime
### Scope Sufficiency
For each API endpoint the connector calls:
- [ ] Identify which scopes are required per the API docs
- [ ] Verify those scopes are included in the connector's `requiredScopes`
- [ ] If the connector calls endpoints requiring scopes not in `requiredScopes`, flag as **warning**
### Token Refresh Config
- [ ] Check the `getOAuthTokenRefreshConfig` function in `lib/oauth/oauth.ts` for this provider
- [ ]`useBasicAuth` matches the service's token exchange requirements
- [ ]`supportsRefreshTokenRotation` matches whether the service issues rotating refresh tokens
- [ ] Token endpoint URL is correct
## Step 5: Validate Pagination
### listDocuments Pagination
- [ ] Cursor/pagination parameter name matches the API docs
- [ ] Response pagination field is correctly extracted (e.g., `next_cursor`, `nextPageToken`, `@odata.nextLink`, `offset`)
- [ ]`hasMore` is correctly determined from the response
- [ ]`nextCursor` is correctly passed back for the next page
- [ ]`maxItems` / `maxRecords` cap is correctly applied across pages using `syncContext.totalDocsFetched`
- [ ] Page size is within the API's allowed range (not exceeding max page size)
- [ ] Last page precision: when a `maxItems` cap exists, the final page request uses `Math.min(PAGE_SIZE, remaining)` to avoid fetching more records than needed
- [ ] No off-by-one errors in pagination tracking
- [ ] The connector does NOT hit known API pagination limits silently (e.g., HubSpot search 10k cap)
### Pagination State Across Pages
- [ ]`syncContext` is used to cache state across pages (user names, field maps, instance URLs, portal IDs, etc.)
- [ ] Cached state in `syncContext` is correctly initialized on first page and reused on subsequent pages
## Step 6: Validate Data Transformation
### ExternalDocument Construction
- [ ]`externalId` is a stable, unique identifier from the source API
- [ ]`title` is extracted from the correct field and has a sensible fallback (e.g., `'Untitled'`)
- [ ]`content` is plain text — HTML content is stripped using `htmlToPlainText` from `@/connectors/utils`
- [ ]`mimeType` is `'text/plain'`
- [ ]`contentHash` is computed using `computeContentHash` from `@/connectors/utils`
- [ ]`sourceUrl` is a valid, complete URL back to the original resource (not relative)
- [ ]`metadata` contains all fields referenced by `mapTags` and `tagDefinitions`
### Content Extraction
- [ ] Rich text / HTML fields are converted to plain text before indexing
- [ ] Important content is not silently dropped (e.g., nested blocks, table cells, code blocks)
- [ ] Content is not silently truncated without logging a warning
- [ ] Empty/blank documents are properly filtered out
- [ ] Size checks use `Buffer.byteLength(text, 'utf8')` not `text.length` when comparing against byte-based limits (e.g., `MAX_FILE_SIZE` in bytes)
## Step 7: Validate Tag Definitions and mapTags
### tagDefinitions
- [ ] Each `tagDefinition` has an `id`, `displayName`, and `fieldType`
- [ ]`fieldType` matches the actual data type: `'text'` for strings, `'number'` for numbers, `'date'` for dates, `'boolean'` for booleans
- [ ] Every `id` in `tagDefinitions` is returned by `mapTags`
- [ ] No `tagDefinition` references a field that `mapTags` never produces
### mapTags
- [ ] Return keys match `tagDefinition``id` values exactly
- [ ] Date values are properly parsed using `parseTagDate` from `@/connectors/utils`
- [ ] Array values are properly joined using `joinTagArray` from `@/connectors/utils`
- [ ] Number values are validated (not `NaN`)
- [ ] Metadata field names accessed in `mapTags` match what `listDocuments`/`getDocument` store in `metadata`
## Step 8: Validate Config Fields and Validation
### configFields
- [ ] Every field has `id`, `title`, `type`
- [ ]`required` is set explicitly (not omitted)
- [ ] Dropdown fields have `options` with `label` and `id` for each option
- [ ] Selector fields follow the canonical pair pattern:
- A `type: 'selector'` field with `selectorKey`, `canonicalParamId`, `mode: 'basic'`
- A `type: 'short-input'` field with the same `canonicalParamId`, `mode: 'advanced'`
-`required` is identical on both fields in the pair
- [ ]`selectorKey` values exist in the selector registry
- [ ]`dependsOn` references selector field `id` values, not `canonicalParamId`
### validateConfig
- [ ] Validates all required fields are present before making API calls
- [ ] Catches exceptions and returns user-friendly error messages
- [ ] Does NOT make expensive calls (full data listing, large queries)
## Step 9: Validate getDocument
- [ ] Fetches a single document by `externalId`
- [ ] Returns `null` for 404 / not found (does not throw)
- [ ] Returns the same `ExternalDocument` shape as `listDocuments`
- [ ] Handles all content types that `listDocuments` can produce (e.g., if `listDocuments` returns both pages and blogposts, `getDocument` must handle both — not hardcode one endpoint)
- [ ] Forwards `syncContext` if it needs cached state (user names, field maps, etc.)
- [ ] Error handling is graceful (catches, logs, returns null or throws with context)
- [ ] Does not redundantly re-fetch data already included in the initial API response (e.g., if comments come back with the post, don't fetch them again separately)
## Step 10: Validate General Quality
### fetchWithRetry Usage
- [ ] All external API calls use `fetchWithRetry` from `@/lib/knowledge/documents/utils`
- [ ] No raw `fetch()` calls to external APIs
- [ ]`VALIDATE_RETRY_OPTIONS` used in `validateConfig`
- [ ] If `validateConfig` calls a shared helper (e.g., `linearGraphQL`, `resolveId`), that helper must accept and forward `retryOptions` to `fetchWithRetry`
- [ ] Default retry options used in `listDocuments`/`getDocument`
### API Efficiency
- [ ] APIs that support field selection (e.g., `$select`, `sysparm_fields`, `fields`) should request only the fields the connector needs — in both `listDocuments` AND `getDocument`
- [ ] No redundant API calls: if a helper already fetches data (e.g., site metadata), callers should reuse the result instead of making a second call for the same information
- [ ] Sequential per-item API calls (fetching details for each document in a loop) should be batched with `Promise.all` and a concurrency limit of 3-5
### Error Handling
- [ ] Individual document failures are caught and logged without aborting the sync
- [ ] API error responses include status codes in error messages
- [ ] No unhandled promise rejections in concurrent operations
### Concurrency
- [ ] Concurrent API calls use reasonable batch sizes (3-5 is typical)
- [ ] No unbounded `Promise.all` over large arrays
### Logging
- [ ] Uses `createLogger` from `@sim/logger` (not `console.log`)
- [ ] Logs sync progress at `info` level
- [ ] Logs errors at `warn` or `error` level with context
### Registry
- [ ] Connector is exported from `connectors/{service}/index.ts`
- [ ] Connector is registered in `connectors/registry.ts`
- [ ] Registry key matches the connector's `id` field
## Step 11: Report and Fix
### Report Format
Group findings by severity:
**Critical** (will cause runtime errors, data loss, or auth failures):
- Wrong API endpoint URL or HTTP method
- Invalid or missing OAuth scopes (not in provider config)
- Incorrect response field mapping (accessing wrong path)
- SOQL/query fields that don't exist on the target object
- Pagination that silently hits undocumented API limits
- Missing error handling that would crash the sync
-`requiredScopes` not a subset of OAuth provider scopes
- Query/filter injection: user-controlled values interpolated into OData `$filter`, SOQL, or query strings without escaping
**Warning** (incorrect behavior, data quality issues, or convention violations):
- HTML content not stripped via `htmlToPlainText`
-`getDocument` not forwarding `syncContext`
-`getDocument` hardcoded to one content type when `listDocuments` returns multiple (e.g., only pages but not blogposts)
- Missing `tagDefinition` for metadata fields returned by `mapTags`
- Incorrect `useBasicAuth` or `supportsRefreshTokenRotation` in token refresh config
- Invalid scope names that the API doesn't recognize (even if silently ignored)
- Private resources excluded from name-based lookup despite scopes being available
- Silent data truncation without logging
- Size checks using `text.length` (character count) instead of `Buffer.byteLength` (byte count) for byte-based limits
- URL-type config fields not normalized (protocol prefix, trailing slashes cause API failures)
-`VALIDATE_RETRY_OPTIONS` not threaded through helper functions called by `validateConfig`
**Suggestion** (minor improvements):
- Missing incremental sync support despite API supporting it
- Overly broad scopes that could be narrowed (not wrong, but could be tighter)
- Source URL format could be more specific
- Missing `orderBy` for deterministic pagination
- Redundant API calls that could be cached in `syncContext`
- Sequential per-item API calls that could be batched with `Promise.all` (concurrency 3-5)
- API supports field selection but connector fetches all fields (e.g., missing `$select`, `sysparm_fields`, `fields`)
-`getDocument` re-fetches data already included in the initial API response (e.g., comments returned with post)
- Last page of pagination requests full `PAGE_SIZE` when fewer records remain (`Math.min(PAGE_SIZE, remaining)`)
### Fix All Issues
After reporting, fix every **critical** and **warning** issue. Apply **suggestions** where they don't add unnecessary complexity.
### Validation Output
After fixing, confirm:
1.`bun run lint` passes
2. TypeScript compiles clean
3. Re-read all modified files to verify fixes are correct
description: Validate an existing Sim webhook trigger against provider API docs and repository conventions
argument-hint: <service-name> [api-docs-url]
---
# Validate Trigger
You are an expert auditor for Sim webhook triggers. Your job is to validate that an existing trigger implementation is correct, complete, secure, and aligned across all layers.
## Your Task
1. Read the service's webhook/API documentation (via WebFetch)
2. Read every trigger file, provider handler, and registry entry
3. Cross-reference against the API docs and Sim conventions
4. Report all issues grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the trigger — do not skip any:
```
apps/sim/triggers/{service}/ # All trigger files, utils.ts, index.ts
All React Query hooks live in `hooks/queries/`. All server state must go through React Query — never use `useState` + `fetch` in components for data fetching or mutations.
## Query Key Factory
Every query file defines a keys factory:
Every query file defines a hierarchical keys factory with an `all` root key and intermediate plural keys for prefix-level invalidation:
For optimistic mutations, use `onSettled` (not `onSuccess`) for cache reconciliation — `onSettled` fires on both success and error, ensuring the cache is always reconciled with the server.
For optimistic mutations syncing with Zustand, use `createOptimisticMutationHandlers` from `@/hooks/queries/utils/optimistic-mutation`.
## useCallback Dependencies
Never include mutation objects (e.g., `createEntity`) in `useCallback` dependency arrays — the mutation object is not referentially stable and changes on every state update. The `.mutate()` and `.mutateAsync()` functions are stable in TanStack Query v5.
You are an expert at creating block configurations for Sim. You understand the serializer, subBlock types, conditions, dependsOn, modes, and all UI patterns.
## Your Task
When the user asks you to create a block:
1. Create the block file in `apps/sim/blocks/blocks/{service}.ts`
2. Configure all subBlocks with proper types, conditions, and dependencies
serviceId:'{service}',// Must match OAuth provider service key
requiredScopes: getScopesForService('{service}'),// Import from @/lib/oauth/utils
placeholder:'Select account',
required: true,
}
```
**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`.
**Scope descriptions:** When adding a new OAuth provider, also add human-readable descriptions for all scopes in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`.
-`'json-object'` - Raw JSON (adds "no markdown" instruction)
-`'json-schema'` - JSON Schema definitions
-`'sql-query'` - SQL statements
-`'timestamp'` - Adds current date/time context
## Tools Configuration
**Important:**`tools.config.tool` runs during serialization before variable resolution. Put `Number()` and other type coercions in `tools.config.params` instead, which runs at execution time after variables are resolved.
**Preferred:** Use tool names directly as dropdown option IDs to avoid switch cases:
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:
```typescript
outputs:{
// BAD: Opaque json with no info about what's inside
plan:{type:'json',description:'Zone plan information'},
// GOOD: Describe the known fields in the description
plan:{
type:'json',
description:'Zone plan information (id, name, price, currency, frequency, is_subscribed)',
},
// BEST: Use nested output definition when the shape is stable and well-known
See the `/add-trigger` skill for creating triggers.
## Icon Requirement
If the icon doesn't already exist in `@/components/icons.tsx`, **do NOT search for it yourself**. After completing the block, ask the user to provide the SVG:
```
The block is complete, but I need an icon for {Service}.
Please provide the SVG and I'll convert it to a React component.
You can usually find this in the service's brand/press kit page, or copy it from their website.
```
## Advanced Mode for Optional Fields
Optional fields that are rarely used should be set to `mode: 'advanced'` so they don't clutter the basic UI. This includes:
mode:'advanced',// Rarely used, hide from basic view
}
```
## WandConfig for Complex Inputs
Use `wandConfig` for fields that are hard to fill out manually, such as timestamps, comma-separated lists, and complex query strings. This gives users an AI-assisted input experience.
```typescript
// Timestamps - use generationType: 'timestamp' to inject current date context
{
id:'startTime',
title:'Start Time',
type:'short-input',
mode:'advanced',
wandConfig:{
enabled: true,
prompt:'Generate an ISO 8601 timestamp based on the user description. Return ONLY the timestamp string.',
generationType:'timestamp',
},
}
// Comma-separated lists - simple prompt without generationType
{
id:'mediaIds',
title:'Media IDs',
type:'short-input',
mode:'advanced',
wandConfig:{
enabled: true,
prompt:'Generate a comma-separated list of media IDs. Return ONLY the comma-separated values.',
},
}
```
## Naming Convention
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.
## Checklist Before Finishing
- [ ]`integrationType` is set to the correct `IntegrationType` enum value
- [ ]`tags` array includes all applicable `IntegrationTag` values
- [ ] All subBlocks have `id`, `title` (except switch), and `type`
You are an expert at adding knowledge base connectors to Sim. A connector syncs documents from an external source (Confluence, Google Drive, Notion, etc.) into a knowledge base.
## Your Task
When the user asks you to create a connector:
1. Use Context7 or WebFetch to read the service's API documentation
2. Determine the auth mode: **OAuth** (if Sim already has an OAuth provider for the service) or **API key** (if the service uses API key / Bearer token auth)
3. Create the connector directory and config
4. Register it in the connector registry
## Directory Structure
Create files in `apps/sim/connectors/{service}/`:
```
connectors/{service}/
├── index.ts # Barrel export
└── {service}.ts # ConnectorConfig definition
```
## Authentication
Connectors use a discriminated union for auth config (`ConnectorAuthConfig` in `connectors/types.ts`):
For services with existing OAuth providers in `apps/sim/lib/oauth/types.ts`. The `provider` must match an `OAuthService`. The modal shows a credential picker and handles token refresh automatically.
### API key mode
For services that use API key / Bearer token auth. The modal shows a password input with the configured `label` and `placeholder`. The API key is encrypted at rest using AES-256-GCM and stored in a dedicated `encryptedApiKey` column on the connector record. The sync engine decrypts it automatically — connectors receive the raw access token in `listDocuments`, `getDocument`, and `validateConfig`.
The add-connector modal renders these automatically — no custom UI needed.
Three field types are supported: `short-input`, `dropdown`, and `selector`.
```typescript
// Text input
{
id:'domain',
title:'Domain',
type:'short-input',
placeholder:'yoursite.example.com',
required: true,
}
// Dropdown (static options)
{
id:'contentType',
title:'Content Type',
type:'dropdown',
required: false,
options:[
{label:'Pages only',id:'page'},
{label:'Blog posts only',id:'blogpost'},
{label:'All content',id:'all'},
],
}
```
## Dynamic Selectors (Canonical Pairs)
Use `type: 'selector'` to fetch options dynamically from the existing selector registry (`hooks/selectors/registry.ts`). Selectors are always paired with a manual fallback input using the **canonical pair** pattern — a `selector` field (basic mode) and a `short-input` field (advanced mode) linked by `canonicalParamId`.
The user sees a toggle button (ArrowLeftRight) to switch between the selector dropdown and manual text input. On submit, the modal resolves each canonical pair to the active mode's value, keyed by `canonicalParamId`.
### Rules
1.**Every selector field MUST have a canonical pair** — a corresponding `short-input` (or `dropdown`) field with the same `canonicalParamId` and `mode: 'advanced'`.
2.**`required` must be set identically on both fields** in a pair. If the selector is required, the manual input must also be required.
3.**`canonicalParamId` must match the key the connector expects in `sourceConfig`** (e.g. `baseId`, `channel`, `teamId`). The advanced field's `id` should typically match `canonicalParamId`.
4.**`dependsOn` references the selector field's `id`**, not the `canonicalParamId`. The modal propagates dependency clearing across canonical siblings automatically — changing either field in a parent pair clears dependent children.
### Selector canonical pair example (Airtable base → table cascade)
```typescript
configFields:[
// Base: selector (basic) + manual (advanced)
{
id:'baseSelector',
title:'Base',
type:'selector',
selectorKey:'airtable.bases',// Must exist in hooks/selectors/registry.ts
canonicalParamId:'baseId',
mode:'basic',
placeholder:'Select a base',
required: true,
},
{
id:'baseId',
title:'Base ID',
type:'short-input',
canonicalParamId:'baseId',
mode:'advanced',
placeholder:'e.g. appXXXXXXXXXXXXXX',
required: true,
},
// Table: selector depends on base (basic) + manual (advanced)
{
id:'tableSelector',
title:'Table',
type:'selector',
selectorKey:'airtable.tables',
canonicalParamId:'tableIdOrName',
mode:'basic',
dependsOn:['baseSelector'],// References the selector field ID
### Selector with domain dependency (Jira/Confluence pattern)
When a selector depends on a plain `short-input` field (no canonical pair), `dependsOn` references that field's `id` directly. The `domain` field's value maps to `SelectorContext.domain` automatically via `SELECTOR_CONTEXT_FIELDS`.
```typescript
configFields:[
{
id:'domain',
title:'Jira Domain',
type:'short-input',
placeholder:'yoursite.atlassian.net',
required: true,
},
{
id:'projectSelector',
title:'Project',
type:'selector',
selectorKey:'jira.projects',
canonicalParamId:'projectKey',
mode:'basic',
dependsOn:['domain'],
placeholder:'Select a project',
required: true,
},
{
id:'projectKey',
title:'Project Key',
type:'short-input',
canonicalParamId:'projectKey',
mode:'advanced',
placeholder:'e.g. ENG, PROJ',
required: true,
},
]
```
### How `dependsOn` maps to `SelectorContext`
The connector selector field builds a `SelectorContext` from dependency values. For the mapping to work, each dependency's `canonicalParamId` (or field `id` for non-canonical fields) must exist in `SELECTOR_CONTEXT_FIELDS` (`lib/workflows/subblocks/context.ts`):
| `confluence.spaces` | credential, `domain` | Space key + name |
| `notion.databases` | credential | Database ID + name |
| `asana.workspaces` | credential | Workspace GID + name |
| `microsoft.teams` | credential | Team ID + name |
| `microsoft.channels` | credential, `teamId` | Channel ID + name |
| `webflow.sites` | credential | Site ID + name |
| `outlook.folders` | credential | Folder ID + name |
## ExternalDocument Shape
Every document returned from `listDocuments`/`getDocument` must include:
```typescript
{
externalId: string// Source-specific unique ID
title: string// Document title
content: string// Extracted plain text (or '' if contentDeferred)
contentDeferred?: boolean// true = content will be fetched via getDocument
mimeType:'text/plain'// Always text/plain (content is extracted)
contentHash: string// Metadata-based hash for change detection
sourceUrl?: string// Link back to original (stored on document record)
metadata?: Record<string,unknown>// Source-specific data (fed to mapTags)
}
```
## Content Deferral (Required for file/content-download connectors)
**All connectors that require per-document API calls to fetch content MUST use `contentDeferred: true`.** This is the standard pattern — `listDocuments` returns lightweight metadata stubs, and content is fetched lazily by the sync engine via `getDocument` only for new/changed documents.
This pattern is critical for reliability: the sync engine processes documents in batches and enqueues each batch for processing immediately. If a sync times out, all previously-batched documents are already queued. Without deferral, content downloads during listing can exhaust the sync task's time budget before any documents are saved.
### When to use `contentDeferred: true`
- The service's list API does NOT return document content (only metadata)
- Content requires a separate download/export API call per document
- The list API already returns the full content inline (e.g., Slack messages, Reddit posts, HubSpot notes)
- No per-document API call is needed to get content
### Content Hash Strategy
Use a **metadata-based**`contentHash` — never a content-based hash. The hash must be derivable from the list response metadata alone, so the sync engine can detect changes without downloading content.
Good metadata hash sources:
-`modifiedTime` / `lastModifiedDateTime` — changes when file is edited
**Critical invariant:** The `contentHash` MUST be identical whether produced by `listDocuments` (stub) or `getDocument` (full doc). Both should use the same stub function to guarantee this.
All external API calls must use `fetchWithRetry` from `@/lib/knowledge/documents/utils` instead of raw `fetch()`. This provides exponential backoff with retries on 429/502/503/504 errors. It returns a standard `Response` — all `.ok`, `.json()`, `.text()` checks work unchanged.
For `validateConfig` (user-facing, called on save), pass `VALIDATE_RETRY_OPTIONS` to cap wait time at ~7s. Background operations (`listDocuments`, `getDocument`) use the built-in defaults (5 retries, ~31s max).
If `ExternalDocument.sourceUrl` is set, the sync engine stores it on the document record. Always construct the full URL (not a relative path).
## Sync Engine Behavior (Do Not Modify)
The sync engine (`lib/knowledge/connectors/sync-engine.ts`) is connector-agnostic. It:
1. Calls `listDocuments` with pagination until `hasMore` is false
2. Compares `contentHash` to detect new/changed/unchanged documents
3. Stores `sourceUrl` and calls `mapTags` on insert/update automatically
4. Handles soft-delete of removed documents
5. Resolves access tokens automatically — OAuth tokens are refreshed, API keys are decrypted from the `encryptedApiKey` column
You never need to modify the sync engine when adding a connector.
## Icon
The `icon` field on `ConnectorConfig` is used throughout the UI — in the connector list, the add-connector modal, and as the document icon in the knowledge base table (replacing the generic file type icon for connector-sourced documents). The icon is read from `CONNECTOR_REGISTRY[connectorType].icon` at runtime — no separate icon map to maintain.
If the service already has an icon in `apps/sim/components/icons.tsx` (from a tool integration), reuse it. Otherwise, ask the user to provide the SVG.
## Registering
Add one line to `apps/sim/connectors/registry.ts`:
When a tool has hosted key support, Sim provides its own API key if the user hasn't configured one (via BYOK or env var). Usage is metered and billed to the workspace.
## Step 2: Research the API's Pricing Model and Rate Limits
**Before writing any `getCost` or `rateLimit` code**, look up the service's official documentation for both pricing and rate limits. You need to understand:
### Pricing
1.**How the API charges** — per request, per credit, per token, per step, per minute, etc.
2.**Whether the API reports cost in its response** — look for fields like `creditsUsed`, `costDollars`, `tokensUsed`, or similar in the response body or headers
3.**Whether cost varies by endpoint/options** — some APIs charge more for certain features (e.g., Firecrawl charges 1 credit/page base but +4 for JSON format, +4 for enhanced mode)
4.**The dollar-per-unit rate** — what each credit/token/unit costs in dollars on our plan
### Rate Limits
1.**What rate limits the API enforces** — requests per minute/second, tokens per minute, concurrent requests, etc.
2.**Whether limits vary by plan tier** — free vs paid vs enterprise often have different ceilings
3.**Whether limits are per-key or per-account** — determines whether adding more hosted keys actually increases total throughput
4.**What the API returns when rate limited** — HTTP 429, `Retry-After` header, error body format, etc.
5.**Whether there are multiple dimensions** — some APIs limit both requests/min AND tokens/min independently
Search the API's docs/pricing page (use WebSearch/WebFetch). Capture the pricing model as a comment in `getCost` so future maintainers know the source of truth.
### Setting Our Rate Limits
Our rate limiter (`lib/core/rate-limiter/hosted-key/`) uses a token-bucket algorithm applied **per billing actor** (workspace). It supports two modes:
- **`per_request`** — simple; just `requestsPerMinute`. Good when the API charges flat per-request or cost doesn't vary much.
- **`custom`** — `requestsPerMinute` plus additional `dimensions` (e.g., `tokens`, `search_units`). Each dimension has its own `limitPerMinute` and an `extractUsage` function that reads actual usage from the response. Use when the API charges on a variable metric (tokens, credits) and you want to cap that metric too.
When choosing values for `requestsPerMinute` and any dimension limits:
- **Stay well below the API's per-key limit** — our keys are shared across all workspaces. If the API allows 60 RPM per key and we have 3 keys, the global ceiling is ~180 RPM. Set the per-workspace limit low enough (e.g., 20-60 RPM) that many workspaces can coexist without collectively hitting the API's ceiling.
- **Account for key pooling** — our round-robin distributes requests across `N` hosted keys, so the effective API-side rate per key is `(total requests) / N`. But per-workspace limits are enforced *before* key selection, so they apply regardless of key count.
- **Prefer conservative defaults** — it's easy to raise limits later but hard to claw back after users depend on high throughput.
## Step 3: Add `hosting` Config to the Tool
Add a `hosting` object to the tool's `ToolConfig`. This tells the execution layer how to acquire hosted keys, calculate cost, and rate-limit.
Keys use a numbered naming pattern driven by a count env var:
```
YOUR_SERVICE_API_KEY_COUNT=3
YOUR_SERVICE_API_KEY_1=sk-...
YOUR_SERVICE_API_KEY_2=sk-...
YOUR_SERVICE_API_KEY_3=sk-...
```
The `envKeyPrefix` value (`YOUR_SERVICE_API_KEY`) determines which env vars are read at runtime. Adding more keys only requires bumping the count and adding the new env var.
### Pricing: Prefer API-Reported Cost
Always prefer using cost data returned by the API (e.g., `creditsUsed`, `costDollars`). This is the most accurate because it accounts for variable pricing tiers, feature modifiers, and plan-level discounts.
**When the API reports cost** — use it directly and throw if missing:
// Serper: 1 credit for <=10 results, 2 credits for >10 — from https://serper.dev/pricing
constcredits=Number(params.num)>10?2 : 1
return{cost: credits*0.001,metadata:{credits}}
},
},
```
**`getCost` must always throw** if it cannot determine cost. Never silently fall back to a default — this would hide billing inaccuracies.
### Capturing Cost Data from the API
If the API returns cost info, capture it in `transformResponse` so `getCost` can read it from the output:
```typescript
transformResponse: async(response: Response)=>{
constdata=awaitresponse.json()
return{
success: true,
output:{
results: data.results,
creditsUsed: data.creditsUsed,// pass through for getCost
},
}
},
```
For async/polling tools, capture it in `postProcess` when the job completes:
```typescript
if(jobData.status==='completed'){
result.output={
data: jobData.data,
creditsUsed: jobData.creditsUsed,
}
}
```
## Step 4: Hide the API Key Field When Hosted
In the block config (`blocks/blocks/{service}.ts`), add `hideWhenHosted: true` to the API key subblock. This hides the field on hosted Sim since the platform provides the key:
```typescript
{
id:'apiKey',
title:'API Key',
type:'short-input',
placeholder:'Enter your API key',
password: true,
required: true,
hideWhenHosted: true,
},
```
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`).
### Excluding Specific Operations from Hosted Key Support
When a block has multiple operations but some operations should **not** use a hosted key (e.g., the underlying API is deprecated, unsupported, or too expensive), use the **duplicate apiKey subblock** pattern. This is the same pattern Exa uses for its `research` operation:
1.**Remove the `hosting` config** from the tool definition for that operation — it must not have a `hosting` object at all.
2.**Duplicate the `apiKey` subblock** in the block config with opposing conditions:
```typescript
// API Key — hidden when hosted for operations with hosted key support
Both subblocks share the same `id: 'apiKey'`, so the same value flows to the tool. The conditions ensure only one is visible at a time. The first has `hideWhenHosted: true` and shows for all hosted operations; the second has no `hideWhenHosted` and shows only for the excluded operation — meaning users must always provide their own key for that operation.
To exclude multiple operations, use an array: `{ field: 'operation', value: ['op_a', 'op_b'] }`.
Add an entry to the `PROVIDERS` array in the BYOK settings component so users can bring their own key. You need the service icon from `components/icons.tsx`:
```typescript
{
id:'your_service',
name:'Your Service',
icon: YourServiceIcon,
description:'What this service does',
placeholder:'Enter your API key',
},
```
## Step 6: Summarize Pricing and Throttling Comparison
After all code changes are complete, output a detailed summary to the user covering:
### What to include
1.**API's pricing model** — how the service charges (per token, per credit, per request, etc.), the specific rates found in docs, and whether the API reports cost in responses.
2.**Our `getCost` approach** — how we calculate cost, what fields we depend on, and any assumptions or estimates (especially when the API doesn't report exact dollar cost).
3.**API's rate limits** — the documented limits (RPM, TPM, concurrent, etc.), which plan tier they apply to, and whether they're per-key or per-account.
4.**Our `rateLimit` config** — what we set for `requestsPerMinute` (and dimensions if custom mode), why we chose those values, and how they compare to the API's limits.
5.**Key pooling impact** — how many hosted keys we expect, and how round-robin distribution affects the effective per-key rate at the API.
6.**Gaps or risks** — anything the API charges for that we don't meter, rate limit dimensions we chose not to enforce, or pricing that may be inaccurate due to variable model/tier costs.
### Format
Present this as a structured summary with clear headings. Example:
```
### Pricing
- **API charges**: $X per 1M tokens (input), $Y per 1M tokens (output) — varies by model
- **Response reports cost?**: No — only token counts in `usage` field
- **Our getCost**: Estimates cost at $Z per 1M total tokens based on median model pricing
- **Risk**: Actual cost varies by model; our estimate may over/undercharge for cheap/expensive models
-`visibility: 'user-only'` for API keys and user credentials
-`visibility: 'user-or-llm'` for operation parameters
- Always use `?? null` for nullable API response fields
- Always use `?? []` for optional array fields
- Set `optional: true` for outputs that may not exist
- Never output raw JSON dumps - extract meaningful fields
- When using `type: 'json'` and you know the object shape, define `properties` with the inner fields so downstream consumers know the structure. Only use bare `type: 'json'` when the shape is truly dynamic
-`canonicalParamId` must NOT match any subblock's `id` in the block
-`canonicalParamId` must be unique per operation/condition context
- Only use `canonicalParamId` to link basic/advanced alternatives for the same logical parameter
-`mode` only controls UI visibility, NOT serialization. Without `canonicalParamId`, both basic and advanced field values would be sent
- Every subblock `id` must be unique within the block. Duplicate IDs cause conflicts even with different conditions
- **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)
- **Inputs section:** Must list canonical param IDs (e.g., `fileId`), NOT raw subblock IDs (e.g., `fileSelector`, `manualFileId`)
- **Params function:** Must use canonical param IDs, NOT raw subblock IDs (raw IDs are deleted after canonical transformation)
- [ ] Secondary triggers do NOT have `includeDropdown`
- [ ] All triggers use `buildTriggerSubBlocks` helper
- [ ] Created `index.ts` barrel export
- [ ] Registered all triggers in `triggers/registry.ts`
### Docs
- [ ] Ran `bun run scripts/generate-docs.ts`
- [ ] Verified docs file created
### Final Validation (Required)
- [ ] Read every tool file and cross-referenced inputs/outputs against the API docs
- [ ] Verified block subBlocks cover all required tool params with correct conditions
- [ ] Verified block outputs match what the tools actually return
- [ ] Verified `tools.config.params` correctly maps and coerces all param types
## Example Command
When the user asks to add an integration:
```
User: Add a Stripe integration
You: I'll add the Stripe integration. Let me:
1. First, research the Stripe API using Context7
2. Create the tools for key operations (payments, subscriptions, etc.)
3. Create the block with operation dropdown
4. Register everything
5. Generate docs
6. Ask you for the Stripe icon SVG
[Proceed with implementation...]
[After completing steps 1-5...]
I've completed the Stripe integration. Before I can add the icon, please provide the SVG for Stripe.
You can usually find this in the service's brand/press kit page, or copy it from their website.
Paste the SVG code here and I'll convert it to a React component.
```
## File Handling
When your integration handles file uploads or downloads, follow these patterns to work with `UserFile` objects consistently.
### What is a UserFile?
A `UserFile` is the standard file representation in Sim:
```typescript
interfaceUserFile{
id: string// Unique identifier
name: string// Original filename
url: string// Presigned URL for download
size: number// File size in bytes
type:string// MIME type (e.g., 'application/pdf')
base64?: string// Optional base64 content (if small file)
key?: string// Internal storage key
context?: object// Storage context metadata
}
```
### File Input Pattern (Uploads)
For tools that accept file uploads, **always route through an internal API endpoint** rather than calling external APIs directly. This ensures proper file content retrieval.
Optional fields that are rarely used should be set to `mode: 'advanced'` so they don't clutter the basic UI. Examples: pagination tokens, time range filters, sort order, max results, reply settings.
### WandConfig for Complex Inputs
Use `wandConfig` for fields that are hard to fill out manually:
- **Timestamps**: Use `generationType: 'timestamp'` to inject current date context into the AI prompt
- **JSON arrays**: Use `generationType: 'json-object'` for structured data
- **Complex queries**: Use a descriptive prompt explaining the expected format
```typescript
{
id:'startTime',
title:'Start Time',
type:'short-input',
mode:'advanced',
wandConfig:{
enabled: true,
prompt:'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
generationType:'timestamp',
},
}
```
### OAuth Scopes (Centralized System)
Scopes are maintained in a single source of truth and reused everywhere:
1.**Define scopes** in `lib/oauth/oauth.ts` under `OAUTH_PROVIDERS[provider].services[service].scopes`
2.**Add descriptions** in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts` for the OAuth modal UI
3.**Reference in auth.ts** using `getCanonicalScopesForProvider(providerId)` from `@/lib/oauth/utils`
4.**Reference in blocks** using `getScopesForService(serviceId)` from `@/lib/oauth/utils`
**Never hardcode scope arrays** in `auth.ts` or block `requiredScopes`. Always import from the centralized source.
1.**OAuth serviceId must match** - The `serviceId` in oauth-input must match the OAuth provider configuration
2.**All tool IDs MUST be snake_case** - `stripe_create_payment`, not `stripeCreatePayment`. This applies to tool `id` fields, registry keys, `tools.access` arrays, and `tools.config.tool` return values
3.**Block type is snake_case** - `type: 'stripe'`, not `type: 'Stripe'`
4.**Alphabetical ordering** - Keep imports and registry entries alphabetically sorted
5.**Required can be conditional** - Use `required: { field: 'op', value: 'create' }` instead of always true
6.**DependsOn clears options** - When a dependency changes, selector options are refetched
7.**Never pass Buffer directly to fetch** - Convert to `new Uint8Array(buffer)` for TypeScript compatibility
You are an expert at creating tool configurations for Sim integrations. Your job is to read API documentation and create properly structured tool files.
## Your Task
When the user asks you to create tools for a service:
1. Use Context7 or WebFetch to read the service's API documentation
2. Create the tools directory structure
3. Generate properly typed tool configurations
## Directory Structure
Create files in `apps/sim/tools/{service}/`:
```
tools/{service}/
├── index.ts # Barrel export
├── types.ts # Parameter & response types
└── {action}.ts # Individual tool files (one per operation)
// Trim ID fields to prevent copy-paste whitespace errors:
// userId: params.userId?.trim(),
}),
},
transformResponse: async(response: Response)=>{
constdata=awaitresponse.json()
return{
success: true,
output:{
// Map API response to output
// Use ?? null for nullable fields
// Use ?? [] for optional arrays
},
}
},
outputs:{
// Define each output field
},
}
```
## Critical Rules for Parameters
### Visibility Options
-`'hidden'` - System-injected (OAuth tokens, internal params). User never sees.
-`'user-only'` - User must provide (credentials, api keys, account-specific IDs)
-`'user-or-llm'` - User provides OR LLM can compute (search queries, content, filters, most fall into this category)
### Parameter Types
-`'string'` - Text values
-`'number'` - Numeric values
-`'boolean'` - True/false
-`'json'` - Complex objects (NOT 'object', use 'json')
-`'file'` - Single file
-`'file[]'` - Multiple files
### Required vs Optional
- Always explicitly set `required: true` or `required: false`
- Optional params should have `required: false`
## Critical Rules for Outputs
### Output Types
-`'string'`, `'number'`, `'boolean'` - Primitives
-`'json'` - Complex objects (use this, NOT 'object')
-`'array'` - Arrays with `items` property
-`'object'` - Objects with `properties` property
### Optional Outputs
Add `optional: true` for fields that may not exist in the response:
```typescript
closedAt:{
type:'string',
description:'When the issue was closed',
optional: true,
},
```
### Typed JSON Outputs
When using `type: 'json'` and you know the object shape in advance, **always define the inner structure** using `properties` so downstream consumers know what fields are available:
```typescript
// BAD: Opaque json with no info about what's inside
2. Add to the `tools` object with snake_case keys:
```typescript
import{serviceActionTool}from'@/tools/{service}'
exportconsttools={
// ... existing tools ...
{service}_{action}:serviceActionTool,
}
```
## V2 Tool Pattern
If creating V2 tools (API-aligned outputs), use `_v2` suffix:
- Tool ID: `{service}_{action}_v2`
- Variable name: `{action}V2Tool`
- Version: `'2.0.0'`
- Outputs: Flat, API-aligned (no content/metadata wrapper)
## Naming Convention
All tool IDs MUST use `snake_case`: `{service}_{action}` (e.g., `x_create_tweet`, `slack_send_message`). Never use camelCase or PascalCase for tool IDs.
## Checklist Before Finishing
- [ ] All tool IDs use snake_case
- [ ] All params have explicit `required: true` or `required: false`
- [ ] All params have appropriate `visibility`
- [ ] All nullable response fields use `?? null`
- [ ] All optional outputs have `optional: true`
- [ ] No raw JSON dumps in outputs
- [ ] Types file has all interfaces
- [ ] Index.ts exports all tools
## Final Validation (Required)
After creating all tools, you MUST validate every tool before finishing:
1.**Read every tool file** you created — do not skip any
2.**Cross-reference with the API docs** to verify:
- All required params are marked `required: true`
- All optional params are marked `required: false`
- Param types match the API (string, number, boolean, json)
- Request URL, method, headers, and body match the API spec
-`transformResponse` extracts the correct fields from the API response
- All output fields match what the API actually returns
- No fields are missing from outputs that the API provides
- No extra fields are defined in outputs that the API doesn't return
3.**Verify consistency** across tools:
- Shared types in `types.ts` match all tools that use them
- Tool IDs in the barrel export match the tool file definitions
- Error handling is consistent (error checks, meaningful messages)
You are an expert at creating webhook triggers for Sim. You understand the trigger system, the generic `buildTriggerSubBlocks` helper, and how triggers connect to blocks.
## Your Task
1. Research what webhook events the service supports
2. Create the trigger files using the generic builder
3. Create a provider handler if custom auth, formatting, or subscriptions are needed
4. Register triggers and connect them to the block
## Directory Structure
```
apps/sim/triggers/{service}/
├── index.ts # Barrel exports
├── utils.ts # Service-specific helpers (options, instructions, extra fields, outputs)
If the service API supports programmatic webhook creation, implement `createSubscription` and `deleteSubscription` on the handler. The orchestration layer calls these automatically — **no code touches `route.ts`, `provider-subscriptions.ts`, or `deploy.ts`**.
You are an expert auditor for Sim knowledge base connectors. Your job is to thoroughly validate that an existing connector is correct, complete, and follows all conventions.
## Your Task
When the user asks you to validate a connector:
1. Read the service's API documentation (via Context7 or WebFetch)
2. Read the connector implementation, OAuth config, and registry entries
3. Cross-reference everything against the API docs and Sim conventions
4. Report all issues found, grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the connector — do not skip any:
Fetch the official API docs for the service. This is the **source of truth** for:
- Endpoint URLs, HTTP methods, and auth headers
- Required vs optional parameters
- Parameter types and allowed values
- Response shapes and field names
- Pagination patterns (cursor, offset, next token)
- Rate limits and error formats
- OAuth scopes and their meanings
Use Context7 (resolve-library-id → query-docs) or WebFetch to retrieve documentation. If both fail, note which claims are based on training knowledge vs verified docs.
## Step 3: Validate API Endpoints
For **every** API call in the connector (`listDocuments`, `getDocument`, `validateConfig`, and any helper functions), verify against the API docs:
### URLs and Methods
- [ ] Base URL is correct for the service's API version
- [ ] Endpoint paths match the API docs exactly
- [ ] HTTP method is correct (GET, POST, PUT, PATCH, DELETE)
- [ ] Path parameters are correctly interpolated and URI-encoded where needed
- [ ] Query parameters use correct names and formats per the API docs
### Headers
- [ ] Authorization header uses the correct format:
- OAuth: `Authorization: Bearer ${accessToken}`
- API Key: correct header name per the service's docs
- [ ]`Content-Type` is set for POST/PUT/PATCH requests
- [ ] Any service-specific headers are present (e.g., `Notion-Version`, `Dropbox-API-Arg`)
- [ ] No headers are sent that the API doesn't support or silently ignores
### Request Bodies
- [ ] POST/PUT body fields match API parameter names exactly
- [ ] Required fields are always sent
- [ ] Optional fields are conditionally included (not sent as `null` or empty unless the API expects that)
- [ ] Field value types match API expectations (string vs number vs boolean)
### Input Sanitization
- [ ] User-controlled values interpolated into query strings are properly escaped:
- OData `$filter`: single quotes escaped with `''` (e.g., `externalId.replace(/'/g, "''")`)
- SOQL: single quotes escaped with `\'`
- GraphQL variables: passed as variables, not interpolated into query strings
Scopes must be correctly declared and sufficient for all API calls the connector makes.
### Connector requiredScopes
- [ ]`requiredScopes` in the connector's `auth` config lists all scopes needed by the connector
- [ ] Each scope in `requiredScopes` is a real, valid scope recognized by the service's API
- [ ] No invalid, deprecated, or made-up scopes are listed
- [ ] No unnecessary excess scopes beyond what the connector actually needs
### Scope Subset Validation (CRITICAL)
- [ ] Every scope in `requiredScopes` exists in the OAuth provider's `scopes` array in `lib/oauth/oauth.ts`
- [ ] Find the provider in `OAUTH_PROVIDERS[providerGroup].services[serviceId].scopes`
- [ ] Verify: `requiredScopes` ⊆ `OAUTH_PROVIDERS scopes` (every required scope is present in the provider config)
- [ ] If a required scope is NOT in the provider config, flag as **critical** — the connector will fail at runtime
### Scope Sufficiency
For each API endpoint the connector calls:
- [ ] Identify which scopes are required per the API docs
- [ ] Verify those scopes are included in the connector's `requiredScopes`
- [ ] If the connector calls endpoints requiring scopes not in `requiredScopes`, flag as **warning**
### Token Refresh Config
- [ ] Check the `getOAuthTokenRefreshConfig` function in `lib/oauth/oauth.ts` for this provider
- [ ]`useBasicAuth` matches the service's token exchange requirements
- [ ]`supportsRefreshTokenRotation` matches whether the service issues rotating refresh tokens
- [ ] Token endpoint URL is correct
## Step 5: Validate Pagination
### listDocuments Pagination
- [ ] Cursor/pagination parameter name matches the API docs
- [ ] Response pagination field is correctly extracted (e.g., `next_cursor`, `nextPageToken`, `@odata.nextLink`, `offset`)
- [ ]`hasMore` is correctly determined from the response
- [ ]`nextCursor` is correctly passed back for the next page
- [ ]`maxItems` / `maxRecords` cap is correctly applied across pages using `syncContext.totalDocsFetched`
- [ ] Page size is within the API's allowed range (not exceeding max page size)
- [ ] Last page precision: when a `maxItems` cap exists, the final page request uses `Math.min(PAGE_SIZE, remaining)` to avoid fetching more records than needed
- [ ] No off-by-one errors in pagination tracking
- [ ] The connector does NOT hit known API pagination limits silently (e.g., HubSpot search 10k cap)
### Pagination State Across Pages
- [ ]`syncContext` is used to cache state across pages (user names, field maps, instance URLs, portal IDs, etc.)
- [ ] Cached state in `syncContext` is correctly initialized on first page and reused on subsequent pages
## Step 6: Validate Data Transformation
### ExternalDocument Construction
- [ ]`externalId` is a stable, unique identifier from the source API
- [ ]`title` is extracted from the correct field and has a sensible fallback (e.g., `'Untitled'`)
- [ ]`content` is plain text — HTML content is stripped using `htmlToPlainText` from `@/connectors/utils`
- [ ]`mimeType` is `'text/plain'`
- [ ]`contentHash` is computed using `computeContentHash` from `@/connectors/utils`
- [ ]`sourceUrl` is a valid, complete URL back to the original resource (not relative)
- [ ]`metadata` contains all fields referenced by `mapTags` and `tagDefinitions`
### Content Extraction
- [ ] Rich text / HTML fields are converted to plain text before indexing
- [ ] Important content is not silently dropped (e.g., nested blocks, table cells, code blocks)
- [ ] Content is not silently truncated without logging a warning
- [ ] Empty/blank documents are properly filtered out
- [ ] Size checks use `Buffer.byteLength(text, 'utf8')` not `text.length` when comparing against byte-based limits (e.g., `MAX_FILE_SIZE` in bytes)
## Step 7: Validate Tag Definitions and mapTags
### tagDefinitions
- [ ] Each `tagDefinition` has an `id`, `displayName`, and `fieldType`
- [ ]`fieldType` matches the actual data type: `'text'` for strings, `'number'` for numbers, `'date'` for dates, `'boolean'` for booleans
- [ ] Every `id` in `tagDefinitions` is returned by `mapTags`
- [ ] No `tagDefinition` references a field that `mapTags` never produces
### mapTags
- [ ] Return keys match `tagDefinition``id` values exactly
- [ ] Date values are properly parsed using `parseTagDate` from `@/connectors/utils`
- [ ] Array values are properly joined using `joinTagArray` from `@/connectors/utils`
- [ ] Number values are validated (not `NaN`)
- [ ] Metadata field names accessed in `mapTags` match what `listDocuments`/`getDocument` store in `metadata`
## Step 8: Validate Config Fields and Validation
### configFields
- [ ] Every field has `id`, `title`, `type`
- [ ]`required` is set explicitly (not omitted)
- [ ] Dropdown fields have `options` with `label` and `id` for each option
- [ ] Selector fields follow the canonical pair pattern:
- A `type: 'selector'` field with `selectorKey`, `canonicalParamId`, `mode: 'basic'`
- A `type: 'short-input'` field with the same `canonicalParamId`, `mode: 'advanced'`
-`required` is identical on both fields in the pair
- [ ]`selectorKey` values exist in the selector registry
- [ ]`dependsOn` references selector field `id` values, not `canonicalParamId`
### validateConfig
- [ ] Validates all required fields are present before making API calls
- [ ] Catches exceptions and returns user-friendly error messages
- [ ] Does NOT make expensive calls (full data listing, large queries)
## Step 9: Validate getDocument
- [ ] Fetches a single document by `externalId`
- [ ] Returns `null` for 404 / not found (does not throw)
- [ ] Returns the same `ExternalDocument` shape as `listDocuments`
- [ ] Handles all content types that `listDocuments` can produce (e.g., if `listDocuments` returns both pages and blogposts, `getDocument` must handle both — not hardcode one endpoint)
- [ ] Forwards `syncContext` if it needs cached state (user names, field maps, etc.)
- [ ] Error handling is graceful (catches, logs, returns null or throws with context)
- [ ] Does not redundantly re-fetch data already included in the initial API response (e.g., if comments come back with the post, don't fetch them again separately)
## Step 10: Validate General Quality
### fetchWithRetry Usage
- [ ] All external API calls use `fetchWithRetry` from `@/lib/knowledge/documents/utils`
- [ ] No raw `fetch()` calls to external APIs
- [ ]`VALIDATE_RETRY_OPTIONS` used in `validateConfig`
- [ ] If `validateConfig` calls a shared helper (e.g., `linearGraphQL`, `resolveId`), that helper must accept and forward `retryOptions` to `fetchWithRetry`
- [ ] Default retry options used in `listDocuments`/`getDocument`
### API Efficiency
- [ ] APIs that support field selection (e.g., `$select`, `sysparm_fields`, `fields`) should request only the fields the connector needs — in both `listDocuments` AND `getDocument`
- [ ] No redundant API calls: if a helper already fetches data (e.g., site metadata), callers should reuse the result instead of making a second call for the same information
- [ ] Sequential per-item API calls (fetching details for each document in a loop) should be batched with `Promise.all` and a concurrency limit of 3-5
### Error Handling
- [ ] Individual document failures are caught and logged without aborting the sync
- [ ] API error responses include status codes in error messages
- [ ] No unhandled promise rejections in concurrent operations
### Concurrency
- [ ] Concurrent API calls use reasonable batch sizes (3-5 is typical)
- [ ] No unbounded `Promise.all` over large arrays
### Logging
- [ ] Uses `createLogger` from `@sim/logger` (not `console.log`)
- [ ] Logs sync progress at `info` level
- [ ] Logs errors at `warn` or `error` level with context
### Registry
- [ ] Connector is exported from `connectors/{service}/index.ts`
- [ ] Connector is registered in `connectors/registry.ts`
- [ ] Registry key matches the connector's `id` field
## Step 11: Report and Fix
### Report Format
Group findings by severity:
**Critical** (will cause runtime errors, data loss, or auth failures):
- Wrong API endpoint URL or HTTP method
- Invalid or missing OAuth scopes (not in provider config)
- Incorrect response field mapping (accessing wrong path)
- SOQL/query fields that don't exist on the target object
- Pagination that silently hits undocumented API limits
- Missing error handling that would crash the sync
-`requiredScopes` not a subset of OAuth provider scopes
- Query/filter injection: user-controlled values interpolated into OData `$filter`, SOQL, or query strings without escaping
**Warning** (incorrect behavior, data quality issues, or convention violations):
- HTML content not stripped via `htmlToPlainText`
-`getDocument` not forwarding `syncContext`
-`getDocument` hardcoded to one content type when `listDocuments` returns multiple (e.g., only pages but not blogposts)
- Missing `tagDefinition` for metadata fields returned by `mapTags`
- Incorrect `useBasicAuth` or `supportsRefreshTokenRotation` in token refresh config
- Invalid scope names that the API doesn't recognize (even if silently ignored)
- Private resources excluded from name-based lookup despite scopes being available
- Silent data truncation without logging
- Size checks using `text.length` (character count) instead of `Buffer.byteLength` (byte count) for byte-based limits
- URL-type config fields not normalized (protocol prefix, trailing slashes cause API failures)
-`VALIDATE_RETRY_OPTIONS` not threaded through helper functions called by `validateConfig`
**Suggestion** (minor improvements):
- Missing incremental sync support despite API supporting it
- Overly broad scopes that could be narrowed (not wrong, but could be tighter)
- Source URL format could be more specific
- Missing `orderBy` for deterministic pagination
- Redundant API calls that could be cached in `syncContext`
- Sequential per-item API calls that could be batched with `Promise.all` (concurrency 3-5)
- API supports field selection but connector fetches all fields (e.g., missing `$select`, `sysparm_fields`, `fields`)
-`getDocument` re-fetches data already included in the initial API response (e.g., comments returned with post)
- Last page of pagination requests full `PAGE_SIZE` when fewer records remain (`Math.min(PAGE_SIZE, remaining)`)
### Fix All Issues
After reporting, fix every **critical** and **warning** issue. Apply **suggestions** where they don't add unnecessary complexity.
### Validation Output
After fixing, confirm:
1.`bun run lint` passes
2. TypeScript compiles clean
3. Re-read all modified files to verify fixes are correct
You are an expert auditor for Sim integrations. Your job is to thoroughly validate that an existing integration is correct, complete, and follows all conventions.
## Your Task
When the user asks you to validate an integration:
1. Read the service's API documentation (via WebFetch or Context7)
2. Read every tool, the block, and registry entries
3. Cross-reference everything against the API docs and Sim conventions
4. Report all issues found, grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the integration — do not skip any:
```
apps/sim/tools/{service}/ # All tool files, types.ts, index.ts
- Credentials → `oauth-input` with correct `serviceId`
- [ ] Dropdown `value: () => 'default'` is set for dropdowns with a sensible default
### Advanced Mode
- [ ] Optional, rarely-used fields are set to `mode: 'advanced'`:
- Pagination tokens / next tokens
- Time range filters (start/end time)
- Sort order / direction options
- Max results / per page limits
- Reply settings / threading options
- Rarely used IDs (reply-to, quote-tweet, etc.)
- Exclude filters
- [ ]**Required** fields are NEVER set to `mode: 'advanced'`
- [ ] Fields that users fill in most of the time are NOT set to `mode: 'advanced'`
### WandConfig
- [ ] Timestamp fields have `wandConfig` with `generationType: 'timestamp'`
- [ ] Comma-separated list fields have `wandConfig` with a descriptive prompt
- [ ] Complex filter/query fields have `wandConfig` with format examples in the prompt
- [ ] All `wandConfig` prompts end with "Return ONLY the [format] - no explanations, no extra text."
- [ ]`wandConfig.placeholder` describes what to type in natural language
### Tools Config
- [ ]`tools.access` lists **every** tool ID the block can use — none missing
- [ ]`tools.config.tool` returns the correct tool ID for each operation
- [ ] Type coercions are in `tools.config.params` (runs at execution time), NOT in `tools.config.tool` (runs at serialization time before variable resolution)
- [ ]`tools.config.params` handles:
-`Number()` conversion for numeric params that come as strings from inputs
-`Boolean` / string-to-boolean conversion for toggle params
- Empty string → `undefined` conversion for optional dropdown values
- Any subBlock ID → tool param name remapping
- [ ] No `Number()`, `JSON.parse()`, or other coercions in `tools.config.tool` — these would destroy dynamic references like `<Block.output>`
### Block Outputs
- [ ] Outputs cover the key fields returned by ALL tools (not just one operation)
You are an expert auditor for Sim webhook triggers. Your job is to validate that an existing trigger implementation is correct, complete, secure, and aligned across all layers.
## Your Task
1. Read the service's webhook/API documentation (via WebFetch)
2. Read every trigger file, provider handler, and registry entry
3. Cross-reference against the API docs and Sim conventions
4. Report all issues grouped by severity (critical, warning, suggestion)
5. Fix all issues after reporting them
## Step 1: Gather All Files
Read **every** file for the trigger — do not skip any:
```
apps/sim/triggers/{service}/ # All trigger files, utils.ts, index.ts
All React Query hooks live in `hooks/queries/`. All server state must go through React Query — never use `useState` + `fetch` in components for data fetching or mutations.
## Query Key Factory
Every query file defines a keys factory:
Every query file defines a hierarchical keys factory with an `all` root key and intermediate plural keys for prefix-level invalidation:
For optimistic mutations, use `onSettled` (not `onSuccess`) for cache reconciliation — `onSettled` fires on both success and error, ensuring the cache is always reconciled with the server.
For optimistic mutations syncing with Zustand, use `createOptimisticMutationHandlers` from `@/hooks/queries/utils/optimistic-mutation`.
## useCallback Dependencies
Never include mutation objects (e.g., `createEntity`) in `useCallback` dependency arrays — the mutation object is not referentially stable and changes on every state update. The `.mutate()` and `.mutateAsync()` functions are stable in TanStack Query v5.
description: Add hosted API key support to a tool so Sim provides the key when users don't bring their own. Use when adding hosted keys, BYOK support, hideWhenHosted, or hosted key pricing to a tool or block.
---
# Adding Hosted Key Support to a Tool
When a tool has hosted key support, Sim provides its own API key if the user hasn't configured one (via BYOK or env var). Usage is metered and billed to the workspace.
## Step 2: Research the API's Pricing Model and Rate Limits
**Before writing any `getCost` or `rateLimit` code**, look up the service's official documentation for both pricing and rate limits. You need to understand:
### Pricing
1.**How the API charges** — per request, per credit, per token, per step, per minute, etc.
2.**Whether the API reports cost in its response** — look for fields like `creditsUsed`, `costDollars`, `tokensUsed`, or similar in the response body or headers
3.**Whether cost varies by endpoint/options** — some APIs charge more for certain features (e.g., Firecrawl charges 1 credit/page base but +4 for JSON format, +4 for enhanced mode)
4.**The dollar-per-unit rate** — what each credit/token/unit costs in dollars on our plan
### Rate Limits
1.**What rate limits the API enforces** — requests per minute/second, tokens per minute, concurrent requests, etc.
2.**Whether limits vary by plan tier** — free vs paid vs enterprise often have different ceilings
3.**Whether limits are per-key or per-account** — determines whether adding more hosted keys actually increases total throughput
4.**What the API returns when rate limited** — HTTP 429, `Retry-After` header, error body format, etc.
5.**Whether there are multiple dimensions** — some APIs limit both requests/min AND tokens/min independently
Search the API's docs/pricing page (use WebSearch/WebFetch). Capture the pricing model as a comment in `getCost` so future maintainers know the source of truth.
### Setting Our Rate Limits
Our rate limiter (`lib/core/rate-limiter/hosted-key/`) uses a token-bucket algorithm applied **per billing actor** (workspace). It supports two modes:
- **`per_request`** — simple; just `requestsPerMinute`. Good when the API charges flat per-request or cost doesn't vary much.
- **`custom`** — `requestsPerMinute` plus additional `dimensions` (e.g., `tokens`, `search_units`). Each dimension has its own `limitPerMinute` and an `extractUsage` function that reads actual usage from the response. Use when the API charges on a variable metric (tokens, credits) and you want to cap that metric too.
When choosing values for `requestsPerMinute` and any dimension limits:
- **Stay well below the API's per-key limit** — our keys are shared across all workspaces. If the API allows 60 RPM per key and we have 3 keys, the global ceiling is ~180 RPM. Set the per-workspace limit low enough (e.g., 20-60 RPM) that many workspaces can coexist without collectively hitting the API's ceiling.
- **Account for key pooling** — our round-robin distributes requests across `N` hosted keys, so the effective API-side rate per key is `(total requests) / N`. But per-workspace limits are enforced *before* key selection, so they apply regardless of key count.
- **Prefer conservative defaults** — it's easy to raise limits later but hard to claw back after users depend on high throughput.
## Step 3: Add `hosting` Config to the Tool
Add a `hosting` object to the tool's `ToolConfig`. This tells the execution layer how to acquire hosted keys, calculate cost, and rate-limit.
Keys use a numbered naming pattern driven by a count env var:
```
YOUR_SERVICE_API_KEY_COUNT=3
YOUR_SERVICE_API_KEY_1=sk-...
YOUR_SERVICE_API_KEY_2=sk-...
YOUR_SERVICE_API_KEY_3=sk-...
```
The `envKeyPrefix` value (`YOUR_SERVICE_API_KEY`) determines which env vars are read at runtime. Adding more keys only requires bumping the count and adding the new env var.
### Pricing: Prefer API-Reported Cost
Always prefer using cost data returned by the API (e.g., `creditsUsed`, `costDollars`). This is the most accurate because it accounts for variable pricing tiers, feature modifiers, and plan-level discounts.
**When the API reports cost** — use it directly and throw if missing:
// Serper: 1 credit for <=10 results, 2 credits for >10 — from https://serper.dev/pricing
constcredits=Number(params.num)>10?2 : 1
return{cost: credits*0.001,metadata:{credits}}
},
},
```
**`getCost` must always throw** if it cannot determine cost. Never silently fall back to a default — this would hide billing inaccuracies.
### Capturing Cost Data from the API
If the API returns cost info, capture it in `transformResponse` so `getCost` can read it from the output:
```typescript
transformResponse: async(response: Response)=>{
constdata=awaitresponse.json()
return{
success: true,
output:{
results: data.results,
creditsUsed: data.creditsUsed,// pass through for getCost
},
}
},
```
For async/polling tools, capture it in `postProcess` when the job completes:
```typescript
if(jobData.status==='completed'){
result.output={
data: jobData.data,
creditsUsed: jobData.creditsUsed,
}
}
```
## Step 4: Hide the API Key Field When Hosted
In the block config (`blocks/blocks/{service}.ts`), add `hideWhenHosted: true` to the API key subblock. This hides the field on hosted Sim since the platform provides the key:
```typescript
{
id:'apiKey',
title:'API Key',
type:'short-input',
placeholder:'Enter your API key',
password: true,
required: true,
hideWhenHosted: true,
},
```
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`).
### Excluding Specific Operations from Hosted Key Support
When a block has multiple operations but some operations should **not** use a hosted key (e.g., the underlying API is deprecated, unsupported, or too expensive), use the **duplicate apiKey subblock** pattern. This is the same pattern Exa uses for its `research` operation:
1.**Remove the `hosting` config** from the tool definition for that operation — it must not have a `hosting` object at all.
2.**Duplicate the `apiKey` subblock** in the block config with opposing conditions:
```typescript
// API Key — hidden when hosted for operations with hosted key support
Both subblocks share the same `id: 'apiKey'`, so the same value flows to the tool. The conditions ensure only one is visible at a time. The first has `hideWhenHosted: true` and shows for all hosted operations; the second has no `hideWhenHosted` and shows only for the excluded operation — meaning users must always provide their own key for that operation.
To exclude multiple operations, use an array: `{ field: 'operation', value: ['op_a', 'op_b'] }`.
Add an entry to the `PROVIDERS` array in the BYOK settings component so users can bring their own key. You need the service icon from `components/icons.tsx`:
```typescript
{
id:'your_service',
name:'Your Service',
icon: YourServiceIcon,
description:'What this service does',
placeholder:'Enter your API key',
},
```
## Step 6: Summarize Pricing and Throttling Comparison
After all code changes are complete, output a detailed summary to the user covering:
### What to include
1.**API's pricing model** — how the service charges (per token, per credit, per request, etc.), the specific rates found in docs, and whether the API reports cost in responses.
2.**Our `getCost` approach** — how we calculate cost, what fields we depend on, and any assumptions or estimates (especially when the API doesn't report exact dollar cost).
3.**API's rate limits** — the documented limits (RPM, TPM, concurrent, etc.), which plan tier they apply to, and whether they're per-key or per-account.
4.**Our `rateLimit` config** — what we set for `requestsPerMinute` (and dimensions if custom mode), why we chose those values, and how they compare to the API's limits.
5.**Key pooling impact** — how many hosted keys we expect, and how round-robin distribution affects the effective per-key rate at the API.
6.**Gaps or risks** — anything the API charges for that we don't meter, rate limit dimensions we chose not to enforce, or pricing that may be inaccurate due to variable model/tier costs.
### Format
Present this as a structured summary with clear headings. Example:
```
### Pricing
- **API charges**: $X per 1M tokens (input), $Y per 1M tokens (output) — varies by model
- **Response reports cost?**: No — only token counts in `usage` field
- **Our getCost**: Estimates cost at $Z per 1M total tokens based on median model pricing
- **Risk**: Actual cost varies by model; our estimate may over/undercharge for cheap/expensive models
You are a professional software engineer. All code must follow best practices: accurate, readable, clean, and efficient.
## Global Standards
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
- **Styling**: Never update global styles. Keep all styling local to components
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
## Architecture
### Core Principles
1. Single Responsibility: Each component, hook, store has one clear purpose
2. Composition Over Complexity: Break down complex logic into smaller pieces
3. Type Safety First: TypeScript interfaces for all props, state, return types
4. Predictable State: Zustand for global state, useState for UI-only concerns
### Root Structure
```
apps/sim/
├── app/ # Next.js app router (pages, API routes)
├── blocks/ # Block definitions and registry
├── components/ # Shared UI (emcn/, ui/)
├── executor/ # Workflow execution engine
├── hooks/ # Shared hooks (queries/, selectors/)
├── lib/ # App-wide utilities
├── providers/ # LLM provider integrations
├── stores/ # Zustand stores
├── tools/ # Tool definitions
└── triggers/ # Trigger definitions
```
### Naming Conventions
- Components: PascalCase (`WorkflowList`)
- Hooks: `use` prefix (`useWorkflowOperations`)
- Files: kebab-case (`workflow-list.tsx`)
- Stores: `stores/feature/store.ts`
- Constants: SCREAMING_SNAKE_CASE
- Interfaces: PascalCase with suffix (`WorkflowListProps`)
## Imports
**Always use absolute imports.** Never use relative imports.
Use `devtools` middleware. Use `persist` only when data should survive reload with `partialize` to persist only necessary state.
## React Query
All React Query hooks live in `hooks/queries/`. All server state must go through React Query — never use `useState` + `fetch` in components for data fetching or mutations.
### Query Key Factory
Every file must have a hierarchical key factory with an `all` root key and intermediate plural keys for prefix invalidation:
Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA when 2+ variants exist.
## Testing
Use Vitest. Test files: `feature.ts` → `feature.test.ts`. See `.cursor/rules/sim-testing.mdc` for full details.
### Global Mocks (vitest.setup.ts)
`@sim/db`, `drizzle-orm`, `@sim/logger`, `@/blocks/registry`, `@trigger.dev/sdk`, and store mocks are provided globally. Do NOT re-mock them unless overriding behavior.
tools:{access:['service_action'],config:{tool:(p)=>`service_${p.operation}`,params:(p)=>({/* type coercions here */})}},
inputs:{/* ... */},
outputs:{/* ... */},
}
```
Register in `blocks/registry.ts` (alphabetically).
**Important:**`tools.config.tool` runs during serialization (before variable resolution). Never do `Number()` or other type coercions there — dynamic references like `<Block.output>` will be destroyed. Use `tools.config.params` for type coercions (it runs during execution, after variables are resolved).
For file uploads, create an internal API route (`/api/tools/{service}/upload`) that uses `downloadFileFromStorage` to get file content from `UserFile` objects.
@@ -7,6 +7,7 @@ You are a professional software engineer. All code must follow best practices: a
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
- **Styling**: Never update global styles. Keep all styling local to components
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
## Architecture
@@ -134,21 +135,64 @@ Use `devtools` middleware. Use `persist` only when data should survive reload wi
## React Query
All React Query hooks live in `hooks/queries/`.
All React Query hooks live in `hooks/queries/`. All server state must go through React Query — never use `useState` + `fetch` in components for data fetching or mutations.
### Query Key Factory
Every file must have a hierarchical key factory with an `all` root key and intermediate plural keys for prefix invalidation:
<p align="center">Build and deploy AI agent workflows in minutes.</p>
<p align="center">The open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to orchestrate agentic workflows.</p>
@@ -70,43 +74,11 @@ docker compose -f docker-compose.prod.yml up -d
Open [http://localhost:3000](http://localhost:3000)
#### Using Local Models with Ollama
#### Background worker note
Run Sim with local AI models using [Ollama](https://ollama.ai) - no external APIs required:
The Docker Compose stack starts a dedicated worker container by default. If `REDIS_URL` is not configured, the worker will start, log that it is idle, and do no queue processing. This is expected. Queue-backed API, webhook, and schedule execution requires Redis; installs without Redis continue to use the inline execution path.
```bash
# Start with GPU support (automatically downloads gemma3:4b model)
docker compose -f docker-compose.ollama.yml --profile setup up -d
# For CPU-only systems:
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```
Wait for the model to download, then visit [http://localhost:3000](http://localhost:3000). Add more models with:
If Ollama is running on your host machine, use `host.docker.internal` instead of `localhost`:
```bash
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
```
On Linux, use your host's IP address or add `extra_hosts: ["host.docker.internal:host-gateway"]` to the compose file.
#### Using vLLM
Sim supports [vLLM](https://docs.vllm.ai/) for self-hosted models. Set `VLLM_BASE_URL` and optionally `VLLM_API_KEY` in your environment.
### Self-hosted: Dev Containers
1. Open VS Code with the [Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
2. Open the project and click "Reopen in Container" when prompted
3. Run `bun run dev:full` in the terminal or use the `sim-start` alias
- This starts both the main application and the realtime socket server
Sim also supports local models via [Ollama](https://ollama.ai) and [vLLM](https://docs.vllm.ai/) — see the [Docker self-hosting docs](https://docs.sim.ai/self-hosting/docker) for setup details.
### Self-hosted: Manual Setup
@@ -118,6 +90,7 @@ Sim supports [vLLM](https://docs.vllm.ai/) for self-hosted models. Set `VLLM_BAS
git clone https://github.com/simstudioai/sim.git
cd sim
bun install
bun run prepare # Set up pre-commit hooks
```
2. Set up PostgreSQL with pgvector:
@@ -132,6 +105,11 @@ Or install manually via the [pgvector guide](https://github.com/pgvector/pgvecto
cd packages/db && bunx drizzle-kit migrate --config=./drizzle.config.ts
cd packages/db && bun run db:migrate
```
5. Start development servers:
```bash
bun run dev:full # Starts both Next.js app and realtime socket server
bun run dev:full # Starts Next.js app, realtime socket server, and the BullMQ worker
```
Or run separately: `bun run dev` (Next.js) and `cd apps/sim && bun run dev:sockets` (realtime).
If `REDIS_URL` is not configured, the worker will remain idle and execution continues inline.
Or run separately: `bun run dev` (Next.js), `cd apps/sim && bun run dev:sockets` (realtime), and `cd apps/sim && bun run worker` (BullMQ worker).
## Copilot API Keys
@@ -159,18 +139,7 @@ Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
## Environment Variables
Key environment variables for self-hosted deployments. See [`.env.example`](apps/sim/.env.example) for defaults or [`env.ts`](apps/sim/lib/core/config/env.ts) for the full list.
| `COPILOT_API_KEY` | No | API key from sim.ai for Copilot features |
See the [environment variables reference](https://docs.sim.ai/self-hosting/environment-variables) for the full list, or [`apps/sim/.env.example`](apps/sim/.env.example) for defaults.
'Comprehensive documentation for Sim - the visual workflow builder for AI Agent Workflows.',
'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.',
default:'Sim Documentation - Visual Workflow Builder for AI Applications',
template:'%s',
default:'Sim Documentation — Build AI Agents & Run Your Agentic Workforce',
template:'%s | Sim Docs',
},
description:
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.',
'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.',
title:'Sim Documentation - Visual Workflow Builder for AI Applications',
title:'Sim Documentation — Build AI Agents & Run Your Agentic Workforce',
description:
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.',
title:'Sim Documentation - Visual Workflow Builder for AI Applications',
title:'Sim Documentation — Build AI Agents & Run Your Agentic Workforce',
description:
'Comprehensive documentation for Sim - the visual workflow builder for AI applications.',
'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.',
> The open-source platform to build AI agents and run your agentic workforce.
Sim is a visual workflow builder for AI applications that lets you build AI agent workflows visually. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.
Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders.
@@ -68,29 +66,6 @@ export function StructuredData({
})),
}
constwebsiteStructuredData=url===baseUrl&&{
'@context':'https://schema.org',
'@type':'WebSite',
name:'Sim Documentation',
url: baseUrl,
description:
'Comprehensive documentation for Sim visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
@@ -98,7 +73,7 @@ export function StructuredData({
applicationCategory:'DeveloperApplication',
operatingSystem:'Any',
description:
'Visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.',
'Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs.',
url: baseUrl,
author:{
'@type':'Organization',
@@ -109,45 +84,34 @@ export function StructuredData({
category:'Developer Tools',
},
featureList:[
'Visual workflow builder with drag-and-drop interface',
@@ -190,13 +190,8 @@ console.log(`${processedItems} gültige Elemente verarbeitet`);
### Einschränkungen
<Callout type="warning">
Container-Blöcke (Schleifen und Parallele) können nicht ineinander verschachtelt werden. Das bedeutet:
- Du kannst keinen Schleifenblock in einen anderen Schleifenblock platzieren
- Du kannst keinen Parallel-Block in einen Schleifenblock platzieren
- Du kannst keinen Container-Block in einen anderen Container-Block platzieren
Wenn du mehrdimensionale Iterationen benötigst, erwäge eine Umstrukturierung deines Workflows, um sequentielle Schleifen zu verwenden oder Daten in Stufen zu verarbeiten.
<Callout type="info">
Container-Blöcke (Schleifen und Parallele) unterstützen Verschachtelung. Du kannst Schleifen in Schleifen, Parallele in Schleifen und jede Kombination von Container-Blöcken platzieren, um komplexe mehrdimensionale Workflows zu erstellen.
Container-Blöcke (Schleifen und Parallele) können nicht ineinander verschachtelt werden. Das bedeutet:
- Sie können keinen Schleifenblock in einen Parallelblock platzieren
- Sie können keinen weiteren Parallelblock in einen Parallelblock platzieren
- Sie können keinen Container-Block in einen anderen Container-Block platzieren
<Callout type="info">
Container-Blöcke (Schleifen und Parallele) unterstützen Verschachtelung. Sie können Parallele in Parallele, Schleifen in Parallele und jede Kombination von Container-Blöcken platzieren, um komplexe mehrdimensionale Workflows zu erstellen.
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
The Agent block connects your workflow to Large Language Models (LLMs). It processes natural language inputs, calls external tools, and generates structured or unstructured outputs.
@@ -58,7 +59,7 @@ Controls response randomness and creativity:
### Max Output Tokens
Controls the maximum length of the model's response. For Anthropic models, Sim uses reliable defaults: streaming executions use the model's full capacity (e.g. 64,000 tokens for Claude 4.5), while non-streaming executions default to 8,192 to avoid timeout issues. When using tools with Anthropic models, intermediate tool-calling requests use a capped limit of 8,192 tokens to avoid SDK timeout errors, regardless of your configured max tokens—the final streaming response uses your full configured limit. This only affects Anthropic's direct API; AWS Bedrock handles this automatically. For long-form content generation via API, explicitly set a higher value.
Controls the maximum length of the model's response. Each model defaults to its full max output token limit (e.g., 64,000 tokens for Claude Sonnet 4.5). You can override this with a custom value using the Max Tokens setting. For Anthropic models, when non-streaming requests exceed the SDK's internal threshold, the provider automatically uses internal streaming to avoid timeouts.
### API Key
@@ -78,7 +79,7 @@ Extend agent capabilities with external integrations. Select from 60+ pre-built
**Execution Modes:**
- **Auto**: Model decides when to use tools based on context
- **Required**: Tool must be called in every request
- **None**: Tool available but not suggested to model
- **None**: Tool is completely filtered out and not sent to the model — effectively disables the tool
### Response Format
@@ -113,7 +114,7 @@ After an agent completes, you can access its outputs:
- **`<agent.content>`**: The agent's response text or structured data
- **Be specific in system prompts**: Clearly define the agent's role, tone, and limitations. The more specific your instructions are, the better the agent will be able to fulfill its intended purpose.
- **Choose the right temperature setting**: Use lower temperature settings (0-0.3) when accuracy is important, or increase temperature (0.7-2.0) for more creative or varied responses
- **Leverage tools effectively**: Integrate tools that complement the agent's purpose and enhance its capabilities. Be selective about which tools you provide to avoid overwhelming the agent. For tasks with little overlap, use another Agent block for the best results.
<FAQ items={[
{ question: "What LLM providers does the Agent block support?", answer: "The Agent block supports OpenAI, Anthropic, Google (Gemini), xAI (Grok), DeepSeek, Groq, Cerebras, Azure OpenAI, Azure Anthropic, Google Vertex AI, AWS Bedrock, OpenRouter, and local models via Ollama or VLLM. You can type or select any supported model from the model combobox." },
{ question: "What are the memory options for the Agent block?", answer: "The Agent block has four memory modes: None (no memory, each request is independent), Conversation (full conversation history keyed by a conversation ID), Sliding Window by messages (keeps the N most recent messages), and Sliding Window by tokens (keeps messages up to a token limit). Memory requires a conversation ID to persist across runs." },
{ question: "What is the difference between the tool execution modes (Auto, Required, None)?", answer: "In Auto mode, the model decides when to call a tool based on context. In Required mode, the model must call the tool on every request. In None mode, the tool is completely filtered out and never sent to the model — it effectively disables that tool without removing it from the configuration." },
{ question: "How does the Response Format work?", answer: "Response Format enforces structured output by providing a JSON Schema. When set, the model's response is constrained to match the schema exactly. Fields from the structured response can be accessed directly by downstream blocks using <agent.fieldName> syntax. If no response format is set, the agent returns its standard outputs: content, model, tokens, and toolCalls." },
{ question: "What does the Reasoning Effort / Thinking Level setting do?", answer: "These are advanced settings that appear only for models that support extended reasoning. Reasoning Effort (for OpenAI o-series and GPT-5 models) and Thinking Level (for Anthropic Claude and Gemini models with thinking) control how much compute the model spends reasoning before responding. Higher levels produce more thorough answers but cost more tokens and take longer." },
{ question: "How does max output tokens work with Anthropic models specifically?", answer: "The Agent block uses each Anthropic model's full max output token limit by default (e.g., 64,000 for Claude Sonnet 4.5). You can override this with the Max Tokens setting. For non-streaming requests that exceed the SDK's internal threshold, the provider automatically uses internal streaming to avoid timeouts." },
{ question: "Can I use the Agent block with a custom or self-hosted model?", answer: "Yes. You can use any Ollama or VLLM-compatible model by typing the model name directly into the model combobox. This lets you connect to locally hosted or custom-deployed models as long as they expose a compatible API endpoint." },
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
The API block connects your workflow to external services through HTTP requests. Supports GET, POST, PUT, DELETE, and PATCH methods for interacting with REST APIs.
@@ -79,7 +80,6 @@ After an API request completes, you can access its outputs:
- **`<api.data>`**: The response body data from the API
- **`<api.status>`**: HTTP status code (200, 404, 500, etc.)
- **`<api.headers>`**: Response headers from the server
- **`<api.error>`**: Error details if the request failed
## Advanced Features
@@ -127,7 +127,6 @@ if (<api.status> === 200) {
- **`<api.data>`**: Response body data from the API
- **`<api.status>`**: HTTP status code
- **`<api.headers>`**: Response headers
- **`<api.error>`**: Error details if request failed
## Example Use Cases
@@ -147,3 +146,12 @@ Function (Validate) → API (Stripe) → Condition (Success) → Supabase (Updat
- **Validate responses**: Check status codes and response formats before processing data
- **Respect rate limits**: Be mindful of API rate limits and implement appropriate throttling
<FAQ items={[
{ question: "What is the default request timeout?", answer: "The default timeout is 300,000 milliseconds (5 minutes). You can configure it up to a maximum of 600,000 milliseconds (10 minutes) in the block's Advanced settings." },
{ question: "Which HTTP errors trigger automatic retries?", answer: "Retries are attempted for network/connection failures, timeouts, rate limit responses (HTTP 429), and server errors (5xx). Client errors like 400 or 404 are not retried." },
{ question: "How does retry backoff work?", answer: "Retries use exponential backoff starting from the configured retry delay (default 500ms). Each subsequent retry doubles the delay, up to the maximum retry delay (default 30,000ms)." },
{ question: "Are POST and PATCH requests retried by default?", answer: "No. POST and PATCH are non-idempotent methods, so retries are disabled for them by default to avoid creating duplicate resources. You can enable retries for these methods with the 'Retry non-idempotent methods' toggle in Advanced settings, but be aware this may cause duplicate requests." },
{ question: "What headers are included automatically?", answer: "Standard headers such as User-Agent, Accept, and Cache-Control are added automatically. Any custom headers you configure will be merged with these defaults, and your custom values will override automatic headers with the same name." },
{ question: "Can I send form data or file uploads?", answer: "The API block primarily sends JSON request bodies through the UI. The underlying HTTP tool also supports form data natively — if you pass form data parameters, it will construct a proper multipart/form-data request automatically. For most use cases, the JSON body field in the block UI is sufficient." },
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
The Condition block branches workflow execution based on boolean expressions. Evaluate conditions using previous block outputs and route to different paths without requiring an LLM.
@@ -60,10 +61,9 @@ Conditions use JavaScript syntax and can reference input values from previous bl
After a condition evaluates, you can access its outputs:
- **`<condition.result>`**: Boolean result of the condition evaluation
- **`<condition.matched_condition>`**: ID of the condition that was matched
- **`<condition.content>`**: Description of the evaluation result
- **`<condition.path>`**: Details of the chosen routing destination
- **`<condition.conditionResult>`**: Boolean result of the condition evaluation
- **`<condition.selectedOption>`**: ID of the condition that was matched
- **`<condition.selectedPath>`**: Details of the chosen routing destination
## Advanced Features
@@ -102,18 +102,13 @@ true
### Error Handling
Conditions automatically handle:
- Undefined or null values with safe evaluation
- Type mismatches with appropriate fallbacks
- Invalid expressions with error logging
- Missing variables with default values
If a condition expression references an undefined variable or throws a runtime error, the block will throw an error and the execution will fail (or follow the error path if one is connected). Use optional chaining (`?.`) or explicit null checks in your expressions to handle missing values safely.
## Outputs
- **`<condition.result>`**: Boolean result of the evaluation
- **`<condition.matched_condition>`**: ID of the matched condition
- **`<condition.content>`**: Description of the evaluation result
- **`<condition.path>`**: Details of the chosen routing destination
- **`<condition.conditionResult>`**: Boolean result of the evaluation
- **`<condition.selectedOption>`**: ID of the matched condition
- **`<condition.selectedPath>`**: Details of the chosen routing destination
## Example Use Cases
@@ -139,3 +134,11 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
- **Keep expressions simple**: Use clear, straightforward boolean expressions for better readability and easier debugging
- **Document your conditions**: Add descriptions to explain the purpose of each condition for better team collaboration and maintenance
- **Test edge cases**: Verify conditions handle boundary values correctly by testing with values at the edges of your condition ranges
<FAQ items={[
{ question: "Does the Condition block use an LLM?", answer: "No. The Condition block evaluates boolean expressions using JavaScript syntax directly — it does not call any AI model. This makes it fast, deterministic, and free of API costs. If you need AI-powered routing decisions, use the Router block instead." },
{ question: "What happens if no condition matches?", answer: "If none of your defined conditions evaluate to true, the workflow follows the else branch. If the else branch is not connected to any downstream block, that workflow path ends gracefully without an error. Add a fallback condition of simply true as the last condition to guarantee a match." },
{ question: "In what order are conditions evaluated?", answer: "Conditions are evaluated from top to bottom in the order they are defined. The first condition that evaluates to true determines the execution path. Subsequent conditions are not evaluated after a match is found, so place more specific conditions before general ones." },
{ question: "What JavaScript features can I use in condition expressions?", answer: "You can use standard JavaScript operators and methods including comparison operators (===, !==, >, <), logical operators (&&, ||, !), string methods (.includes(), .endsWith(), .toLowerCase()), array methods (.includes(), .length), mathematical operations, and Date comparisons. Reference block outputs using <blockName.output> syntax." },
{ question: "How does the Condition block handle null or undefined values?", answer: "If a condition expression references an undefined variable or throws a runtime error, the Condition block will throw an error and the execution will fail (or follow the error path if one is connected). Use optional chaining (?.) or explicit null checks in your expressions to handle missing values safely." },
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
The Credential block has two operations: **Select Credential** picks a single OAuth credential and outputs its ID reference for downstream blocks; **List Credentials** returns all OAuth credentials in the workspace (optionally filtered by provider) as an array for iteration.
<div className="flex justify-center">
<Image
src="/static/blocks/credential.png"
alt="Credential Block"
width={400}
height={300}
className="my-6"
/>
</div>
<Callout>
The Credential block outputs credential **ID references**, not secrets. Downstream blocks receive the ID and resolve the actual OAuth token securely during their own execution.
</Callout>
## Configuration Options
### Operation
| Value | Description |
|---|---|
| **Select Credential** | Pick one OAuth credential and output its reference — use this to wire a single credential into downstream blocks |
| **List Credentials** | Return all OAuth credentials in the workspace as an array — use this with a ForEach loop |
### Credential (Select operation)
Select an OAuth credential from your workspace. The dropdown shows all connected OAuth accounts (Google, GitHub, Slack, etc.).
In advanced mode, paste a credential ID directly. You can copy a credential ID from your workspace's Credentials settings page.
### Provider (List operation)
Filter the returned OAuth credentials by provider. Select one or more providers from the dropdown — only providers you have credentials for will appear. Leave empty to return all OAuth credentials.
1. Drop a **Credential** block and select your OAuth credential from the picker
2. In the downstream block, switch to **advanced mode** on its credential field
3. Enter `<credentialBlockName.credentialId>` as the value
<Tabs items={['Gmail', 'Slack']}>
<Tab>
In the Gmail block's credential field (advanced mode):
```
<myCredential.credentialId>
```
</Tab>
<Tab>
In the Slack block's credential field (advanced mode):
```
<myCredential.credentialId>
```
</Tab>
</Tabs>
### List Credentials
1. Drop a **Credential** block, set Operation to **List Credentials**
2. Optionally select one or more **Providers** to narrow results (only your connected providers appear)
3. Wire `<credentialBlockName.credentials>` into a **ForEach Loop** as the items source
4. Inside the loop, reference `<loop.currentItem.credentialId>` in downstream blocks' credential fields
## Best Practices
- **Define once, reference many times**: When five blocks use the same Google account, use one Credential block and wire all five to `<credential.credentialId>` instead of selecting the account five times
- **Outputs are safe to log**: The `credentialId` output is a UUID reference, not a secret. It is safe to inspect in execution logs
- **Use for environment switching**: Pair with a Condition block to route to a production or staging OAuth credential based on a workflow variable
- **Advanced mode is required**: Downstream blocks must be in advanced mode on their credential field to accept a dynamic reference
- **Use List + ForEach for fan-out**: When you need to run the same action across all accounts of a provider, List Credentials feeds naturally into a ForEach loop
- **Narrow by provider**: Use the Provider multiselect to filter to specific services — only providers you have credentials for are shown
<FAQ items={[
{ question: "Does the Credential block expose my secret or token?", answer: "No. The block outputs a credential ID (a UUID), not the actual OAuth token. Downstream blocks receive the ID and resolve the token securely in their own execution context. Secrets never appear in workflow state, logs, or the canvas." },
{ question: "What credential types does it support?", answer: "OAuth connected accounts only (Google, GitHub, Slack, etc.). Environment variables and service accounts cannot be resolved by ID in downstream blocks, so they are not supported." },
{ question: "How is Select different from just copying a credential ID into advanced mode?", answer: "Functionally identical — both pass the same credential ID to the downstream block. The Credential block adds value when you need to use one credential in many blocks (change it once), or when you want to select between credentials dynamically using a Condition block." },
{ question: "Can I list all OAuth credentials in my workspace?", answer: "Yes. Set the Operation to 'List Credentials'. Optionally filter by provider using the Provider multiselect. Wire the credentials output into a ForEach loop to process each credential individually." },
{ question: "Can I use a Credential block output in a Function block?", answer: "Yes. Reference <credential.credentialId> in your Function block's code. Note that the function will receive the raw UUID string — if you need the resolved token, the downstream block must handle the resolution (as integration blocks do). The Function block does not automatically resolve credential IDs." },
{ question: "What happens if the credential is deleted?", answer: "The Select operation will throw an error at execution time: 'Credential not found'. The List operation will simply omit the deleted credential from the results. Update the Credential block to select a valid credential before re-running." },
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'
import { FAQ } from '@/components/ui/faq'
The Evaluator block uses AI to score and assess content quality against custom metrics. Perfect for quality control, A/B testing, and ensuring AI outputs meet specific standards.
@@ -49,12 +50,12 @@ The content to be evaluated. This can be:
- **Connect with Agent blocks**: Use Evaluator blocks to assess Agent block outputs and create feedback loops
- **Use consistent metrics**: For comparative analysis, maintain consistent metrics across similar evaluations
- **Combine multiple metrics**: Use several metrics to get a comprehensive evaluation
<FAQ items={[
{ question: "What format does the Evaluator return scores in?", answer: "The Evaluator returns a JSON object where each key is the lowercase version of your metric name and the value is a numeric score within the range you defined. For example, a metric named 'Accuracy' with range 1-5 would appear as { \"accuracy\": 4 } in the output." },
{ question: "Which models work best for evaluation?", answer: "Models with strong reasoning capabilities produce the most consistent evaluations. GPT-4o and Claude Sonnet are recommended. The default model is Claude Sonnet 4.5." },
{ question: "Can I evaluate non-text content?", answer: "The content field accepts any string input. If you pass JSON or structured data, the Evaluator will automatically detect and format it before evaluation. However, the evaluation is text-based — it cannot directly evaluate images or audio." },
{ question: "What happens if a metric name is invalid or incomplete?", answer: "Metrics missing a name or range are automatically filtered out. The Evaluator only scores metrics that have both a valid name and a defined min/max range." },
{ question: "Does the Evaluator use structured output?", answer: "Yes. The Evaluator generates a JSON Schema response format based on your metrics and enforces strict mode, so the LLM is constrained to return only the expected metric scores as numbers — no extra text or explanations." },
{ question: "How are evaluation costs calculated?", answer: "Costs are based on the token usage of the underlying LLM call. The Evaluator outputs include token counts (prompt, completion, total) and cost breakdown (input, output, total) so you can track spending per evaluation." },
The Function block executes custom JavaScript or TypeScript code in your workflows. Transform data, perform calculations, or implement custom logic.
@@ -71,3 +72,12 @@ return {
- **Test edge cases**: Ensure your code handles unusual inputs, null values, and boundary conditions correctly
- **Optimize for performance**: Be mindful of computational complexity and memory usage for large datasets
- **Use console.log() for debugging**: Leverage stdout output to debug and monitor function execution
<FAQ items={[
{ question: "What languages does the Function block support?", answer: "The Function block supports JavaScript and Python. JavaScript is the default. Python support requires the E2B feature to be enabled, as Python code always runs in a secure E2B sandbox environment." },
{ question: "When does code run locally vs. in a sandbox?", answer: "JavaScript code without external imports runs in a local isolated VM for fast execution. JavaScript code that uses import or require statements requires E2B and runs in a secure sandbox. Python code always runs in the E2B sandbox regardless of whether it has imports." },
{ question: "How do I reference outputs from other blocks inside my code?", answer: "Use the angle bracket syntax directly in your code, like <agent.content> or <api.data>. Do not wrap these references in quotes — the system replaces them with actual values before execution. For environment variables, use double curly braces: {{API_KEY}}." },
{ question: "What does the Function block return?", answer: "The Function block has two outputs: result (the return value of your code, accessed via <function.result>) and stdout (anything logged with console.log(), accessed via <function.stdout>). Make sure your code includes a return statement if you need to pass data to downstream blocks." },
{ question: "Can I make HTTP requests from a Function block?", answer: "Yes. The fetch() API is available in the JavaScript execution environment. You can use async/await with fetch to call external APIs. However, you cannot use libraries like axios or request — only the built-in fetch is supported. Your code runs inside an async context automatically, so you can use await directly." },
{ question: "Is there a timeout for Function block execution?", answer: "Yes. Function blocks have a configurable execution timeout. If your code exceeds the timeout, the execution is terminated and the block reports an error. Keep this in mind when making external API calls or processing large datasets." },
import { Callout } from 'fumadocs-ui/components/callout'
import { Image } from '@/components/ui/image'
import { Video } from '@/components/ui/video'
import { FAQ } from '@/components/ui/faq'
The Guardrails block validates and protects your AI workflows by checking content against multiple validation types. Ensure data quality, prevent hallucinations, detect PII, and enforce format requirements before content moves through your workflow.
@@ -66,8 +67,8 @@ Uses Retrieval-Augmented Generation (RAG) with LLM scoring to detect when AI-gen
- **Knowledge Base**: Select from your existing knowledge bases
- **Model**: Choose LLM for scoring (requires strong reasoning - GPT-4o, Claude 3.7 Sonnet recommended)
- **API Key**: Authentication for selected LLM provider (auto-hidden for hosted/Ollama or VLLM compatible models)
- **Confidence Threshold**: Minimum score to pass (0-10, default: 3)
- **Top K** (Advanced): Number of knowledge base chunks to retrieve (default: 10)
- **Confidence**: Minimum score to pass (0-10, default: 3)
- **Top K** (Advanced): Number of knowledge base chunks to retrieve (default: 5)
**Output:**
- `passed`: `true` if confidence score ≥ threshold
@@ -83,7 +84,7 @@ Uses Retrieval-Augmented Generation (RAG) with LLM scoring to detect when AI-gen
### PII Detection
Detects personally identifiable information using Microsoft Presidio. Supports 40+ entity types across multiple countries and languages.
Detects personally identifiable information using Microsoft Presidio. Supports over 30 entity types across multiple countries and languages.
<div className="flex justify-center">
<Image
@@ -98,7 +99,7 @@ Detects personally identifiable information using Microsoft Presidio. Supports 4
**How It Works:**
1. Pass content to validate (e.g., `<agent1.content>`)
2. Select PII types to detect using the modal selector
3. Choose detection mode (Detect or Mask)
3. Choose the action (Block Request or Mask PII)
4. Content is scanned for matching PII entities
5. Returns detection results and optionally masked text
@@ -109,17 +110,17 @@ Detects personally identifiable information using Microsoft Presidio. Supports 4
**Configuration:**
- **PII Types to Detect**: Select from grouped categories via modal selector
- **Common**: Person name, Email, Phone, Credit card, IP address, etc.
- **USA**: SSN, Driver's license, Passport, etc.
- **USA**: SSN, Driver's license, Passport, Bank account number, ITIN
- **Mask PII**: Replace detected PII with masked values
- **Language**: Detection language (default: English)
**Output:**
@@ -131,7 +132,7 @@ Detects personally identifiable information using Microsoft Presidio. Supports 4
**Use Cases:**
- Block content containing sensitive personal information
- Mask PII before logging or storing data
- Compliance with GDPR, HIPAA, and other privacy regulations
- Compliance with GDPR and other privacy regulations
- Sanitize user inputs before processing
## Configuration
@@ -140,7 +141,7 @@ Detects personally identifiable information using Microsoft Presidio. Supports 4
The input content to validate. This typically comes from:
- Agent block outputs: `<agent.content>`
- Function block results: `<function.output>`
- Function block results: `<function.result>`
- API responses: `<api.output>`
- Any other block output
@@ -203,3 +204,13 @@ Input → Guardrails (Detect PII) → Condition (No PII) → Process or Reject
Guardrails validation happens synchronously in your workflow. For hallucination detection, choose faster models (like GPT-4o-mini) if latency is critical.
</Callout>
<FAQ items={[
{ question: "Can I run multiple validation types on the same content?", answer: "Each Guardrails block performs one validation type at a time. To apply multiple validations, chain several Guardrails blocks in sequence — for example, first validate JSON format, then check for PII." },
{ question: "What does the hallucination confidence score mean?", answer: "The score ranges from 0 to 10. A score of 0 means the content is completely ungrounded (full hallucination), and 10 means it is fully supported by the knowledge base. Validation passes when the score meets or exceeds your configured threshold (default: 3)." },
{ question: "How many knowledge base chunks are retrieved for hallucination detection?", answer: "By default, 5 chunks are retrieved. You can adjust this up to 20 in the Advanced settings using the 'Number of Chunks to Retrieve' slider. More chunks provide broader context but increase latency and token usage." },
{ question: "What PII detection engine is used?", answer: "PII detection is powered by Microsoft Presidio. It supports over 30 entity types across multiple countries including the US, UK, Spain, Italy, Poland, Singapore, Australia, and India." },
{ question: "What is the difference between Block and Mask modes for PII?", answer: "Block mode fails the validation (passed = false) if any selected PII types are detected. Mask mode also detects PII but replaces it with masked values in the output, making the content safe to use downstream. Both modes return the list of detected entities." },
{ question: "Which languages does PII detection support?", answer: "PII detection supports English, Spanish, Italian, Polish, and Finnish. The language setting affects the NLP models used for entity recognition, so selecting the correct language improves detection accuracy." },
{ question: "Does JSON validation check schema structure or just syntax?", answer: "JSON validation only checks that the content is syntactically valid JSON (i.e., it can be parsed without errors). It does not validate against a specific schema. For schema validation, use a Function block after the Guardrails check." },
]} />
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.