mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
cf84e051cd4143aec11cf6fa3f44bf7b93e7b254
5024
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cf84e051cd | improvement(path): append, patch snapshot based streaming (#5161) | ||
|
|
cb17207447 |
fix(rich-md-editor): stop the editor flashing during an agent rewrite (#5160)
* fix(rich-md-editor): stop the editor flashing during an agent rewrite Only reveal streamed chunks that extend what's already shown. A divergent chunk (an agent rewrite/edit, e.g. removing appended text) would collapse the document to the partial result and flash on every chunk; now the current content is held in place and the final result is applied once on settle. Fresh writes still reveal live. * fix(rich-markdown-editor): seed shown-body on settled mount and track local edits Seed lastSyncedBodyRef from a settled (non-streaming) mount and update it on local edits via onUpdate, so the streaming hold engages on the very first agent rewrite chunk (no collapse/flash) and the settle still applies the rewrite that removes a local edit. |
||
|
|
39cfae9016 | improvement(scheduled-tasks): render prompt chips in task details and align weekday picker (#5159) | ||
|
|
2bbf70ec20 |
improvement(rich-md-editor): stabilize bubble-menu plugin key + comment cleanup (#5158)
* improvement(rich-md-editor): stabilize the bubble-menu plugin key and move inline rationale into TSDoc * docs(rich-md-editor): preserve bubble reveal + blur rationale via a helper + TSDoc |
||
|
|
4bf791716e |
improvement(rich-md-editor): streaming, performance, minor bugfixes (#5148)
* fix(files): isAgentEditing flag passthrough * use smooth streaming hook * improve performance * remove comments * improvement(rich-md-editor): reveal bubble after drag-select, keep it on-screen for tall selections, restyle task-list checkbox * improvement(share-modal): use Send icon in the share file header * improvement(rich-md-editor): pin the formatting toolbar so it stays put while scrolling * improvement(rich-md-editor): show the formatting toolbar in the mothership file view * fix(sidebar): drive collapsed width from server-rendered attribute A collapsed rail painted at the expanded width then animated to 51px on refresh: structure came from the cookie (server) while width came from independent cookie reads (blocking script + store), so any disagreement left the collapsed structure at the persisted expanded width until the store corrected it. Unify collapse into one derivation in WorkspaceChrome and drive the collapsed width from a server-rendered data-collapsed attribute via CSS (.sidebar-shell-outer[data-collapsed]) — the same cookie source as the structure, so width can never diverge from it. This is shadcn's documented pattern (data-attribute selectors over JS ternaries for collapsed dimensions). Also removes the redundant migratedCollapsed reconciliation (the store already seeds from the migrated cookie and hasHydrated flips in the same pre-paint effect) and the now-unused per-Sidebar derivation; Sidebar takes isCollapsed as a prop. * feat(rich-md-editor): let focused editors claim shortcuts from the global command registry * refactor(rich-md-editor): freeze the formatting toolbar on scroll and extract the shared toolbar button * feat(rich-md-editor): add a link hover card and claim Cmd+K for the link shortcut * fix(rich-md-editor): portal the toolbar + link card to body so a transformed ancestor can't offset them; align fade with the tooltip * fix(rich-md-editor): hide the code line-wrap toggle in read-only * fix(sidebar): pass isCollapsed to Sidebar in the error fallback The error UI renders Sidebar outside WorkspaceChrome, so it has no derived collapse state; feed it the same source of truth via readCollapsedCookie() now that isCollapsed is a required prop. * address greptile comment * docs(rich-md-editor): note why table cells escape only pipes (renderChildren pre-escapes backslashes) * fix(rich-md-editor): lock the editor immediately when an agent edit starts, even if the body is unchanged --------- Co-authored-by: waleed <walif6@gmail.com> |
||
|
|
55f432637f |
improvement(auth): make Microsoft emailVerified derivation total (#5157)
* improvement(auth): make Microsoft emailVerified derivation total deriveMicrosoftEmailVerified cast the verified-email claims to string[] and called .includes through optional chaining, which only guards null/undefined. A claim arriving as a non-array, non-string value (e.g. a number) would throw inside getUserInfo and fail the OAuth flow. Array-check the claims with a proper type guard so any claim shape resolves to unverified instead of throwing. * test(auth): lock in unverified for a string verified-email claim Add a boundary case asserting a string verified_primary_email/ verified_secondary_email equal to the email resolves to unverified — the old string[] cast would have returned true via String.includes. |
||
|
|
d643be0b93 |
feat(triggers): add GitLab, PagerDuty, and Zendesk webhook triggers (#5150)
* feat(triggers): add GitLab, PagerDuty, and Zendesk webhook triggers Add webhook trigger support for three integrations that previously had blocks but no triggers: - GitLab: push, merge request, issue, pipeline, comment, and all-events. Verifies the X-Gitlab-Token secret token; filters by object_kind. - PagerDuty: incident triggered/acknowledged/resolved/escalated/reassigned and all-events. Verifies X-PagerDuty-Signature (HMAC-SHA256 over raw body, comma-separated rotation); idempotency on event id. - Zendesk: ticket created/status changed/comment added/priority changed and all-events. Verifies X-Zendesk-Webhook-Signature (base64 HMAC-SHA256 over timestamp+body); idempotency on event id. Register GitLab's X-Gitlab-Event-UUID delivery header for webhook idempotency dedup. * fix(triggers): scope webhook secrets to owner and add Zendesk replay protection Address review feedback: - Add paramVisibility: 'user-only' to the webhookSecret fields for GitLab, PagerDuty, and Zendesk so signing secrets are scoped to the credential owner and not exposed to workspace collaborators (repo convention). - Reject Zendesk deliveries whose signed timestamp is more than 5 minutes from now, closing a replay window once an event id ages out of the idempotency cache. The X-Zendesk-Webhook-Signature-Timestamp header is ISO-8601, so it is parsed with Date.parse (matches the Slack handler's skew-check convention). * feat(triggers): auto-register GitLab, PagerDuty, and Zendesk webhooks Replace the manual-registration model with automatic webhook creation on deploy and cleanup on undeploy, via createSubscription/deleteSubscription on each provider handler: - GitLab: POST /projects/:id/hooks with a Personal Access Token; generates the secret token (stored for X-Gitlab-Token verification) and enables only the event flags for the selected trigger. Deletes the hook on undeploy. - PagerDuty: POST /webhook_subscriptions (account-scoped) with a REST API key; captures delivery_method.secret (returned only on create) for X-PagerDuty-Signature verification. Deletes the subscription on undeploy. - Zendesk: POST /api/v2/webhooks with native event subscriptions, then GET /webhooks/:id/signing_secret for X-Zendesk-Webhook-Signature verification. Deletes the webhook on undeploy. Trigger config now collects the provider credentials (user-only) instead of a pasted signing secret; the signing secret is generated or fetched and stored in providerConfig by the orchestration layer (no route/deploy changes). * fix(triggers): fail closed on missing webhook secret and clean up Zendesk orphans Address review feedback on the auto-registration changes: - verifyAuth now rejects (401) when webhookSecret is absent for GitLab, PagerDuty, and Zendesk. Since the secret is generated/fetched during auto-registration and stored before the webhook can receive deliveries, a missing secret indicates misconfiguration and must fail closed rather than skip signature verification. Adds an opt-in requireSecret flag to createHmacVerifier (default off, preserving behavior for other providers). - Zendesk createSubscription now deletes the just-created webhook if the follow-up signing-secret fetch fails, avoiding an orphaned subscription in Zendesk when setup cannot complete. * fix(triggers): clean up GitLab and PagerDuty webhooks on failed setup Extend the orphan-prevention fix to the remaining providers. When a create call succeeds but post-create validation fails, the created webhook is now deleted before throwing: - GitLab: if the create response can't be parsed for its hook id, the hook is located by its URL and deleted. - PagerDuty: if the subscription response lacks an id or signing secret, the subscription is deleted (by id when known, otherwise located by URL). Both cleanups are best-effort and never throw. * docs(triggers): note GitLab tag_push only flows through the all-events trigger |
||
|
|
aa57f10b44 |
fix(auth): close nOAuth account takeover via email-based OAuth linking (#5156)
* fix(auth): close nOAuth account takeover via email-based OAuth linking Restrict the unauthenticated sign-in endpoints to first-party login providers, trim trustedProviders to providers that verify email ownership, and stop hardcoding emailVerified for multi-tenant Microsoft and Salesforce connectors. * test(auth): cover Microsoft id-token emailVerified derivation Extract the Microsoft ID-token email-verification logic into a pure deriveMicrosoftEmailVerified helper and add unit coverage for explicit, verified-claim, partial, absent, and malformed Azure AD claim combinations. * fix(auth): check the provider field the sign-in handler actually uses The allowlist guard resolved the provider with `provider ?? providerId`, but Better Auth reads `provider` on /sign-in/social and `providerId` on /sign-in/oauth2. A request to /sign-in/oauth2 with an allowed `provider` and a blocked `providerId` could pass the guard while the handler started OAuth for the blocked connector. Resolve the field per path via getRequestedSignInProviderId so the guard checks the same field the handler acts on. |
||
|
|
82cb324638 |
improvement(access-controls): default workspace experience includes all members (#5153)
* improvement(access-controls): default workspace experience includes all members * update ui * address comments * improve copy * address zero-member edge case |
||
|
|
2f7d60745a |
fix(uploads): close multipart storage-quota bypass via quota-exempt contexts (#5155)
The multipart endpoint accepted the quota-exempt public-asset contexts (og-images, profile-pictures, workspace-logos), which skip checkStorageQuota, letting any authenticated writer open arbitrarily large upload sessions that never count against their plan limit. These contexts have no large-file flow: their client hooks hard-cap uploads at 5MB (image-only) and the direct-upload strategy only uses multipart above 50MB, so they always route through the presigned endpoint. Remove them from ALLOWED_UPLOAD_CONTEXTS (joining logs) so every context the multipart endpoint serves is quota-enforced. |
||
|
|
83dc806da8 |
fix(file-decompress): enforce decompression caps on inflated stream, not declared zip size (#5154)
* fix(file-decompress): enforce decompression caps on inflated stream, not declared zip size * fix(file-decompress): destroy inflate stream on error to avoid resource leak |
||
|
|
35a7bf61d2 |
fix(executor): stop HITL error edges from firing on successful resume (#5152)
* fix(executor): stop HITL error edges from firing on successful resume * add comments |
||
|
|
3ebb9a5029 |
feat(connectors): add Google Meet knowledge base connector (#5149)
* feat(connectors): add Google Meet knowledge base connector Syncs Google Meet meeting transcripts into a knowledge base via the Meet REST API v2. Lists conference records, fetches transcript entries lazily per meeting (contentDeferred), resolves speaker display names, and maps participants/duration/meeting-date tags. OAuth via the existing google-meet provider (meetings.space.readonly). * fix(connectors): finalize Google Meet transcripts before indexing - Only index a meeting once every transcript is FILE_GENERATED, so a partial transcript is never stored under an endTime-keyed hash that would never refresh - Sort merged transcript entries by start time to preserve chronology across multiple transcripts in one conference * refactor(connectors): dedicated TRANSCRIPTS_PAGE_SIZE constant for Meet transcripts * fix(connectors): only flag Meet listing capped when cap truncates source Previously listingCapped was set whenever the fetched count reached maxMeetings, even when the API returned every record and no next page existed. That suppressed the sync engine's deletion reconciliation when the cap happened to equal the true source size. Now flag only when more pages remain or records were dropped from the page. |
||
|
|
ce283fa1c9 |
feat(scheduled-tasks): expose Google Calendar-style recurrence options (#5146)
* feat(scheduled-tasks): expose Google Calendar-style recurrence options Add a per-day weekly toggle (repeat on arbitrary weekdays), monthly nth-/last-weekday anchoring, and a yearly frequency to the scheduled task modal, closing the gap with a calendar app's recurrence picker. The recurrence UI compiles to cron, so this is front-end only: croner already speaks the nth/last-weekday (#/#L) syntax, and the display path normalizes #L to cronstrue's L so labels read "last Monday" not "null". * fix(scheduled-tasks): preserve monthly anchor when reselecting Monthly Selecting Monthly from the frequency dropdown hard-reset monthlyMode to day-of-month, silently dropping a previously chosen nth-/last-weekday anchor when switching cadence away and back. Preserve the existing mode on reselect, mirroring how the last recurring cadence is restored across the recurring toggle. * fix(scheduled-tasks): fold 5th-occurrence monthly into last-weekday; align weekday-digit parsing Address review edge cases in the monthly recurrence anchors: - The picker no longer offers a fifth weekday (a 5th occurrence is always the month's last), and recurrenceToCron clamps any nth-weekday that resolves to a 5th occurrence to #L — so a launch date drifting to day 29-31 can never emit #5 and silently skip months without one. - cronToRecurrence accepts croner's alternate Sunday digit (7) for the #/#L monthly anchors, matching parseCronToHumanReadable's normalizer; externally-authored 7#L crons now round-trip (canonicalized to 0#L). - #5 crons are left as custom pass-through so their month-skipping behavior is preserved verbatim rather than rewritten. |
||
|
|
1248f8eef4 | fix(files): only show Share in context menu for files, not folders (#5147) | ||
|
|
3b78436ae3 |
feat(pii): gate data retention PII redaction behind feature flag (#5144)
* feat(pii): gate data retention PII redaction behind feature flag * fix(pii): evaluate pii-redaction flag globally with no org/user context |
||
|
|
ecbe1919d8 |
feat(files): inline rich markdown editor (#5133)
* feat(files): inline rich markdown editor
Replace the raw/preview split for markdown files with a Linear-style inline WYSIWYG editor (TipTap/ProseMirror): bubble + slash menus, code-block language picker with Prism highlighting and line-wrap, resizable images (HTML <img>), GFM tables, and frontmatter held byte-exact out of band.
A round-trip preflight gate (decided once per open) falls back to the raw Monaco editor for any file that can't be edited losslessly, so the rich editor never silently corrupts a file.
* fix(files): chain autosave unmount flush after in-flight save
The unmount flush no longer fires a concurrent PUT alongside an in-flight save; it awaits the in-flight save and then writes the latest content sequentially, so an out-of-order completion can't clobber newer edits with a stale snapshot (addresses Cursor Bugbot).
* fix(files): read pasted images from clipboard items, not just files
Some browsers expose a pasted or copied image only via DataTransfer.items (with an empty files list), so screenshot paste was silently ignored. extractImageFiles now falls back to items; moved to a testable module with unit tests (addresses Cursor Bugbot).
* fix(files): destroy round-trip probe editor on serialization error
Wrap the probe serialize() in try/finally so the throwaway Editor is always destroyed even if setContent/getMarkdown throws (addresses Greptile). Adds a test proving PipeSafeTable escapes only interior cell pipes, not structural delimiters.
* fix(resource): hold breadcrumb nav latch across the route swap
scheduleClose fired on the pointer/focus exit that immediately follows a click-to-navigate and was clearing the reopen latch before the route swapped, letting the popover flash back open. The latch is now released by a short timer instead (addresses Cursor Bugbot).
* chore(files): drop platform references and non-essential inline comments
* fix(files): scope inline markdown editor to the files view
The mothership preview was routing streaming markdown through the inline editor path: it showed Monaco during streaming (previewMode fell back to 'editor') and lost the streamed content on the TextEditor→MarkdownFileEditor swap (the TextEditor unmounted before it could reconcile + autosave). The inline rich editor is now opt-in via a FileViewer prop that only the files view sets, so the mothership keeps its raw/preview streaming editor and persists as before.
* fix(mothership): use the inline markdown editor in the chat resource view
Idle markdown in the chat resource view now renders the single-surface inline editor (no raw/split/preview pencil toggle), matching the files view. While the agent streams, FileViewer forces the rendered preview instead of Monaco, and the streamed file persists via the agent's server write + the existing content-query invalidation on tool completion — so the idle editor refetches the persisted content.
* refactor(files): collapse the duplicate raw-editor fallback branch in the markdown gate
* fix(mothership): swap to the inline editor once a file preview finishes streaming
The preview session keeps status='complete' and previewText after streaming ends, so streamingContent stayed defined and the file stuck on the read-only rendered preview. Treat content as streaming only while status==='streaming'; once complete the EmbeddedFile sees no streamingContent and mounts the editable inline editor (which refetches the persisted content). The synthetic streaming-file stays a pure preview.
* Revert "fix(mothership): swap to the inline editor once a file preview finishes streaming"
This reverts commit 25b12e4caa389109c2d5ed7f7d122e35d441f980.
* Revert "fix(mothership): use the inline markdown editor in the chat resource view"
This reverts commit 9430aa7fdc5050d002f2312d31dcf4a255e2de18.
* feat(files): rich markdown editor across files + chat, read-only for unsafe, robust load/save
- chat resource view streams into the rich editor (streamdown while streaming → editable on completion); agent persists server-side, editor never saves mid-stream
- round-trip-unsafe / >128KB markdown renders read-only in the rich editor (no Monaco, no corruption)
- markdown always uses the rich editor (dropped the inline-markdown opt-in flag)
- editor loads content as TipTap's initial content keyed by file id — strict-mode/SSR-safe, no content-sync effect
- fix autosave "Saving…" status suppression under React strict mode
- lock the streamed-file persistence handoff with a state-machine lifecycle test
* chore(files): remove dead code (unused FileViewer logger + EmbeddedWorkflowActions router)
* fix(files): derive markdown round-trip verdict from live content, not a locked stale snapshot
The gate locked isRoundTripSafe on the first post-stream snapshot, which is often the empty create_file buffer before the agent's server write lands — wrongly leaving an unsafe document editable. Derive the verdict from the current content (memoized on the bytes) so canEdit tracks the real payload.
* test(files): guard the rich editor dirty signal — open is never dirty, edits emit
* fix(files): lock the markdown round-trip verdict on opened content, never strand dirty edits
The round-trip-safety verdict now gates editability only at open time — computed once, on the exact
content the editor mounts with, and locked for its lifetime. A dirty document is round-trip-safe by
construction (the editor only emits safe markdown), so the verdict must never flip off mid-edit:
doing so disabled autosave, ⌘S, the toolbar Save and the unmount flush, stranding unsaved edits.
Locking on the opened (reconciled) content also fixes the stale post-stream empty-buffer snapshot,
and lets the redundant MarkdownFileEditor gate (plus its duplicate content fetch) be deleted.
* improvement(file-viewer): reuse shared copy hook, lazy frontmatter split
- code-block: replace hand-rolled copy-with-timeout with shared useCopyToClipboard
- rich-markdown-editor: compute frontmatter split once via lazy ref, drop redundant frontmatterRef
- round-trip-safety: correct stale comments (read-only, not raw editor fallback)
* feat(file-viewer): linked images, typed-link input rule, drag-to-reorder, churn fixes
- image: round-trip linked images/badges via an href attr + custom markdown tokenizer; make
the image a drag handle so it can be grabbed and reordered
- link-input-rule: convert typed [text](url) to a link on the closing paren (normalized href)
- markdown-paste: render pasted markdown as rich content, guarded against code blocks
- round-trip-safety: behavioral link-count check replaces the static linked-image rejection
- extensions: trim the table serializer's blank lines to stop interior-table whitespace churn
* improvement(file-viewer): Backspace at start of a heading reverts it to a paragraph
Notion-style: ProseMirror's default joins or no-ops at a heading boundary, stranding the
heading style. A second Backspace then merges as usual.
* fix(file-viewer): don't upload pasted/dropped images into a read-only editor
handlePaste/handleDrop ran the workspace image upload without checking editability, so a
read-only doc (canEdit=false or a round-trip-unsafe file) could still trigger an upload.
Guard both on view.editable.
* fix(file-viewer): sanitize linked-image href; drop global leading-newline strip
- image: run the linked-image (badge) anchor target through normalizeLinkHref so a
javascript:/data: href in a file can't execute on click; the markdown still preserves the
raw target (file content unchanged)
- markdown-fidelity: the table serializer now trims its own surrounding blank lines, so the
global leading-newline strip in postProcessSerializedMarkdown is redundant — removing it
stops clobbering content that legitimately begins with whitespace
* feat(file-viewer): stream agent output directly into the rich editor; add more code languages
- rich-markdown-editor: the TipTap editor is now the only markdown surface. Agent output streams
into it read-only (synced per chunk, autoscrolled), then the same instance hands off to an
editable editor on settle — no separate streamdown preview, so no stream→edit flash. The
round-trip verdict + frontmatter lock when the content settles.
- code-block/code-highlight/detect-language: register Go, Rust, Java, C, C++, C#, Ruby, PHP grammars
and add detectors, so those blocks highlight and the picker offers them.
- css: style h5/h6 in the prose stylesheet.
* fix(sidebar): hydrate collapse state before paint to stop refresh flash
The collapsed sidebar swaps entire subtrees (collapsed flyout vs expanded
lists), but isCollapsed only resolved after the first paint via auto
rehydration, so a collapsed reload rendered the expanded tree into the 51px
rail and then reflowed — the misplaced/flashing content on refresh.
Adopt zustand's documented SSR pattern: skipHydration on the persist config
(first render keeps the default false, matching SSR HTML) and flush
persist.rehydrate() from a useLayoutEffect so the correct structure commits
in the same pre-paint frame. Removes the old race where onRehydrateStorage
lifted the data-sidebar-collapsed mask before React committed the rail.
* refactor(file-viewer): audit fixes — stale docs, DRY settle-lock, language detection
- rich-markdown-editor: rewrite the now-stale single-surface docstring (no PreviewPanel); extract a
shared lockSettled() helper used by both the mount and stream-settle paths; guard the settle
re-seed so it only setContent's when the body actually changed (no redundant doc rebuild)
- detect-language: stop misreading generics (List<String>) as HTML markup; detect Go type/struct
- code-block: export LANGUAGE_OPTIONS + add a test asserting every picker language has a registered
Prism grammar (prevents picker/highlighter drift)
* refactor(file-viewer): remove dead markdown-preview renderer now superseded by the rich editor
Markdown files route exclusively to RichMarkdownEditor on both the read-only
and editable paths, so PreviewPanel's markdown branch and its Streamdown-based
renderer were unreachable. Delete MarkdownPreview and its renderers, callout/
frontmatter/checkbox machinery, and the now-unused remark/rehype/prism/streamdown
imports; drop the dead toggleMarkdownCheckbox/onCheckboxToggle plumbing in
text-editor. Keep the html/csv/svg/mermaid branches intact.
* refactor(file-viewer): drop dead streamingMode/append path, align naming, cover autosave
The streaming engine only ever runs in 'replace' mode (the only runtime callers
pass it); the 'append' branch of resolveStreamingEditorContent was unreachable.
Remove streamingMode + the StreamingMode type and thread it out of the 6
components that forwarded it — nextContent is now simply the streamed snapshot,
behavior-identical on the live path. Rename for codebase semantics: the boolean
prop streaming -> isStreaming, EditorKeymap -> RichMarkdownKeymap, the highlight
PluginKey KEY -> HIGHLIGHT_PLUGIN_KEY. Add a defensive isEditable guard to the
markdown paste handler (parity with the image handler; read-only must never
mutate). Add a dependency-free useAutosave test suite (debounce, min-display
window, no-data-loss when an edit lands mid-save, error/no-retry, Cmd+S flush,
streaming-disabled lock, unmount flush).
* fix(file-viewer): re-lock round-trip verdict + frontmatter on each stream settle
LoadedRichMarkdownEditor stays mounted across multiple agent edits to the same
file within a chat (previewContextKey is the chat id), but the settle effect only
locked settledRef when it was null — so a second stream into the same instance
kept editability and frontmatter tied to the first settled snapshot. A repeat
edit that is round-trip-unsafe would stay editable, and saves would re-attach the
stale frontmatter. Track wasStreaming and re-derive the verdict + frontmatter on
every stream->settle transition (user edits never re-derive, preserving the
don't-strand-edits rule). Verified red/green in the e2e streaming harness.
* test(file-viewer): lock link href sanitization for dangerous schemes from file content
Greptile flagged a possible javascript: link XSS. Verified TipTap 3.26.1 already
neutralizes javascript:/data:/vbscript: (and mixed-case/whitespace variants) from
file-loaded markdown to an empty href. Add a committed regression test that asserts
this against the real headless editor, so a future TipTap bump can't silently
reintroduce the issue.
* perf(file-viewer): cap the round-trip probe at 24KB and coalesce streaming syncs
@tiptap/markdown's parse is superlinear (~O(n2)) in document size — measured ~170ms
at 11KB, ~875ms at 23KB, multiple seconds past ~35KB — and it runs synchronously at
mount inside the round-trip-safety probe (twice) and the editor's own setContent. The
128KB cap allowed multi-second main-thread freezes; lower it to 24KB so the worst-case
mount stays near a second while still covering the vast majority of real markdown files
(larger files open read-only). Separately, coalesce streaming chunk-syncs to one re-parse
per animation frame so a fast-streaming agent doesn't re-parse the whole accumulating
doc per token. Typing latency was measured to be already excellent (sub-ms median, no
change needed); the only hot cost was the mount parse.
* perf(file-viewer): chunked markdown parsing to remove the O(n2) mount cost
@tiptap/markdown's whole-document setContent(md,'markdown') is superlinear in size,
freezing the main thread at mount for large files (~2.5s at 34KB, ~11s at 65KB) and
forcing a restrictive read-only cap. Parse block-by-block instead: a conservative
blank-line/fence-aware splitter (merges list/quote runs and indented continuations so
ambiguous structures stay atomic; reference-link/footnote/raw-HTML docs fall back to a
whole parse), each block parsed with the editor's own lexer via one reused headless
parser, assembled into a doc. This is linear and byte-identical to the one-shot parse —
measured ~15ms vs multiple seconds at 124KB+ — so the editor mount, streaming sync, and
round-trip probe are all linear, and the editable-size cap goes 24KB -> 256KB (covers
the p99 of real files). Fidelity + idempotency are pinned by unit tests, a 400-document
property/fuzz test, and adversarial edge cases (nested/loose lists, blockquotes, setext,
indented code, lazy continuation, HTML, reference links).
* fix(sidebar): render collapse state from a cookie so SSR matches
The server couldn't read localStorage, so a collapsed user's first paint
rendered the *expanded* tree at 51px — prefetched chat/workflow lists,
pinned-chat pin icons, and loading skeletons all crammed into the rail and
then reflowed once the store hydrated.
Mirror the collapse state into a sidebar_collapsed cookie (the shadcn/ui
sidebar pattern), read it in the workspace server layout, and seed the
sidebar's first render with it: structure is now correct on the server, so
the first paint is the real rail with no skeleton/pin/shift. The store
remains the post-hydration source of truth; the blocking script honors the
cookie for width when localStorage is absent so width and structure agree.
* refactor(sidebar): make the cookie the single source of truth for collapse
Consolidates the collapse machinery onto one source of truth instead of
layering the cookie on top of the legacy localStorage + CSS-mask system:
- Collapse persists only in the sidebar_collapsed cookie; the store seeds
isCollapsed from it and drops it from localStorage (partialize + merge),
removing the dual-write and the cross-tab desync it caused.
- Retire the redundant html[data-sidebar-collapsed] attribute + CSS mask now
that the server emits the correct data-collapsed structure; also delete the
dead sidebar-collapse-show/-remove/-btn rules.
- Blocking script reads the cookie for collapse (width stays in localStorage)
and seeds the cookie once from the legacy flag so existing collapsed users
keep their preference.
- Keep skipHydration + a pre-paint rehydrate for width only — the documented
zustand SSR pattern, so _hasHydrated is deterministically false during SSR.
Width stays in localStorage; each field now has exactly one home.
* refactor(file-viewer): simplify + cleanup chunked-parse (linear merge, parse-once seed)
From the /simplify + /cleanup passes:
- splitMarkdownBlocks: build continuation runs and join each once instead of
concatenating onto the growing previous block per group, which was O(n2) for a
pathological single long loose list (now linear: 208KB loose list splits in ~3ms).
- rich-markdown-editor: seed the editor's initial content via a lazy useState
initializer instead of useRef(parseMarkdownToDoc(...)), whose argument re-parsed the
whole document on every render (i.e. every keystroke). Parses exactly once at mount.
- Document that the indent-merge rule is load-bearing for nested fenced code, and
tighten the verbose inline comment blocks.
* refactor(sidebar): drop orphaned sidebar-collapse-btn class
Its CSS rule was removed with the data-sidebar-collapsed mask; the button's
collapse behavior is fully driven by the React isCollapsed ternary, leaving the
class name pointing at nothing.
* test(file-viewer): consolidate split test files into one per module
Match the dir's one-test-per-module convention: fold the markdown-parse property/fuzz
suite into markdown-parse.test.ts and the editability corpus into round-trip-safety.test.ts
(both already tested the same module from a separate-concern file). No coverage change —
same assertions, fewer files (12 -> 10).
* fix(file-viewer): make all editor controls respect read-only permissions
Every interactive control that calls updateAttributes/dispatches a command mutates
the doc even when read-only (ProseMirror commands run regardless of editable), so
gate them on editor.isEditable:
- bubble menu: the Cmd/Ctrl+K shortcut and shouldShow now bail when not editable, so
a read-only doc can't open the link bar and setLink into it (Cursor finding).
- code block: the language picker renders as a static label when read-only (its
onSelect mutates); copy + view-only wrap stay.
- image: no drag-to-reorder (draggable=false, no drag handle) and no resize handle
when read-only; the image still renders and follows its link.
- links: a plain click now follows the link in read-only (reader) mode, while edit
mode still requires a modifier so a plain click can place the cursor (Cursor finding).
Verified with new read-only permission e2e tests.
* fix(sidebar): honor collapsed cookie even when localStorage is corrupt
The blocking script read the collapse cookie inside the same try as
JSON.parse(localStorage); invalid persisted JSON fell through to the 248px
fallback and ignored a collapsed cookie, painting an expanded-width rail on
first load. Read collapse from the cookie first and parse the persisted width
in its own try so the two are independent.
* docs(sidebar): convert inline comments to TSDoc
* fix(file-viewer): resolve in-app workspace image URLs in the rich editor
The removed MarkdownPreview rewrote /workspace/{id}/files/{fileId} image src to the
serving endpoint /api/files/view/{fileId}; without it, in-app image URLs 404 in the
rich editor (Cursor finding). Re-add the rewrite as a display-only transform on the
rendered <img src> — the node's stored src attribute keeps the original path so markdown
round-trips unchanged. Absolute/non-workspace URLs pass through. Unit tested.
* fix(files): restore same-page anchor links in the rich markdown editor
Headings rendered by the TipTap editor had no slug ids (the old MarkdownPreview
got them from rehype-slug), so in-document table-of-contents links like
[section](#section) had no targets. Resolve the slug to its heading on click
(GitHub-style, duplicate-disambiguated) and scroll to it, with zero per-keystroke
cost.
* feat(files): render mermaid diagrams in the rich markdown editor
A code block renders as a Mermaid diagram when it is fenced ```mermaid or
auto-detected (an untagged fence whose first line opens with a diagram keyword,
the Linear/GitHub heuristic). Detection is display-only — the node stays an
ordinary code block and the markdown round-trips unchanged.
- Source while the caret is inside the block, diagram on blur; a Show source /
Show diagram control plus copy, matching the code block's hover chrome.
- Clicking the diagram selects the node (same ring as an image), not flips source.
- Theme-aware (light/dark) via next-themes; the diagram frame shares the code
block's chrome (one CSS source of truth).
- Extracted MermaidDiagram into a shared module so the editor reuses it without
pulling preview-panel's heavy deps; rendered SVGs are memoized so toggling the
source view and back is instant.
Covered by mermaid-diagram unit tests and the editor e2e harness.
* fix(files): harden the markdown editor (CRLF chunking, href allowlist, image escaping)
Final-audit follow-ups:
- splitMarkdownBlocks normalizes CRLF/CR first — a closing fence ending in \r
no longer fails to match, which had collapsed Windows-authored files with
fenced code into one block and defeated the linear chunker (perf regression).
- normalizeLinkHref rejects file://, blob:, and other non-network schemes
(script/data schemes already rejected); network scheme:// (http/ftp/…) and
bare host:port still pass.
- Image markdown serialization escapes alt/title delimiters and angle-brackets
a src with spaces/parens, so they round-trip losslessly; linked-image anchors
open in a new tab (target=_blank).
- Markdown paste routes through the chunker so a large pasted blob can't freeze
the main thread.
* test(files): cover the code-highlight incremental re-tokenization gate
Export and unit-test changeTouchesCodeBlock: prose-only edits map decorations
(false), edits inside a code block or a setNodeMarkup language change re-tokenize
(true) — the perf-correctness path that keeps highlighting off the keystroke path.
* fix(files): keep relative links relative, navigate in-app links within the SPA
- normalizeLinkHref no longer prefixes `./`/`../` relative paths into `https://./…`
(they round-trip and resolve correctly).
- Following a same-origin in-app link (e.g. /workspace/…) routes through the
Next router (same tab) instead of always opening a new tab; modifier-click and
external URLs still open a new tab.
* fix(files): linked images don't open a tab on a plain click in the editor
The linked-image anchor's native navigation was firing on a plain click in edit
mode (where handleClick intentionally returns false for caret placement). Prevent
the anchor's default so the editor's handleClick — gated on editable/modifier,
matching text links via openOnClick:false — is the sole navigator.
* fix(sidebar): match the collapse cookie value strictly (not a substring)
A substring search for 'sidebar_collapsed=1' also matched 'sidebar_collapsed=10',
desyncing the pre-paint sidebar rail and client store from the strict server read.
Parse the cookie value and compare it to '1' exactly, in both the pre-paint inline
script and readCollapsedCookie. Added a store test.
* fix(sidebar): reconcile migrated-legacy collapse before paint
A user whose collapse lived only in localStorage has no sidebar_collapsed cookie
at SSR (initialCollapsed=false), but the pre-paint script migrates them to a
cookie. The store's persist.rehydrate() is async (flips _hasHydrated after paint),
so the first paint showed expanded labels in the collapsed 51px rail. Reconcile to
the cookie synchronously in a useLayoutEffect (first render still matches the
server, so no hydration mismatch) — no narrow-rail flash.
|
||
|
|
7349bf403f |
feat(files): password, email-OTP, and SSO auth for public file shares (#5140)
* feat(files): password, email-OTP, and SSO auth for public file shares * fix(files): suppress filename in share previews for email/sso, not just password * fix(files): normalize allow-list emails to lowercase; genericize shared SSO denial message * fix(security): make isEmailAllowed case-insensitive; normalize email at client gates * test(security): cover isEmailAllowed case-insensitive matching * fix(security): bind auth cookie to auth type; password endpoint rejects non-password shares * chore(db): format generated migration meta * fix(files): share upsert validation returns 400 not 500; disabling always succeeds * feat(access-control): org admins can restrict allowed file-share auth types |
||
|
|
5925651cbc |
feat(vfs): add lazy vfs + remove dynamic fields for prompt caching hits (#5138)
* feat(vfs): add lazy vfs + remove dynamic fields for prompt caching hits * feat(vfs): send typed workspace snapshot for append-only deltas Build the workspace inventory from the primary db (fixes replica-lag staleness) and emit it as a typed VfsSnapshotV1 `vfs` payload alongside the markdown, so the mothership can diff it into append-only baseline/delta messages. Generate the TS contract mirror from the Go-owned JSON schema (sync-vfs-snapshot-contract) and sort connector types so diffs stay byte-stable. * fix(lint): fix lint * fix(vfs): forward the typed snapshot through the branch payload builder The branch buildPayload implementations hand-list the params they pass to buildCopilotRequestPayload and forwarded workspaceContext but dropped vfs, so the typed snapshot never reached the Go request (req.Vfs was always nil and the append-only delta path never engaged). Forward vfs in both the workflow and workspace branches, and add a regression guard asserting the branch threads it through (the bug slipped past tests because post.test mocked the payload builder and payload.test called it directly, bypassing the branch). * improvement(contracts): update vfs contracts |
||
|
|
208d135dac |
feat(enrichment): add enrichment details sidebar with cost + provider cascade (#5139)
* feat(enrichment): add enrichment details sidebar with cost + provider cascade * fix(enrichment): address review — persist detail on cancel/skip, exclude not_run from ran count, refetch on panel open * fix(enrichment): keep cascade detail sticky on upsert; mark unattempted providers not_run on abort * fix(enrichment): show Cancelled in details panel for aborted runs |
||
|
|
9d2a6ef043 |
feat(logs): redact PII from workflow logs via configurable rules (#5136)
* feat(logs): redact PII from workflow logs via configurable rules Enterprise PII redaction for workflow execution logs, configured under Data Retention as org-scoped rules (each rule picks entity types + which workspaces it applies to). Reuses the guardrails Presidio engine in mask mode at the log-persist choke point, with a check-digit-validated VIN recognizer. Also adds per-workspace data-retention-hours overrides. * fix(logs): widen PII entity visibleValues to string[] for strict build typecheck * fix(logs): redact error/trigger/executionState; keep guardrails import lazy - Extend PII redaction to span error/errorMessage/toolCalls and top-level error/completionFailure/trigger/executionState (Bugbot: PII in execution metadata). executionState is safe to redact — resume reads from the separate pausedExecutions table, not the log copy. - Lazy-import validate_pii in pii-redaction so the Python/child_process guardrails module stays out of the static middleware/RSC graph. - Type the org retention mutation to the contract body (optional, non-null). * refactor(logs): drop per-workspace retention override; PII redaction stays org-scoped - Remove the unused per-workspace data-retention-hours override (no UI; superseded by workspace-scoped PII rules). Reverts cleanup-dispatcher to org-only retention, drops resolveEffectiveRetentionHours, the workspace.dataRetentionSettings column + migration, and the workspace data-retention route/contract/hooks. Fixes Bugbot's null-as-unset finding by removing the buggy path entirely; org retention behavior is unchanged. - Stop re-checking isWorkspaceOnEnterprisePlan at persist time (it returns false on transient errors, which would fail-open and leak PII). Enabled rules already imply entitlement; redact whenever rules apply (fail-safe). * fix(logs): redact oversized strings and executionData.environment - Drop the per-string size cap in PII redaction: oversized strings were left unmasked (leak). Nothing is skipped now; large payloads still fail-safe via the total-bytes ceiling + per-chunk timeout (scrub, never leak). - Add executionData.environment (incl. variables) to the redaction set. * refactor(logs): single-scope PII rules with most-specific-wins resolution Each rule now targets one scope — all workspaces (workspaceId: null) or a single workspace — with workspaceId unique across rules. Resolution is most-specific-wins (a workspace's own rule overrides the all rule), not union; an empty specific rule exempts that workspace. Matches Access Control's resolveWorkspaceGroup precedence. UI 'Applies to' becomes a single-select; Add rule disables when all scopes are taken. * feat(logs): default + workspace-overrides UI for PII redaction Reshape the PII redaction settings into a 'Default (all workspaces)' block plus a 'Workspace overrides' list, making the most-specific-wins precedence explicit (overrides replace the default; unlisted workspaces use it). Same data model (workspaceId null = default), UI only. * improvement(logs): clearer default/overrides PII UI Drop the uppercase section labels and the overrides description; gate the Workspace overrides section behind a configured default; use a single Delete action; 'Add redaction' creates the all-workspaces default and disappears once set. * fix(guardrails): handle stdin EPIPE in PII python spawns Attach an 'error' listener to the child's stdin in both runPythonScript (the batch masking hot path) and executePythonPIIDetection. A 256KB chunk can exceed the OS pipe buffer, so if the Python process exits mid-read (OOM/kill) the EPIPE emitted on stdin was unhandled and would crash the Node process. Funnel it into the promise rejection so the fail-safe scrub path handles it gracefully. * fix(logs): redact executionData.correlation The top-level correlation field is copied from pre-redaction trigger data, so webhook/schedule correlation values could persist unredacted. Add it to the redaction set alongside trigger/environment. * fix(logs): enforce unique PII rule scope server-side The contract accepted multiple rules with the same workspaceId (or several null all-rules); resolution is first-match, so duplicates could disagree with the UI. Add a schema refine rejecting duplicate scopes. * fix(logs): re-hydrate data-retention form on org switch The form hydrated once via a boolean ref, so switching the active org left stale retention days + PII rules and saves targeted the new org with old config. Key hydration on orgId so it re-loads per org. |
||
|
|
13b5d215e9 |
improvement(access-controls): docs, terminology, fix delete bug (#5141)
* improvement(access-controls): dedup independent block names * improvement(access-controls): fix delete button, naming in perm group modal |
||
|
|
91f9dfdaec |
improvement(governance): derived access (#5134)
* improvement(governance): org-ws-credential roles clarity * revert isHosted * improvement(credentials): code cleanup * address comments * make kb cascade delete on user hard delete * revert env flags * chore(db): drop local 0242 migration to regenerate after merging staging Our 0242 collides with staging's 0242. Remove it (and its snapshot + journal entry) so the KB-cascade migration can be regenerated with the correct number on top of the merged staging migrations. * chore(db): regenerate kb→workspace cascade migration as 0243 Regenerated via drizzle-kit generate on top of the merged staging migrations (staging took 0242). Re-applied the safety edits: NOT VALID + separate VALIDATE on the FK re-add, and the -- migration-safe note on the DROP. check:migrations passes. * improve copy * update docs |
||
|
|
c419a34317 |
feat(tables): raise per-plan table limits (free 5/50k, pro 100/100k, max 1k/500k) (#5135)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f0b3550729 |
feat(files): public share links for workspace files (#5130)
* feat(files): public share links for workspace files * improvement(files): drop reserved public_share columns until used; sync audit mock * fix(files): share modal tracks authoritative saved state until toggled * feat(files): per-IP rate limit on public share endpoints * fix(files): address PR review — public CSV OOM, content cache, share FK, soft-delete filter, download anchor * fix(files): disable CSV import action in read-only preview (public share) * refactor(files): drive CSV preview import affordance off readOnly, not disableImport * fix(files): version public viewer caches by file updatedAt so edits aren't stale * fix(files): 409 (not corrupt source) when a shared generated doc has no compiled artifact * feat(files): gate public sharing behind an access-control permission |
||
|
|
267e49c69c |
improvement(workspaces): auto-add without invite if part of organization (#5132)
* feat(workspaces): auto-add without invite if part of organization * reverse feature flag hardcoding * address comments * improve ux for org invite modal |
||
|
|
63fdc472c1 |
improvement(block): table empty-state filter/sort builders + upsert conflict-column selection (#5123)
* ci(migrations): fail dev schema push with an actionable error on rename/drop prompt
`drizzle-kit push --force` only suppresses the data-loss confirm, not the
rename-vs-drop disambiguation prompt. That prompt fires whenever a diff both
adds and drops tables/columns at once (e.g. migration 0231 created
sim_trigger_state while dropping the workspace_notification_* tables), and in
CI it crashes with a bare "Interactive prompts require a TTY" stack trace.
Catch that specific failure in the dev push step and emit a GitHub error
annotation explaining the cause and the fix (drop the stale objects on the dev
DB to match schema.ts — the same DROPs the versioned migration already applied
to staging/prod), instead of leaving an opaque trace. Exit status is preserved
either way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* improvement(tables): empty-state filter/sort builders + upsert conflict-column selection
* improvement(tables): throw on ambiguous upsert instead of guessing the conflict column
* Revert "ci(migrations): fail dev schema push with an actionable error on rename/drop prompt"
This reverts commit
|
||
|
|
58312a10e5 | improvement(misc): add more sportmonks tools, improvestreaming ux (#5129) | ||
|
|
ea83be3bbe |
fix(mship): add folder rename tools and locked workflow status (#5126)
* fix(mship): add folder rename tools and locked workflow status * fix(mship): manage_folder bug fixes * improvement(mship): clean up deprecated fields from contracts * test(mship): update tool-call display title tests for client-derived titles Display titles now come from the Sim-side name resolver, not the stream's ui.title/phaseLabel. Update the read lifecycle test to expect the name-derived title and drop the obsolete phaseLabel-fallback test. * fix(contracts): lint |
||
|
|
e5f3965ed1 |
feat(mship): add parallel subagents, improve streaming performance (#5122)
* feat(subagents): add support for parallel subagents * fix(subagents): address parallel-subagent bugs * progress on streaming refactor * improvement(subagents): update comment to reflect new go feature flag * debug mode progress * remove debug logs * fix(validation): add escape annotation * improvement(code): remove dead fallbacks * fix subagent lane fallback issue * fix(mothership): increase default redis event limit to 100k from 5k * fix(mothership): streaming invariant projection enforcement --------- Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> |
||
|
|
597d7eafb5 |
fix(tables): enforce row limits against the current plan, not a frozen per-table cap (#5120)
* fix(tables): enforce row limits against the current plan, not a frozen per-table cap * fix(tables): gate multi-batch CSV create + initial rows against the plan, harden limits cache bound * fix(tables): thread running row count through copilot batchInsertAll capacity check * chore(tables): align tx-variant capacity docstrings * fix(tables): map row-limit errors to 400 in create-from-CSV import * feat(tables): add Upgrade action to the row-limit toast * fix(tables): keep CreateTableData.maxRows so staging callers type-check after merge * improvement(tables): route row-limit Upgrade action to the explore-plans page |
||
|
|
badfbc3bdf |
fix(resource): left-align table filter/sort when there's no search (#5128)
The unconditional ml-auto from #5117 right-aligned the embedded table editor's filter/sort cluster, which has no search bar. Only push the aside + filter/sort group right when a search occupies the left; without a search it stays left-aligned as before. |
||
|
|
63a3e6d2cb |
feat(files): stream large CSV previews and add import-as-table (#5125)
* feat(files): stream large CSV previews and add import-as-table * fix(files): validate fileId in csv-preview route, guard double-import, fix sniff perf and toggle flash * fix(files): scope mothership preview-toggle loading guard to CSV files only |
||
|
|
a028d07e7b |
improvement(mothership): user_table speed parity — limit bounds, background import/delete/update jobs (#5012)
* improvement(mothership): user_table speed parity — limit bounds, async import/delete/update jobs
- query_rows / filter ops clamp limit to the contract maxes; query_rows
skips execution metadata.
- import_file / create_from_file (large CSV/TSV) and delete_rows_by_filter
(>1000 unbounded matches) dispatch background table jobs, claiming the
per-table job slot; inline paths claim the slot too.
- update_rows_by_filter now escalates the same way: >1000 unbounded matches
run as a background table job (new 'update' job type + runTableUpdate worker
+ tableUpdateTask), so a broad update on a huge table no longer loads every
row into the request. Best-effort/non-atomic and skips workflow recompute
(documented); unique-column patches stay inline. Pagination is limit/offset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(mothership): trim user_table catalog copy to the essentials
Drop the verbose doomedCount/affectedCount, delete-mask, workflow-recompute,
and unique-column asides from the bulk-op descriptions. The model only needs:
large ops return { jobId }, limit maxes at 1000, one job per table.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* improvement(mothership): make user_table limit cap internal, not model-facing
The model can now pass any limit — no "cannot exceed 1000" rejection. 1000
becomes an internal threshold: query_rows clamps the page to MAX_QUERY_LIMIT
(totalCount signals truncation; the model pages with offset), and bulk filter
ops above the cap run as background jobs.
update_rows_by_filter loads full row data inline, so an explicit limit above
the cap escalates to the background worker with a new maxRows budget (the worker
stops after maxRows; update has no read mask so the cap is exact). delete only
loads ids inline, so an explicit limit (any size) stays inline — only unbounded
deletes use the masked background path, which would over-hide a bounded delete.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* improvement(mothership): bounded delete above the cap runs async, not inline
An explicit delete limit now mirrors update: ≤1000 runs inline, above the cap it
escalates to the background worker honoring the limit via maxRows — instead of
always staying inline. The worker stops after maxRows (per-page fetch capped to
the remaining budget).
Bounded background deletes skip pendingDeleteMask: the filter-based mask hides
every match, which would over-hide the rows beyond the cap the job never deletes.
Unmasked, a bounded delete is eventually consistent like a bounded update (rows
disappear as deleted), and doomedCount is omitted from the payload so the count
isn't double-subtracted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(mothership): tidy user_table limit/offset param copy
Drop "Any value is allowed" from the limit description and restore the original
offset description.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tables): skip pendingDeleteMask for bounded background deletes
The bounded-delete commit (
|
||
|
|
7d46103d09 |
chore(deps): remove unused dependencies and harden CI supply chain (#5119)
* chore(deps): remove unused dependencies and harden CI supply chain Dependency cleanup: - Remove unused deps: papaparse, unified, and 6 unused Radix primitives (alert-dialog, radio-group, scroll-area, separator, toggle, visually-hidden) plus @tanstack/react-query-devtools (all verified zero imports repo-wide) - Consolidate jwt-decode into the existing jose dependency (decodeJwt) - Migrate react-window to @tanstack/react-virtual to drop a redundant virtualization library (terminal, structured-output, code viewer) - Remove the better-auth-harmony plugin and its gating env flag Supply-chain hardening: - SHA-pin every GitHub Action to a full commit SHA with a version comment - Pin CI bun-version to 1.3.13 (was "latest" in the release job) - Raise bun minimumReleaseAge cooldown from 3 to 7 days - Add a non-blocking `bun audit` step in CI - Add a CODEOWNERS gate routing dependency-manifest changes to @simstudioai/deps * chore(deps): remove unused apps/docs dependencies (@tabler/icons-react, dotenv-cli) * style(search-modal): use Send icon for Invite teammates action * feat(search-modal): surface New chat as the top action above Create workflow * feat(search-modal): add Secrets to the pages list |
||
|
|
08bcacd74e | fix(copilot): mount input tables with display-name CSV headers, not column IDs (#5121) | ||
|
|
fcfa41cd42 | fix(azure): replace Azure DevOps icon with Azure icon and remove AzureDevOpsIcon (#5118) | ||
|
|
cae1769117 |
improvement(knowledge): align connected-sources rows and move source chip left of filter/sort (#5117)
* improvement(knowledge): align connected-sources rows and move source chip left of filter/sort - Drop the -mx-2 on the connectors list so rows respect the ChipModalBody gutter: the row hover no longer bleeds to the modal edges and row content lines up with the px-4 header. - Add a 'leading' slot to ResourceOptions (left of the filter/sort cluster) and render the knowledge connected-source chip there instead of the far-right 'aside', so it reads as part of the control row. 'aside' stays right-aligned for the table editor's run/stop control. * improvement(resource): render options aside left of filter/sort The options-bar aside has a single other consumer (the table editor's embedded run/stop control), so instead of adding a separate slot, render aside itself to the left of the filter/sort cluster. Drops the extra slot and keeps one canonical control position; the run/stop control moves left too, which is fine for a status widget. * fix(resource): keep options aside grouped with filter/sort without a search bar Group aside + the filter/sort cluster in one ml-auto right-aligned container instead of relying on the search's flex-1 to anchor them. Without this, an options bar with no search (the embedded mothership table editor) split aside to the far left and filter/sort to the far right via justify-between. * docs(resource): clarify aside groups with filter/sort regardless of search |
||
|
|
4d39b0cbf8 |
feat(connectors): use resource selectors for KB connector config (#5116)
* feat(connectors): use resource selectors for KB connector config Replace raw ID text inputs with selector pickers (canonical selector + manual-input pairs) across Google Drive/Docs/Forms/Sheets, Notion, Monday, and Webflow KB connectors, so users pick folders/spreadsheets/pages/boards/ collections instead of pasting IDs — matching the workflow blocks. - Add multi-select where the sync handler supports it (Drive/Docs/Forms folders, Monday boards, Webflow collections) via parseMultiValue - Add shared escapeDriveQueryValue/buildDriveParentsClause helpers for safe multi-folder Drive queries - Add ConnectorConfigField.mimeType, plumbed into the selector context - Fix Webflow listingCapped not set on maxItems truncation (deletion- reconciliation data-loss safety) Fully backward compatible: legacy single-string IDs and CSV both normalize via parseMultiValue; resolved canonical keys are unchanged. * fix(webflow): set listingCapped on within-page maxItems truncation When a collection's items fit in a single API page but maxItems cuts the list within that page, neither hasMoreInCollection nor hasMoreCollections is true, so listingCapped was not set and the sync engine could hard-delete still-existing documents. Add the within-page drop signal to the guard. |
||
|
|
ea505f0388 |
improvement(tables): versioned CSV snapshot cache for table mounts + parallel multipart uploader (#5108)
* improvement(tables): versioned CSV snapshot cache for table mounts + parallel multipart uploader * chore(db): drop colliding 0239 migration (renumber pending) * chore(db): renumber rows_version migration to 0240 (off staging's 0239) * improvement(tables): mount snapshots by presigned URL so the sandbox fetches directly (raise cap to 500MB) * fix(tables): allow url sandbox entries in the function-execute contract; key snapshot by column shape so schema edits invalidate it * chore(e2b): log sandbox inputs split by url-fetch vs inline write * improvement(tables): order export + snapshot rows by order_key so the CSV matches the grid under fractional ordering |
||
|
|
c907b1194b |
improvement(supabase): add Edge Functions tool; correct storage output shapes + harden tools (#5112)
* improvement(supabase): add Edge Functions tool; correct storage output shapes + harden tools
- Add supabase_invoke_function tool (POST/GET/PUT/PATCH/DELETE /functions/v1/{name})
- upsert: support on_conflict; storage upload: support cache-control
- Fix storage copy/move/upload/delete-bucket output properties to match live API
- get_public_url: build URL via directExecution (no spurious network call)
- text_search: validate column identifier
- Strip non-TSDoc section-label comments
* fix(supabase): harden rpc/text_search identifiers; drop unused get_public_url apiKey
- rpc: validate + encode functionName (SSRF/injection parity with vector_search)
- text_search: validate language config interpolated into the PostgREST operator
- get_public_url: remove unused apiKey param + dead auth headers (public endpoint needs no auth)
- create_bucket: tighten output description to match the {name}-only response
* improvement(tavily): mark optional params advanced; fix empty content output
- Mark 29 optional search/extract/crawl/map subBlocks as mode: advanced (keep query/urls/url/apiKey basic)
- Fix search transformResponse: populate content from result.content (was result.snippet, always empty)
- Guard data.results with ?? []; correct country placeholder to a lowercase name
- Rewrite stale TavilySearch/Extract response interfaces; drop dead duplicate interfaces
* fix(supabase): valid Cache-Control directive on upload; clarify functionName description
- storage upload: expand a bare numeric cache-control to `max-age=<n>` (a raw number is not a valid Cache-Control header)
- block: functionName input description now covers RPC, vector search, and Edge Function invoke
* fix(supabase): edge-function error handling + reject array headers
- invoke_function: drop unreachable !response.ok branch (executor throws on non-OK before transformResponse runs and surfaces the error body); document the success-only contract
- invoke_function: ignore non-object (array) headers so JSON arrays can't produce numeric-index header names
- block: reject array/non-object Edge Function headers with a clear error in config.params
* fix(supabase): scope Edge Function method/body/headers to invoke_function
Prevents a stale `method` value (e.g. from the Edge Function field) from leaking
into other operations' params. The tool executor lets `params.method` override a
tool's static verb (tools/utils.ts), so an unscoped value could turn a read into
DELETE/POST against PostgREST. Now method/body/headers are only passed for the
invoke_function operation. Adds a block-level regression test.
* fix(supabase): only parse Edge Function body/headers for invoke_function
Stale or invalid functionBody/functionHeaders left in the block (common when
switching operations) were parsed and validated for every operation, so they
could throw before unrelated tools ran even while hidden. Moved parsing and
validation inside the invoke_function guard; added a regression test.
* fix(supabase): include last_accessed_at as a storage list sort option
The Storage list API accepts last_accessed_at for sortBy; add it to the tool
description and the block dropdown so the surfaced options match the API.
|
||
|
|
11e23131fe |
feat(google): Maps Pollen/Solar, Custom Search expansion, and live-API fixes across Google integrations (#5113)
* feat(google): add Maps Pollen/Solar, expand Custom Search, fix Ads/Groups/Contacts/Slides New capability: - Google Maps: add Pollen Forecast and Solar Potential tools (API-key, google_cloud BYOK) - Google Custom Search: add start/dateRestrict/fileType/safe/searchType/siteSearch/ siteSearchFilter/lr/gl/sort params, htmlTitle/htmlSnippet/formattedUrl/mime/fileFormat/ cacheId/image result fields, and nextPageStartIndex pagination Fixes (validated against live API docs): - Google Ads: bump all tools from sunset v19 to v24 - Google Groups: forward OAuth credential under oauthCredential (was dropping token in 11 ops), forward all update_settings fields, JSON.stringify update_settings/add_alias bodies - Google Contacts: include required metadata.sources[].etag in updateContact body (fixed 400) - Google Slides: remove unsupported GIF thumbnail mimeType (API only allows PNG) - Google Sheets: wire delete_rows/delete_sheet/delete_spreadsheet into the V2 block - Google Custom Search: throw on API error responses instead of returning empty success; num optional + Number-coerced; pagemap typed unknown * docs(google): regenerate integration docs for new and updated operations * fix(google_maps): correct Solar requiredQuality enum to BASE The Solar API ImageryQuality enum is HIGH/MEDIUM/BASE (+ UNSPECIFIED) per the live docs; there is no LOW. Selecting "Low" sent requiredQuality=LOW which the API rejects as INVALID_ARGUMENT, and the valid BASE tier was unreachable. Replace LOW with BASE in the tool param/output descriptions, the type union, and the block dropdown. * fix(google_maps): guard !response.ok in Pollen/Solar; use ?? for color channels Address Greptile review: - Pollen and Solar transformResponse now check !response.ok || data.error (matches the Custom Search fix); a gateway error without an error key in the body no longer returns empty/zeroed output silently. - Pollen color channels use ?? instead of || so a legitimate 0 isn't treated as missing (consistent with the other numeric fields in the file). * fix(google_maps): guard against NaN days in Pollen forecast Address Cursor Bugbot: a non-numeric `days` input parsed to NaN and was forwarded as `days=NaN` (the tool's `?? 1` only catches undefined, not NaN), breaking the forecast call. The block now coerces invalid input to undefined, and the tool defaults to 1 unless `days` is a finite number. * fix(google): clamp Pollen days to 1-5; stop forwarding stale group settings fields Address Cursor Bugbot: - Pollen: clamp days to the documented 1-5 range (truncating fractionals) so 0, negatives, or >5 can't be sent to the API. - Google Groups update_settings: the block has no dedicated settings subblocks, so forwarding name/description from params could leak stale values from create_group/update_group and unintentionally rename the group. Forward only oauthCredential + groupEmail from the block (the tool's own param schema still exposes the settings fields for the agent path). * fix(google_sheets): fail fast on non-numeric delete indices Address Cursor Bugbot: delete_sheet/delete_rows parsed deleteSheetId/startIndex/ endIndex with Number.parseInt but didn't validate, so non-numeric UI input became NaN and was forwarded (the v2 delete tools only reject null/undefined), breaking the batchUpdate. The block now throws a clear error when any of these is not a valid number. * fix(google_search): clamp num to 1-10 and normalize start Address Cursor Bugbot: num was coerced with Number() but not bounded, so values like 11 or fractionals reached the API and failed. The tool now truncates and clamps num to the documented 1-10 range and only sends a positive integer start, ignoring non-numeric/out-of-range input. |
||
|
|
d7fd0405f9 |
improvement(search): align cmd+k action icons + highlight with the design system (#5114)
* improvement(search): align cmd+k action icons + highlight with the design system - Each Actions verb now uses the exact icon from its real location: Fit to view -> Scan (workflow-controls), Copy workflow link -> Duplicate (nav context menu), Invite teammates -> User (settings teammates nav). Run/Create/Import already matched. - Remove the Toggle theme action and its now-dead useTheme wiring. - Matched-text highlight now uses the design-system search tokens (--highlight-match-bg / --highlight-match-text), matching the SearchHighlight component used in knowledge-base and code search, instead of an ad-hoc font-semibold. * improvement(search): use font-medium for matched-text emphasis in cmd+k Drop the colored background highlight in favor of the design system's standard emphasis weight (font-medium, used by Button/Label/Input/Table). Lighter than the previous semibold and avoids a background, keeping the palette's clean, undecorated text style. |
||
|
|
9e9f2b9e1f |
fix(realtime): debounce the reconnecting toast to stop transient-blip flashes (#5111)
* fix(realtime): debounce the reconnecting toast to stop transient-blip flashes The "Reconnecting..." persistent toast fired the instant isReconnecting flipped true, so sub-second transport blips that self-heal on the first retry flashed a scary alert. Add useStableFlag, an anti-flicker boolean that delays the rising edge (2s, so brief blips never surface) and holds the falling edge (1.5s min visible, so a drop just past the delay does not flash-and-vanish). The socket flag stays accurate; only the user-facing alarm is smoothed. State machine extracted into a framework-agnostic controller with unit coverage for both flicker modes. * fix(realtime): reset stable-flag React state on options change; de-vacuous blip test Address Greptile review: - useStableFlag: reset React state to the fresh controller's baseline when the controller is recreated on an options change, so a dynamic consumer changing delayMs/minVisibleMs while active with value already false can no longer strand the flag at true. - test: read the live probe.active getter in the blip test instead of a destructured snapshot, which was bound to false at destructure time and made the assertion vacuous. |
||
|
|
05cd7d92a8 |
feat(search): actions, fuzzy matching, and highlighting in cmd+k palette (#5110)
* feat(search): actions, fuzzy matching, and highlighting in cmd+k palette Add a context-aware actions layer to the cmd+k search palette (Run workflow, Create workflow/folder, Import workflow, Fit to view, Copy link, Invite teammates, Toggle theme), replace the substring matcher with a boundary-anchored fuzzy matcher (initialisms, typos, multi-word) that is a strict superset of the old behavior, highlight matched characters, and rank against clean human text instead of structural id/uuid tokens. Expose invoke() on the global commands provider so the palette runs real registered commands. * fix(search): highlight the matched substring, not an earlier scattered occurrence Contiguous substring matches (exact/prefix/contains) now report the substring's own indices instead of the greedy subsequence scan positions, so HighlightedText bolds the characters the user actually matched. Restructures fuzzyMatch to handle the substring tier first; scores are unchanged for these cases. * fix(search): log clipboard copy failures and make fuzzy positions read-only - Copy workflow link now logs on clipboard write failure instead of silently swallowing the error, matching the sidebar's copy-link convention. - FuzzyResult.positions is now readonly and the NO_MATCH singleton's array is frozen, so the shared instance can never be mutated by a caller. |
||
|
|
8b93e43037 |
improvement(integrations): validate BigQuery/Forms/PageSpeed + regenerate integration docs (#5109)
* improvement(integrations): validate BigQuery/Forms/PageSpeed + regenerate integration docs - BigQuery: mark null-defaulted outputs optional (get_table type/numRows/numBytes/creationTime/lastModifiedTime/location, list_datasets location, list_tables type, query totalBytesProcessed) - Google Forms: add response pagination (pageToken + filter params, nextPageToken output), fix pageSize visibility, advanced-mode pagination subBlocks + filter wandConfig - PageSpeed: add a 7th BlockMeta template (competitor benchmark) - Regenerate integration docs; add manual intro sections to new datagma/dropcontact/enrow/icypeas/leadmagic pages * fix(docs-gen): preserve apostrophes in tool descriptions when generating docs The doc generator extracted tool descriptions with a character class that excluded both quote types (['"]([^'"]...)['"]), so a double-quoted description containing an apostrophe (e.g. "Find someone's email") was truncated at the apostrophe — the generated docs/catalog showed stubs like "Find someone". Anchor extraction on the actual opening quote (single/double/backtick), matching the existing extractDescription helper, in both buildToolDescriptionMap and extractToolInfo. Regenerated docs restore full descriptions across all affected integrations (Apollo, Ahrefs, LeadMagic, Findymail, OpenAI, Slack, etc.). * fix(docs-gen): resolve tools defined in a sibling file + scope params per tool The doc generator located a tool's definition only by filename convention (decompress.ts / index.ts), so file_decompress — which lives in compress.ts alongside file_compress — fell back to index.ts and rendered an empty Input table. It also read the params block from the first tool in a multi-tool file, so every tool in such a file inherited the first tool's inputs/outputs. - getToolInfo: when no candidate file declares the exact tool ID, scan the whole tool-prefix directory for the file that does. - extractToolInfo: read the params block scoped to the specific tool, falling back to the full file for tools that inherit params via spread. Regenerated docs eliminate ~50 empty/incorrect input tables across integrations (clickhouse, rb2b, reddit, file, etc.); param-less OAuth-only tools correctly keep an empty input table. |
||
|
|
80735b424b |
fix(locks): enforce workflow/folder locks on the agent + close manual-UI create gaps (#5107)
* fix(locks): enforce workflow/folder locks on the agent + close manual-UI create gaps The copilot/agent workflow & folder mutation tools and the edit_workflow tool bypassed lock enforcement, so the agent could edit a locked workflow and move/create workflows into a locked folder. Add assertWorkflowMutable/ assertFolderMutable guards (from @sim/workflow-authz) to every agent mutation path, mirroring the REST API. Also close two parity gaps on the manual-UI REST side: creating a workflow into a locked folder and creating a subfolder under a locked parent were previously unguarded. The realtime collaborative canvas already enforced workflow-level locks server-side. * fix(locks): normalize optional folderId to null for assertFolderMutable * refactor(locks): hoist constant folder-lock check out of move loop; scope test mocks with Once * refactor(locks): drop redundant ensureWorkflowAccess fetch in rename |
||
|
|
83531452e5 |
fix(sidebar): prefetch chats + workflows so cold loads don't flash skeletons (#5104)
* fix(sidebar): prefetch chats + workflows so cold loads don't flash skeletons On a cold load (e.g. when the browser discards an idle tab and reloads), the persistent sidebar started with an empty React Query cache and client-fetched its chat + workflow lists, flashing loading skeletons. Prefetch both lists server-side in the workspace layout and hydrate them via HydrationBoundary, under the same query keys and mappers the client hooks use, so the sidebar paints populated on the first render. The prefetch runs concurrently with the existing org-settings fetch and never throws, so it adds no blocking work in the common case and falls back to client fetching on error. * refactor(prefetch): call data layer directly instead of internal HTTP self-fetch The sidebar and settings prefetches fetched their data by making internal HTTP requests to our own API routes. Replace those self-fetches with direct calls to shared server-side data functions, so each route handler and its prefetch read from one source with no extra network hop, serialization, or re-auth. - Extract listWorkflowsForUser (lib/workflows/queries) and listMothershipChats (lib/copilot/chat) from their routes; both routes and the sidebar prefetch now call them. - Extract getUserSettings/getUserProfile (lib/users/queries) shared by the settings/profile routes and their prefetches. - Subscription prefetch calls the existing getSimplifiedBillingSummary + getEffectiveBillingStatus directly. - Sidebar prefetch checks workspace access once via checkWorkspaceAccess and skips silently when denied. * refactor(prefetch): share mothership chat list staleTime constant Export MOTHERSHIP_CHAT_LIST_STALE_TIME from the chats hook and use it in both useMothershipChats and the sidebar prefetch, mirroring WORKFLOW_LIST_STALE_TIME so the prefetch and client hook can't drift. * fix(prefetch): keep subscription prefetch on the wire shape via internal billing API The billing summary returns Date fields (and an untyped metadata blob) that the JSON API serializes to strings. Calling the data layer directly would cache Date objects (App Router preserves them through RSC serialization), mismatching the string wire shape the client useSubscriptionData hook caches. Route the subscription prefetch through the internal billing API so server-hydrated and client-fetched data share the exact same shape. The date-free general-settings and profile prefetches keep calling the data layer directly. |
||
|
|
a82b44d36d |
perf(db): logs-list index, drop redundant indexes, replica routing, hot-path write cleanups (#5105)
* perf(db): logs-list index, drop redundant indexes, replica routing, hot-path write cleanups * fix(logs): keep /api/v1/logs on primary db — its permissions join is the auth gate, not replica-safe |
||
|
|
8fe090a3a1 |
fix(input-format): field not editable race condition (#5102)
* fix(input-format): field not editable race condition * remove dead code * simplify |