* fix(ci): replace dynamic secret access with explicit secret references
Resolves CodeQL "Excessive Secrets Exposure" warning by replacing
secrets[matrix.ecr_repo_secret] with conditional expressions that
reference only the specific secrets needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ci): add explicit ECR_REALTIME guard and use env block for secret injection
- Prevent silent fallthrough to ECR_REALTIME for unrecognized secret keys
- Move build-amd64 secret resolution to env: block matching build-dev pattern
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(ui): restore smooth streaming animation, fix follow-up auto-scroll, move CopyCodeButton to emcn
* fix(ui): restore delayed animation, handle tilde fences, fix follow-up scroll root cause
* fix(ui): extract useStreamingReveal to followup, keep cleanup changes
* fix(ui): restore hydratedStreamingRef for reconnect path order-of-ops
* fix(ui): restore full hydratedStreamingRef effect for reconnect path
* fix(ui): use hover-hover prefix on CopyCodeButton callers to correctly override ghost variant
* fix(logs): remove destructive color from cancel execution menu item
* feat(logs): optimistic cancelling status on cancel execution
* feat(logs): allow cancellation of pending (paused) executions
* fix(hitl): cancel paused executions directly in DB
Paused HITL executions are idle in the DB — they don't poll Redis or
run in-process, so the existing cancel signals had no effect. The DB
status stayed 'pending', causing the optimistic 'cancelling' update to
revert on refetch.
- Add PauseResumeManager.cancelPausedExecution: atomically sets
paused_executions.status and workflow_execution_logs.status to
'cancelled' inside a FOR UPDATE transaction
- Guard enqueueOrStartResume against resuming a cancelled execution
- Include pausedCancelled in the cancel route success check
* upgrade turbo
* test(hitl): update cancel route tests for paused execution cancellation
- Mock PauseResumeManager.cancelPausedExecution to prevent DB calls
- Add pausedCancelled to all expected response objects
- Add test for HITL paused execution cancellation path
- Add missing auth/authz tests
- Switch to vi.hoisted pattern for all mocks
* fix(hitl): set endedAt when cancelling paused execution
Without endedAt, the logs API running filter (isNull(endedAt)) would
keep cancelled paused executions in the running view indefinitely.
* fix(hitl): emit execution:cancelled event to canvas when cancelling paused execution
Paused HITL executions have no active SSE stream, so the canvas never
received the cancellation event. Now writes execution:cancelled to the
event buffer and updates the stream meta so the canvas reconnect path
picks it up and shows 'Execution Cancelled'.
* fix(hitl): isolate cancelPausedExecution failure from successful cancellation
Wrap cancelPausedExecution in try/catch so a DB error does not mask
a prior successful Redis or in-process cancellation. Also move the
resource-collapse side effect in home.tsx to a useEffect to avoid the
stale closure on the resources array.
* fix(hitl): add .catch() to fire-and-forget event buffer calls in cancel route
* fix(security): resolve ReDoS vulnerability in function execute tag pattern
Simplified regex to eliminate overlapping quantifiers that caused exponential
backtracking on malformed input without closing delimiter.
* fix(security): exclude trailing-dot refs and hoist tag pattern to module level
* fix(security): align tag pattern with codebase standard [^<>]+ pattern
Matches createReferencePattern() from reference-validation.ts used by the
core executor. Invalid refs handled gracefully by resolveBlockReference.
* refactor(security): use createReferencePattern() instead of inline regex
getTrigger() namespaces condition-gated subBlock IDs (e.g. webhookUrlDisplay
→ webhookUrlDisplay_github_release_published). The block card's useMemo was
checking for an exact match on 'webhookUrlDisplay', which never matched.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(jsm): add all Forms API endpoints for two-step form workflow
* removed tyoes
* fix(jsm): handle 204 No Content on action endpoints and reject array answers
* fix(jsm): validate formIds is an array in copy_forms route and block
* fix(jsm): add formTemplateId validation and conditional required on formAnswers
* feat(aws): add IAM and STS integrations
* fix(sts): address PR review comments
- Fix CrowdStrike tags to include "security" (unintended removal)
- Standardize STS tool versions to '1.0.0' (matching IAM convention)
- Add range validation to durationSeconds in Zod schemas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* icon
* lint
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Auto-focus input boxes for modals and copilot
* Fix focus in emcn modal
* Fix integrations manager focus
* Change modal tabs to auto focus on first text input
* Auto-focus mothership task chats
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(workspaces): add workspace logo upload
* feat(workspaces): add workspace logo upload
* fix(workspaces): validate logoUrl accepts only paths or HTTPS URLs
* fix(workspaces): add admin authorization, audit log, and posthog event for workspace logo uploads
* lint
* fix: add WebP support and use refs pattern in useProfilePictureUpload
- Add image/webp to ACCEPTED_IMAGE_TYPES in useProfilePictureUpload
- Add image/webp to file input accept attributes in whitelabeling settings
- Refactor useProfilePictureUpload to use refs for onUpload, onError, and
currentImage callbacks, matching the established codebase pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore cloudwatch/cloudformation files from staging
These files were accidentally regressed during rebase conflict resolution,
reverting changes from #4027. Restoring to staging versions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add workspace_logo_uploaded to PostHogEventMap
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: separate workspaceId ref sync to prevent overwrite on re-render
Split the ref sync useEffect so workspaceIdRef only updates when the
workspaceId prop changes, not when onUpload/onError callbacks get new
references. Prevents setTargetWorkspaceId from being overwritten by
a re-render before the file upload completes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use Pick type for workspace dropdown in knowledge header
The shared Workspace type requires ownerId and other fields that aren't
available from the workspaces API response mapping. Use a Pick type to
accurately represent the subset of fields actually constructed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace raw fetch with useWorkspacesQuery in knowledge header
Remove useState + useEffect + fetch anti-pattern for loading workspaces.
Use useWorkspacesQuery from React Query with inline filter for write/admin
permissions. Eliminates ~30 lines of manual state management, any casts,
and the Pick type workaround.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(atlassian): unify error message extraction across all Jira, JSM, and Confluence routes
Add parseAtlassianErrorMessage() to jira/utils.ts as single source of truth for
parsing all 5 Atlassian error formats. Update 51 proxy routes (18 JSM, 5 Jira,
28 Confluence) to use it instead of hardcoded generic errors. Remove dead
errorExtractor field from 95 Atlassian tool files — the compat loop in
extractErrorMessage() already handles all formats without it. Consolidate
duplicate parseJsmErrorMessage into a re-export from the shared utility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review comments from Bugbot
- Remove debug logger.info for formAnswers in JSM request route
- Restore user-friendly spaceId error message in Confluence create-page route
- Restore details field in Jira write and update route error responses
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove re-exports from jsm/utils and import directly from source
Remove re-exports of getJiraCloudId, parseAtlassianErrorMessage, and
parseJsmErrorMessage from jsm/utils.ts. Update all 21 JSM routes to
import directly from @/tools/jira/utils per CLAUDE.md import rules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* regen docs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(agiloft): add Agiloft CLM integration with token-based auth
Add 12 tools (CRUD, search, select, saved search, attachments, lock),
block, icon, docs, and internal API route for file attachments.
Uses EWLogin/EWLogout for short-lived Bearer tokens — credentials
are never embedded in API request URLs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agiloft): address PR review feedback
- Add HTTPS enforcement guard to agiloftLogin to prevent plaintext credential transit
- Add null guard on data.output in attach_file transformResponse
- Change empty AgiloftSavedSearchParams interface to type alias
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agiloft): add SSRF protection via DNS validation on instanceUrl
Validates user-supplied instanceUrl against private/reserved IP ranges
using validateUrlWithDNS before making any outbound requests. Uses dynamic
import to avoid bundling Node.js dns module in client-side code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agiloft): fix SSRF protection to avoid client bundle breakage
Replace dynamic import of input-validation.server (which Turbopack traces
into the client bundle) with client-safe validateExternalUrl in utils.ts.
Add full DNS-level SSRF validation via validateUrlWithDNS in the attach
API route (server-only file). This matches the Okta pattern for
directExecution tools and the textract pattern for API routes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agiloft): use DELETE method for EWRemoveAttachment endpoint
The remove_attachment tool was incorrectly using GET instead of DELETE
for the Agiloft EWRemoveAttachment endpoint, which would cause removals
to fail at runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agiloft): correct HTTP methods and parameter names per Agiloft API docs
- EWRemoveAttachment uses GET, not DELETE (revert incorrect change)
- EWRetrieve uses `filePosition` parameter, not `position`
- EWAttach uses PUT, not POST
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(models): exclude reseller providers from model catalog pages
Reseller providers like OpenRouter, Fireworks, Azure, Vertex, and Bedrock
are aggregators that proxy other providers' models. Their model detail
pages were generating broken links. Filter them out of
MODEL_PROVIDERS_WITH_CATALOGS so they don't generate static pages or
appear as clickable entries in the model directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(models): use filtered catalog for JSON-LD structured data
Switch flatModels in page.tsx from MODEL_CATALOG_PROVIDERS to
MODEL_PROVIDERS_WITH_CATALOGS so the Schema.org ItemList excludes
reseller models, matching TOTAL_MODELS and avoiding broken URLs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ee): enterprise feature flags, permission group platform controls, audit logs ui, delete account
* fix(settings): improve sidebar skeleton fidelity and fix credit purchase org cache invalidation
- Bump skeleton icon and text from 16/14px to 24px to better match real nav item visual weight
- Add orgId support to usePurchaseCredits so org billing/subscription caches are invalidated on credit purchase, matching the pattern used by useUpgradeSubscription
- Polish ColorInput in whitelabeling settings with auto-prefix and select-on-focus UX
* revert(settings): remove delete account feature
* fix(settings): address pr review — atomic autoAddNewMembers, extract query hook, fix types and signal forwarding
* chore(helm): add CREDENTIAL_SETS_ENABLED to values.yaml
* fix(access-control): dynamic platform category columns, atomic permission group delete
* fix(access-control): restore triggers section in blocks tab
* fix(access-control): merge triggers into tools section in blocks tab
* upgrade tubro
* fix(access-control): fix Select All state when config has stale blacklisted provider IDs
* fix(access-control): derive platform Select All from features list; revert turbo schema version
* fix(access-control): fix blocks Select All check, filter empty platform columns
* revert(settings): restore original skeleton icon and text sizes
* improvement: seo, geo, signup, posthog
* fix(landing): address PR review issues and convention violations
- Fix auth modal race condition: show loading state instead of redirecting when provider status hasn't loaded yet
- Fix auth modal HTTP error caching: reject non-200 responses so they aren't permanently cached
- Replace <img> with next/image <Image> in auth modal
- Use cn() instead of template literal class concatenation in hero, footer-cta
- Remove commented-out dead code in footer, landing, sitemap
- Remove unused arrow property from FooterItem interface
- Convert relative imports to absolute in integrations/[slug]/page
- Remove no-op sanitizedName variable in signup form
- Remove unnecessary async from llms-full.txt route
- Remove extraneous non-TSDoc comment in auth modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(landing): apply linter formatting fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): second pass — fix remaining code quality issues
- auth-modal: add @sim/logger, log social sign-in errors instead of swallowing silently
- auth-modal: extract duplicated social button classes into SOCIAL_BTN constant
- auth-modal: remove unused isProduction from ProviderStatus interface
- auth-modal: memoize getBrandConfig() call
- footer: remove stale arrow destructuring left after interface cleanup, use cn() throughout
- footer-cta: replace inline styles on submit button with Tailwind classes via cn()
- footer-cta: replace caretColor inline style with caret-white utility
- templates: fix incorrect section value 'landing_preview' → 'templates' for PostHog tracking
- events: add 'templates' to landing_cta_clicked section union
- integrations: replace "canvas" with "workflow builder" per constitution rules
- llms-full: replace "canvas" terminology with "visual builder"/"workflow builder"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): point Mothership and Workflows footer links to docs root
These docs pages don't exist yet — link to docs.sim.ai until they are published.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): complete rebrand in blog fallback description
Remove "workflows" from the non-tagged blog meta description to
align with the AI workspace rebrand across the rest of the PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): strip isProduction from provider response and handle late-resolve redirect
- Destructure only githubAvailable/googleAvailable from getOAuthProviderStatus
so isProduction is not leaked to unauthenticated callers.
- Add useEffect to redirect away from the modal if provider status resolves
after the modal is already open and no social providers are configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): align auth modal with login/signup page logic
- Add SSO button when NEXT_PUBLIC_SSO_ENABLED is set
- Gate "Continue with email" behind EMAIL_PASSWORD_SIGNUP_ENABLED
- Expose registrationDisabled from /api/auth/providers and hide
the "Sign up" toggle when registration is disabled
- Simplify skip-modal logic: redirect to full page when no social
providers or SSO are available (hasModalContent)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): force login view when registration is disabled
When a CTA passes defaultView='signup' but registration is disabled,
the modal now opens in login mode instead of showing "Create free
account" with social buttons that would fail on the backend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(landing): correct signup view when registrationDisabled loads late
When the user opens the modal before providerStatus resolves and
registrationDisabled comes back true, the view was stuck on 'signup'.
Now the late-resolve useEffect also forces the view to 'login'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): add click tracking to integration page CTAs
Create IntegrationCtaButton client component that wraps AuthModal
and fires trackLandingCta on click, matching the pattern used by
every other landing section CTA.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(landing): prevent mobile auth modal from unmounting on open
Remove setMobileMenuOpen(false) from mobile AuthModal button onClick
handlers. Closing the mobile menu unmounts the AuthModal before it
can open. The modal overlay or page redirect makes the menu
irrelevant without needing to explicitly close it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ee): add enterprise audit logs settings page with server-side search
Add a new audit logs page under enterprise settings that displays all
actions captured via recordAudit. Includes server-side search, resource
type filtering, date range selection, and cursor-based pagination.
- Add internal API route (app/api/audit-logs) with session auth
- Extract shared query logic (buildFilterConditions, buildOrgScopeCondition,
queryAuditLogs) into app/api/v1/audit-logs/query.ts
- Refactor v1 and admin audit log routes to use shared query module
- Add React Query hook with useInfiniteQuery and cursor pagination
- Add audit logs UI with debounced search, combobox filters, expandable rows
- Gate behind requiresHosted + requiresEnterprise navigation flags
- Place all enterprise audit log code in ee/audit-logs/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(ee): fix build error and address PR review comments
- Fix import path: @/lib/utils → @/lib/core/utils/cn
- Guard against empty orgMemberIds array in buildOrgScopeCondition
- Skip debounce effect on mount when search is already synced
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(ee): fix type error with unknown metadata in JSX expression
Use ternary instead of && chain to prevent unknown type from being
returned as ReactNode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ee): align skeleton filter width with actual component layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* feat(audit): add audit logging for passwords, credentials, and schedules
- Add PASSWORD_RESET_REQUESTED audit on forget-password with user lookup
- Add CREDENTIAL_CREATED/UPDATED/DELETED audit on credential CRUD routes
with metadata (credentialType, providerId, updatedFields, envKey)
- Add SCHEDULE_CREATED audit on schedule creation with cron/timezone metadata
- Fix SCHEDULE_DELETED (was incorrectly using SCHEDULE_UPDATED for deletes)
- Enhance existing schedule update/disable/reactivate audit with structured
metadata (operation, updatedFields, sourceType, previousStatus)
- Add CREDENTIAL resource type and Credential filter option to audit logs UI
- Enhance password reset completed description with user email
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): align metadata with established recordAudit patterns
- Add actorName/actorEmail to all new credential and schedule audit calls
to match the established pattern (e.g., api-keys, byok-keys, knowledge)
- Add resourceId and resourceName to forget-password audit call
- Enhance forget-password description with user email
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(testing): sync audit mock with new AuditAction and AuditResourceType entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(audit-logs): derive resource type filter from AuditResourceType
Instead of maintaining a separate hardcoded list, the filter dropdown
now derives its options directly from the AuditResourceType const object.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(audit): enrich all recordAudit calls with structured metadata
- Move resource type filter options to ee/audit-logs/constants.ts
(derived from AuditResourceType, no separate list to maintain)
- Remove export from internal cursor helpers in query.ts
- Add 5 new AuditAction entries: BYOK_KEY_UPDATED, ENVIRONMENT_DELETED,
INVITATION_RESENT, WORKSPACE_UPDATED, ORG_INVITATION_RESENT
- Enrich ~80 recordAudit calls across the codebase with structured
metadata (knowledge bases, connectors, documents, workspaces, members,
invitations, workflows, deployments, templates, MCP servers, credential
sets, organizations, permission groups, files, tables, notifications,
copilot operations)
- Sync audit mock with all new entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): remove redundant metadata fields duplicating top-level audit fields
Remove metadata entries that duplicate resourceName, workspaceId, or
other top-level recordAudit fields. Also remove noisy fileNames arrays
from bulk document upload audits (kept fileCount).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): split audit types from server-only log module
Extract AuditAction, AuditResourceType, and their types into
lib/audit/types.ts (client-safe, no @sim/db dependency). The
server-only recordAudit stays in log.ts and re-exports the types
for backwards compatibility. constants.ts now imports from types.ts
directly, breaking the postgres -> tls client bundle chain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): escape LIKE wildcards in audit log search query
Escape %, _, and \ characters in the search parameter before embedding
in the LIKE pattern to prevent unintended broad matches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit): use actual deletedCount in bulk API key revoke description
The description was using keys.length (requested count) instead of
deletedCount (actual count), which could differ if some keys didn't
exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(audit-logs): fix OAuth label displaying as "Oauth" in filter dropdown
ACRONYMS set stored 'OAuth' but lookup used toUpperCase() producing
'OAUTH' which never matched. Now store all acronyms uppercase and use
a display override map for special casing like OAuth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(trigger): auto-detect header row and rename lastKnownRowCount to lastIndexChecked
- Replace hardcoded !1:1 header fetch with detectHeaderRow(), which scans
the first 10 rows and returns the first non-empty row as headers. This
fixes row: null / headers: [] when a sheet has blank rows or a title row
above the actual column headers (e.g. headers in row 3).
- Rename lastKnownRowCount → lastIndexChecked in GoogleSheetsWebhookConfig
and all usage sites to clarify that the value is a row index pointer, not
a total count.
- Remove config parameter from processRows() since it was unused after the
includeHeaders flag was removed.
* fix(trigger): combine sheet state fetch, skip header/blank rows from data emission
- Replace separate getDataRowCount() + detectHeaderRow() with a single
fetchSheetState() call that returns rowCount, headers, and headerRowIndex
from one A:Z fetch. Saves one Sheets API round-trip per poll cycle when
new rows are detected.
- Use headerRowIndex to compute adjustedStartRow, preventing the header row
(and any blank rows above it) from being emitted as data events when
lastIndexChecked was seeded from an empty sheet.
- Handle the edge case where the entire batch falls within the header/blank
window by advancing the pointer and returning early without fetching rows.
- Skip empty rows (row.length === 0) in processRows rather than firing a
workflow run with no meaningful data.
* fix(trigger): preserve lastModifiedTime when remaining rows exist after header skip
When all rows in a batch fall within the header/blank window (adjustedStartRow
> endRow), the early return was unconditionally updating lastModifiedTime to the
current value. If there were additional rows beyond the batch cap, the next
Drive pre-check would see an unchanged modifiedTime and skip polling entirely,
leaving those rows unprocessed. Mirror the hasRemainingOrFailed pattern from the
normal processing path.
* chore(trigger): remove verbose inline comments from google-sheets poller
* fix(trigger): revert to full-width A:Z fetch for correct row count and consistent column scope
* fix(trigger): don't count skipped empty rows as processed
* chore(triggers): deprecate trigger-save subblock
Remove the defunct triggerSave subblock from all 102 trigger definitions,
the SubBlockType union, SYSTEM_SUBBLOCK_IDS, tool params, and command
templates. Retain the backwards-compat filter in getTrigger() for any
legacy stored data.
* fix(triggers): remove leftover no-op blocks.push() in linear utils
* chore(triggers): remove orphaned triggerId property and stale comments
* feat(knowledge): add token, sentence, recursive, and regex chunkers
* fix(chunkers): standardize token estimation and use emcn dropdown
- Refactor all existing chunkers (Text, JsonYaml, StructuredData, Docs) to use shared utils
- Fix inconsistent token estimation (JsonYaml used tiktoken, StructuredData used /3 ratio)
- Fix DocsChunker operator precedence bug and hard-coded 300-token limit
- Fix JsonYamlChunker isStructuredData false positive on plain strings
- Add MAX_DEPTH recursion guard to JsonYamlChunker
- Replace @/components/ui/select with emcn DropdownMenu in strategy selector
* fix(chunkers): address research audit findings
- Expand RecursiveChunker recipes: markdown adds horizontal rules, code
fences, blockquotes; code adds const/let/var/if/for/while/switch/return
- RecursiveChunker fallback uses splitAtWordBoundaries instead of char slicing
- RegexChunker ReDoS test uses adversarial strings (repeated chars, spaces)
- SentenceChunker abbreviation list adds St/Rev/Gen/No/Fig/Vol/months
and single-capital-letter lookbehind
- Add overlap < maxSize validation in Zod schema and UI form
- Add pattern max length (500) validation in Zod schema
- Fix StructuredDataChunker footer grammar
* fix(chunkers): fix remaining audit issues across all chunkers
- DocsChunker: extract headers from cleaned content (not raw markdown)
to fix position mismatch between header positions and chunk positions
- DocsChunker: strip export statements and JSX expressions in cleanContent
- DocsChunker: fix table merge dedup using equality instead of includes
- JsonYamlChunker: preserve path breadcrumbs when nested value fits in
one chunk, matching LangChain RecursiveJsonSplitter behavior
- StructuredDataChunker: detect 2-column CSV (lowered threshold from >2
to >=1) and use 20% relative tolerance instead of absolute +/-2
- TokenChunker: use sliding window overlap (matching LangChain/Chonkie)
where chunks stay within chunkSize instead of exceeding it
- utils: splitAtWordBoundaries accepts optional stepChars for sliding
window overlap; addOverlap uses newline join instead of space
* chore(chunkers): lint formatting
* updated styling
* fix(chunkers): audit fixes and comprehensive tests
- Fix SentenceChunker regex: lookbehinds now include the period to correctly handle abbreviations (Mr., Dr., etc.), initials (J.K.), and decimals
- Fix RegexChunker ReDoS: reset lastIndex between adversarial test iterations, add poisoned-suffix test strings
- Fix DocsChunker: skip code blocks during table boundary detection to prevent false positives from pipe characters
- Fix JsonYamlChunker: oversized primitive leaf values now fall back to text chunking instead of emitting a single chunk
- Fix TokenChunker: pass 0 to buildChunks for overlap metadata since sliding window handles overlap inherently
- Add defensive guard in splitAtWordBoundaries to prevent infinite loops if step is 0
- Add tests for utils, TokenChunker, SentenceChunker, RecursiveChunker, RegexChunker (236 total tests, 0 failures)
- Fix existing test expectations for updated footer format and isStructuredData behavior
* chore(chunkers): remove unnecessary comments and dead code
Strip 445 lines of redundant TSDoc, math calculation comments,
implementation rationale notes, and assertion-restating comments
across all chunker source and test files.
* fix(chunkers): address PR review comments
- Fix regex fallback path: use sliding window for overlap instead of
passing chunkOverlap to buildChunks without prepended overlap text
- Fix misleading strategy label: "Text (hierarchical splitting)" →
"Text (word boundary splitting)"
* fix(chunkers): use consistent overlap pattern in regex fallback
Use addOverlap + buildChunks(chunks, overlap) in the regex fallback
path to match the main path and all other chunkers (TextChunker,
RecursiveChunker). The sliding window approach was inconsistent.
* fix(chunkers): prevent content loss in word boundary splitting
When splitAtWordBoundaries snaps end back to a word boundary, advance
pos from end (not pos + step) in non-overlapping mode. The step-based
advancement is preserved for the sliding window case (TokenChunker).
* fix(chunkers): restore structured data token ratio and overlap joiner
- Restore /3 token estimation for StructuredDataChunker (structured data
is denser than prose, ~3 chars/token vs ~4)
- Change addOverlap joiner from \n to space to match original TextChunker
behavior
* lint
* fix(chunkers): fall back to character-level overlap in sentence chunker
When no complete sentence fits within the overlap budget,
fall back to character-level word-boundary overlap from the
previous group's text. This ensures buildChunks metadata is
always correct.
* fix(chunkers): fix log message and add missing month abbreviations
- Fix regex fallback log: "character splitting" → "word-boundary splitting"
- Add Jun and Jul to sentence chunker abbreviation list
* lint
* fix(chunkers): restore structured data detection threshold to > 2
avgCount >= 1 was too permissive — prose with consistent comma usage
would be misclassified as CSV. Restore original > 2 threshold while
keeping the improved proportional tolerance.
* fix(chunkers): pass chunkOverlap to buildChunks in TokenChunker
* fix(chunkers): restore separator-as-joiner pattern in splitRecursively
Separator was unconditionally prepended to parts after the first,
leaving leading punctuation on chunks after a boundary reset.
* feat(knowledge): add JSONL file support for knowledge base uploads
Parses JSON Lines files by splitting on newlines and converting to a
JSON array, which then flows through the existing JsonYamlChunker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(integrations, models): ui/ux
* fix(models, integrations): dedup ChevronArrow/provider colors, fix UTC date rendering
- Extract PROVIDER_COLORS and getProviderColor to model-colors.ts to eliminate
identical definitions in model-comparison-charts and model-timeline-chart
- Remove duplicate private ChevronArrow from integration-card; import the
exported one from model-primitives instead
- Add timeZone: 'UTC' to formatShortDate so ISO date-only strings (parsed as
UTC midnight) render the correct calendar day in all timezones
* refactor(models): rename model-colors.ts to consts.ts
* improvement(models): derive provider colors/resellers from definitions, reorient FAQs to agent builder
Dynamic data:
- Add `color` and `isReseller` fields to ProviderDefinition interface
- Move brand colors for all 10 providers into their definitions
- Mark 6 reseller providers (Azure, Bedrock, Vertex, OpenRouter, Fireworks)
- consts.ts now derives color map from MODEL_CATALOG_PROVIDERS
- model-comparison-charts derives RESELLER_PROVIDERS from catalog
- Fix deepseek name: Deepseek → DeepSeek; remove now-redundant
PROVIDER_NAME_OVERRIDES and getProviderDisplayName from utils
- Add color/isReseller fields to CatalogProvider; clean up duplicate
providerDisplayName in searchText array
FAQs:
- Replace all 4 main-page FAQs with 5 agent-builder-oriented ones
covering model selection, context windows, pricing, tool use, and
how to use models in a Sim agent workflow
- buildProviderFaqs: add conditional tool use FAQ per provider
- buildModelFaqs: add bestFor FAQ (conditional on field presence);
improve context window answer to explain agent implications;
tighten capabilities answer wording
* chore(models): remove model-colors.ts (superseded by consts.ts)
* update footer
---------
Co-authored-by: waleed <walif6@gmail.com>
* fix(trigger): fix polling trigger config defaults, row count, clock-skew, and stale config clearing
* fix(deploy): track first-pass fills to prevent stale baseConfig bypassing required-field validation
Use a dedicated `filledSubBlockIds` Set populated during the first pass so the second-pass skip guard is based solely on live `getConfigValue` results, not on stale entries spread from `baseConfig` (`triggerConfig`).
* fix(trigger): prevent calendar cursor regression when all events are filtered client-side
* fix(ui): support Tab key to select items in tag, env-var, and resource dropdowns
* fix(ui): support Tab key to select items in tag, env-var, and resource dropdowns
* fix(ui): guard Tab selection against Shift+Tab and undefined index
The Forms API has a different base URL for OAuth vs Basic Auth.
Per Atlassian support, OAuth requires the /ex/jira/{cloudId}/forms
pattern, not /jira/forms/cloud/{cloudId} which only works with
Basic Auth. This was causing 401 Unauthorized errors.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(trigger): show selector display names on canvas for trigger file/sheet selectors
* fix(trigger): use isNonEmptyValue in canonical member scan to match visibility contract
* feat(trigger): add Google Sheets, Drive, and Calendar polling triggers
Add polling triggers for Google Sheets (new rows), Google Drive (file
changes via changes.list API), and Google Calendar (event updates via
updatedMin). Each includes OAuth credential support, configurable
filters (event type, MIME type, folder, search term, render options),
idempotency, and first-poll seeding. Wire triggers into block configs
and regenerate integrations.json. Update add-trigger skill with polling
instructions and versioned block wiring guidance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(polling): address PR review feedback for Google polling triggers
- Fix Drive cursor stall: use nextPageToken as resume point when
breaking early from pagination instead of re-using the original token
- Eliminate redundant Drive API call in Sheets poller by returning
modifiedTime from the pre-check function
- Add 403/429 rate-limit handling to Sheets API calls matching the
Calendar handler pattern
- Remove unused changeType field from DriveChangeEntry interface
- Rename triggers/google_drive to triggers/google-drive for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(polling): fix Drive pre-check never activating in Sheets poller
isDriveFileUnchanged short-circuited when lastModifiedTime was
undefined, never calling the Drive API — so currentModifiedTime
was never populated, creating a permanent chicken-and-egg loop.
Now always calls the Drive API and returns the modifiedTime
regardless of whether there's a previous value to compare against.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(lint): fix import ordering in triggers registry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(polling): address PR review feedback for Google polling handlers
- Fix fetchHeaderRow to throw on 403/429 rate limits instead of silently
returning empty headers (prevents rows from being processed without
headers and lastKnownRowCount from advancing past them permanently)
- Fix Drive pagination to avoid advancing resume cursor past sliced
changes (prevents permanent change loss when allChanges > maxFiles)
- Remove unused logger import from Google Drive trigger config
* fix(polling): prevent data loss on partial row failures and harden idempotency key
- Sheets: only advance lastKnownRowCount by processedCount when there
are failures, so failed rows are retried on the next poll cycle
(idempotency deduplicates already-processed rows on re-fetch)
- Drive: add fallback for change.time in idempotency key to prevent
key collisions if the field is ever absent from the API response
* fix(polling): remove unused variable and preserve lastModifiedTime on Drive API failure
- Remove unused `now` variable from Google Drive polling handler
- Preserve stored lastModifiedTime when Drive API pre-check fails
(previously wrote undefined, disabling the optimization until the
next successful Drive API call)
* fix(polling): don't advance state when all events fail across sheets, calendar, drive handlers
* fix(polling): retry failed idempotency keys, fix drive cursor overshoot, fix calendar inclusive updatedMin
* fix(polling): revert calendar timestamp on any failure, not just all-fail
* fix(polling): revert drive cursor on any failure, not just all-fail
* feat(triggers): add canonical selector toggle to google polling triggers
- Add 'trigger-advanced' mode to SubBlockConfig so canonical pairs work in trigger mode
- Fix buildCanonicalIndex: trigger-mode subblocks don't overwrite non-trigger basicId, deduplicate advancedIds from block spreads
- Update editor, subblock layout, and trigger config aggregation to include trigger-advanced subblocks
- Replace dropdown+fetchOptions in Calendar/Sheets/Drive pollers with file-selector (basic) + short-input (advanced) canonical pairs
- Add canonicalParamId: 'oauthCredential' to triggerCredentials for selector context resolution
- Update polling handlers to read canonical fallbacks (calendarId||manualCalendarId, etc.)
* test(blocks): handle trigger-advanced mode in canonical validation tests
* fix(triggers): handle trigger-advanced mode in deploy, preview, params, and copilot
* fix(polling): use position-only idempotency key for sheets rows
* fix(polling): don't advance calendar timestamp to client clock on empty poll
* fix(polling): remove extraneous comment from calendar poller
* fix(polling): drive cursor stall on full page, calendar latestUpdated past filtered events
* fix(polling): advance calendar cursor past fully-filtered event batches
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(tools): add fields parameter to Jira search block
Expose the Jira REST API `fields` parameter on the search operation,
allowing users to specify which fields to return per issue. This reduces
response payload size by 10-15x, preventing 10MB workflow state limit
errors for users with high ticket volume.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(tools): remove redundant type annotation in fields map callback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tools): restore type annotation for implicit any in params callback
The params object is untyped, so TypeScript cannot infer the string
element type from .split() — the explicit annotation is required.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add the generated human-in-the-loop group to the docs navigation
and create meta.json listing all HITL operation IDs so endpoints
render in the API reference.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(log): log cleanup sql query
* perf(log): use startedAt index for cleanup query filter
Switch cleanup WHERE clause from createdAt to startedAt to leverage
the existing composite index (workspaceId, startedAt), converting a
full table scan to an index range scan. Also remove explanatory comment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Update parseJsmErrorMessage to extract errors from all Atlassian API
response formats: errorMessage (JSM), errorMessages array (Jira),
errors[].title RFC 7807 (Confluence/Forms), field-level errors object,
and message (gateway). Remove redundant prefix wrapping so the raw
error message surfaces cleanly through the extractor.
* fix(tools): add Atlassian error extractor to all Jira, JSM, and Confluence tools
Wire up the existing `atlassian-errors` error extractor to all 95 Atlassian
tool configs so the executor surfaces meaningful error messages instead of
generic status codes. Also fix the extractor itself to handle all three
Atlassian error response formats: `errorMessage` (JSM), `errorMessages`
array (Jira), and `message` (Confluence).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(tools): lint formatting fix for error extractor
* fix(tools): handle all Atlassian error formats in error extractor
Add RFC 7807 errors[].title format (Confluence v2, Forms/ProForma API)
and Jira field-level errors object to the atlassian-errors extractor.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(ci): parallelize Docker builds with tests and remove duplicate turbo install
* fix(test): use SecureFetchResponse shape in mock instead of standard Response
* chore(ci): bump actions/checkout to v6 and dorny/paths-filter to v4
* fix(ci): mock secureFetchWithPinnedIP in tools tests to prevent timeouts
* lint
* docs(openapi): add Human in the Loop API endpoints
Add HITL pause/resume endpoints to the OpenAPI spec covering
the full workflow pause lifecycle: listing paused executions,
inspecting pause details, and resuming with input.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(openapi): add 403 and 500 responses to HITL endpoints
Address PR review feedback: add missing 403 Forbidden response
to all HITL endpoints (from validateWorkflowAccess), and 500
responses to resume endpoints that have explicit error paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(trigger): add ServiceNow webhook triggers
* fix(trigger): add webhook secret field and remove non-TSDoc comment
Add webhookSecret field to ServiceNow triggers (matching Salesforce pattern)
so users are prompted to protect the webhook endpoint. Update setup
instructions to include Authorization header in the Business Rule example.
Remove non-TSDoc inline comment in the block config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(trigger): add ServiceNow provider handler with event matching
Add dedicated ServiceNow webhook provider handler with:
- verifyAuth: validates webhookSecret via Bearer token or X-Sim-Webhook-Secret
- matchEvent: filters events by trigger type and table name using
isServiceNowEventMatch utility (matching Salesforce/GitHub pattern)
The event matcher handles incident created/updated and change request
created/updated triggers with table name enforcement and event type
normalization. The generic webhook trigger passes through all events
but still respects the optional table name filter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(jsm): add ProForma/JSM Forms discovery tools
Add three new tools for discovering and inspecting JSM Forms (ProForma) templates
and their structure, enabling dynamic form-based workflows:
- jsm_get_form_templates: List form templates in a project with request type bindings
- jsm_get_form_structure: Get full form design (questions, layout, conditions, sections)
- jsm_get_issue_forms: List forms attached to an issue with submission status
All endpoints validated against the official Atlassian Forms REST API OpenAPI spec.
Uses the Forms Cloud API base URL (jira/forms/cloud/{cloudId}) with X-ExperimentalApi header.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(jsm): add input validation and extract shared error parser
- Add validateJiraIssueKey for projectIdOrKey in templates and structure routes
- Add validateJiraCloudId for formId (UUID) in structure route
- Extract parseJsmErrorMessage to shared utils.ts (was duplicated across 3 routes)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(jsm): remove unused FORM_QUESTION_PROPERTIES constant
Dead code — the get_form_structure tool passes the raw design object
through as JSON, so this output constant had no consumers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(polling): fix correctness and efficiency across all polling handlers
- Gmail: paginate history API, add historyTypes filter, differentiate 403/429,
fetch fresh historyId on fallback to break 404 retry loop
- Outlook: follow @odata.nextLink pagination, use fetchWithRetry for all Graph
calls, fix $top alignment, skip folder filter on partial resolution failure,
remove Content-Type from GET requests
- RSS: add conditional GET (ETag/If-None-Match), raise GUID cap to 500, fix 304
ETag capture per RFC 9111, align GUID tracking with idempotency fallback key
- IMAP: single connection reuse, UIDVALIDITY tracking per mailbox, advance UID
only on successful fetch, fix messageFlagsAdd range type, remove cross-mailbox
legacy UID fallback
- Dispatch polling via trigger.dev task with per-provider concurrency key;
fall back to synchronous Redis-locked polling for self-hosted
* fix(rss): align idempotency key GUID fallback with tracking/filter guard
* removed comments
* fix(imap): clear stale UID when UIDVALIDITY changes during state merge
* fix(rss): skip items with no identifiable GUID to avoid idempotency key collisions
* fix(schedules): convert dynamic import of getWorkflowById to static import
* fix(imap): preserve fresh UID after UIDVALIDITY reset in state merge
* improvement(polling): remove trigger.dev dispatch, use synchronous Redis-locked polling
* fix(polling): decouple outlook page size from total email cap so pagination works
* feat(block): Add cloudwatch publish operation
* fix(integrations): validate and fix cloudwatch, cloudformation, athena conventions
- Update tool version strings from '1.0' to '1.0.0' across all three integrations
- Add missing `export * from './types'` barrel re-exports (cloudwatch, cloudformation)
- Add docsLink, wandConfig timestamps, mode: 'advanced' on optional fields (cloudwatch)
- Add dropdown defaults, ZodError handling, docs intro section (cloudwatch)
- Add mode: 'advanced' on limit field (cloudformation)
- Alphabetize registry entries (cloudwatch, cloudformation)
- Fix athena docs maxResults range (1-999)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cloudwatch): complete put_metric_data unit dropdown, add missing outputs, fix JSON error handling
- Add all 27 valid CloudWatch StandardUnit values to metricUnit dropdown (was 13)
- Add missing block outputs for put_metric_data: success, namespace, metricName, value, unit
- Add try-catch around dimensions JSON.parse in put-metric-data route for proper 400 errors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cloudwatch): fix DescribeAlarms returning only MetricAlarm when "All Types" selected
Per AWS docs, omitting AlarmTypes returns only MetricAlarm. Now explicitly
sends both MetricAlarm and CompositeAlarm when no filter is selected.
Also fix dimensions JSON parse errors returning 500 instead of 400 in
get-metric-statistics route.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cloudwatch): validate dimensions JSON at Zod schema level
Move dimensions validation from runtime try-catch to Zod refinement,
catching malformed JSON and arrays at schema validation time (400)
instead of runtime (500). Also rejects JSON arrays that would produce
meaningless numeric dimension names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cloudwatch): reject non-numeric metricValue instead of silently publishing 0
Add NaN guard in block config and .finite() refinement in Zod schema
so "abc" → NaN is caught at both layers instead of coercing to 0.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cloudwatch): use Number.isFinite to also reject Infinity in block config
Aligns block-level validation with route's Zod .finite() refinement so
Infinity/-Infinity are caught at the block config layer, not just the API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(billing): skip billing on streamed workflows with byok
* Simplify logic
* Address comments, skip tokenization billing fallback
* Fix tool usage billing for streamed outputs
* fix(webhook): throw webhook errors as 4xxs (#4050)
* fix(webhook): throw webhook errors as 4xxs
* Fix shadowing body var
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(enterprise): cloud whitelabeling for enterprise orgs (#4047)
* feat(enterprise): cloud whitelabeling for enterprise orgs
* fix(enterprise): scope enterprise plan check to target org in whitelabel PUT
* fix(enterprise): use isOrganizationOnEnterprisePlan for org-scoped enterprise check
* fix(enterprise): allow clearing whitelabel fields and guard against empty update result
* fix(enterprise): remove webp from logo accept attribute to match upload hook validation
* improvement(billing): use isBillingEnabled instead of isProd for plan gate bypasses
* fix(enterprise): show whitelabeling nav item when billing is enabled on non-hosted environments
* fix(enterprise): accept relative paths for logoUrl since upload API returns /api/files/serve/ paths
* fix(whitelabeling): prevent logo flash on refresh by hiding logo while branding loads
* fix(whitelabeling): wire hover color through CSS token on tertiary buttons
* fix(whitelabeling): show sim logo by default, only replace when org logo loads
* fix(whitelabeling): cache org logo url in localstorage to eliminate flash on repeat visits
* feat(whitelabeling): add wordmark support with drag/drop upload
* updated turbo
* fix(whitelabeling): defer localstorage read to effect to prevent hydration mismatch
* fix(whitelabeling): use layout effect for cache read to eliminate logo flash before paint
* fix(whitelabeling): cache theme css to eliminate color flash before org settings resolve
* fix(whitelabeling): deduplicate HEX_COLOR_REGEX into lib/branding and remove mutation from useCallback deps
* fix(whitelabeling): use cookie-based SSR cache to eliminate brand flash on all page loads
* fix(whitelabeling): use !orgSettings condition to fix SSR brand cache injection
React Query returns isLoading: false with data: undefined during SSR, so the
previous brandingLoading condition was always false on the server — initialCache
was never injected into brandConfig. Changing to !orgSettings correctly applies
the cookie cache both during SSR and while the client-side query loads, eliminating
the logo flash on hard refresh.
* fix(editor): stop highlighting start.input as blue when block is not connected to starter (#4054)
* fix: merge subblock values in auto-layout to prevent losing router context (#4055)
Auto-layout was reading from getWorkflowState() without merging subblock
store values, then persisting stale subblock data to the database. This
caused runtime-edited values (e.g. router_v2 context) to be overwritten
with their initial/empty values whenever auto-layout was triggered.
* fix(whitelabeling): eliminate logo flash by fetching org settings server-side (#4057)
* fix(whitelabeling): eliminate logo flash by fetching org settings server-side
* improvement(whitelabeling): add SVG support for logo and wordmark uploads
* skelly in workspace header
* remove dead code
* fix(whitelabeling): hydration error, SVG support, skeleton shimmer, dead code removal
* fix(whitelabeling): blob preview dep cycle and missing color fallback
* fix(whitelabeling): use brand-accent as color fallback when workspace color is undefined
* chore(whitelabeling): inline hasOrgBrand
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* fix(whitelabeling): eliminate logo flash by fetching org settings server-side
* improvement(whitelabeling): add SVG support for logo and wordmark uploads
* skelly in workspace header
* remove dead code
* fix(whitelabeling): hydration error, SVG support, skeleton shimmer, dead code removal
* fix(whitelabeling): blob preview dep cycle and missing color fallback
* fix(whitelabeling): use brand-accent as color fallback when workspace color is undefined
* chore(whitelabeling): inline hasOrgBrand
Auto-layout was reading from getWorkflowState() without merging subblock
store values, then persisting stale subblock data to the database. This
caused runtime-edited values (e.g. router_v2 context) to be overwritten
with their initial/empty values whenever auto-layout was triggered.
* feat(enterprise): cloud whitelabeling for enterprise orgs
* fix(enterprise): scope enterprise plan check to target org in whitelabel PUT
* fix(enterprise): use isOrganizationOnEnterprisePlan for org-scoped enterprise check
* fix(enterprise): allow clearing whitelabel fields and guard against empty update result
* fix(enterprise): remove webp from logo accept attribute to match upload hook validation
* improvement(billing): use isBillingEnabled instead of isProd for plan gate bypasses
* fix(enterprise): show whitelabeling nav item when billing is enabled on non-hosted environments
* fix(enterprise): accept relative paths for logoUrl since upload API returns /api/files/serve/ paths
* fix(whitelabeling): prevent logo flash on refresh by hiding logo while branding loads
* fix(whitelabeling): wire hover color through CSS token on tertiary buttons
* fix(whitelabeling): show sim logo by default, only replace when org logo loads
* fix(whitelabeling): cache org logo url in localstorage to eliminate flash on repeat visits
* feat(whitelabeling): add wordmark support with drag/drop upload
* updated turbo
* fix(whitelabeling): defer localstorage read to effect to prevent hydration mismatch
* fix(whitelabeling): use layout effect for cache read to eliminate logo flash before paint
* fix(whitelabeling): cache theme css to eliminate color flash before org settings resolve
* fix(whitelabeling): deduplicate HEX_COLOR_REGEX into lib/branding and remove mutation from useCallback deps
* fix(whitelabeling): use cookie-based SSR cache to eliminate brand flash on all page loads
* fix(whitelabeling): use !orgSettings condition to fix SSR brand cache injection
React Query returns isLoading: false with data: undefined during SSR, so the
previous brandingLoading condition was always false on the server — initialCache
was never injected into brandConfig. Changing to !orgSettings correctly applies
the cookie cache both during SSR and while the client-side query loads, eliminating
the logo flash on hard refresh.
* 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>
* feat(tools): advanced fields for youtube, vercel; added cloudflare and dataverse tools (#3257)
* refactor(vercel): mark optional fields as advanced mode
Move optional/power-user fields behind the advanced toggle:
- List Deployments: project filter, target, state
- Create Deployment: project ID override, redeploy from, target
- List Projects: search
- Create/Update Project: framework, build/output/install commands
- Env Vars: variable type
- Webhooks: project IDs filter
- Checks: path, details URL
- Team Members: role filter
- All operations: team ID scope
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style(youtube): mark optional params as advanced mode
Hide pagination, sort order, and filter fields behind the advanced
toggle for a cleaner default UX across all YouTube operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* added advanced fields for vercel and youtube, added cloudflare and dataverse block
* addded desc for dataverse
* add more tools
* ack comment
* more
* ops
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(tables): added tables (#2867)
* updates
* required
* trashy table viewer
* updates
* updates
* filtering ui
* updates
* updates
* updates
* one input mode
* format
* fix lints
* improved errors
* updates
* updates
* chages
* doc strings
* breaking down file
* update comments with ai
* updates
* comments
* changes
* revert
* updates
* dedupe
* updates
* updates
* updates
* refactoring
* renames & refactors
* refactoring
* updates
* undo
* update db
* wand
* updates
* fix comments
* fixes
* simplify comments
* u[dates
* renames
* better comments
* validation
* updates
* updates
* updates
* fix sorting
* fix appearnce
* updating prompt to make it user sort
* rm
* updates
* rename
* comments
* clean comments
* simplicifcaiton
* updates
* updates
* refactor
* reduced type confusion
* undo
* rename
* undo changes
* undo
* simplify
* updates
* updates
* revert
* updates
* db updates
* type fix
* fix
* fix error handling
* updates
* docs
* docs
* updates
* rename
* dedupe
* revert
* uncook
* updates
* fix
* fix
* fix
* fix
* prepare merge
* readd migrations
* add back missed code
* migrate enrichment logic to general abstraction
* address bugbot concerns
* adhere to size limits for tables
* remove conflicting migration
* add back migrations
* fix tables auth
* fix permissive auth
* fix lint
* reran migrations
* migrate to use tanstack query for all server state
* update table-selector
* update names
* added tables to permission groups, updated subblock types
---------
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: waleed <walif6@gmail.com>
* fix(snapshot): changed insert to upsert when concurrent identical child workflows are running (#3259)
* fix(snapshot): changed insert to upsert when concurrent identical child workflows are running
* fixed ci tests failing
* fix(workflows): disallow duplicate workflow names at the same folder level (#3260)
* feat(tools): added redis, upstash, algolia, and revenuecat (#3261)
* feat(tools): added redis, upstash, algolia, and revenuecat
* ack comment
* feat(models): add gemini-3.1-pro-preview and update gemini-3-pro thinking levels (#3263)
* fix(audit-log): lazily resolve actor name/email when missing (#3262)
* fix(blocks): move type coercions from tools.config.tool to tools.config.params (#3264)
* fix(blocks): move type coercions from tools.config.tool to tools.config.params
Number() coercions in tools.config.tool ran at serialization time before
variable resolution, destroying dynamic references like <block.result.count>
by converting them to NaN/null. Moved all coercions to tools.config.params
which runs at execution time after variables are resolved.
Fixed in 15 blocks: exa, arxiv, sentry, incidentio, wikipedia, ahrefs,
posthog, elasticsearch, dropbox, hunter, lemlist, spotify, youtube, grafana,
parallel. Also added mode: 'advanced' to optional exa fields.
Closes#3258
* fix(blocks): address PR review — move remaining param mutations from tool() to params()
- Moved field mappings from tool() to params() in grafana, posthog,
lemlist, spotify, dropbox (same dynamic reference bug)
- Fixed parallel.ts excerpts/full_content boolean logic
- Fixed parallel.ts search_queries empty case (must set undefined)
- Fixed elasticsearch.ts timeout not included when already ends with 's'
- Restored dropbox.ts tool() switch for proper default fallback
* fix(blocks): restore field renames to tool() for serialization-time validation
Field renames (e.g. personalApiKey→apiKey) must be in tool() because
validateRequiredFieldsBeforeExecution calls selectToolId()→tool() then
checks renamed field names on params. Only type coercions (Number(),
boolean) stay in params() to avoid destroying dynamic variable references.
* improvement(resolver): resovled empty sentinel to not pass through unexecuted valid refs to text inputs (#3266)
* fix(blocks): add required constraint for serviceDeskId in JSM block (#3268)
* fix(blocks): add required constraint for serviceDeskId in JSM block
* fix(blocks): rename custom field values to request field values in JSM create request
* fix(trigger): add isolated-vm support to trigger.dev container builds (#3269)
Scheduled workflow executions running in trigger.dev containers were
failing to spawn isolated-vm workers because the native module wasn't
available in the container. This caused loop condition evaluation to
silently fail and exit after one iteration.
- Add isolated-vm to build.external and additionalPackages in trigger config
- Include isolated-vm-worker.cjs via additionalFiles for child process spawning
- Add fallback path resolution for worker file in trigger.dev environment
* fix(tables): hide tables from sidebar and block registry (#3270)
* fix(tables): hide tables from sidebar and block registry
* fix(trigger): add isolated-vm support to trigger.dev container builds (#3269)
Scheduled workflow executions running in trigger.dev containers were
failing to spawn isolated-vm workers because the native module wasn't
available in the container. This caused loop condition evaluation to
silently fail and exit after one iteration.
- Add isolated-vm to build.external and additionalPackages in trigger config
- Include isolated-vm-worker.cjs via additionalFiles for child process spawning
- Add fallback path resolution for worker file in trigger.dev environment
* lint
* fix(trigger): update node version to align with main app (#3272)
* fix(build): fix corrupted sticky disk cache on blacksmith (#3273)
---------
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>
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
3. Wire up tools correctly
## Hard Rule: No Guessed Tool Outputs
Blocks depend on tool outputs. If the underlying tool response schema is not documented or live-verified, you MUST tell the user instead of guessing block outputs.
- Do NOT invent block outputs for undocumented tool responses
- Do NOT describe unknown JSON shapes as if they were confirmed
- Do NOT wire fields into the block just because they seem likely to exist
If the tool outputs are not known, do one of these instead:
1. Ask the user for sample tool responses
2. Ask the user for test credentials so the tool responses can be verified
3. Limit the block to operations whose outputs are documented
4. Leave uncertain outputs out and explicitly tell the user what remains unknown
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
- The object has a small, stable set of fields (< 10)
- Downstream blocks will commonly access specific properties
- The API response shape is well-documented and unlikely to change
Use `type: 'json'` with a descriptive string when:
- The object has many fields or a dynamic shape
- It represents a list/array of items
- The shape varies by operation
If the output shape is unknown because the underlying tool response is undocumented, you MUST tell the user and stop. Unknown is not the same as variable. Never guess block outputs.
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
## Hard Rule: No Guessed Response Or Document Schemas
If the service docs do not clearly show the document list response, document fetch response, pagination shape, or metadata fields, you MUST tell the user instead of guessing.
- Do NOT invent document fields
- Do NOT guess pagination cursors or next-page fields
- Do NOT infer metadata/tag mappings from unrelated endpoints
- Do NOT fabricate `ExternalDocument` content structure from partial docs
If the source schema is unknown, do one of these instead:
1. Ask the user for sample API responses
2. Ask the user for test credentials so you can verify live payloads
3. Implement only the documented parts of the connector
4. Leave the connector incomplete and explicitly say which fields remain unknown
## 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`.
// Optional: map source metadata to semantic tag keys (translated to slots by sync engine)
mapTags:(metadata)=>{
// Return Record<string, unknown> with keys matching tagDefinitions[].id
},
}
```
Only map fields in `listDocuments`, `getDocument`, `validateConfig`, and `mapTags` when the source payload shape is documented or live-verified. If not, tell the user and stop rather than guessing.
### API key connector example
```typescript
exportconst{service}Connector: ConnectorConfig={
id:'{service}',
name:'{Service}',
description:'Sync documents from {Service} into your knowledge base',
version:'1.0.0',
icon:{Service}Icon,
auth:{
mode:'apiKey',
label:'API Key',// Shown above the input field
placeholder:'Enter your {Service} API key',// Input placeholder
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`
2. Or use WebFetch to read API docs directly
3. Identify:
- Authentication method (OAuth, API Key, both)
- Available operations (CRUD, search, etc.)
- Required vs optional parameters
- Response structures
### Hard Rule: No Guessed Response Schemas
If the official docs do not clearly show the response JSON shape for an endpoint, you MUST stop and tell the user exactly which outputs are unknown.
- Do NOT guess response field names
- Do NOT infer nested JSON paths from related endpoints
- Do NOT invent output properties just because they seem likely
- Do NOT implement `transformResponse` against unverified payload shapes
If response schemas are missing or incomplete, do one of the following before proceeding:
1. Ask the user for sample responses
2. Ask the user for test credentials so you can verify the live payload
3. Reduce the scope to only endpoints whose response shapes are documented
4. Leave the tool unimplemented and explicitly report why
-`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
- If you do not know the response JSON shape from docs or verified examples, you MUST tell the user and stop. Never guess outputs or response mappings.
-`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
- [ ] Verified every tool output and `transformResponse` path against documented or live-verified JSON responses
- [ ] If any response schema remained unknown, explicitly told the user instead of guessing
## 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
## Hard Rule: No Guessed Response Schemas
If the docs do not clearly show the response JSON for a tool, you MUST tell the user exactly which outputs are unknown and stop short of guessing.
- Do NOT invent response field names
- Do NOT infer nested paths from nearby endpoints
- Do NOT guess array item shapes
- Do NOT write `transformResponse` against unverified payloads
If the response shape is unknown, do one of these instead:
1. Ask the user for sample responses
2. Ask the user for test credentials so you can verify live responses
3. Implement only the endpoints whose outputs are documented
4. Leave the tool unimplemented and explicitly say why
## 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
Only use bare `type: 'json'` without `properties` when the shape is truly dynamic or unknown.
If the response shape is unknown because the docs do not provide it, you MUST tell the user and stop. Unknown is not the same as dynamic. Never guess outputs.
## Critical Rules for transformResponse
### Handle Nullable Fields
ALWAYS use `?? null` for fields that may be undefined:
```typescript
transformResponse: async(response: Response)=>{
constdata=awaitresponse.json()
return{
success: true,
output:{
id: data.id,
title: data.title,
body: data.body??null,// May be undefined
assignee: data.assignee??null,// May be undefined
labels: data.labels??[],// Default to empty array
closedAt: data.closed_at??null,// May be undefined
},
}
}
```
### Never Output Raw JSON Dumps
DON'T do this:
```typescript
output:{
data: data,// BAD - raw JSON dump
}
```
DO this instead - extract meaningful fields:
```typescript
output:{
id: data.id,
name: data.name,
status: data.status,
metadata:{
createdAt: data.created_at,
updatedAt: data.updated_at,
},
}
```
## Types File Pattern
Create `types.ts` with interfaces for all params and responses:
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
## Hard Rule: No Guessed Webhook Payload Schemas
If the service docs do not clearly show the webhook payload JSON for an event, you MUST tell the user instead of guessing trigger outputs or `formatInput` mappings.
- Do NOT invent payload field names
- Do NOT guess nested event object paths
- Do NOT infer output fields from the UI or marketing docs
- Do NOT write `formatInput` against unverified webhook bodies
If the payload shape is unknown, do one of these instead:
1. Ask the user for sample webhook payloads
2. Ask the user for a test webhook source so you can inspect a real event
3. Implement only the event registration/setup portions whose payloads are documented
4. Leave the trigger unimplemented and explicitly say which payload fields are unknown
## 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: Run all code quality skills in sequence — effects, memo, callbacks, state, React Query, and emcn design review
---
# Cleanup
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Steps
Run each of these skills in order on the specified scope, passing through the scope and fix arguments. After each skill completes, move to the next. Do not skip any.
1.`/you-might-not-need-an-effect $ARGUMENTS`
2.`/you-might-not-need-a-memo $ARGUMENTS`
3.`/you-might-not-need-a-callback $ARGUMENTS`
4.`/you-might-not-need-state $ARGUMENTS`
5.`/react-query-best-practices $ARGUMENTS`
6.`/emcn-design-review $ARGUMENTS`
After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.
description: Review UI code for alignment with the emcn design system — components, tokens, patterns, and conventions
---
# EMCN Design Review
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA (class-variance-authority) variants and CSS variable design tokens. All UI must use emcn components and tokens — never raw HTML elements or hardcoded colors.
## Steps
1. Read the emcn barrel export at `apps/sim/components/emcn/components/index.ts` to know what's available
2. Read `apps/sim/app/_styles/globals.css` for the full set of CSS variable tokens
3. Analyze the specified scope against every rule below
4. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
---
## Imports
- Import components from `@/components/emcn`, never from subpaths
- Import icons from `@/components/emcn/icons` or `lucide-react`
- Import `cn` from `@/lib/core/utils/cn` for conditional class merging
- Import app-specific wrappers (Select, VerifiedBadge) from `@/components/ui`
Never use raw color values. Always use CSS variable tokens via Tailwind arbitrary values: `text-[var(--text-primary)]`, not `text-gray-500` or `#333`. The CSS variable pattern is canonical (1,700+ uses) — do not use Tailwind semantic classes like `text-muted-foreground`.
### Text hierarchy
| Token | Use |
|-------|-----|
| `text-[var(--text-primary)]` | Main content text |
| `text-[var(--text-secondary)]` | Secondary/supporting text |
| `text-[var(--text-tertiary)]` | Tertiary text |
| `text-[var(--text-muted)]` | Disabled, placeholder text |
| `text-[var(--text-icon)]` | Icon tinting |
| `text-[var(--text-inverse)]` | Text on dark backgrounds |
Use `text-[var(--text-icon)]` for icon color (113+ uses in codebase).
---
## Styling Rules
1.**Use `cn()` for conditional classes**: `cn('base', condition && 'conditional')` — never template literal concatenation like `` `base ${condition ? 'active' : ''}` ``
2.**Inline styles**: Avoid. Exception: dynamic values that can't be expressed as Tailwind classes (e.g., `style={{ width: dynamicVar }}` or CSS variable references). Never use inline styles for colors or static values.
3.**Never hardcode colors**: Use CSS variable tokens. Never `text-gray-500`, `bg-red-100`, `#fff`, or `rgb()`. Always `text-[var(--text-*)]`, `bg-[var(--surface-*)]`, etc.
4.**Never use Tailwind semantic color classes**: Use `text-[var(--text-muted)]` not `text-muted-foreground`. The CSS variable pattern is canonical.
5.**Never use global styles**: Keep all styling local to components
6.**Hover states**: Use `hover-hover:` pseudo-class for hover-capable devices
7.**Transitions**: Use `transition-colors` for color changes, `transition-colors duration-100` for fast hover
description: Audit React Query usage for best practices — key factories, staleTime, mutations, and server state ownership
---
# React Query Best Practices
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/hooks/queries/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query (TanStack Query) as the single source of truth for all server state. All query hooks live in `hooks/queries/`. Zustand is used only for client-only UI state. Server data must never be duplicated into useState or Zustand outside of mutation callbacks that coordinate cross-store state.
## References
Read these before analyzing:
1. https://tkdodo.eu/blog/practical-react-query — foundational defaults, custom hooks, avoiding local state copies
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.
### Hard Rule: No Guessed Source Schemas
If the service docs do not clearly show document list responses, document fetch responses, metadata fields, or pagination shapes, you MUST tell the user instead of guessing.
- Do NOT infer document fields from unrelated endpoints
- Do NOT guess pagination cursors or response wrappers
- Do NOT assume metadata keys that are not documented
- Do NOT treat probable shapes as validated
If a schema is unknown, validation must explicitly recommend:
1. sample API responses,
2. live test credentials, or
3. trimming the connector to only documented fields.
## 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
4. Any remaining unknown source schemas were explicitly reported to the user instead of guessed
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
description: Analyze and fix useCallback anti-patterns in your code
---
# You Might Not Need a Callback
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://react.dev/reference/react/useCallback — official docs on when useCallback is actually needed
## When useCallback IS needed
- Passing a callback to a child wrapped in `React.memo` (to preserve referential equality)
- The callback is a dependency of another hook (`useEffect`, `useMemo`)
- The callback is used in a custom hook that documents referential stability requirements
## Anti-patterns to detect
1.**useCallback on functions not passed as props or deps**: If the function is only called within the same component and isn't in any dependency array, useCallback adds overhead for no benefit. Just declare the function normally.
2.**useCallback with exhaustive deps that change every render**: If the dependency array includes values that change on every render, useCallback recalculates every time. The memoization is wasted. Either stabilize the deps (use refs) or remove the useCallback.
3.**useCallback on event handlers passed to native elements**: `<button onClick={handleClick}>` — native elements don't benefit from stable references. Only child components wrapped in React.memo do.
4.**useCallback wrapping a function that creates new objects/arrays**: If the callback returns `{ ...newObj }` or `[...newArr]`, memoizing the callback doesn't prevent the child from re-rendering due to new return values. The memoization is at the wrong level.
5.**useCallback with an empty dep array when deps are needed**: Stale closures — the callback captures outdated values. Either add proper deps or use refs for values that shouldn't trigger re-creation.
6.**Pairing useCallback with React.memo unnecessarily**: If the child component is cheap to render, neither useCallback nor React.memo adds value. Only optimize when you've measured a performance problem.
7.**useCallback in custom hooks that don't need stable references**: Not every hook return needs to be memoized. Only stabilize callbacks when consumers depend on referential equality.
## Codebase-specific notes
This codebase uses a ref pattern for stable callbacks in hooks:
```tsx
constidRef=useRef(id)
useEffect(()=>{idRef.current=id},[id])
constfetchData=useCallback(async()=>{
// use idRef.current instead of id
},[])// empty deps because refs are used
```
This pattern is correct — don't flag it as an anti-pattern.
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
description: Analyze and fix useMemo/React.memo anti-patterns in your code
---
# You Might Not Need a Memo
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://overreacted.io/before-you-memo/ — two techniques to avoid memo entirely
## Anti-patterns to detect
1.**Wrapping a slow component in React.memo when state can be moved down**: If a component re-renders because of state it doesn't use, move that state into a smaller child component instead of memoizing. The slow component stops re-rendering without memo.
2.**Wrapping in React.memo when children can be lifted up**: If a parent owns state that changes frequently, extract the stateful part and pass the expensive subtree as `children`. Children passed as props don't re-render when the parent's state changes.
3.**useMemo on cheap computations**: Filtering or mapping a small array, string concatenation, simple arithmetic — these don't need memoization. Only memoize when you've measured a performance problem.
4.**useMemo with constantly-changing deps**: If the dependency array changes on every render, useMemo does nothing — it recalculates every time. Fix the deps or remove the memo.
5.**useMemo to create objects/arrays passed as props**: Instead of memoizing to prevent child re-renders, consider whether the child even needs referential stability. If the child doesn't use React.memo or pass it to a dep array, the memo is wasted.
6.**React.memo on components that always receive new props**: If the parent always passes new objects, arrays, or callbacks, React.memo's shallow comparison always fails. Fix the parent instead of memoizing the child.
7.**useMemo for derived state**: If you're computing a value from props or state, just compute it inline during render. React renders are fast. `const fullName = first + ' ' + last` doesn't need useMemo.
## Steps
1. Read the reference above to understand the two core techniques (move state down, lift content up)
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
description: Analyze and fix unnecessary useState, derived state, and server-state-in-local-state anti-patterns
---
# You Might Not Need State
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query for all server state and Zustand for client-only global state. useState should only be used for ephemeral UI concerns (open/closed, hover, local form input). Server data should never be copied into useState or Zustand — React Query is the single source of truth.
## References
Read these before analyzing:
1. https://react.dev/learn/choosing-the-state-structure — 5 principles for structuring state
2. https://tkdodo.eu/blog/dont-over-use-state — never store derived/computed values in state
3. https://tkdodo.eu/blog/putting-props-to-use-state — never mirror props into state via useEffect
## Anti-patterns to detect
1.**Derived state stored in useState**: If a value can be computed from props, other state, or query data, compute it inline during render instead of storing it in state.
2.**Server state copied into useState**: Never `useState` + `useEffect` to sync React Query data into local state. Use query data directly. The only exception is forms where users edit server data.
3.**Props mirrored into state**: Never `useState(prop)` + `useEffect(() => setState(prop))`. Use the prop directly, or use a key to reset component state.
4.**Chained useEffect state updates**: Never chain Effects that set state to trigger other Effects. Calculate all derived values in the event handler or inline during render.
5.**Storing objects when an ID suffices**: Store `selectedId` not a copy of the selected object. Derive the object: `items.find(i => i.id === selectedId)`.
6.**State that duplicates Zustand or React Query**: If the data already lives in a store or query cache, don't create a parallel useState.
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
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
description: Run all code quality skills in sequence — effects, memo, callbacks, state, React Query, and emcn design review
argument-hint: [scope] [fix=true|false]
---
# Cleanup
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Steps
Run each of these skills in order on the specified scope, passing through the scope and fix arguments. After each skill completes, move to the next. Do not skip any.
1.`/you-might-not-need-an-effect $ARGUMENTS`
2.`/you-might-not-need-a-memo $ARGUMENTS`
3.`/you-might-not-need-a-callback $ARGUMENTS`
4.`/you-might-not-need-state $ARGUMENTS`
5.`/react-query-best-practices $ARGUMENTS`
6.`/emcn-design-review $ARGUMENTS`
After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.
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: Review UI code for alignment with the emcn design system — components, tokens, patterns, and conventions
argument-hint: [scope] [fix=true|false]
---
# EMCN Design Review
Arguments:
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA variants and CSS variable design tokens. All UI must use emcn components and tokens.
## Steps
1. Read the emcn barrel export at `apps/sim/components/emcn/components/index.ts` to know what's available
2. Read `apps/sim/app/_styles/globals.css` for CSS variable tokens
3. Analyze the specified scope against every rule below
4. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
---
## Imports
- Import from `@/components/emcn` barrel, never subpaths
- Icons from `@/components/emcn/icons` or `lucide-react`
- Use `cn` from `@/lib/core/utils/cn` for conditional classes
## Design Tokens
Use CSS variable pattern (`text-[var(--text-primary)]`), never Tailwind semantics (`text-muted-foreground`) or hardcoded colors (`text-gray-500`, `#333`).
Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` action button, `variant="default"` cancel. Cancel left, action right (100% compliance). Use `text-[var(--text-error)]` for irreversible warnings.
## Toast
`toast.success()`, `toast.error()`, `toast()` from `@/components/emcn`. Never custom notification UI.
## Badges
`red`=error/failed, `gray-secondary`=metadata/roles, `type`=type annotations, `green`=success/active, `gray`=neutral, `amber`=processing, `orange`=paused, `blue`=info. Use `dot` prop for status indicators.
description: Audit React Query usage for best practices — key factories, staleTime, mutations, and server state ownership
argument-hint: [scope] [fix=true|false]
---
# React Query Best Practices
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/hooks/queries/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query (TanStack Query) as the single source of truth for all server state. All query hooks live in `hooks/queries/`. Zustand is used only for client-only UI state. Server data must never be duplicated into useState or Zustand outside of mutation callbacks that coordinate cross-store state.
## References
Read these before analyzing:
1. https://tkdodo.eu/blog/practical-react-query — foundational defaults, custom hooks, avoiding local state copies
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
description: Analyze and fix useMemo/React.memo anti-patterns in your code
argument-hint: [scope] [fix=true|false]
---
# You Might Not Need a Memo
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://overreacted.io/before-you-memo/ — two techniques to avoid memo entirely
## Anti-patterns to detect
1.**State can be moved down instead of memoizing**: Move state into a smaller child so the slow component stops re-rendering without memo.
2.**Children can be lifted up**: Extract stateful part, pass expensive subtree as `children` — children as props don't re-render when parent state changes.
3.**useMemo on cheap computations**: Small array filters, string concat, arithmetic don't need memoization.
4.**useMemo with constantly-changing deps**: Deps change every render = useMemo does nothing.
5.**useMemo to stabilize props for non-memoized children**: If the child isn't wrapped in React.memo, stable references don't matter.
6.**React.memo on components that always receive new props**: Fix the parent instead.
7.**useMemo for derived state**: Just compute inline during render.
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
description: Analyze and fix unnecessary useState, derived state, and server-state-in-local-state anti-patterns
argument-hint: [scope] [fix=true|false]
---
# You Might Not Need State
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query for all server state and Zustand for client-only global state. useState should only be used for ephemeral UI concerns (open/closed, hover, local form input). Server data should never be copied into useState or Zustand — React Query is the single source of truth.
## References
Read these before analyzing:
1. https://react.dev/learn/choosing-the-state-structure — 5 principles for structuring state
2. https://tkdodo.eu/blog/dont-over-use-state — never store derived/computed values in state
3. https://tkdodo.eu/blog/putting-props-to-use-state — never mirror props into state via useEffect
## Anti-patterns to detect
1.**Derived state stored in useState**: If a value can be computed from props, other state, or query data, compute it inline during render instead of storing it in state.
2.**Server state copied into useState**: Never `useState` + `useEffect` to sync React Query data into local state. Use query data directly. The only exception is forms where users edit server data.
3.**Props mirrored into state**: Never `useState(prop)` + `useEffect(() => setState(prop))`. Use the prop directly, or use a key to reset component state.
4.**Chained useEffect state updates**: Never chain Effects that set state to trigger other Effects. Calculate all derived values in the event handler or inline during render.
5.**Storing objects when an ID suffices**: Store `selectedId` not a copy of the selected object. Derive the object: `items.find(i => i.id === selectedId)`.
6.**State that duplicates Zustand or React Query**: If the data already lives in a store or query cache, don't create a parallel useState.
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
When editing user-facing copy (landing pages, docs, metadata, marketing), follow these rules.
## Identity
Sim is the **AI workspace** where teams build and run AI agents. Not a workflow tool, not an agent framework, not an automation platform.
**Short definition:** Sim is the open-source AI workspace where teams build, deploy, and manage AI agents.
**Full definition:** Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM to create agents that automate real work — visually, conversationally, or with code.
## Audience
**Primary:** Teams building AI agents for their organization — IT, operations, and technical teams who need governance, security, lifecycle management, and collaboration.
**Secondary:** Individual builders and developers who care about speed, flexibility, and open source.
| **Tables** | A database, built in. Store, query, and wire structured data into agent runs. |
| **Files** | Upload, create, and share. One store for your team and every agent. |
| **Logs** | Full visibility, every run. Trace execution block by block. |
## What We Never Say
- Never call Sim "just a workflow tool"
- Never compare only on integration count — we win on AI-native capabilities
- Never use "no-code" as the primary descriptor — say "visually, conversationally, or with code"
- Never promise unshipped features
- Never use jargon ("RAG", "vector database", "MCP") without plain-English explanation on public pages
- Avoid "agentic workforce" as a primary term — use "AI agents"
## Vision
Sim becomes the default environment where teams build AI agents — not a tool you visit for one task, but a workspace you live in. Workflows are one module; Mothership is another. The workspace is the constant; the interface adapts.
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 and polling triggers for Sim. You understand the trigger system, the generic `buildTriggerSubBlocks` helper, polling infrastructure, and how triggers connect to blocks.
## Your Task
1. Research what webhook events the service supports — if the service lacks reliable webhooks, use polling
2. Create the trigger files using the generic builder (webhook) or manual config (polling)
3. Create a provider handler (webhook) or polling handler (polling)
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)
**Versioned blocks (V1 + V2):** Many integrations have a hidden V1 block and a visible V2 block. Where you add the trigger wiring depends on how V2 inherits from V1:
- **V2 uses `...V1Block` spread** (e.g., Google Calendar): Add trigger to V1 — V2 inherits both `subBlocks` and `triggers` automatically.
- **V2 defines its own `subBlocks`** (e.g., Google Sheets): Add trigger to V2 (the visible block). V1 is hidden and doesn't need it.
- **Single block, no V2** (e.g., Google Drive): Add trigger directly.
`generate-docs.ts` deduplicates by base type (first match wins). If V1 is processed first without triggers, the V2 triggers won't appear in `integrations.json`. Always verify by checking the output after running the script.
## Provider Handler
All provider-specific webhook logic lives in a single handler file: `apps/sim/lib/webhooks/providers/{service}.ts`.
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`**.
Use polling when the service lacks reliable webhooks (e.g., Google Sheets, Google Drive, Google Calendar, Gmail, RSS, IMAP). Polling triggers do NOT use `buildTriggerSubBlocks` — they define subBlocks manually.
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Steps
Run each of these skills in order on the specified scope, passing through the scope and fix arguments. After each skill completes, move to the next. Do not skip any.
1.`/you-might-not-need-an-effect $ARGUMENTS`
2.`/you-might-not-need-a-memo $ARGUMENTS`
3.`/you-might-not-need-a-callback $ARGUMENTS`
4.`/you-might-not-need-state $ARGUMENTS`
5.`/react-query-best-practices $ARGUMENTS`
6.`/emcn-design-review $ARGUMENTS`
After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.
- scope: what to review (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA variants and CSS variable design tokens. All UI must use emcn components and tokens.
## Steps
1. Read the emcn barrel export at `apps/sim/components/emcn/components/index.ts` to know what's available
2. Read `apps/sim/app/_styles/globals.css` for CSS variable tokens
3. Analyze the specified scope against every rule below
4. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
---
## Imports
- Import from `@/components/emcn` barrel, never subpaths
- Icons from `@/components/emcn/icons` or `lucide-react`
- Use `cn` from `@/lib/core/utils/cn` for conditional classes
## Design Tokens
Use CSS variable pattern (`text-[var(--text-primary)]`), never Tailwind semantics (`text-muted-foreground`) or hardcoded colors (`text-gray-500`, `#333`).
Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` action button, `variant="default"` cancel. Cancel left, action right (100% compliance). Use `text-[var(--text-error)]` for irreversible warnings.
## Toast
`toast.success()`, `toast.error()`, `toast()` from `@/components/emcn`. Never custom notification UI.
## Badges
`red`=error/failed, `gray-secondary`=metadata/roles, `type`=type annotations, `green`=success/active, `gray`=neutral, `amber`=processing, `orange`=paused, `blue`=info. Use `dot` prop for status indicators.
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/hooks/queries/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query (TanStack Query) as the single source of truth for all server state. All query hooks live in `hooks/queries/`. Zustand is used only for client-only UI state. Server data must never be duplicated into useState or Zustand outside of mutation callbacks that coordinate cross-store state.
## References
Read these before analyzing:
1. https://tkdodo.eu/blog/practical-react-query — foundational defaults, custom hooks, avoiding local state copies
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
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## References
Read before analyzing:
1. https://overreacted.io/before-you-memo/ — two techniques to avoid memo entirely
## Anti-patterns to detect
1.**State can be moved down instead of memoizing**: Move state into a smaller child so the slow component stops re-rendering without memo.
2.**Children can be lifted up**: Extract stateful part, pass expensive subtree as `children` — children as props don't re-render when parent state changes.
3.**useMemo on cheap computations**: Small array filters, string concat, arithmetic don't need memoization.
4.**useMemo with constantly-changing deps**: Deps change every render = useMemo does nothing.
5.**useMemo to stabilize props for non-memoized children**: If the child isn't wrapped in React.memo, stable references don't matter.
6.**React.memo on components that always receive new props**: Fix the parent instead.
7.**useMemo for derived state**: Just compute inline during render.
## Steps
1. Read the reference above
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/components/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
## Context
This codebase uses React Query for all server state and Zustand for client-only global state. useState should only be used for ephemeral UI concerns (open/closed, hover, local form input). Server data should never be copied into useState or Zustand — React Query is the single source of truth.
## References
Read these before analyzing:
1. https://react.dev/learn/choosing-the-state-structure — 5 principles for structuring state
2. https://tkdodo.eu/blog/dont-over-use-state — never store derived/computed values in state
3. https://tkdodo.eu/blog/putting-props-to-use-state — never mirror props into state via useEffect
## Anti-patterns to detect
1.**Derived state stored in useState**: If a value can be computed from props, other state, or query data, compute it inline during render instead of storing it in state.
2.**Server state copied into useState**: Never `useState` + `useEffect` to sync React Query data into local state. Use query data directly. The only exception is forms where users edit server data.
3.**Props mirrored into state**: Never `useState(prop)` + `useEffect(() => setState(prop))`. Use the prop directly, or use a key to reset component state.
4.**Chained useEffect state updates**: Never chain Effects that set state to trigger other Effects. Calculate all derived values in the event handler or inline during render.
5.**Storing objects when an ID suffices**: Store `selectedId` not a copy of the selected object. Derive the object: `items.find(i => i.id === selectedId)`.
6.**State that duplicates Zustand or React Query**: If the data already lives in a store or query cache, don't create a parallel useState.
## Steps
1. Read the references above to understand the guidelines
2. Analyze the specified scope for the anti-patterns listed above
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
When editing user-facing copy (landing pages, docs, metadata, marketing), follow these rules.
## Identity
Sim is the **AI workspace** where teams build and run AI agents. Not a workflow tool, not an agent framework, not an automation platform.
**Short definition:** Sim is the open-source AI workspace where teams build, deploy, and manage AI agents.
**Full definition:** Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM to create agents that automate real work — visually, conversationally, or with code.
## Audience
**Primary:** Teams building AI agents for their organization — IT, operations, and technical teams who need governance, security, lifecycle management, and collaboration.
**Secondary:** Individual builders and developers who care about speed, flexibility, and open source.
| **Tables** | A database, built in. Store, query, and wire structured data into agent runs. |
| **Files** | Upload, create, and share. One store for your team and every agent. |
| **Logs** | Full visibility, every run. Trace execution block by block. |
## What We Never Say
- Never call Sim "just a workflow tool"
- Never compare only on integration count — we win on AI-native capabilities
- Never use "no-code" as the primary descriptor — say "visually, conversationally, or with code"
- Never promise unshipped features
- Never use jargon ("RAG", "vector database", "MCP") without plain-English explanation on public pages
- Avoid "agentic workforce" as a primary term — use "AI agents"
## Vision
Sim becomes the default environment where teams build AI agents — not a tool you visit for one task, but a workspace you live in. Workflows are one module; Mothership is another. The workspace is the constant; the interface adapts.
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>
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 +86,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 +101,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 and realtime socket server
```
Or run separately: `bun run dev` (Next.js) and `cd apps/sim && bun run dev:sockets` (realtime).
@@ -159,18 +133,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 AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
default:'Sim Documentation - Visual Workflow Builder for AI Applications',
template:'%s',
default:'Sim Documentation — The AI Workspace for Teams',
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 AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
title:'Sim Documentation - Visual Workflow Builder for AI Applications',
title:'Sim Documentation — The AI Workspace for Teams',
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 AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
title:'Sim Documentation - Visual Workflow Builder for AI Applications',
title:'Sim Documentation — The AI Workspace for Teams',
description:
'Comprehensive documentation for Sim - the visual workflow builder for AI applications.',
'Documentation for Sim — the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM.',
> The open-source AI workspace where teams build, deploy, and manage AI agents.
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 AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM to create agents that automate real work — visually, conversationally, or with code. Trusted by over 100,000 builders.
@@ -68,37 +66,15 @@ 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.',
'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 AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM to create agents that automate real work.',
url: baseUrl,
author:{
'@type':'Organization',
@@ -109,45 +85,35 @@ export function StructuredData({
category:'Developer Tools',
},
featureList:[
'Visual workflow builder with drag-and-drop interface',
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.