mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-22 05:19:54 +08:00
48c1b453dfd659707a1f876ca5ffdc63ca59067d
5112
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
48c1b453df |
feat(integrations): extend ElevenLabs, Google Drive, Firecrawl, Pinecone, Resend, and S3 tool depth (#5270)
* feat(firecrawl): add crawl status/cancel, batch scrape + status, extract status, credit usage tools * feat(resend): add audiences, broadcasts, and cancel-email tools * feat(pinecone): add delete/update vectors, index, and stats tools * feat(google-drive): add revisions, comments, and export tools * feat(elevenlabs): add voices, settings, models, user, sound-effects, speech-to-speech, audio-isolation tools * feat(s3): add bucket CRUD, head-object, presigned-url, and batch-delete tools * chore(api-validation): bump route baseline to 873 for wave-3 internal tool routes (s3, elevenlabs, google_drive export) * docs(integrations): regenerate docs + catalog for wave-3 tools * fix(integrations): audit fixes for wave-3 - pinecone: read camelCase vectorType/deletionProtection (with snake_case fallback) so list_indexes/describe_index populate them; make describe_index_stats casing defensive - google-drive: URL-encode fileId in the export route - remove extraneous inline/section-divider comments across new blocks/tools; convert type docs to TSDoc * fix(integrations): address review — elevenlabs settings bleed, batch-scrape job-id guard, s3 head-object existence - elevenlabs: select stability/similarityBoost by operation so a stale edit-settings value can't bleed into a TTS call - firecrawl: fail fast with a clear error when batch scrape returns no job id (avoids a misleading polling timeout) - s3: head_object on a missing key now returns exists:false instead of a generic failure * fix(s3): allow exists:false in head-object response contract (schema must match the missing-object output) * fix(pinecone): guard JSON.parse of ids/filter/values/sparseValues/setMetadata Malformed JSON-string input now throws a clear '<field> must be valid JSON' error via a shared parseJsonParam helper instead of crashing the request body builder. * fix(pinecone): enforce mutual exclusivity of ids/deleteAll/filter in delete_vectors Pinecone treats these delete selectors as mutually exclusive; the tool now requires exactly one and throws a clear error otherwise, instead of sending a conflicting body. * fix(integrations): I/O fidelity vs API docs (wave-3 audit) - pinecone: normalize describe_index_stats per-namespace vector_count -> vectorCount - firecrawl: remove phantom 'sources' from extract_status, add real creditsUsed/tokensUsed; expose batch_scrape maxConcurrency/ignoreInvalidURLs - google-drive: drop undocumented supportsAllDrives from the files.export URL - elevenlabs: add next_page_token input to list_voices (fixes pagination dead-end) - resend: surface segment_id on get_broadcast; declare replyTo + segment_id in block outputs |
||
|
|
c7bb37d488 |
feat(workflow-renderer): extract pure WorkflowBlockView + SubBlockRowView (#5267)
* feat(workflow-renderer): extract pure SubBlockRowView from the block's summary row Splits the canvas block's collapsed subblock summary row into a pure SubBlockRowView (title + resolved displayValue + monospace flag) in @sim/workflow-renderer. The ~9 selector-name hydration hooks stay in the SubBlockRow container behind its memo comparator; the view receives only resolved strings. Byte-identical row JSX. First step toward the full WorkflowBlockView. * feat(workflow-renderer): extract pure WorkflowBlockView shell Moves the canvas block's render (header, badges, dynamic handles, ring) into a pure WorkflowBlockView in @sim/workflow-renderer. WorkflowBlock becomes a thin container that resolves all stores/hooks/permissions, builds the subblock rows + actionBar slots, and binds wouldCreateConnectionCycle (reads the edge store fresh per call to preserve cycle prevention). getHandleClasses/getHandleStyle move into the view; config.icon/bgColor, the webhook provider name, and every visual flag cross as props. Byte-identical JSX — every handle id/class/style/offset and badge guard preserved (verified by an independent adversarial audit). Container drops from 1137 to 829 lines. |
||
|
|
69e3550a72 |
feat(integrations): extend Telegram, Outlook, and Notion tool depth (#5265)
* feat(telegram): add edit, forward, copy, location, contact, poll, pin, reaction, chat-action, and chat-info tools * feat(outlook): add reply, reply-all, folders, attachments, search, and message-update tools * feat(notion): add block children CRUD, comments, and users tools (v1 + v2) * docs(integrations): regenerate docs + catalog for telegram, outlook, notion tools * fix(notion): guard pageSize coercion with Number.isFinite so non-numeric input isn't forwarded as NaN * fix(telegram): normalize send_poll options (array, JSON string, or newlines) so json-typed input can't crash on .map * fix(integrations): harden outlook search quoting, notion archive default, and telegram poll options - outlook: strip embedded double quotes from the $search term so they can't break the quoted KQL query - notion: default the update_block Archive dropdown to 'Leave unchanged' so updates don't send an unintended archived:false restore flag - telegram: pass raw poll options through to the tool's normalizePollOptions (handles array/JSON-string/newlines) so the block layer no longer mishandles JSON-string input * fix(integrations): normalize outlook categories input and clamp notion pageSize - outlook: normalize update_message categories (array, JSON string, or comma/newline) so a JSON-string value isn't silently dropped - notion: clamp pageSize to Notion's 1-100 range (truncated) so out-of-range values don't hit an API error * fix(outlook): pass raw categories to the tool's normalizeCategories so the block handles JSON-string input too * fix(outlook): allow clearing message categories by passing an empty array An explicit empty array now sends categories:[] to clear all labels, a non-empty value replaces, and an absent/empty value leaves them untouched — matching the documented replace semantics. * fix(outlook): only clear categories on an explicit empty array, not a delimiter-only string A string that normalizes to no categories (e.g. just commas) is now a no-op rather than clearing all labels; clearing requires an explicit empty array. * fix(notion): clamp page_size to 1-100 at the tool layer for list comments/users and block children Adds a shared clampNotionPageSize helper so the agent-direct path is bounded to Notion's range, not just the block path. * fix(outlook): use consistent set/replace semantics for message categories Categories are replaced with the provided non-empty list and left unchanged when empty, consistent across the block and agent paths. Drops the ambiguous empty-array clear (clearing all categories isn't expressible unambiguously from the comma-separated field) and updates the description to match. |
||
|
|
950c2602c2 |
fix(uploads): attach compiled binary for AI-generated docs, not source (#5266)
* fix(uploads): attach compiled binary for AI-generated docs, not source AI-generated documents (pdf/docx/pptx/xlsx) created in Chat are stored as their generation source, with the rendered binary in a separate content-addressed artifact store. Read/preview paths swap in the binary, but attachment/upload/provider paths downloaded the raw source — so a generated PDF emailed via Gmail (and 30+ other tools) arrived as the generator script renamed .pdf. - Add shared resolveServableDocBytes resolver + downloadServableFileFromStorage wrapper; the file-serve route now delegates to the same resolver so the two paths resolve identically. - Migrate ~34 attachment/upload/parse tool routes + the LLM provider attachment path to the servable download; media-only tools and source-editing paths keep the raw download intentionally. - Surface a retryable 409 (shared docNotReadyResponse) when a doc artifact is still compiling instead of shipping source. * fix(uploads): return retryable 409 for not-ready docs in slack/teams sends The slack send-message and teams write_channel/write_chat routes call download helpers that can throw DocCompileUserError while a generated doc is still compiling. Map it to the shared docNotReadyResponse 409 (matching the other migrated tool routes) instead of a generic 500. The provider attachment path is internal LLM execution (no HTTP response), so it intentionally propagates the typed error. * fix(uploads): not-ready 409 for uptimerobot, real MIME for non-doc, xlsx tests Address review findings: - uptimerobot create-psp/update-psp now map DocCompileUserError to the shared 409 (Greptile + Cursor flagged the gap alongside slack/teams). - downloadServableFileFromStorage returns the extension-derived MIME (getMimeTypeFromExtension) for non-doc files instead of an empty string when userFile.type is unset. - Add resolveServableDocBytes tests for the three xlsx branches (binary ZIP passthrough, not-ready throw under E2B+beta, no-workspaceId raw passthrough). * fix(uploads): enforce attachment size limits on resolved bytes Size limits were checked against userFile.size (source metadata) before resolution, but a generated doc resolves to a larger compiled binary — so a small-source doc could pass the pre-check yet exceed the service limit. Add a post-resolution check on the actual resolved bytes (mirroring docusign/vanta) across gmail send/draft/edit-draft, smtp, outlook send/draft, telegram, sftp, and teams; the cheap source pre-check stays as an early reject. * chore(uploads): drop extraneous inline comments from servable-file changes * fix(sftp): enforce 100MB cap on cumulative resolved bytes, not per-file The SFTP batch upload checked each resolved file against the 100MB cap individually, so multiple resolved attachments could each pass while their combined size exceeded the limit. Accumulate resolved bytes across the loop and reject once the running total exceeds the cap. * fix(sendgrid): reject attachments exceeding SendGrid's 30MB limit on resolved bytes SendGrid had no attachment-size guard, so a generated doc resolving to a large compiled binary could be sent and fail opaquely at the API. Add a post-resolution total-size check (30MB, SendGrid's documented message limit) matching the gmail/smtp/outlook routes. |
||
|
|
a6196e0c86 |
perf(dev): curate SIM_DEV_MINIMAL_REGISTRY to core toolbar blocks (#5251)
Rebuild the dev-only minimal block registry from the canonical, toolbar-visible core (category blocks/triggers, not hideFromToolbar, latest version). Adds the visible workflow_input and table blocks (previously only the hidden `workflow` was present, so the Workflow block never rendered in dev:minimal), drops the superseded/hidden entries (api_trigger, chat_trigger, input_trigger, manual_trigger, router, starter, workflow), and hand-prunes the heaviest / rarely-core-dev blocks (mothership, pi, tts, stt_v2, image_generator_v2, video_generator_v3, circleback). Dev-only, gated on SIM_DEV_MINIMAL_REGISTRY=1; never aliased in production. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
27b2a4f585 |
feat(workflow-renderer): extract edge, subflow, and note Views into @sim/workflow-renderer (#5263)
Adds a @sim/workflow-renderer package with pure, props-driven WorkflowEdgeView, SubflowNodeView, and NoteBlockView shared by the editor and (future) docs preview. Moves block-dimensions constants into the package. Each editor node becomes a thin Container that wires stores/permissions and injects the editor-only ActionBar via a slot. No optimizePackageImports for the workspace component packages (avoids the toast-style module duplication); Tailwind scans the package source. |
||
|
|
23ec96b02a |
feat(pii): add redaction timing metrics across sidecar and persist path (#5264)
- Log per-request duration in the Presidio sidecar (/analyze, /anonymize) - Add durationMs to the mask-batch endpoint log line - Emit per-execution PII redaction timing (stringCount, totalBytes, durationMs, scrubbed) |
||
|
|
56f9393d7e |
fix(prism): load prismjs core before language components (#5262)
prismjs language components (prismjs/components/prism-*) register on the global Prism that core installs; importing a component before core throws 'Prism is not defined' (in SSR and the client). The @sim/emcn extraction changed bundling so core no longer loaded eagerly first, exposing the latent bad order on the home (chat-content) and workflow-editor pages. - chat-content.tsx: import prismjs core first (it needs ts/bash/css/markup, which emcn does not register). - code-editor.tsx, input-format.tsx, code.tsx: drop the redundant prism-json/prism-python imports — these highlight via emcn's languages, which already registers js/json/python in the correct order. |
||
|
|
f5116f45c3 |
fix(emcn): resolve Calendar icon/component barrel collision and preserve prism side effects (#5261)
Two post-extraction regressions: 1. The barrel resolves the Calendar name-collision to the date-picker COMPONENT (symmetric with Table). Scheduled-tasks imported Calendar from the barrel but used it as an ICON, so its header rendered a date picker. Route the icon consumers (scheduled-tasks + loading) to @sim/emcn/icons, matching the 'icons come from the /icons subpath' convention. 2. The package's new sideEffects: ['**/*.css'] marked code/prism.ts as side-effect-free, but it registers prismjs languages on the global Prism (a real JS side effect). The bundler could drop/reorder its core init, causing 'Prism is not defined'. Add code/prism.ts to sideEffects so the bundler preserves it. |
||
|
|
6dea1dc5ed |
fix(knowledge): send tag filters as a JSON string so the document filter works (#5259)
* fix(knowledge): send tag filters as a JSON string so the document filter works The document-list tag filter never reached the database. The `tagFilters` query field was a Zod `.transform()` that decoded the JSON string into an array of objects; the client's `requestJson` parses the query before serializing, so `appendQuery` received the array and emitted `tagFilters=[object Object]` into the URL. The route then failed to `JSON.parse` it and returned 400, so the list came back empty (or stale via keepPreviousData) regardless of operator or value. - Model `tagFilters` as the wire string it actually is; decode it server-side via a new `parseDocumentTagFiltersParam` helper (route maps a bad value to 400). - Harden `appendQuery`: throw on an array-of-objects query param instead of silently serializing `[object Object]`, so this whole class fails loudly. - Default the text tag-filter operator to `contains` so a partial value matches. - Tests: requestJson serializes the JSON param verbatim + the guard throws; the query schema keeps tagFilters a string; the decode helper round-trips. A full sweep of every GET/DELETE contract query field confirmed this was the only field of this class — logs filters and table filter/sort are unaffected. * fix(knowledge): reject tag-filter operators invalid for the field type Greptile P2: documentTagFilterSchema accepted any non-empty operator string, so an unsupported operator was silently dropped by the query builder instead of returning 400. Validate the operator against the field type's allowed set (single source of truth in filters/types) via superRefine. * fix(knowledge): validate tag-filter type against the slot, not the client claim Greptile P1: operator validation trusted the client-supplied fieldType, so a numeric slot could be sent with fieldType 'text' + 'contains' and slip through to build a text LIKE on a numeric column. Validate against the slot's inherent type via getFieldTypeForSlot (the source of truth): reject unknown slots and fieldType/slot mismatches at the boundary before checking the operator. * fix(knowledge): validate tag-filter values against the field type Greptile P1: value/valueTo were z.unknown(), so a number filter accepted 'abc', a date filter 'not-a-date', etc. — unusable values the query builder then silently dropped. Add a shared isValidFilterValue (single source of truth in filters/types) and reject unusable value/valueTo at the boundary, including the between upper bound. * fix(knowledge): only send a between tag filter once both bounds are set Cursor Bugbot: the strict valueTo validation made a partially-entered between filter (lower bound only) 400 and break the whole document list mid-entry. activeTagFilters now withholds a between row until both bounds are filled — consistent with already requiring the lower bound before sending any filter — so the list keeps loading while the range is being entered. * fix(knowledge): reject impossible calendar dates in tag filters Greptile P1: the date value check was format-only, so 2026-02-30 / 2026-99-99 passed the boundary and then made the document query's ::date cast throw a 500. Validate real calendar dates by round-tripping the parsed parts. |
||
|
|
f5f87de96d |
fix(emcn): repair app-wide crash and unstyled UI after package extraction (#5258)
Two regressions from moving emcn into @sim/emcn: 1. optimizePackageImports['@sim/emcn'] rewrote barrel imports to direct subpaths, duplicating the toast module so ToastProvider (layout) and useToast (workspace permissions provider) resolved different ToastContext objects — useToast threw 'must be used within <ToastProvider>' on every workspace route. Removed @sim/emcn from optimizePackageImports. 2. emcn's source left apps/sim's Tailwind content globs (and docs' v4 auto-content scope, which excludes node_modules), so utility classes used only inside emcn components stopped generating and components rendered unstyled. Added the package to apps/sim's Tailwind content and a @source to docs. |
||
|
|
bcf6a804f9 |
improvement(emcn): extract design system into shared @sim/emcn package (#5257)
Moves apps/sim/components/emcn into a shared @sim/emcn package consumed directly by apps/sim and apps/docs. cn/keyboard/use-copy-to-clipboard move into the package; all imports become direct @sim/emcn (icons via @sim/emcn/icons, CSS via file path). ChipModal email validation is now prop-driven (quickValidateEmail stays in apps/sim, injected via validate). Docs drops its local chip/chip-dropdown/dropdown-menu mirrors and consumes @sim/emcn. |
||
|
|
d878f15815 |
feat(integrations): extend Airtable, Google Docs, WhatsApp, and Excel tool depth (#5256)
* feat(airtable): add delete and upsert record tools * feat(google-docs): add batchUpdate text, table, image, and style tools * feat(whatsapp): add template, media, interactive, reaction, and mark-read tools * feat(microsoft-excel): add clear, format, create-table, sort, and delete-worksheet tools * fix(integrations): address review feedback - google-docs: use camelCase fontSize field mask; normalize string booleans for bold/italic/underline and matchCase - microsoft-excel: escape OData single quotes in worksheet/table keys; validate range for clear/format - airtable: enforce batch limits (delete <=10 ids, upsert <=10 records and 1-3 merge fields) with clear errors * fix(integrations): address round-2 review - airtable: coerce upsert typecast as string-aware boolean (string "false" no longer truthy) - microsoft-excel: format_range surfaces precise partial-state error when fill PATCH fails after font (no atomic font+fill endpoint in Graph) * fix(whatsapp): treat 2xx mark-as-read as success unless body says success:false * fix(integrations): build fix, doc accuracy, and comment cleanup - google-docs: align manualDocumentId condition with the document selector so the documentId canonical group has matching conditions (fixes canonical-pair block test / build) - microsoft-excel: describe fill/font color as hex code only (Graph does not reliably accept named colors) - remove verbose explanatory inline comments from new tools (keep idiomatic section dividers) - regenerate integration docs + integrations.json catalog from the block registry * fix(integrations): harden delete-record id coercion and excel sort-column validation - airtable: coerce recordIds entries via String() so numeric JSON values don't crash on .trim() - microsoft-excel: drop the silent sortColumn default to 0 so invalid input surfaces the tool's clear validation error (both v1 and v2 blocks) * fix(airtable): coerce upsert fieldsToMergeOn entries via String() to handle non-string values |
||
|
|
c59631698f |
chore(deploy): remove deploy as a2a (#5255)
* chore(deploy): remove a2a * add block |
||
|
|
66315f19e7 |
improvement(docs): flatten the academy learn/chapters panels (#5253)
* improvement(docs): flatten the academy learn/chapters panels The "What you will learn" and "Chapters" panels were filled, bordered cards — the only boxed elements on the page. The docs design system is explicitly flat: global.css strips fumadocs cards/callouts/card-grids to transparent/divider-based, and the right-rail "On this page" TOC is small, muted, and borderless. - WhatYouWillLearn (inline): flat divider list like the FAQ, with a small panel label at the app's text scale instead of a page-h2-scale title - VideoChapters (right rail): borderless, matching the TOC — small muted label + flat hover rows, no card chrome * improvement(docs): drop the repeated per-row play icon from the chapters list The CirclePlay glyph repeated on every chapter row read as noise — a column of identical icons down the rail. The "On this page" TOC this list mirrors has no per-row icons; the timestamps already signal video chapters and the hover highlight signals they're seekable. Rows are now text + time only. * improvement(docs): drop the under-label rule on the learn callout A full-width rule under the small "What you will learn" label read as an awkward heading underline and blurred into the inter-item dividers. The label is now a quiet muted marker (matching the Chapters label and the TOC heading), with dividers only between items — so it never competes with the bold item titles or looks like an underlined heading. |
||
|
|
5a8134119a |
feat(workspaces): fork + push/pull (#5210)
* feat(workspaces): fork + push/pull * type fix * fix tests * progress on ux * remove modal section * improve UI of modal * update more ui * make rollback part of the footer * track skipped count correctly * address comments * make it workspace admin level * update skipped count * address more comments * deal with unbounded memory possibility * fix deleted kb article bug * no deployed workflow case * UI/UX cleanup * fix oauth dropdown case * fix oauth selector issue * infra work + activity log * consolidate migration * update modal state * more UI simplification * grammar * update audit report ui * perf improvements * fix tool input scenarios and add dependsOn UI handling * minor comments * fix webhook stability issues + drift detection removal * make dependsOn subblock mapping cleanly stored * fix: harden fork dependent-value mapping (clear stale rows, identity-guard first-sync fallback, perf + cleanup) * address comments * update comment * enforce admin perms for activity api * fix required + dependsOn combo |
||
|
|
3766582e7d |
fix(webhooks): run inactive deployment-version cleanup inline on deploy (#5250)
When a deploy activates a new version, superseded versions' webhooks are removed by a separate, best-effort CLEANUP_INACTIVE outbox event. When that event is lost/dead-letters, old-version webhooks linger as is_active orphans that fetchActiveWebhooks skips (version mismatch), so they silently stop polling (~515 webhooks across ~130 workflows in prod). Run the existing cleanupInactiveDeploymentVersions synchronously in the SYNC_ACTIVE handler, right after the active version's webhooks/schedules are registered, falling back to the deferred outbox event only if the inline pass throws. This reuses the existing guarded cleanup, which re-checks each version is still inactive before tearing anything down (so it never touches the active version) and runs strictly after registration (so a teardown failure can't block it). |
||
|
|
3e03f8c285 |
fix(webhooks): cast json provider_config for atomic jsonb merge (#5249)
updateWebhookProviderConfig built a DB-side merge with jsonb operators
(COALESCE(provider_config, '{}'::jsonb) || $1::jsonb), but the
provider_config column is json, not jsonb. Postgres cannot apply jsonb
merge operators to a json column, so every polling state write failed
with "could not convert type jsonb to json" — silently breaking
historyId/lastCheckedTimestamp/pageToken/lastSeenGuids persistence for
all polling webhooks (Gmail, RSS, Google Sheets/Drive, Outlook, IMAP)
since the atomic-merge change landed.
Cast the column to jsonb for the || / - merge and cast the result back
to json for storage, matching the existing pattern in subscription.ts.
|
||
|
|
7a2103e2d4 |
improvement(logs): move per-block progress markers to Redis to cut write amplification (#5248)
* improvement(logs): move per-block progress markers to Redis to cut write amplification
Per-block lastStartedBlock/lastCompletedBlock markers were persisted via a
jsonb_set UPDATE on workflow_execution_logs on every block start and complete
(~2N UPDATEs per run) — the heaviest write query in the DB. These are live
progress breadcrumbs with no DB-polling consumer (live progress comes from the
executor over WebSocket); their only durable value is a breadcrumb folded into
the final record.
Behind the redis-progress-markers flag, markers now live in Redis during the run
and are folded into the single terminal UPDATE at completion, dropping per-run
row UPDATEs from ~2N+1 to 1.
- New progress-markers module: HASH execution:progress:{id}, atomic Lua
monotonic-guard writes preserving the existing <= ordering, reservation-aligned
TTL backstop, graceful no-op when Redis is unavailable
- Deterministic GC: cleared at every terminal/pause boundary; TTL covers crashes
- Flag resolved once per logging session so a run never mixes write paths
- Fold markers into the completion record (Redis wins, falls back to row markers)
- Merge live markers for in-flight detail reads
- Extract shared getExecutionReservationTtlMs so marker and admission-slot TTLs
share one source of truth
* fix(logs): SQL fallback when Redis marker write fails, fold markers on force-fail, validate marker shape
Addresses review feedback on the redis-progress-markers PR:
- persistLast* now falls back to the jsonb_set UPDATE when Redis is unavailable or the write fails (setLast* returns whether it persisted), so a marker is never dropped when the flag is on without a healthy Redis.
- markExecutionAsFailed folds live Redis markers into execution_data before clearing, so the last-started/last-completed breadcrumb survives the force-fail path.
- getProgressMarkers validates marker shape (rebuilds from typed fields), so a stale or wrong-shaped Redis value can never reach API consumers.
* chore(logs): convert inline marker comments to TSDoc
* fix(logs): preserve markers when the completion read fails
getProgressMarkers now returns null on a Redis read error (vs {} for genuinely empty). completeWorkflowExecution and markExecutionAsFailed skip clearProgressMarkers when the read returns null, so a transient read error at completion no longer wipes markers that are still durably in Redis — the TTL reclaims them instead.
* fix(logs): resolve marker store split-brain by latest-timestamp-wins + drain on force-fail
- When a Redis marker write falls back to SQL, Redis and the row can each hold a marker for a different block; reads/folds previously preferred Redis unconditionally and could pick a stale value. Now the completion fold, the in-flight detail read, and the force-fail fold all pick the marker with the later timestamp (pickLatestStartedMarker/pickLatestCompletedMarker; markExecutionAsFailed uses a monotonic SQL guard).
- markAsFailed now drains pending per-block marker writes (not just the completion promise) before folding, so a force-fail racing onBlockStart/onBlockComplete still captures the latest breadcrumb.
* fix(logs): harden Lua marker guard against non-table decoded values
Guard the monotonic-check index with type(decoded) == 'table' so a corrupted Redis field that decodes to a non-table (e.g. a number) can't error the eval; our write path only ever stores JSON objects, so this is defense-in-depth.
* perf(logs): skip completion Redis read/clear when markers went to SQL
completeWorkflowExecution now takes readProgressMarkers (the session's resolved marker mode); when the flag is off it skips the per-completion HGETALL+DEL entirely instead of probing a key that was never written. Sticky to the session so it stays flip-safe (an execution that wrote to Redis always folds+clears Redis). Non-session callers default to true (safe read-and-fold). Also hardened the Lua guard with type(decoded)=='table'.
|
||
|
|
b8d0b4f318 |
fix(sso): keep an exit affordance in edit mode when clean (#5247)
After clicking Edit on an existing SSO provider, the only header action was SaveDiscardActions, which renders nothing when the form is clean — so there was no way to leave edit mode back to the read-only summary without first changing a field or navigating away. Render a Cancel chip when isEditing && !hasChanges (the dirty-gated Discard already exits when there are changes). |
||
|
|
d49326316a |
improvement(clickhouse): expand block templates and skills, normalize tool versions (#5246)
* improvement(clickhouse): expand block templates and skills, normalize tool versions - Expand ClickHouseBlockMeta templates from 3 to 9 (schema docs, table maintenance, partition retention, long-running-query alerts, table provisioning, storage growth report) - Add document-schema and maintain-tables skills (now 5, all grounded in tools.access) - Normalize tool version '1.0' to '1.0.0' across all 26 tools for repo consistency * improvement(clickhouse): enforce explicit safeguards in destructive-op guidance Address Greptile P1 review: tighten the partition-retention and kill-query templates/skill so agent guidance requires an explicit retention cutoff and elapsed-time threshold, lists/verifies targets first, defaults to alert-only, and never drops a partition or kills a query without confirmation. * improvement(clickhouse): make long-running-query template alert-only Address Greptile P1: the kill-query path is a low-level primitive (like drop_table/delete) and shouldn't carry tool-specific kill policy. Remove the autonomous-kill suggestion from the scheduled template so no shipped template steers an agent toward killing a query unattended; alert a human instead. The kill tool stays available for explicit manual use. |
||
|
|
2686f043bc |
perf(dev): SIM_DEV_MINIMAL_REGISTRY mode to slash local dev-server RAM (#5223)
* perf(dev): SIM_DEV_MINIMAL_REGISTRY mode to slash local dev-server RAM Adds a dev-only escape hatch (`bun run dev:minimal`, or `dev:full:minimal` with the realtime server): when SIM_DEV_MINIMAL_REGISTRY=1, a Turbopack/webpack resolve-alias swaps the two heavy registries for tiny curated variants — `@/tools/registry` → 2 tools, `@/blocks/registry-maps` → ~20 core blocks. The shared workspace layout drags the full ~247-tool registry (~2,074 modules) into every route via providers/utils → tools/params, and the editor/executor pull all ~268 block configs; aliasing both stops Turbopack from compiling those graphs at all. To make the blocks alias clean, the heavy block import maps move out of registry.ts into registry-maps.ts (registry.ts keeps only its accessors, importing the maps); its public API is unchanged and full builds/tests use the full maps. The alias is gated on isDev + the flag and is never applied in production. Measured (Turbopack dev, authenticated, /logs): peak next-server RSS ~16 GB → ~4.7 GB, compile 4.9 min → ~18 s; the workflow editor route similarly drops to ~5 GB / ~17 s. Only http_request + function_execute and the curated core blocks work in minimal mode; unset the flag for the full set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * perf(dev): add table block to minimal registry; doc block registration in registry-maps Adds the Table block to the SIM_DEV_MINIMAL_REGISTRY curated set so the tables surface works under dev:minimal. Updates the integration skills/rules and CLAUDE.md to point block registration at blocks/registry-maps.ts (the BLOCK_REGISTRY / BLOCK_META_REGISTRY maps), reflecting that registry.ts now holds only the accessor functions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(dev): rename dev:full:minimal → dev:full:minimal-registry Matches the dev:full:* formatting and makes the suffix self-explanatory — it is the registry that's minimal, not the dev stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2554725de2 |
improvement(execution): stop rewriting execution snapshots on reuse + skip redundant actor lookup (#5242)
* improvement(execution): stop rewriting execution snapshots on reuse + skip redundant actor lookup - SnapshotService.createSnapshotWithDeduplication: switch the per-execution dedup write from onConflictDoUpdate(set state_data) to onConflictDoNothing + a conditional select. A (workflowId, stateHash) row is byte-identical by hash, so rewriting the full state jsonb every run only churned a dead tuple + TOAST/WAL under Postgres MVCC. The reuse path (the common case) now performs no write. - preprocessExecution: add an optional resolvedActorUserId so a caller that already resolved the billing actor upstream can skip the redundant workspace billed-account lookup. The ban/usage/rate/archived gates still run against the actor — only the resolution is reused, never a gate. The webhook background job passes the route-resolved payload.userId. * fix(webhooks): scope actor reuse to inline execution only Addresses review: a queued/Trigger.dev webhook can outlive a workspace billed-account change, so reusing the route-resolved actor there could gate against a stale account. Set resolvedActorUserId only on the in-process inline payload (sub-second after resolution); queued and persisted payloads omit it, so the background pass re-resolves the current billed account. Gates unchanged. * docs(webhooks): convert inline comments on actor-reuse to TSDoc * fix(logs): keep snapshot dedup a single atomic upsert (no select race) Addresses review: the DO NOTHING + follow-up select could fail if cleanup deletes the conflicting (orphaned, aged) snapshot between the no-op insert and the select. Revert to one atomic upsert but SET only state_hash, so RETURNING always yields the row (no race) while the unchanged TOASTed state_data jsonb is still not rewritten under MVCC — keeping the per-execution write tiny. |
||
|
|
ac95a27dab |
fix(security): gate credential-set invitation listing to admins and drop token (#5243)
GET /api/credential-sets/[id]/invite listed every invitation row — including the bearer token — to any org member, matching neither its sibling methods (POST/DELETE enforce admin/owner) nor the self-scoped /invitations endpoint. A non-privileged member could harvest a null-email invite token and self-join the credential set via POST /api/credential-sets/invite/[token]. - Add the admin/owner role gate to GET, matching POST/DELETE on the same route - Project explicit columns (drop token) so the secret is never returned to the management list; the creating admin still receives it via the create response |
||
|
|
9b66b40aab | fix(mcp): pin public IP-literal server URLs to block SSRF redirect bypass (#5244) | ||
|
|
b2d43c1085 |
fix(copilot): gate post-tool output writes behind write permission (#5241)
* fix(copilot): gate post-tool output writes behind write permission The Copilot/Mothership executor runs three post-tool output-redirection sinks (maybeWriteOutputToFile, maybeWriteOutputToTable, maybeWriteReadCsvToTable) that persist a tool's result into the workspace. They were gated only on identity (workspaceId + userId), not on permission. Because function_execute/user_table/read are read-allowed for execution (absent from WRITE_ACTIONS in tools/server/router.ts), a read-only collaborator could drive the agent to durably create/overwrite workspace files and insert/overwrite table rows via output declarations — a function-level authorization bypass (CWE-862) that the dedicated write tools correctly reject. Add a shared denyOutputWriteWithoutWritePermission guard built on the canonical permissionSatisfies predicate and apply it to all three sinks, once a write is actually intended, so read-only principals get the same Permission denied outcome as the dedicated mutation tools. * fix(copilot): move file output write-permission gate after no-op skip branches Address Cursor review: in maybeWriteOutputToFile the gate ran before the sandbox-export skip branch (which returns the result unchanged without writing), so a read-only caller with a sandbox files payload was denied even though no workspace write would occur. Move the check to immediately before writeWorkspaceFileByPath so it only fires when a write is actually performed. |
||
|
|
0420feed1d |
chore(data-drains): remove settings callout and unused InfoNote component (#5235)
* chore(data-drains): remove settings callout and unused InfoNote component * improvement(data-retention): convert policy editor from modal to full-surface page Mirror the access-control group-detail pattern: clicking a retention policy now drills into a full-surface PolicyDetail page (back chip + dirty-gated header Save/Discard + Remove) instead of an xl modal. Form fields use SettingsSection (Workspaces / Retention / PII redaction) and the page chrome matches group-detail exactly. The unsaved-changes confirm stays a small modal. * improvement(settings): shared save/discard + unsaved-changes guard Consolidate every editable settings surface onto one stack instead of per-page custom logic: - SaveDiscardActions: the canonical dirty-gated Discard+Save chip pair - useSettingsUnsavedGuard: syncs local dirty into useSettingsDirtyStore (so the sidebar section-switch confirm applies) + provides guardBack/UnsavedChangesModal for detail sub-views' back chip - useSettingsBeforeUnload: a single beforeunload in the settings shell Migrate whitelabeling, sso, access-control group-detail, data-retention, and secrets-manager (drops its duplicate beforeunload). Deletes the hand-rolled 'Unsaved changes' modals; the leave-confirm standardizes on Keep editing / Discard. Documents the pattern in the settings rule + add-settings-page skill. * fix(sso): reset originalFormData on discard/save so dirty state clears handleDiscard and the post-save cleanup reset formData to DEFAULT but left originalFormData on the edit snapshot, so hasChanges stayed true after leaving the form — leaking a stuck-dirty state into the shared settings guard. Reset originalFormData alongside formData in both leave paths (handleEdit re-seeds both on re-entry). * fix(settings): auto-dismiss unsaved-changes modal when page goes clean useSettingsUnsavedGuard stashed a deferred leave + opened UnsavedChangesModal when back was pressed while dirty, but never cleared them if isDirty later became false. Confirming Discard could then run a stale leave with no unsaved edits. Clear the pending leave and close the modal in the dirty-sync effect whenever isDirty is false. |
||
|
|
37962a05b2 |
fix(file-parsers): guard OOXML parsers against decompression-bomb memory exhaustion (#5239)
* fix(file-parsers): guard OOXML parsers against decompression-bomb memory exhaustion Pre-inspect the ZIP central directory of xlsx/docx/pptx buffers and reject archives whose declared expanded size (>1 GiB) or compression ratio (>150x) exceeds safe bounds, before SheetJS/mammoth/officeparser inflate them. The existing pipeline only capped the compressed input (100 MB), which does not bound decompressed size, so a crafted zip bomb could expand to many GB and OOM the worker. * fix(file-parsers): fail closed on unverifiable ZIP-shaped OOXML archives Address review: the guard previously no-opped (fell through to the decompression library) whenever the central directory could not be parsed, and findEocdOffset accepted the first backward EOCD signature without checking it sat at the buffer tail. A crafted archive with a decoy EOCD or an unsupported directory layout could bypass the size limits. - findEocdOffset now requires the EOCD comment length to place the record exactly at the buffer tail, defeating decoy signatures planted in the trailing region. - assertOoxmlArchiveWithinLimits now fails closed: a ZIP-shaped buffer (local file header / EOCD magic) whose central directory cannot be parsed is rejected rather than passed through. Genuine non-ZIP inputs (legacy OLE .xls/.doc, plaintext) still no-op and defer to the downstream parser. |
||
|
|
b8a197b99c |
fix(security): cap KB document download size to prevent memory-exhaustion DoS (#5240)
Knowledge-base ingestion downloaded an attacker-controlled external fileUrl with no byte cap: downloadFileFromUrl defaults maxBytes to MAX_SAFE_INTEGER, so the streaming reader buffered the entire response into memory uncapped. An authenticated user could OOM the processing worker by pointing fileUrl at a server that streams an unbounded body. Wire the documented 100MB file-size limit (MAX_FILE_SIZE) into the ingestion download helper. The existing stream limiter aborts the read once the cap is exceeded and rejects up front on an oversized Content-Length, so the body is never fully buffered. |
||
|
|
845a6276d9 |
perf(db): per-role Postgres connection-pool profiles (#5232)
* perf(db): drive Postgres pool size + application_name from per-role profiles Replace ad-hoc DB_APP_NAME sizing with a per-role profile map keyed by SIM_DB_ROLE (web/trigger/realtime), defaulting to web. Trigger machines open a small pool instead of 15 to avoid PgBouncer connection exhaustion. Also size realtime's separate socketDb pool down to 10. * fix(db): throw on invalid SIM_DB_ROLE instead of silently using web pools * fix(db): use Object.hasOwn for SIM_DB_ROLE validation to avoid prototype keys |
||
|
|
6264cba52f |
fix(connectors): harden Zendesk connector against SSRF (#5237)
* fix(connectors): harden Zendesk connector against SSRF Route the Zendesk connector through the SSRF-safe secureFetchWithRetry (DNS-resolve + IP-pin + per-redirect revalidation) instead of the plain fetchWithRetry, and validate the user-supplied subdomain against a strict DNS-label pattern before building the base URL. Matches the GitLab/Sentry/Obsidian/S3 precedent. * fix(connectors): retry transient DNS failures in secureFetchWithRetry secureFetchWithValidation throws a validation error before the request when a hostname temporarily fails to resolve. Classify that transient DNS failure as retryable so secureFetchWithRetry mirrors the old fetchWithRetry network-retry behavior, while keeping the deterministic blocked-IP SSRF rejection non-retryable. |
||
|
|
3c6c6b1515 |
improvement(webhooks): add trigger-age instrumentation + guard env decryption (#5236)
- Add dispatch-latency / trigger-age instrumentation: capture webhook receipt
time + Slack x-slack-request-timestamp at the route and log structured
dispatchLatencyMs + triggerAgeMs before execution, surfacing the pre-execution
latency that per-block timings cannot see (Slack trigger_id expires at 3s).
- Guard the effective-env fetch in verifyProviderAuth: only fetch+decrypt when
the handler verifies auth AND the providerConfig references env vars ({{VAR}}),
avoiding a needless DB read/decrypt on the synchronous pre-ack path. The guard
scope exactly matches resolveProviderConfigEnvVars, so resolution is identical.
|
||
|
|
e08fb022e6 |
perf(trigger): cap concurrency on background DB tasks (#5231)
* perf(trigger): cap concurrency on background DB tasks * test(trigger): update schedule concurrency assertion to 30 |
||
|
|
d9da544a13 |
feat(triggers): add Twilio SMS, Clerk, incident.io, Rootly, RevenueCat, Loops, and Sentry webhook triggers (#5230)
* feat(triggers): add Twilio SMS, Clerk, incident.io, Rootly, RevenueCat, Loops, Sentry webhook triggers Adds inbound webhook triggers for seven existing integrations, each verified against the provider's official webhook docs (event types, payload fields, signature scheme) and aligned with Sim's trigger conventions. - Twilio SMS: inbound message + status callback (X-Twilio-Signature, HMAC-SHA1) - Clerk: user/session/organization lifecycle (Svix) - incident.io: incident created/updated/status + alert created (Svix) - Rootly: incident created/updated/resolved + alert created (HMAC-SHA256); auto-registers and tears down the webhook via the Rootly API - RevenueCat: purchase/renewal/cancellation/expiration/product-change (Authorization header); auto-registers via the RevenueCat v2 API - Loops: email lifecycle + campaign/loop/transactional sent (Svix-compatible) - Sentry: issue/error/issue-alert/metric-alert (Sentry-Hook-Signature, fail-closed) Clerk, incident.io, Loops, Twilio, and Sentry use manual signing-secret setup where the provider exposes no clean webhook-management API; Rootly and RevenueCat auto-provision so the user only supplies an API key. * fix(triggers): address review — fail-closed auth, Twilio event filtering, user-only secrets - Twilio: add matchEvent so inbound-SMS and status-callback triggers don't cross-fire on a shared webhook URL (inbound = SmsStatus 'received') - Rootly + RevenueCat: verifyAuth now fails closed (401) when the signing secret is absent from provider config (both auto-register, so it is always present after deploy) instead of skipping verification - Mark every user-provided credential field paramVisibility 'user-only' (Clerk, incident.io, RevenueCat, Sentry, Twilio) so secrets are never exposed as LLM-visible trigger params - Sentry: correct metric_alert web_url description per docs * fix(triggers): key Twilio status callbacks by SID + status for idempotency Twilio sends multiple delivery callbacks per message (sent -> delivered -> ...) sharing one MessageSid; keying idempotency on the SID alone dropped every status after the first. Status callbacks now key on SID + delivery status so each state is distinct (while still deduping Twilio's retries of the same status); inbound messages still key by SID since they fire once. * fix(triggers): require a positive signal in Twilio matchEvent routing Previously an empty MessageStatus/SmsStatus was treated as a status callback, so an ambiguous or partial payload could match twilio_sms_status (or skip twilio_sms_received). Both triggers now require a positive signal — inbound needs status 'received', status callbacks need a non-'received' status — so a payload missing both fields matches neither rather than misrouting. * fix(triggers): attach Twilio auth fields to both triggers so status callbacks are verified The Account SID / Auth Token fields were only on the primary trigger, so deploying twilio_sms_status alone never captured authToken into providerConfig and signature verification was silently skipped. Following the incident.io / Rootly / RevenueCat pattern, the auth fields are now added to each trigger conditioned on its own selectedTriggerId; the shared inner subBlock IDs keep the values persisted across trigger types. * fix(triggers): mark shared block-level API keys user-only to protect trigger secrets The trigger credential fields (RevenueCat/Rootly apiKey, Twilio authToken) are user-only, but the same-id tool-level block fields were not, so the stored secret stayed reachable as an LLM-visible block parameter. Mark those block credential fields paramVisibility 'user-only' too (matching instantly.ts) so the secret is user-only on every path. accountSid is an identifier, not a secret, so it is left as-is. |
||
|
|
3143a15dde |
feat(uptimerobot): add UptimeRobot v3 integration (#5229)
* feat(uptimerobot): add UptimeRobot v3 integration
- 24 tools across monitors, incidents, maintenance windows, alert contacts,
public status pages, and account (UptimeRobot v3 REST API, Bearer auth)
- Block with operation-scoped subBlocks, status-page logo/icon file uploads
via internal multipart routes, and BlockMeta templates + skills
- Registered tools/block, added icon, generated docs
- Updated add-integration/add-block/validate-integration docs links to /integrations
* fix(uptimerobot): address review — heartbeat URL, file/JSON edge cases
- Block: URL is not required for HEARTBEAT monitors (no URL)
- buildMonitorBody: throw on malformed assignedAlertContacts/customHttpHeaders
JSON instead of silently dropping the field
- PSP route: error (400) when a supplied logo/icon cannot be resolved to a
stored file instead of silently omitting the image
- PSP route: guard success-path JSON parsing; return a controlled 502 on a
non-JSON provider response instead of an uncaught 500
* fix(uptimerobot): spec-conformance audit fixes
- pause/start monitor: send Content-Type: application/json (v3 spec requires it
on these POSTs even with an empty body)
- update maintenance window: drop autoAddMonitors (not in UpdateMaintenanceWindowDto);
gate the block field to create only
* fix(uptimerobot): rename monitor timeout param to avoid reserved name
The tool runner treats a top-level `timeout` param as the outbound HTTP-client
timeout (ms), so a monitor check-timeout of e.g. 30s would abort the API call in
30ms. Rename the input to `checkTimeout` (block subBlock, tool params, inputs,
numeric coercion) and map it to the API body's `timeout` key in buildMonitorBody.
* fix(uptimerobot): reject empty/non-object PSP responses
A successful PSP create/update must return the PspDto object; an empty or
non-object body now returns a controlled 502 instead of mapping a phantom
status page (id: 0, empty name, null images) back to the workflow.
* fix(uptimerobot): validate core PSP fields before mapping
Reject successful PSP responses that lack a positive numeric id and non-empty
friendlyName (a {} or metadata envelope) with a controlled 502, instead of
mapping a phantom status page.
|
||
|
|
35acc42d2b | feat(downdetector): add Downdetector outage-monitoring integration (#5228) | ||
|
|
c7eda5b217 |
feat(rich-editor): rich markdown field + @ mentions for skill & deploy modals (#5215)
* feat(rich-editor): rich markdown field + @ mentions for skill & deploy modals
- Add controlled, file-less RichMarkdownField (sibling of the file editor) used for
skill Content and deploy version descriptions; placeholder/typography match chip fields
- Add @-mention menu (TipTap suggestion) inserting portable [label](sim:kind/id) links;
wired into the field and the file viewer via a shared useEditorMentions hook
- Extract a shared suggestion-popup renderer + menu chrome (slash + mention)
- Fix false dirty-on-open: normalize the editor's dirty baseline to canonical markdown
- Always show the deployment version number (v3 · name) so named versions keep a short ref
- Skill import: drop the paste box (Create-tab editor auto-destructures a pasted SKILL.md),
reorder GitHub → Upload
* fix(rich-editor): address review feedback on modal field
- RichMarkdownField reports the original value when the doc matches its canonical
form, so a non-canonical input never reads as a false unsaved change (skill +
version description modals)
- Add sim: mention link navigation (Cmd/Ctrl-click) to the modal field
- versions: keep the v{n} fallback as the rename guard/seed so re-submitting the
displayed token is a no-op (no redundant "v3 · v3"); document the clear-name no-op
- Clarify the lazy query-gating comment in useMarkdownMentions
* fix(skills): re-seed Content editor when initialValues changes
Bump the field's remount key in the reset guard so the seed-once rich editor
re-seeds when content is reset from a changed initialValues (same skill id keeps
the React key otherwise stable), keeping the editor and saved value in sync.
* feat(rich-editor): render mentions as icon chips + menu/limit polish
- Render @ mentions as an inline chip node (entity icon + label) instead of a
blue link; still serializes to the portable [label](sim:kind/id) markdown so
it round-trips and stays agent-readable (shared mentionIcon resolver)
- Cap the mention/slash menu height + width and scroll it, matching the chat menu
- Give the version description editor more height; lift the 2000-char limit to a
high anti-abuse cap (client + contract) and drop the visible counter
* fix(rich-editor): make suggestion menus scrollable inside modals
- Mount the slash/@ menu popup inside the host dialog (when present) instead of
document.body: Radix's scroll-lock blocks wheel events outside the dialog
subtree, so a body-level popup couldn't scroll in a modal. position:fixed keeps
it viewport-positioned (the modal centers via flex, no transform) so it isn't clipped
- Fix the invalid max-w arbitrary value (calc needs spaces) that left the menu uncapped
- Match the version-description editor's dynamic-import loading height to the field
so the modal doesn't grow when the chunk loads
* fix(rich-editor): escape bracketed mention labels + disable images in field editors
- Escape/unescape `[`/`]` in mention labels so an entity named e.g. `data[1].csv`
round-trips into a chip instead of degrading to a plain link
- Hide the `/Image` command where image upload isn't wired (the skill + version
description field editors), so images can't be inserted there; the file viewer
keeps image support
* fix(rich-editor): keep suggestion keyboard nav working after async items load
The suggestion plugin captures the list's onKeyDown handle via ReactRenderer.ref
once at mount. The mention list's items arrive asynchronously from the workspace
store, so the captured handle closed over an empty `flat` and returned false for
arrow/enter — letting the editor move the caret instead of navigating the menu.
Read live values through a ref so the mount-time handle always sees current
items/activeIndex. Hardened the slash list the same way.
* test(rich-editor): cover suggestion keyboard nav through ReactRenderer; drop inline comments
Adds a test that drives the real ReactRenderer path the suggestion plugin uses:
the captured onKeyDown handle returns false while the store is empty and true
once async workspace items land, and arrow+enter select the right item. Removes
the explanatory inline comments from the two imperative handles.
* fix(rich-editor): suggestion menus keep arrow keys when a divider is adjacent
The leaf-selection keymap (ArrowUp/Down selects an adjacent divider/image) runs at
priority 1000, above the suggestion plugins, so it stole ArrowDown to select the
next horizontal rule instead of moving the open @/ menu selection. It now yields
while a mention or slash menu is active, detected via the plugins' exported keys.
* feat(rich-editor): Tab accepts a suggestion; unify list keyboard nav; match chip styling
- Extract useSuggestionKeyboard: one hook owns the @/ menus' active-row state,
scroll-into-view, and arrow/enter/tab handling (removes the duplication between
the two list components)
- Tab now accepts the active item like Enter, matching the chat composer
- Render the mention chip like the chat input's mention token: borderless inline
icon + label (no pill), 12px icon with brand color via getBareIconStyle, so the
styling is consistent across surfaces
* fix(rich-editor): harden editor edge cases found in full audit
- Skill paste: only auto-destructure on a real YAML name key, so a stray `---`
break or heading snippet no longer overwrites all three fields (parseSkillMarkdown
reports nameFromFrontmatter)
- Skill modal: reset by skill id, not object identity, so a background refetch of
the open skill can't clobber in-progress edits
- Field editor: claim Mod+K (inline link editor wins over global search) and
swallow file drops so the browser doesn't navigate away from the modal
- File editor: swallow non-image file drops (same navigation guard)
- Frontmatter: a leading `---` thematic break (e.g. a changelog) is no longer
mistaken for frontmatter and hidden from the editor
- Mention chip: renderText emits the portable link so copying a chip into a
plain-text target (e.g. chat) pastes back as a mention
- Suggestion nav: clamp a one-frame stale active index on Enter/Tab
* style(rich-editor): selected link reads as normal text, not standout blue
Follows the standard MD-editor convention (Linear, Slack): a highlighted link
takes the primary text color so the selection stays legible, instead of keeping
its blue on the selection highlight. Scoped to selected links only — no effect on
unselected links, regular text, the selection background, or any other surface.
* fix(rich-editor): guard async suggestion + generate lifecycles against teardown
- Suggestion onStart can fire after the editor is destroyed (the update awaits
items()), throwing on its now-gone view/storage — e.g. a modal closing while the
menu opens. Bail when the editor is destroyed; optional-chain mention storage.
This also removes the unhandled rejections the headless keymap test surfaced.
- Generate version description: thread an AbortSignal so closing the modal
mid-stream aborts the diff fetches + SSE read instead of streaming into a gone
component.
* refactor(rich-editor): fold inline comments into TSDoc; display-only chip; polish
- Convert the editor's inline `//` comments to TSDoc on the nearest declaration and
drop the self-explanatory ones (no logic change)
- Mention chip is now display-only (icon + label), matching the chat input exactly:
removes select-none (so a range selection highlights the label), the cursor-pointer
over-promise, and the cmd-click nav that could route away from a modal mid-edit
- Don't log a deliberate generate-abort as an error
- Selected strike-through text reads in the primary color so the selection is uniform
* fix(rich-editor): clean selection for the mention chip
The chip is an inline atom, so a range selection now highlights it as a whole unit
(the prior select-none left it an un-highlighted gap). A direct click selects it
with a subtle fill instead of the block-leaf outline ring meant for dividers/images.
* feat(rich-editor): Cmd/Ctrl-click a mention to its resource in the file viewer
Threads a `navigable` flag through the mention storage: the file viewer opts in so
a chip routes to its file/table/workflow/etc., while modal fields stay inert so a
click can't navigate away from an unsaved edit. Styling is identical either way.
* fix(rich-editor): icon fallback for removed integrations; smoother divider nav
- A mention to a since-removed integration falls back to a generic icon so the chip
is never icon-less (indistinguishable from prose)
- Arrowing from a selected divider/image to an adjacent one selects it directly
instead of stopping on the gap cursor between them, so stepping through a run of
dividers is one press each
* fix(rich-editor): integration mentions are display-only; robust chip selection
- An integration mention's id is a block type (gmail_v2), not a routable resource —
/integrations/[block] expects a slug and a type maps to zero-or-many credentials —
so it no longer links to a 404. The chip only shows a pointer/navigates on kinds
that resolve to a real page.
- Scope the block-leaf selection ring off the mention chip robustly (covers a
node-view wrapper via :has), so a selected chip shows a subtle fill, not the outline.
* feat(icypeas): update brand icon and bgColor; regenerate integration docs
* fix(rich-editor): remove the misfiring chip selection fill (full-width gray band)
The :has fill could paint a full-width band; drop it. A selected chip just skips the
block-leaf outline ring and uses the same native text-selection highlight as the prose.
* feat(rich-editor): show an "Uploading…" toast while an image uploads
A persistent progress toast appears per image during upload and is dismissed once
it settles, when the upload hook's "Uploaded"/"Failed" toast takes over — previously
nothing showed until the upload finished.
* refactor(rich-editor): drop inline comments (TSDoc on the declarations instead)
Fold the image-upload toast note into the insert function's TSDoc and remove the
remaining inline // comments.
* refactor(rich-editor): cleanup + simplify pass over the markdown editor
- Delete dead parseSimHref (+ barrel/test); mentions parse via the node tokenizer
- Extract serializeMarkdownDocument — one canonical serialize pipeline shared by the
dirty-check baseline and the round-trip-safety probe (was inlined in both)
- Extract selectLeafAcross — shared tail of the two arrow leaf-selection handlers
- Reset the suggestion active-row during render (prevX idiom) instead of an effect
- Inline the skill modal's trivial hasChanges (drop the useMemo)
- image.tsx: cn() over a template-literal className
* refactor(rich-editor): share the suggestion-list shell and link-URL editor
- Extract SuggestionList: the grouped-list surface, empty state, listbox/option a11y
structure, and active-row/hover/select wiring shared by the @ and / menus. Each menu
keeps only its own grouping + itemKey/renderItem.
- Extract link-editing (LinkUrlInput + applyLink): the inline link field and the
normalize→extendMarkRange→set/unset commit logic, shared by the bubble menu and the
link hover card.
* improvement(settings): align access-control detail UI + nav-driven docs link
- Move permission-group Save/Discard into the detail header (matching
secrets/whitelabeling) and delete the one-off sticky 'Unsaved changes' bar
- Convert the Platform and Blocks config tabs to SettingsSection (drop the
custom multi-column masonry + hand-rolled section labels); add an optional
far-right action slot to SettingsSection for the per-section Select All
- Replace the file-share auth-mode checkboxes with a multi-select ChipDropdown
- Normalize per-tab spacing to gap-7, align the expand-chevron token to
--text-icon, and match the list-row arrow size to the integrations precedent
- Add a nav docsLink surfaced as a header 'Docs' ChipLink by SettingsPanel,
wired for the six enterprise settings pages
* feat(rich-editor): copy-link button shows a checkmark on copy
Use the shared useCopyToClipboard hook so the link hover card's Copy button swaps to a
Check for ~2s after copying, matching the rest of the platform.
* fix(rich-editor): RichMarkdownField falls back to raw text for lossy markdown
Mirror the file editor's safety gate: decide once from the initial value via
isRoundTripSafe — round-trip-safe content opens in the WYSIWYG editor, while lossy
markdown (raw HTML, footnotes, comments) edits as raw text, so an edit can't silently
drop those constructs.
* feat(rich-editor): divider/leaf editing — backspace, select-all, gap cursor
- Backspace at the start of an empty block whose previous sibling is a divider/image removes the
blank line (instead of deleting the leaf) and selects the divider above; a non-empty block selects
the leaf so a second Backspace deletes it (highlight-before-delete).
- Select-all (and any range selection) now visibly highlights dividers/images, which the native text
highlight skips because leaves carry no text — via a decoration that paints a selection band.
- The gap cursor between two adjacent leaves no longer draws its stray caret (matching Linear); the
position stays functional. Leading/trailing gap cursors keep their caret.
- Unit tests for the backspace + select-all behavior.
* refactor(rich-editor): decouple headless bundle, fix linked-image round-trip, a11y
- Split mention-node into the schema-only `MarkdownMention` (mention-node.ts, no React/registry)
and the live `MentionChip` node view (mention-chip.tsx); move the live factory to
editor-extensions.ts and inject node views via DI. The headless round-trip path
(markdown-parse/normalize-content/round-trip-safety) no longer pulls the 269-block registry —
it now bundles for the browser with zero node-builtin deps.
- A sized + linked image serializes as `[](href)` (dropping the unrepresentable size)
instead of `[<img>](href)`, which the tokenizer can't reparse — the link is preserved, no silent
data loss. Also escape the href title symmetrically.
- Wire the suggestion menus as an ARIA combobox: while open, the editor gets
aria-haspopup/expanded/controls and an aria-activedescendant tracking the active option, so screen
readers announce it; cleared on close. Empty state is a role=status live region.
* fix(rich-editor): exempt an active @-mention query from the per-group cap
The per-group MAX_PER_GROUP limit is meant to keep the unfiltered menu from flooding; applying it
while a query is active hid matches past the eighth in a category, so search couldn't reach them.
Cap only when there's no query. Adds a regression test (12 matches shown when searching).
* fix(rich-editor): mention icon fallback + typed sim-link input rule
- mentionIcon never returns undefined: an empty/unrecognized kind (schema default '', or a future
kind on a sim: link) falls back to a generic icon instead of crashing the chip's render. Adds tests.
- Add a mention input rule so typing `[label](sim:kind/id)` becomes a chip on the closing paren —
matching the paste/load path (the tokenizer), which previously left typed syntax as literal text.
A plain InputRule (full-range replace) is used; nodeInputRule would keep the surrounding brackets.
* fix(rich-editor): raw-fallback paste hook + bound the filtered mention list
- RawMarkdownField now honors onPasteText (e.g. skill SKILL.md destructuring), so a full-document
paste is intercepted in the raw fallback too, not only the WYSIWYG path.
- Bound the @-mention list while filtering (MAX_WHEN_FILTERED) so lifting the per-group cap for search
can't render thousands of rows in the non-virtualized menu on a broad query; search still reaches
deep matches well before the bound. Adds a test.
- Tighten an extensions.ts doc comment (the headless path omits the registry + node-view construction,
not React itself).
|
||
|
|
954de0559b |
improvement(docs): align components with the platform design system (#5227)
* improvement(docs): align components with the platform design system Bring the docs app's chrome in line with the main Sim design system, validated against the canonical emcn source in apps/sim. - ask-ai: fix undefined tokens (--text-base x6, --text-link) that broke the send-button fill and link/text colors; send button now matches the canonical primary fill (text-primary/text-inverse, dark:bg-white); use --shadow-medium and chip gap rhythm - not-found: replace the hand-rolled brand pill with <ChipLink variant='brand'> and swap fumadocs tokens for platform tokens - search-trigger: compose the exported chip chrome constants instead of re-spelling them (single source of truth) - what-you-will-learn, video-chapters: fumadocs fd-* tokens -> platform tokens - workflow-preview: add --wp-highlight token; route the #33b4ff highlight, #ef4444 error dots, and toggle/slider green through tokens - video-placeholder: tokenize the status pill (bespoke illustration art intentionally left as-is) dropdown-menu, faq, theme-toggle, and page-type-badge were deliberately left at their canonical values (14px row icons, rounded-md badge) after validation showed those match the platform, not the chip-pill, standard. * improvement(docs): neutral primary chip for nav CTA + fix cluster spacing Align the docs navbar with the main app, which reserves green for accents/status and uses a neutral high-contrast CTA in nav. - add a canonical `primary` chip variant (inverse fill: dark in light mode, white in dark mode), mirroring the emcn chip's primary action - "Get started" and the 404 "Go home" now use variant='primary' instead of the green brand surface - retire the now-unused `brand` chip variant (no parallel path left behind) - fix navbar right-cluster spacing: gap-2 to match the landing navbar and drop the asymmetric ml-1 on the CTA |
||
|
|
a33c173146 |
fix(copilot): strip hosted apiKey on type-less edit ops + guard hosting.enabled (#5220)
* fix(copilot): strip hosted apiKey on type-less edit ops + guard hosting.enabled The hosted-apiKey strip in preValidateCredentialInputs was gated on op.params.type, but edit ops omit type (they carry only changed inputs). An apiKey-only edit on a hosted-tool block therefore skipped both the model and tool strip paths, so a copilot-authored key persisted and disabled hosted-key injection. Resolve the block type from workflowState when the op omits it, so type-less edits run through the strip. Also guard tool.hosting.enabled() in try/catch like the tool selector. Add a regression test mirroring the observed failure (edit op with only apiKey, type+provider resolved from workflow state). * improvement(copilot): consolidate hosted-apiKey workflowState reads, gate off-hosted Quality cleanup (no behavior change): the main loop read workflowState.blocks[id] three times (block type, model fallback, toolParams merge). Collapse to one existingBlock lookup + one buildSubBlockValues; derive modelValue from the merged toolParams (drops the model-fallback ladder); gate the reconstruction on isHosted so buildSubBlockValues + spreads no longer run off-hosted or for blocks the collectors skip. Add an asRecord helper to cut repeated casts. * fix(copilot): resolve hosted-key strip against same-batch state; strip on enabled throw Addresses review on #5220: - Batch-aware block state: a later type-less edit now sees type/provider changed by an earlier op in the same edit_workflow request (was reading the stale initial snapshot), so a key can't survive on a block an earlier op just made hosted. - hosting.enabled() throwing now fails toward treating the key as managed (strip) instead of preserving it, since the hosting state is unknown on throw. * fix(copilot): unify hosted-key strip resolution across top-level and nested blocks Greptile flagged that the batch/snapshot state reconstruction was applied only to top-level blocks; nested loop/parallel children still used raw childInputs, so a same-batch provider/model change on a nested child followed by a type-less apiKey edit could leave the key. Route both paths through one collectForBlock helper keyed by the block's own id (incl. nested children), so they share batch accumulation + snapshot enrichment and can't drift. Test added for the nested same-batch case. * fix(copilot): recurse nestedNodes so grandchild hosted keys are stripped The collection loop and the strip/credential-removal loops only handled the first nestedNodes level, but the apply path processes nestedNodes recursively (loop/parallel children can themselves contain nestedNodes). A hosted key on a grandchild (e.g. loop-in-loop) survived. Collection now walks the nestedNodes tree recursively; the strip/removal loops locate a descendant's inputs at any depth via findNestedInputs. Test added for a two-level-deep grandchild. * fix(copilot): decide hosted-key strip against final batch state (two-pass) Forward-only accumulation missed reverse order (apiKey set in an early op, block made hosted by a later op) and let an earlier bogus/empty type poison snapshot fallback. Replace it with a two-pass approach: pass 1 folds every op (and nested descendant) into each block's FINAL effective type+values for the batch; pass 2 strips the managed fields each op sets, judged against that final state. Order-independent, any nesting depth, and empty/invalid types no longer block fallback (validType guard). Tests added for reverse order and bogus-type cases. * fix(copilot): tool selector throw falls back to access tools (fail toward strip) Symmetric with the enabled-throw fix: when tools.config.tool throws on partial params, scan all access tools instead of returning, so a hosted key can't slip through. Test added. * fix(copilot): only fold registry-known types into final batch state Pass 1 recorded any non-empty type into finalType, but apply skips type changes to unknown types (keeps the existing block). An unknown type on an earlier op could poison a later type-less apiKey edit. Only advance finalType to a getBlock-resolvable type so the fallback matches what apply persists. Test covers empty and unknown types. |
||
|
|
2fa3dd65bc |
fix(db): retry the migration connection on transient slot exhaustion (#5226)
* ci(migrations): skip db:migrate on merges that change no migration files
Every push to main/staging ran db:migrate against the production/staging
database even when the merge changed no schema, so a no-op migration would dial
the DB and fail whenever it was at its connection limit (53300, slots reserved
for SUPERUSER) — red-X'ing UI-only merges.
Add a detect-migrations job (dorny/paths-filter on packages/db/migrations/**)
and pass the result into the reusable migrations workflow, which now skips the
apply step when no migration files changed. The migrate job still runs so
downstream build/deploy jobs that need it are never skipped, and the flag
defaults to 'true' so manual dispatch and any unknown value always apply
migrations — the gate only ever skips a provably-empty change.
* fix(db): retry the migration connection on transient slot exhaustion
The migration opens its session on the first query (the advisory-lock
acquire). When the deploy database briefly exhausts every non-superuser
connection slot at peak, that connect fails with 53300 ("remaining connection
slots are reserved for roles with the SUPERUSER attribute") and the whole
deploy's migrate step errors out — even when the spike clears within seconds.
Add a bounded connectWithRetry() before acquiring the lock that retries 53300,
the 08xxx connection_exception class, and the driver's transport errors with
backoff (10 attempts, ~90s ceiling). Non-transient errors (auth, bad config)
still fail fast. The migration is a single short-lived session, so waiting out
a transient spike is far safer than failing the deploy.
* ci: drop the migration paths-filter gate (out of scope)
Revert the detect-migrations gate carried over from the closed CI PR; we are
fixing the connection failure at its source (migrate.ts connection retry)
rather than gating db:migrate, which the reviewers correctly noted could leave
a previously-merged migration unapplied after a failed deploy.
|
||
|
|
02d254e267 |
refactor(emcn): consolidate date pickers onto the chip Calendar (range support + retire legacy DatePicker) (#5222)
* improvement(knowledge): use canonical ChipDatePicker in document tags modal The document tags modal used the legacy DatePicker primitive for date-typed tag values while the rest of the knowledge base date inputs use the canonical ChipDatePicker. Swap both usages (edit + create) to ChipDatePicker (same YYYY-MM-DD value contract, full-width to match sibling fields) so the chrome matches the chip design system, and align a tag-row value label to the caption text size used by its sibling. * feat(emcn): add range mode to ChipDatePicker via the canonical Calendar ChipDatePicker was single-date only, so date-range surfaces had to fall back to the legacy DatePicker primitive. Add range support as a discriminated mode on the existing canonical Calendar (not a parallel component): a start/end selection staged behind Clear/Cancel/Apply with optional time-of-day inputs, built from the chip family (CalendarDayCell, chipVariants, ChipTimePicker) and fully tokenized. ChipDatePicker gains a matching mode='range' that renders it behind the same chip trigger. Extract the range-bounds serialization into a pure, unit-tested helper, and remove the dead logs-toolbar directory (LogsToolbar/AutocompleteSearch had no consumers; the logs page renders its toolbar inline). * refactor(emcn): retire legacy DatePicker; migrate all consumers to the chip calendar Removes the parallel DatePicker component (its own dual-month CalendarMonth, hardcoded colors, non-chip chrome) so the chip Calendar is the single date surface. Migrates every consumer: - tables row modal -> ChipDatePicker (single) - tables inline grid editor, logs filter, ee audit-logs filter -> canonical Calendar inside an emcn Popover (the same headless/anchored pattern DatePicker rendered internally; single for the grid cell, range+time for the log filters) - playground -> ChipDatePicker showcase (single, range, range+time) The range bounds keep the exact YYYY-MM-DD / YYYY-MM-DDTHH:mm wire format, so the log filters' parsing is unchanged. Calendar is imported via its component path (the emcn barrel already exports a Calendar icon). * fix(emcn): resync range calendar draft state when bound props change Cursor Bugbot: RangeCalendarView seeded its staged selection from props only once. Closing the popover unmounts it (so a fresh open re-seeds), but if the bound startDate/endDate change while it stays mounted the grid could linger on a stale draft. Add a render-phase reset keyed on the bound props. * refactor(emcn): reset range calendar via key instead of render-phase resync Replaces the imperative render-phase state reset with React's idiomatic key-based reset: the range view is keyed on its committed bounds in the Calendar dispatcher, so a newly applied range remounts it with fresh draft state. Cleaner and fully encapsulated — consumers need no special handling. Move the rationale into TSDoc (no inline comment). * fix(logs): keep the date-range popover anchor from blocking the time-range select The custom-range Calendar is anchored with an always-rendered PopoverAnchor overlaying the time-range combobox (absolute inset-0). Without pointer-events-none it would intercept clicks meant for the select, leaving it unclickable. The anchor is only a positioning reference, so disable its pointer events on both the logs and audit-logs filters. |
||
|
|
365d8be02b |
fix(knowledge): document tag filter matches case-insensitively and by calendar day (#5221)
The Knowledge Base document list applied tag filters with case-sensitive text equality and compared date tags against a midnight-UTC timestamp, so text filters missed on any casing difference and date eq never matched a stored timestamp — both silently returned empty results. Align the document-list filter with the knowledge search filter semantics: text eq/neq are now case-insensitive (LOWER) and date comparisons run on the calendar day (::date). Extract the predicate builder into its own single-responsibility module with unit coverage, and tidy the filter popover's secondary labels to the caption text size for chip-design consistency. |
||
|
|
a1d5870681 |
feat(settings): unify all settings pages under a shared SettingsPanel layout (#5219)
* feat(settings): add SettingsPanel layout with nav-driven page titles Introduce a SettingsPanel scaffold that owns the standard settings chrome (fixed header bar with right-aligned actions, scroll region, centered content column) and renders a consistent page title + description pulled from the active section's navigation metadata. Adds a description to every nav item and a SettingsSectionProvider in the shell so the title is required-by-default without per-page wiring. Migrate the account/subscription pages (general, secrets, teammates, team-management, billing) to SettingsPanel, removing their hand-rolled shells and title blocks. * feat(settings): migrate all settings pages to SettingsPanel + bake in search Extend SettingsPanel with a `search` prop (canonical search field with optional anti-autofill hardening) so the repeated per-page search input is owned by the layout. Migrate every settings page — account, subscription, tools, system, enterprise, and superuser — onto SettingsPanel so each renders a consistent nav-driven title + description, header actions, and search with zero hand-rolled shell. Drill-down detail sub-views (MCP server, workflow MCP server, credential set, permission group) keep their own back-button chrome. Normalize all nav descriptions to a consistent voice and length. * refactor(settings): drop unnecessary search anti-autofill; document SettingsPanel Remove the preventAutofill prop from SettingsPanel — the shared search field no longer needs the read-only-until-focus hack, so secrets uses the plain search like every other page. Pre-existing honeypot inputs are untouched. Add .claude/rules/sim-settings-pages.md (auto-scoped to settings pages) and a settings-page skill documenting the SettingsPanel convention + add/audit procedure. * refactor(settings): extract SettingsEmptyState + RowActionsMenu, dedupe usages Encapsulate two more repeated settings patterns into shared components: - SettingsEmptyState — the muted empty/no-results/gate message (fill | inline), replacing ~42 hand-rolled status divs and normalizing stragglers that used text-small / text-tertiary back to the canonical text-muted + text-sm. - RowActionsMenu — the trailing '...' row-actions dropdown, replacing ~11 per-page DropdownMenu+MoreHorizontal blocks with a props-driven actions list. Pure presentational refactor: every message, action, handler, disabled, and destructive flag preserved (verified by diff review). Document both in .claude/rules/sim-settings-pages.md. * fix(settings): set autoComplete=off on the shared settings search field Keeps browsers from offering saved-credential autofill in a filter box — the lightweight standard guard, distinct from the removed read-only/preventAutofill machinery. Matters most on the secrets page. * chore(settings): strip non-TSDoc inline comments from touched files Remove JSX label comments, section/explanatory // comments, separator block comments, and copilot's commented-out MCP scaffold across the migrated files, per the repo's no-non-TSDoc-comments standard. TSDoc and functional directives kept. Comment-only deletions — no code or behavior changed. * chore(skills): rename settings-page skill to add-settings-page Aligns with the verb-prefixed naming of the other skills (add-integration, etc.). * feat(icons): Thrive icon-only black mark on white Drop the wordmark from ThriveIcon (tighten viewBox to the logo bounds), set the mark to pure black; the block bgColor is already white. Synced the docs icon copy. |
||
|
|
ca4e07baf3 |
chore(deps): bump undici to 7.28.0 and nodemailer to 9.0.1 (#5218)
* chore(deps): bump undici to 7.28.0 and nodemailer to 9.0.1 * chore(deps): dedupe cheerio and e2b transitive undici to 7.28.0 |
||
|
|
7ba0e238cb |
feat(access-control): page-based permission groups, tool-level deny-list, settings row-action consistency (#5216)
* feat(access-control): page-based permission groups, tool-level deny-list, settings row-action consistency - Replace the cramped configure modal with a full-surface tabbed Access Control page (General/Model Providers/Blocks/Platform) with a sticky save bar - Add deniedTools denylist to permission groups: deny individual tools within an allowed integration; enforced at the universal executeTool chokepoint via ToolNotAllowedError, and hidden from the operation dropdown for governed users - Add per-section Select/Deselect All on the Blocks tab and expandable per-tool deny rows (mirrors Providers->Models) - Standardize every settings list row on the canonical "..." DropdownMenu (custom-tools, mcp, workflow-mcp-servers, api-keys, secrets, credential-sets) and align badges (ChipTag), avatars (MemberAvatar), inputs (ChipInput), and the mothership env picker (ChipSelect) * fix(access-control): don't leave the detail view when an unsaved-changes save fails The unsaved-changes dialog's Save action navigated back unconditionally after handleSaveConfig, but that helper swallows mutation errors — so a failed save still exited the view and silently dropped the edits. handleSaveConfig now returns success, and the dialog only closes + navigates back when the save actually succeeded. * fix(access-control): prune deniedTools for blocks that get disabled deniedTools only matters while a block is allowed, but toggleIntegration/setBlocksAllowed left a disabled block's denied tools in the config. Disabling then re-enabling an integration would silently re-apply the old per-tool denials. Both handlers now prune deniedTools to the set of allowed blocks, keeping the invariant that deniedTools only holds tools of currently-allowed integrations. * fix(access-control): attribute denied tools to all exposing blocks when pruning A tool id can appear in more than one block's tools.access. The single tool->block map meant pruneDeniedTools (and the per-block denied count) attributed a shared tool to only one block, so disabling that block could drop a denial while the tool was still exposed by another allowed block. Tools now map to all exposing block types; a denial is pruned only when no allowed block exposes the tool, and the per-block count is derived from each block's own tool list. * fix(access-control): scope Platform Select/Deselect All to the search filter The Platform tab's bulk Select/Deselect All toggled every feature regardless of the active search, unlike the Blocks tab which scopes its per-section toggle to the filtered view. Both the all-visible check and the bulk update now operate on filteredPlatformFeatures for consistent behavior while searching. * fix(access-control): scope Model Providers Select/Deselect All to the search filter Like the Platform fix, the Providers tab's bulk action toggled every provider via allProviderIds regardless of the active search. Added setProvidersAllowed (mirroring setBlocksAllowed) so the bulk toggle and its label operate on filteredProviders, keeping all three tabs (Blocks/Platform/Providers) consistent while searching. * fix(access-control): don't seed a denied operation as a block's default The operation dropdown hides denied tools from the picker, but defaultOptionValue returned the block's defaultValue without checking deniedOperationIds, so a new block could start on an operation the user isn't allowed to run. It now falls back to the first allowed option when the configured default is denied. Existing stored operation values are intentionally left untouched (auto-rewriting a user's saved block would be destructive; the server remains the authoritative gate). * chore(access-control): prefer TSDoc over inline comments Convert declaration-level rationale comments to TSDoc (/** */) and trim redundant/verbose inline comments added during review, per the project's TSDoc convention. |
||
|
|
6355c8e699 |
improvement(docs): Ask AI chat grounded in the docs vector store (#5172)
* docs: Ask AI chat grounded in the docs vector store Adds an Ask AI chat to the docs site. A floating launcher opens a chat panel backed by the Vercel AI SDK (OpenAI provider, OPENAI_API_KEY from the environment). A searchDocs tool runs locale-scoped vector/keyword search over the existing docs embeddings so answers cite real pages. The public endpoint is hardened: per-request size/token/step caps, message sanitization (no client-injected tool results or system prompts), origin checks, and a per-IP rate limit. Non-English retrieval uses keyword search; English vector search applies a similarity threshold. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: harden Ask AI retrieval + fix stale loading state - searchDocs: wrap the keyword query in try/catch too, so each retrieval path (keyword, vector) is independent best-effort - ask-ai: gate the loading ellipsis to the in-progress (last) message so older empty bubbles don't re-show it while a later request streams Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5db5963fc1 |
fix(copilot): strip platform-managed apiKey on hosted-tool blocks in edit_workflow (#5217)
preValidateCredentialInputs only stripped apiKey for hosted LLM models (getHostedModels). Agent-authored apiKey on hosted-tool blocks (Fal video/image, etc.) slipped through, disabling hosted-key injection and misleading the agent into telling users to bring a key. Generalize the strip to key off tool.hosting — the same canonical signal injectHostedKeyIfNeeded uses at execution. Resolve the block's active tool via its tools.config.tool selector, honor the per-provider enabled gate, and strip the exact hosting.apiKeyParam field (no hardcoded 'apiKey' assumption). Surfaces the existing non-fatal note to the agent. Self-hosted and non-hosted providers untouched. |
||
|
|
d20deedf99 |
improvement(docs): add Academy learning surface (#5213)
Adds the Academy section to the docs: video-first lessons (self-hosted MP4 on Vercel Blob), organized into Workflows, Agents, Tables, Files, and Knowledge Bases, each linking back to the reference docs. Lessons use a course layout (hero video with chapter seek, "what you'll learn", block diagrams). Docs only — no runtime or auth changes. The content may move to a separate CMS or its own site (academy.sim.ai) later; the docs are a starting point. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
371cc94851 |
feat(thrive): add Thrive Learning integration (47 tools + block) (#5214)
* feat(thrive): add Thrive Learning integration (47 tools + block) Add a full Thrive Learning (LMS) integration covering the public REST API: users lifecycle, audiences with members/managers, assignments and enrolments, completions, content and activity records, CPD, tags, and skills. Uses HTTP Basic auth (Tenant ID + API key) with a region selector for the v1/v2 hosts. * fix(thrive): surface malformed JSON errors, drop redundant limit param - parseThriveArray/parseThriveJsonObject now throw a descriptive error on malformed JSON instead of silently sending an empty/omitted value - additionalFields parse errors are surfaced to the caller - remove the redundant 'limit' query param (perPage already covers paging and the API prioritises perPage over limit) from the five list tools and block * fix(thrive): use a single status dropdown instead of reused canonicalParamId The block test forbids reusing a canonicalParamId across different operation conditions. Replace the two canonical status subblocks (search_users vs list_enrolments) with one 'status' dropdown whose options are labelled by context, fixing the canonical-pair validation failures. * fix(thrive): split status into context-specific user/enrolment dropdowns Addresses review feedback that one shared status dropdown mixed user lifecycle values (active/inactive/expired/new) with enrolment values (archived/complete/open/...). Use separate userStatus and enrolmentStatus dropdowns (no canonicalParamId) remapped to the tool's 'status' param so each operation only offers valid options. |
||
|
|
7640af0f71 |
improvement(mothership): add workflow lint for custom tool/skills/mcp tool additions to agent block (#5199)
* improvement(workflow-linter): added custom tool validation to workflow linter * fix(comments): address pr comments * improvement(validation): ensure type is known |