Commit Graph
5520 Commits
Author SHA1 Message Date
Waleed d9e793c094 improvement(mcp): bound pool create-invalidation to in-flight creates; document DCR match (#5777) 2026-07-20 14:51:02 -07:00
Waleed c6e8aac317 fix(sidebar): reset drag state on dragend so edge drop zones can't strand over first/last rows (#5774) 2026-07-20 13:50:42 -07:00
Justin Blumencranz 031a29546f fix(gmail-poller): resolve OAuth credential provider aliases (#5770) 2026-07-20 13:18:25 -07:00
Waleed 4d76385915 improvement(mcp): trace OAuth state writes to diagnose invalid_state clobber (#5772)
The MCP OAuth callback fails with invalid_state because the authorization
state is cleared/missing by the time the user authorizes — but clearState was
silent, so the clobber never surfaced in logs. Log every state save and clear
with a caller context so the exact source is visible on the next repro.
2026-07-20 11:51:09 -07:00
Theodore Li 58c87b2c43 feat(copilot): add share_file tool handler and surface file share state to the agent (#5656)
Implement the sim-side share_file server tool (resolves VFS path to file,
keeps the existing org-policy + permission + audit checks, delegates to
upsertFileShare). Register it in the tool router and generated catalog.
Stamp an ambient 'shared'/'shareAuthType' flag onto file metadata via one
batched share lookup, mirroring how workflows expose 'isDeployed'.

Validate the EFFECTIVE auth type when re-enabling a share: upsertFileShare
preserves an existing share's authType when none is passed, so validate the
stored mode (not 'public') or a re-share could reactivate a now-disallowed
password/email/sso share. Same fix applied to the share PUT route.
2026-07-20 13:58:16 -04:00
de4acb3cd6 chore(tiktok): simplify to draft-only Content Posting (#5703)
* Simplify TikTok to draft-only Content Posting

Remove Direct Post stubs and legacy Share Kit webhook triggers that Sim does not ship, and fix draft upload response handling so real TikTok errors are not misreported as size-limit failures.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix TikTok cleanup lint and Greptile review findings

Reject legacy mode:direct publish requests instead of silently drafting, keep deprecated Share Kit triggers registered for saved workflows, and apply biome format/import fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Finish TikTok greenfield draft-only surface

Remove Query Creator Info and video.publish, drop Share Kit trigger stubs, rename publish-video to upload-video-draft, and strip leftover Direct Post mode from the contract.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Hide TikTok from the toolbar until app review is approved.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-19 20:39:11 -07:00
Waleed fb439c99ca fix(mcp): deliver OAuth callback result over BroadcastChannel so COOP can't strand the connect (#5767)
* fix(mcp): deliver OAuth callback result over BroadcastChannel so COOP can't strand the connect

An MCP provider whose authorization page sets `Cross-Origin-Opener-Policy:
same-origin` (e.g. Gauge) severs `window.opener` when the popup navigates
through it, so the callback's `window.opener.postMessage` was silently lost and
the row hung on "Connecting…" forever — even when the callback succeeded.

- Signal completion over a same-origin `BroadcastChannel` instead, which is
  origin-scoped and immune to opener severance (the MDN/Chrome-recommended COOP
  workaround). Scope the message by workspaceId so other open workspaces ignore it.
- Log every OAuth callback failure with its reason + serverId. The early-return
  gates previously returned a silent `ok:false` popup close, so a failed
  authorization was undiagnosable from the server logs.

* fix(mcp): react to the OAuth broadcast only in the tab that opened the popup

A BroadcastChannel reaches every same-origin tab, so the previous workspace-id
scoping (which the success path carried but failure paths omitted) still let
unrelated tabs clear state, refetch, and show spurious toasts. Gate every
reaction on whether this tab actually has an in-flight popup for the result's
server (`popupIntervalsRef`) — strictly more precise than workspace scoping and
correct for both cross-workspace and same-workspace-second-tab cases. Removes
the now-redundant workspaceId from the callback message.

* fix(mcp): decouple OAuth result correlation from popup.closed

Cursor flagged two defects in the previous gate, both rooted in keying the
BroadcastChannel filter on `popupIntervalsRef` (the popup.closed poll map):

- A genuine completion was dropped whenever the poll had already removed the
  server's entry — and COOP can make `popup.closed` misreport, which is exactly
  the case this PR targets, so the fix could silently fail to apply.
- A result without a serverId fell back to "any in-flight popup", waking
  unrelated same-origin tabs with spurious toasts/refetches.

Introduce `pendingFlowsRef` (serverId -> safety timeout) as the correlation
source of truth: cleared only on completion or a 10-min timeout (matching the
server OAuth start TTL), never by popup.closed. The popup.closed poll now only
clears the spinner (best-effort abandon UX) and never touches correlation, so a
real completion is always processed. Results without a serverId are ignored;
the initiating tab's safety timeout clears its own "Connecting…".

* fix(mcp): correlate the OAuth result on the state nonce, not serverId

Cursor flagged that a failure which can't resolve a serverId (notably
invalid_state) broadcasts ok:false with no serverId, so the serverId-keyed gate
ignored it and the initiating tab sat on "Connecting…" until the safety timeout
with no error feedback.

Correlate on the OAuth `state` instead — the per-flow nonce the callback echoes
on every result, success or failure. The client parses it from the authorization
URL and keys the in-flight map by it; the callback includes it on every response
via a `respond` helper. This is the canonical popup-OAuth correlation: it reaches
the initiating tab even when no serverId exists, and — because each flow has a
unique state — it also fixes the same-user-same-server-in-two-tabs edge a serverId
key left open. Results with no parseable state (a malformed callback) are still
ignored; that flow clears via its timeout.

* fix(mcp): drop a server's prior in-flight OAuth flow when it is retried

Each start mints a new `state`, so an abandoned attempt's safety timeout was
keyed under a different state than its retry and never cleared. In the contrived
case where both stayed pending ~10 min, the stale timer would clear the newer
flow's spinner. Sweep any prior in-flight flow for the same serverId on a new
start (replacing the can't-happen same-state check). Result delivery was already
correct; this makes the state machine airtight.
2026-07-19 11:05:16 -07:00
Waleed e337308390 fix(mcp): keep a legacy server row from blanking the whole server list (#5762)
Response-side Zod .catch() tolerance on the three strict-enum-over-free-text MCP columns (transport/authType/connectionStatus) so one legacy row can't fail the whole server-list validation; fork copy normalizes transport; create/upsert path resets connection status on any auth-type flip (mirrors update path); bulk discovery drops the positive tool cache on OAuth-pending. Request bodies stay strict.
2026-07-19 08:56:40 -07:00
Waleed bd636d4be3 feat(mcp): reuse warm connections for tool execution and discovery (#5760)
* feat(mcp): reuse warm connections for tool execution and discovery

* fix(mcp): make connection pool concurrency-safe and auth-scoped

* fix(mcp): decouple pool validity from updatedAt, widen dead-connection detection, guard ping race

* fix(mcp): retire pooled connections on auth failure and server delete

* improvement(mcp): defer bulk-discovery env resolution to the pool miss path

* fix(mcp): retire in-flight creates evicted mid-connect via server generation

* fix(mcp): use evicted-mid-create connections one-shot and decouple pool eviction from cache clear

* improvement(mcp): dedupe create thunks into buildClient, harden dispose against liveness race

* fix(mcp): close acquire-gap races (re-resolve after stale ping, skip closing entries)

* fix(mcp): retry auth failures in executeTool; simplify acquire re-check and clear generations on dispose

* fix(mcp): let bulk discovery reconnect a lost notification connection with current config

* fix(mcp): recover rotated credentials on discovery via shared fetchServerTools auth-retry

* chore(mcp): add debug logging for pool hit/miss/eviction observability
2026-07-19 01:25:47 -07:00
Vikhyath Mondreti 9560ffd2a0 fix(chat): run block execution state loader (#5759) 2026-07-18 13:18:45 -07:00
Waleed 32de4155d8 fix(mcp): auth-type handling, OAuth-pending UX, safe diagnostics + HTTP/2 (#5757)
* improvement(mcp): negotiate HTTP/2 for MCP transport

MCP servers are commonly behind HTTP/2 fronts (CDNs, cloud LBs), but the
SSRF-pinned undici Agent is HTTP/1.1-only unless it opts into h2 via ALPN. Add
an allowH2 option to createPinnedFetch (default false, so LLM-provider callers
are unchanged) and centralize the MCP h2 decision in one createPinnedMcpFetch
routed through the transport, OAuth probe, and SSRF-guarded fetch. Pinning is
unaffected: the pinned lookup forces every connection to the resolved IP.

* fix(mcp): correct auth-type handling, OAuth-pending UX, and safe error logging

- Classify a non-OAuth UnauthorizedError as an auth failure instead of an OAuth
  redirect, so static-bearer servers that merely advertise OAuth stop being
  diverted into the OAuth flow (fixes the class of server that couldn't connect).
- Reset a server to disconnected when it is switched to OAuth, so it can't
  falsely read as connected before completing its auth flow.
- Surface OAuth-pending state as 'OAuth authorization required' in the server
  list and refresh action instead of a generic 'Not Connected'.
- Return an actionable 422 for OAuth servers that don't support dynamic client
  registration.
- Redact error messages/cause/session-ids from MCP transport/connect logs via a
  shared getMcpSafeErrorDiagnostics helper.

* fix(mcp): reset connection on any auth-type flip, scope h2 to live transport

* fix(mcp): close pinned h2 Agent on disconnect and revoke OAuth tokens on auth-type change

* fix(mcp): release pinned Agent on failed connect and point DCR error at client-id/secret setup

* fix(mcp): make pinned Agent teardown idempotent to avoid double-destroy on cancel/failed connect
2026-07-18 13:18:08 -07:00
Waleed 369eef742f feat(library): add BYOK multi-model AI agent builder post (#5758)
* feat(library): add BYOK multi-model AI agent builder post

* fix(library): scope BYOK FAQ to providers in the actual BYOK contract
2026-07-18 13:03:53 -07:00
Waleed 1b06b6c60d fix(security): bound YAML alias expansion in file-parser (billion-laughs DoS) (#5756)
* fix(security): bound YAML alias expansion in file-parser to prevent DoS

The YAML parser called yaml.load() then JSON.stringify() on the result.
YAML aliases (*anchor) resolve to shared references, so yaml.load cheaply
builds a compact DAG, but JSON.stringify expands that DAG into a full tree,
duplicating every shared node. A crafted sub-1KB .yaml/.yml document could
therefore expand to hundreds of MB / GB during serialization, pinning CPU
and OOM-killing the shared parse/ingestion worker (CWE-776).

Add a bounded, iterative traversal that runs before JSON.stringify and
aborts once expanded node count, estimated serialized size, or nesting
depth exceeds safe caps. This detects the amplification against the compact
DAG before any allocation, and also terminates cyclic anchor structures.
Replaces the unbounded recursive getYamlDepth (which spread large arrays
into Math.max(...array), risking a stack overflow) with the same bounded
walk.

* harden YAML guard: charge keys + escaping, bound stack, fail closed

Addresses review findings on the alias-expansion guard:

- Charge object keys, not just values. JSON.stringify re-emits every key on
  each alias expansion, so an aliased object with a long key amplified without
  being counted. Keys are now charged against the size cap.
- Compute the exact JSON-escaped string length (quotes, backslashes, control
  chars) instead of a flat multiplier. Plain text is charged its true 1:1 size
  (no false rejection of large legitimate documents) while escape-heavy strings
  are charged their real, larger cost (no cap bypass).
- Count each node as it is enqueued and only push container nodes onto the
  traversal stack. A pathologically wide fan-out now trips a cap during the
  enqueue loop instead of first materializing millions of stack entries and
  exhausting memory inside the guard itself.
- Fail closed in POST /api/files/parse: a YamlComplexityError rejection is now
  re-thrown (mirroring isPayloadSizeLimitError) rather than silently falling
  back to storing the crafted document as raw text.

* yaml guard: charge lone surrogates at their escaped length

serializedStringLength treated surrogate code units as cost 1, but
well-formed JSON.stringify escapes a lone surrogate to \uXXXX (six units).
Charge lone surrogates as 6 and valid high+low pairs as-is (two units),
matching JSON.stringify exactly. Verified against JSON.stringify across
plain text, escapes, control chars, lone high/low surrogates, and valid
pairs.
2026-07-18 12:08:08 -07:00
Waleed 99f2ca7995 feat(library): add Best Relay.app Alternatives in 2026 post (#5755)
* feat(library): add Best Relay.app Alternatives in 2026 post

* improvement(library): clarify Sim free tier has usage limits in comparison table
2026-07-17 23:33:40 -07:00
Waleed c739dd6b1e improvement(mcp): align tool discovery timeouts and retries with MCP protocol standard (#5753)
* improvement(mcp): align tool discovery timeouts and retries with MCP protocol standard

- Raise tools/list idle timeout 10s -> 30s (derived from per-server config,
  clamped to max execution timeout) with a 60s hard cap via maxTotalTimeout,
  matching the SDK's DEFAULT_REQUEST_TIMEOUT_MSEC. Enable resetTimeoutOnProgress
  with an onprogress handler so slow-but-alive servers are not spuriously failed.
- Retry read-only tools/list on transient transport errors (timeout,
  connection-closed, 429/5xx, session 404/400, network) with jittered backoff.
  tools/call stays conservative (session-error retry only) since it is not
  idempotent. The MCP SDK does not retry POST requests, so the app owns this.
- Wire client.onerror so out-of-band transport failures are observed, not
  silently dropped.
- Map timeouts to a user-facing message and surface refresh/remove failures via
  the standard emcn toast instead of swallowing them.

* chore(mcp): trim comments to match codebase density

* fix(mcp): don't fail refresh with 500 when post-discovery workflow sync throws

Discovery already persists status and caches tools; a syncToolSchemasToWorkflows
failure was escaping the refactored try/catch and returning 500 despite the
refresh having succeeded. Guard the secondary sync so it degrades to zero
workflows updated instead.

* fix(mcp): keep tools/list absolute timeout ceiling hard

A configured per-server timeout above 60s was expanding maxTotalTimeout past
the intended absolute discovery ceiling. Clamp the idle timeout to the ceiling
too so tools/list can never hang the UI beyond it; connect() and callTool()
still honor the full configured/execution timeout.
2026-07-17 17:50:51 -07:00
Waleed 87edbc5d54 perf(mothership): restore static markdown parse for settled chat messages (#5751)
* perf(mothership): restore static markdown parse for settled chat messages

Settled/reloaded chat messages rendered through Streamdown's streaming
parser (remend + incomplete-markdown repair + per-block re-parse, running
the rehype raw/sanitize/harden chain once per block). Because rows are
virtualized, every up/down scroll remounted the messages crossing the
overscan boundary and re-paid that N-block cost — the scroll lag.

- Render never-streamed mounts (reloaded history, or an in-session message
  scrolled out of the virtualized window and back) with mode='static': one
  whole-document parse instead of streaming's per-block re-parse. In-session
  streaming keeps the streaming parser for its mount life (no drain flash).
- Cache Prism highlight output in a module-level bounded LRU so a code block
  re-highlights at most once across the unmount/remount virtualization does
  on scroll (a component useMemo would not survive the unmount).

* fix(mothership): don't cache fallback-highlighted code blocks

An unregistered language highlighted via the JavaScript fallback was cached
under its own name, so if that Prism grammar registered later in the session
a remount would keep serving the stale fallback render. Resolve the grammar
inside the highlight helper and skip the cache entirely on the fallback path.
2026-07-17 16:49:14 -07:00
Theodore Li a4e2fb94ac feat(admin): show recently impersonated users in admin panel (#5750)
* feat(admin): show recently impersonated users in admin panel

* improvement(admin): align user table with settings list idiom

* fix(admin): address review findings on recent impersonations

* fix(admin): exact server-side email filter for recent impersonation lookup
2026-07-17 16:44:38 -07:00
Justin Blumencranz c9baae7f2e fix(mcp): recover cleanly from OAuth failures (#5595)
* fix(mcp): improve OAuth failure recovery

* fix(mcp): address OAuth recovery review findings

* fix(mcp): prefer static bearer auth in connection tests

* fix(mcp): preserve discovery failure state

Treat static-header 401s as credential failures, keep OAuth failures pending, and prevent failed refreshes or reflected upstream errors from masquerading as connected state.
2026-07-17 16:38:21 -07:00
Waleed 7e975e7dae fix(confluence): index mirrored/included page content via rendered view format (#5746)
* fix(confluence): index mirrored/included page content via rendered view format

The KB connector fetched page bodies as body-format=storage, which only
carries unexpanded macro references (Include Page / Excerpt Include). Those
'mirrored' articles were stripped to empty content by htmlToPlainText and never
synced. Switch getDocument to body-format=view (supported on the v2 single-item
page/blogpost GET) so built-in include/excerpt macros render inline and the
included text is indexed.

* fix(confluence): invalidate existing doc hashes on representation change

The version-based contentHash meant already-synced mirrored documents (with
stale empty content) classified as 'unchanged' and never re-hydrated with the
new rendered view content. Embed a body-representation marker in the hash so a
representation change invalidates every previously-synced Confluence document,
forcing a one-time re-hydration that picks up the expanded include/excerpt text.

* feat(connectors): full resync re-hydrates rendered content (transclusions)

Version-based change detection can't see when a Confluence page's rendered view
changes because an *included* page was edited (the container's version doesn't
bump). Add a 'Full resync' path so that drift can be recovered:

- ConnectorMeta.rehydrateOnFullSync flag (set for Confluence)
- on fullSync, classifyExternalDoc promotes unchanged deferred docs to update and
  the hydration guard re-indexes unconditionally, so rendered content is refreshed
- fullSync threaded through the manual sync contract (query param), route, and hook
- 'Sync now' / 'Full resync' dropdown on the connector card

Incremental syncs stay hash-gated and cheap; only the deliberate full resync pays
the re-index cost.

* refactor(connectors): decouple rehydrate from fullSync deletion semantics

The transclusion refresh only needs re-hydration, but reusing the fullSync flag
also activated its deletion-cleanup semantics — which bypass three previously
unreachable safety guards (empty-listing wipe, listingCapped, and the >50%
mass-deletion threshold). Since fullSync had no caller before this PR, the new
'Full resync' button would have exposed all three to any KB editor.

Introduce a dedicated 'rehydrate' request that ONLY forces re-hydration + re-index
of already-synced docs. Listing and deletion reconciliation are identical to a
normal sync (all safety guards stay armed). fullSync's cleanup semantics remain
dormant and untouched.

* refactor(connectors): tidy rehydrate flag + gate Full resync to supported connectors

Cleanup/simplify pass over the connector changes:
- use shared booleanQueryFlagSchema for the rehydrate query param (typed boolean
  at the boundary instead of a hand-rolled 'true'/'false' string enum)
- move rehydrateOnFullSync onto the client-safe ConnectorMeta so the UI can gate on it
- only Confluence (rehydrateOnFullSync) shows the Sync now / Full resync dropdown;
  every other connector keeps its original one-click sync button (Full resync is a
  no-op for them, and this restores the pre-change one-click UX)
- wrap the sync trigger in a span so its tooltip still shows while disabled (cooldown)

* fix(connectors): forward rehydrate flag through the Trigger.dev worker

executeConnectorSyncJob (the production async sync path) destructured only
fullSync from the payload and forwarded only fullSync to executeSync, silently
dropping rehydrate. A manual Full resync would therefore never re-hydrate on the
default Trigger.dev path. Forward rehydrate too.

* fix(connectors): rehydrate forces a full listing so containers aren't omitted

A rehydrate request set forceRehydrate but left listing incremental. For a
connector that is both incremental and rehydrateOnFullSync, an unchanged
container page that transcludes a changed page would be omitted from the
incremental listing and never re-hydrated. Force a full (non-incremental) listing
on rehydrate so every document is seen; deletion-safety guards stay armed (unlike
fullSync). No-op for Confluence, which is already non-incremental.
2026-07-17 15:21:22 -07:00
Vikhyath Mondreti d637cbaac1 improvement(admin): remove included usage for enterprise provisioning (#5749) 2026-07-17 14:51:57 -07:00
Waleed 4c86757380 fix(billing): point enterprise Talk to sales at Cal.com instead of Typeform (#5748) 2026-07-17 14:50:04 -07:00
Waleed 13aaf03152 feat(billing): show invoice descriptions and cap in-app list at 5 (#5747)
* feat(billing): show invoice descriptions and cap in-app list at 5

* test(billing): model six-item Stripe page boundary in pagination mocks

* fix(billing): decouple Stripe scan page size from invoice display cap
2026-07-17 14:42:10 -07:00
Vikhyath Mondreti b2d9c02bb9 fix(chat): hosted key execution via call-integration-tool (#5745)
* fix(chat): hosted key execution via call-integration-tool

* fix optional hosted key
2026-07-17 14:04:35 -07:00
Waleed ec75084dbf fix(billing): standardize credit-usage summary font to proportional figures (#5744) 2026-07-17 13:58:35 -07:00
WaleedandMarcus Chandra fb86b499f8 feat(gitlab): dynamic access levels + group & membership ops + full group filters (#5743)
* feat(gitlab): dynamic access levels + group and membership operations

Access levels can now be bound to runtime references (e.g. a policy-table
lookup), not just picked from a static list. The three access-level fields
(accessLevel, memberAccessLevel, invitationAccessLevel) switch from dropdown
to combobox so a reference expression is accepted at Copilot save-time instead
of being rejected as an invalid enum value. The resolved value is validated at
execution time by coerceGitLabAccessLevel, which accepts an integer, a numeric
string, or a level name ("Developer"), and throws a clear error otherwise -
preserving the real safety property (no bad integer reaches GitLab) while
dropping the false one (levels must be known at design time).

Also closes demand gaps from the AskIT data:
- get_group / list_groups: resolve and list groups (provisioning critical path)
- list_user_memberships: admin GET /users/:id/memberships, gated in its
  description; the non-admin path is composing list_members

The access-level enum is now a single source of truth in tools/gitlab/utils.ts
(GITLAB_ACCESS_LEVELS) that the block options and runtime coercion both derive
from, replacing three inline copies.

* fix(gitlab): treat access level 0 ("No access") as a provided value

A runtime reference can resolve to the integer 0, but the block still gated
access-level presence with truthiness: required ops rejected 0 as missing and
optional ops silently omitted it. Add hasGitLabAccessLevel(value) (0 and '0'
are provided; undefined/null/'' are not) and use it for all six presence
gates so "No access" can be granted or set via a reference.

* feat(gitlab): expand group listing filters and harden access-level enum

- list_groups: add visibility, min_access_level, and all_available filters (documented on GET /groups) plus order_by/sort, now surfaced in the block and forwarded in params
- order_by widened to include GitLab's documented 'similarity' value
- access-level enum label 'Minimal Access' -> 'Minimal access' to match GitLab verbatim; coercion already case-insensitive
- min_access_level guard rejects 0 (GitLab floor is 5); reuse the access-level enum for the block dropdown (drops 'No access')
- tests: out-of-enum numeric-string coercion case + full list_groups filter mapping

* fix(gitlab): validate list_groups min-access-level and similarity ordering at execution time

Greptile round 1 (both P1):
- min_access_level: coerce via the shared access-level enum (coerceGitLabMinAccessLevel) and throw on out-of-enum values (31, 999) or 0, so a direct tool call fails loudly instead of sending an invalid filter to GitLab
- order_by=similarity now requires a non-empty search term (GitLab ignores similarity ordering without one); throws a clear error otherwise
- tests for both validations

---------

Co-authored-by: Marcus Chandra <mzxchandra@gmail.com>
2026-07-17 13:40:23 -07:00
Siddharth Ganesan 772b710e39 fix(mothership): canonical folder ids (#5741)
* folder fix

* fix bug
2026-07-17 13:13:15 -07:00
Waleed 92feb24cb9 perf(workflow): scope resize CSS-var writes to the container, not :root (#5738)
* perf(workflow): scope resize CSS-var writes to the container, not :root

Resizing the editor panel, terminal, output panel, and sidebar was still
laggy on large workflows even after the drag handles stopped re-rendering
React per frame. A CDP trace showed the cost was style recalculation, not
JS or layout: ~3.9s of Document::recalcStyle over a 50-move panel drag.

Root cause: each drag frame wrote its CSS variable to document.documentElement.
On a large document (~42k elements) any inline custom-property write on the
<html> root recalculates style for the whole tree — measured at ~77ms per
write, independent of what actually reads the variable (an unused var costs
the same). Writing the same variable to the element that consumes it recalcs
only that subtree (~0.5ms) — a ~150x reduction.

useDragResize now writes the variable to a caller-provided target element
during the drag and reconciles to :root once on release (so on-demand readers
and the pre-hydration script are unchanged): panel -> .panel-container,
terminal -> .terminal-container, output panel -> .terminal-container (both
consumers inherit it), sidebar -> .sidebar-shell-outer. The terminal's
expanded-threshold sync writes store state directly instead of setTerminalHeight
so it no longer touches :root mid-drag.

Measured (near-empty canvas, 50-move drag): panel style+layout 3891ms -> 56ms,
frame p95 91.6ms -> 8.4ms, main-thread blocking 4566ms -> 0ms; terminal
recalc 3899ms -> 16ms, blocking 5558ms -> 0ms; sidebar recalc ~77ms/frame -> ~1ms/frame.

* perf(workflow): keep float boundary-sync live during scoped resize drags

The resize hooks now write their CSS variable to the consuming container
element during a drag (not :root), so use-float-boundary-sync's MutationObserver
on :root no longer fired mid-drag and open floats (chat, search-replace,
variables) only re-clamped on release. Read each boundary dimension from its
scoped element with a :root fallback, and observe the container elements as
well as :root, so an open float tracks the drag live exactly as before.

* fix(workflow): finalize drag on unmount + clamp reads scoped output width

- useDragResize: unmounting mid-drag now runs endDrag (commit + drop the
  scoped override) instead of a bare cleanup, so navigating away can neither
  lose the resize nor strand an inline override on a surviving target element.
- terminal output-panel clamp: read the live --output-panel-width from the
  terminal element first (where a drag writes its scoped override), then :root,
  then the store, so a mid-drag terminal/window resize can't clamp against a
  stale value.

* fix(workflow): robust drag finalize — last-applied value, sidebar unmount, clamp skip

Addresses three review findings on the scoped-resize change:
- useDragResize: track the last applied value and commit that on release;
  only recompute from the pointer event while the target is still connected.
  On an unmount the target's rect can be detached, so a layout-reading compute
  (e.g. the output panel's) would return a degenerate MIN — committing the last
  shown value avoids persisting the wrong width, and keeps flick-safety on a
  normal release.
- use-sidebar-resize: finalize on unmount (run endDrag, not bare cleanup) so a
  drag interrupted by unmount persists the width and drops the scoped override.
  This matters more than for the panel/terminal because .sidebar-shell-outer
  lives in the workspace chrome and outlives the sidebar, so a stranded override
  would win over :root.
- terminal output-panel clamp: skip while an output-panel drag is active (its
  scoped inline override is present). That drag's own compute clamps every frame
  against the live terminal rect, and a store-driven :root write here would be
  masked by the inline override anyway.

* fix(workflow): sidebar flick-safety + clamp reads committed :root width

- use-sidebar-resize: compute the clamped width synchronously on each pointer
  move (storing lastWidth) and defer only the DOM write to rAF, so a fast flick
  released before the frame runs still commits the final pointer position
  instead of a stale frame or nothing.
- terminal output-panel clamp: when not mid-drag, read the committed
  --output-panel-width from :root (written synchronously by the store setter)
  rather than the React store value, which lags a render behind the commit;
  fall back to the store before any commit. Comment corrected to match.
2026-07-17 13:06:11 -07:00
Vikhyath Mondreti 345369266d fix(mothership): billing for mcps, hosted key tools (#5740)
* fix(mothership): attribution for direct exec tools

* fix hosted key charges for direct tool exec

* fix llm chat tool
2026-07-17 12:56:03 -07:00
Waleed 5eafa86992 feat(email): native Gmail API mail provider for GCP self-hosting (#5736)
* feat(email): native Gmail API mail provider for GCP self-hosting

Adds Gmail as a fifth transactional mail provider (Resend → SES → SMTP →
ACS → Gmail). GCP has no first-party SES/ACS equivalent, so the native
Google path is the Gmail API: a service account with domain-wide
delegation impersonates a Workspace sender (GMAIL_SENDER) and posts the
raw RFC 822 message (built via nodemailer's MailComposer — full parity
incl. attachments, replyTo, unsubscribe headers) to the media-upload
messages.send endpoint. The Workspace SMTP relay alternative is
documented against the existing SMTP provider.

* fix(email): review round 1 + audit hardening for Gmail provider

- a 2xx from Gmail with an empty/malformed body no longer surfaces as a send
  failure (the mailer's fallback chain would deliver the same email twice);
  covered by a regression test
- normalize bare-LF line endings in html/text bodies to CRLF (RFC 822) before
  composing the raw message
- add multi-recipient and text-only test cases
2026-07-17 12:55:21 -07:00
Theodore Li c9399f8413 fix(oauth): serialize version-guard date in slack fan-out sql (#5737) 2026-07-17 12:26:17 -07:00
Theodore Li 61de0b5808 feat(pii): custom user-supplied regex patterns for redaction (#5732)
* feat(pii): custom user-supplied regex patterns for redaction

* fix(pii): enforce custom-regex syntax + safety at the boundary schema

* improvement(pii): always wrap custom-pattern redaction token in angle brackets

* chore(pii): register guardrails_validate in the dev minimal tool registry

* fix(pii): coerce empty guardrails entity-type checkbox (null) so the contract accepts it

* fix(pii): keep detect-all when a custom pattern is added; custom patterns win overlaps
2026-07-17 15:20:58 -04:00
Siddharth Ganesan caa454aeca fix(mothership): bug fixes (#5735)
* fix(mothership): credential connect names, highlighted line, knowledge of connection

* fix(mothership): webhook url is now visible to the mothership

* fix(mship): tool name audit

* fix(mship): hosted key data

* fix(mship): show icons on question exed out

* fix(mship): redis replay

* fix(mothership): description and incremental vfs both nuked

* fix(ci): fix build

* fix(mship): tool names

* fix(mship): stream handling
2026-07-17 11:15:58 -07:00
Theodore Li f1deb31359 fix(oauth): coalesce slack token refresh per installation and preserve provider refresh errors (#5723)
* fix(oauth): coalesce slack token refresh per installation and preserve provider refresh errors

* fix(oauth): keep transient refresh failures errorless and size slack lock budgets past the provider timeout

* fix(oauth): let slack refresh followers poll for the lock's full lifetime

* chore(oauth): drop provider refresh-error surfacing to keep this PR slack-only

* fix(oauth): version-guard slack chain writes against concurrent connects

* fix(oauth): clear slack dead flag before connect fan-out
2026-07-17 13:30:49 -04:00
Waleed 7e6aeb2b7a feat(chat): favicon external links with secure link-preview tooltips (#5734)
* feat(chat): favicon external links with secure link-preview tooltips

* fix(link-preview): address review findings

- allow http fetches to match advertised http(s) link support
- fix meta content regex to handle apostrophes and either quote delimiter
- hash Redis cache keys so sensitive URLs are not stored verbatim
- add per-user rate limit to the outbound-fetching route
- render siteName-only previews instead of falling back to the URL

* fix(link-preview): redact full URLs from failure logs

* improvement(link-preview): render-time preview fetch, cheerio parsing, cleanup pass

- fetch previews when links render (emcn tooltip shows instantly — hover prefetch had no delay to race); tooltip reads the warmed cache, eliminating the URL-then-preview flash
- parse OG metadata with cheerio (already used server-side) instead of hand-rolled regexes + entity decoding, fixing double-decode and quote-handling classes
- drop the no-longer-needed prefetch hook; remove dead side prop on Tooltip.Content
- extract ExternalLink to a sibling module per component-size guidelines; fix TSDoc placement

* fix(link-preview): https-only previews and full-document parsing

- drop allowHttp: plain-http fetches would reach the URL validator's self-host loopback exception; previews are now explicitly https-only on both server (early null) and client (query never fires for http)
- parse the full capped document instead of truncating at the first <body> substring, which could match inside head scripts/comments and drop metadata

* improvement(chat): render mailto links as plain text
2026-07-17 10:10:30 -07:00
Waleed a19fce1cd8 improvement(workflow): zero-render drag-resize for panel, terminal, and output panel (#5730)
* improvement(workflow): zero-render drag-resize for panel, terminal, and output panel

Port the sidebar's pointer-capture + rAF + CSS-variable drag pattern to
use-panel-resize, use-terminal-resize, and use-output-panel-resize so a
drag writes only --panel-width/--terminal-height/--output-panel-width
per frame and commits to Zustand (one re-render + one localStorage
write) on pointerup. Previously every mousemove dispatched a store set,
re-rendering the whole always-mounted Panel tree (Chat/Editor/Toolbar)
and the terminal, plus a persist localStorage write per move. Also drop
the Panel's unused panelWidth subscription and drive the output panel
width via a CSS variable instead of React state.

* improvement(workflow): shared useDragResize hook + review fixes

Extract the drag mechanism into a shared useDragResize hook (pointer
capture, rAF-aligned apply, commit-on-release) consumed by the panel,
terminal, and output-panel resize hooks. Fixes from adversarial review:
commit the last computed value instead of reading the CSS var back (a
fast single-frame flick could be lost to a cancelled rAF, and a
pre-rehydration read returned '' -> NaN), floor the panel/terminal max
clamp at the minimum so narrow viewports can't invert the clamp, guard
pointerup/pointercancel by pointerId so a second touch pointer can't
kill the drag, and capture the terminal rect once on drag start instead
of per-frame getBoundingClientRect. Remove the now-dead isResizing store
state and centralize CONTENT_WINDOW_GAP in stores/constants.

* fix(workflow): compute drag value rAF-aligned from the latest pointer event

Run compute inside the rAF (before the CSS-var write, so any layout read
hits clean layout at most once per frame) and derive the final value from
the latest pointer event on release. The output-panel hook now captures
the terminal element on drag start and re-reads its rect per frame, so
the clamp stays correct when the terminal resizes mid-drag and the live
width can never exceed the current max.

* fix(terminal): clamp output panel against the live CSS-var width

The ResizeObserver clamp compared the persisted store width, which is
intentionally stale during a drag; a terminal shrink mid-drag could
overwrite the live width with a stale store value. Compare against the
live --output-panel-width variable (store as pre-write fallback) so the
clamp converges with the drag instead of fighting it.
2026-07-17 08:05:47 -07:00
Waleed 93ba3c48c5 feat(landing): X pixel conversion tracking on landing pages (#5731)
* feat(landing): X pixel conversion tracking on landing pages

* fix(landing): dedupe X pixel initial PageView by URL to survive Strict Mode effect replay

* fix(landing): scope X pixel URL dedupe to the tracker instance so same-URL returns to landing still track
2026-07-17 00:31:33 -07:00
Waleed 42b9d5f81d improvement(workflows): stop writing placeholder workflow descriptions (#5729)
* improvement(workflows): stop writing placeholder workflow descriptions

* fix(workflows): scrub placeholder descriptions at the import boundary

* test(workflows): pin import-boundary description scrubbing behavior
2026-07-17 00:25:04 -07:00
Waleed f6bb8e6d3e feat(storage): native Google Cloud Storage support for self-hosting (#5728)
* feat(storage): native Google Cloud Storage support for self-hosting

Adds GCS as a third object-storage backend with full parity with S3 and
Azure Blob: uploads, streaming downloads, deletes, head, V4 signed URLs
(single + batch), and browser/server multipart uploads via the GCS XML
API. Selection precedence is Azure Blob > S3 > GCS > local disk.

- new provider client at lib/uploads/providers/gcs (cached singleton,
  ADC/Workload Identity or inline GCS_CREDENTIALS_JSON auth)
- per-context GCS_*_BUCKET_NAME config wired through getStorageConfig
- shared getServeStoragePrefix() replaces hardcoded blob/s3 serve paths
- docs (object-storage, environment-variables), .env.example, helm
  values.yaml + values-gcp.yaml storage section

* fix(storage): review round 1 — GCS per-context bucket fallback + ETag quote normalization

- getGcsConfig falls back to the general bucket for every context (GCS bucket
  names are globally unique, so the S3-style sim-execution-files literal default
  would point at an unowned bucket; empty per-context buckets previously made
  uploads and downloads disagree)
- completeGcsMultipartUpload restores quotes on ETags stripped by the shared
  browser upload client before building the completion XML
- docs/.env.example/helm updated for the fallback behavior

* fix(storage): review round 2 — route chat authz and execution-URL detection through getStorageConfig

- getChatStorageConfig delegates to getStorageConfig('chat') (identical for
  S3/Azure, picks up the GCS general-bucket fallback instead of reading the
  raw chat config and rejecting valid chat files)
- parse route resolves the execution bucket via getStorageConfig('execution')
  for all providers, so GCS execution files in the fallback bucket are still
  recognized as our own objects

* fix(storage): validation pass — gcs serve-prefix parity in key parsers + CORS doc fix

- extractStorageKey, extractFilename, and extractEmbeddedFileRef now strip the
  gcs/ serve prefix like s3/ and blob/, so direct-uploaded files on GCS parse,
  delete, download, and embed correctly (previously only the serve route knew
  the prefix)
- file-download storageProvider union includes 'gcs'
- completeGcsMultipartUpload defensively rejects a 200 response carrying an
  XML error document
- docs: CORS example lists concrete x-goog-meta-* header names (GCS matches
  responseHeader entries exactly; wildcards are only supported for origin)
2026-07-17 00:15:14 -07:00
Vikhyath Mondreti 86e6e1d26c feat(forking): excluded workflows (#5727)
* feat(forking): excluded workflows

* improve sync preview
2026-07-16 20:23:26 -07:00
Vikhyath Mondreti 442eabaf27 improvement(checkout): enforce team/enterprise-only org subscription, block double-covered personal checkouts (#5715)
* improvement(checkout): enforce team/enterprise-only org subscriptions, block double-covered personal checkouts, and drop renewal-triggered workspace detach

* close race with personal pro / org inclusions

* address comments

* fix(billing): compute org coverage independently of the personal-sub lookup and fail closed on unverifiable plan writes
2026-07-16 19:16:44 -07:00
Theodore Li 9d5ed38cd1 feat(table): per-plan table dispatch concurrency with env overrides (#5720)
* feat(table): per-plan table dispatch concurrency with env overrides

* fix(table): enforce shared concurrencyKey cap on database batchEnqueueAndWait

* refactor(table): thread dispatch concurrency via invocation instead of persisting it

* improvement(table): collapse dispatch concurrency env vars to FREE/PAID
2026-07-16 21:16:07 -04:00
Theodore Li 1da346b703 feat(triggers): service-account credentials in the hubspot trigger + token-SA name-collision 409 (#5693) 2026-07-16 19:11:57 -04:00
Waleed 5944c7c54d feat(library): add best AI agent platforms 2026 comparison article (#5722)
* feat(library): add best AI agent platforms 2026 comparison article

* fix(library): count all eleven compared platforms and add Dust/Lindy table rows
2026-07-16 15:56:27 -07:00
Waleed 01ffacc2ed improvement(chat): give wsres resource links the clickable chip treatment (#5719) 2026-07-16 15:49:34 -07:00
Waleed db85ae998f fix(chat): align resource mention icon spacing with mention chips (#5718) 2026-07-16 14:13:10 -07:00
Waleed a218ebe4ed fix(sidebar): align workspace header loading-state spacing with chip geometry (#5717) 2026-07-16 14:09:59 -07:00
Waleed cea1c8940a feat(providers): add Kimi (Moonshot AI) provider (#5716)
* feat(providers): add Kimi (Moonshot AI) provider

* fix(providers): preserve reasoning_content in kimi tool loop
2026-07-16 13:59:42 -07:00
Waleed 2f9144ebe3 improvement(ci): timeouts, docs-only PR skip, fork-isolated caches, Node pin; remove i18n workflow (#5714)
* improvement(ci): job timeouts everywhere, docs-only PR skip, event-scoped sticky disks, Node pin; drop dead i18n workflow

- timeout-minutes on every runnable job (defaults ran hung jobs to the
  6-hour cap — the i18n workflow burned three full 6-hour runs in Feb
  before its schedule was pulled)
- paths-ignore on the pull_request trigger: docs content and markdown
  don't affect the app build or images; push runs stay unfiltered
- sticky-disk keys scoped by event name so fork PR runs never share a
  disk with the push runs that feed production image builds
- node-version pinned to 22 (was 'latest', non-deterministic)
- delete i18n.yml: schedule already removed after repeated 6-hour hangs,
  workflow_dispatch-only since, comments stale

* improvement(ci): 45m migrate timeout (covers 30m lock wait), fork-namespaced PR sticky disks

- migrate.ts waits up to 30 minutes for the migration advisory lock
  (LOCK_ACQUIRE_DEADLINE_MS); the 15m job timeout would preempt that
  designed wait, so the bound is 45m
- fork PRs now get their own sticky-disk namespace so an untrusted fork
  run can't poison the disks that trusted internal-PR runs restore
2026-07-16 13:35:51 -07:00
Waleed 46f6a9ba77 improvement(ci): promote superseded first-attempt runs forward (#5713)
* improvement(ci): promote superseded first-attempt runs forward

The stale-promotion guard skipped any run whose commit was no longer the
branch head. If commit A passed its gate but was superseded mid-run by
commit B, and B then failed tests, A's promotion was skipped and the
deploy tags stayed on pre-A code with no automatic recovery (Cursor
finding on #5712).

A first-attempt run promoting an ancestor of the branch head is always a
forward deploy — runs on a ref are serialized by the concurrency group,
so nothing newer can have promoted first. Only re-runs of superseded
commits (a rollback attempt) and force-pushed-away commits are skipped.
Same semantics applied to the GHCR latest-tag guard.

* fix(ci): grant contents:read to create-ghcr-manifests for the compare-API guard

Job-level permissions replace the workflow defaults, so packages:write
alone left the guard's compare call 403ing — STATUS=unknown would have
silently skipped the GHCR latest tags on every main push.
2026-07-16 13:03:01 -07:00
8adeaab8a5 feat(gitlab): access, membership, and user-admin operations (#5710)
* feat(gitlab): add access, membership, and user-admin tools

Adds member, invitation, access-request, SAML group link, and user
administration tools to the GitLab integration. Resource-scoped ops work
against projects or groups; user-admin ops require an admin token. All tools
reuse the existing host/SSRF guard via getGitLabApiBase and add a shared
getGitLabResourcePath helper.

* feat(gitlab): wire access operations into the GitLab block

Adds the new operations to the block dropdown and tools access list, with a
named access-level dropdown (enum in, integer out), first-class expires_at,
a /members/all default (direct-only opt-in), resource-type selector, and
member_role_id passthrough.

* test(gitlab): cover access operations

Covers the access_level enum-to-integer coercion, the /members/all default vs
direct-only, the 409-duplicate-add soft success, invitation per-email error
handling, user-status-action response parsing, and getGitLabResourcePath.

* docs(gitlab): document access and membership operations

* Update apps/sim/blocks/blocks/gitlab.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix(gitlab): address review findings

- update_user now sends admin:false so the Administrator switch can demote
  (an untouched switch stays undefined and leaves the flag unchanged)
- expose the access-level dropdown for Update Invitation
- normalize comma-separated invite emails so spaced multi-email input works

* fix(gitlab): make update-invitation access level optional

Update Invitation now uses a dedicated dropdown that defaults to 'Leave
unchanged', so updating only the expiration no longer silently resets the
invitation's access level to Developer. The level is sent only when explicitly
chosen.

* fix(gitlab): validation pass — SAML provider param, member/invitation query filter, moderation user guard, registry order

* fix(gitlab): apply 10-agent validation findings across all 62 tools

- MR draft flag now applied via Draft: title prefix (GitLab has no draft body param)
- update_user only sends admin when a real boolean (untouched switch serialized null and could demote admins)
- add_member 409 soft-success now verified against the conflict body
- auto_merge sent alongside deprecated merge_when_pipeline_succeeds
- job log capped at 200k chars, file content at 1M chars, with truncated outputs
- MR diffs signal hasMore beyond 100 files
- wire dropped params: update_issue milestoneId, MR milestone/squash/removeSourceBranch, pipelines ref, tree ref, branches search, commits since/until/path/author, update_file lastCommitId, jobs includeRetried, create_user forceRandomPassword
- complete pipeline/job status enums, access-level enums, widen stale type unions
- guards: update_invitation requires a change; create_user requires a password strategy
- fix double-encoding trap in path descriptions; doc-accuracy touch-ups

* fix(gitlab): review round 1 — dedicated no-default access level for update member, expiration clearing via explicit empty string

* fix(gitlab): explicit Clear Expiration toggle for update member/invitation

* feat(gitlab): expose full documented API surface across tools and block

- membership: add-by-username, remove-member cleanup flags, list-member filters (user_ids/state/seat info), invite_source
- listings: search/visibility/owned/membership, assignee/milestone filters, source/target branch filters, per-domain order-by + sort direction
- CI: pipeline variables + spec:inputs, manual-job variables
- repo: commit authoring (start branch, author, execute flag), release tag message + asset links, cross-fork compare + unidiff, internal notes
- catalog: access-governance template + member-provisioning and access-request-audit skills
- hardening from 3-agent validation: declared release params, tolerate single-object asset links, NaN guard on assignee filter, null/scalar JSON rejection

* fix(gitlab): tri-state controls for update-op booleans (executable flag, MR squash/remove-source-branch)

---------

Co-authored-by: Marcus Chandra <mzxchandra@gmail.com>
Co-authored-by: mzxchandra <129460234+mzxchandra@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-07-16 12:00:12 -07:00