v0.7.41: email fixes, MCP fixes, gemini 3.6 flash, GitHub runners fallback, mothership experience improvements, soft delete chats#5835
v0.7.41: email fixes, MCP fixes, gemini 3.6 flash, GitHub runners fallback, mothership experience improvements, soft delete chats#5835waleedlatif1 wants to merge 36 commits into
Conversation
…hangs (#5798) * fix(mcp): pin outbound connections to IPv4 to avoid unreachable-IPv6 hangs Root cause of the MCP 'connecting forever' / tool-discovery timeouts in production: SSRF pinning forces each outbound connection to a single resolved IP, which strips Happy Eyeballs' IPv4 fallback. dns.lookup(verbatim) returns the IPv6 address first for Cloudflare-fronted dual-stack hosts (Gauge, api.exa.ai), so the connection was pinned to IPv6 — but the production app subnets have no IPv6 egress (AWS NAT gateways are IPv4-only), so the pinned IPv6 connection connects into a void and hangs until the 30s timeout. Intermittent because the resolver rotates A/AAAA order; works on retry when it happens to pick IPv4. This is why h1.1 (#5797) did not fix it (protocol-independent) and why it never reproduced locally (dev machines have IPv6 egress). Empirically verified: pinning only the IPv6 address times out; preferring the IPv4 address connects in ~600ms. Fix: at both pinned-resolution sites (validateMcpServerSsrf and validateUrlWithDNS) resolve all addresses and prefer an IPv4 one; IPv6-only hosts still pin their sole address. No signature/threading changes; SSRF validation of the pinned IP is unchanged. * chore(mcp): trim inline comments on the IPv4-preference fix
…5799) Same class of bug fixed for MCP in #5798: SSRF pinning forces a single resolved IP, which strips Happy Eyeballs' IPv4 fallback. dns.lookup(verbatim) returns the IPv6 address first for dual-stack hosts, so a database host or 1Password Connect server that is dual-stack gets pinned to IPv6 — unreachable on IPv4-only egress (AWS NAT gateways) and hangs until timeout. validateDatabaseHost and validateConnectServerUrl now resolve all addresses and prefer IPv4; IPv6-only hosts still pin their sole address. SSRF validation of the pinned IP is unchanged (the selected address is the one validated and pinned).
…r icon sizes (#5802) - Re-rasterize the email wordmark from vector at 4x with baked-in vertical clear space so it renders crisp and is no longer clipped at the top/bottom; size the header logo box to 68x41 to match. - Normalize the footer social icons (X, LinkedIn, GitHub, Slack) to a uniform cap-height on 40x40 (2x) canvases so they read as the same visual size. - Add display:block + border:0 to the linked social icons to avoid Outlook's inline-image gap and blue link border.
…d wordmark's 41 (#5803)
…/ deprecated red) (#5805) * feat(models): sunset tiers for models mirroring blocks — legacy amber / deprecated red * fix(models): legacy model copy + exclude retired from copilot menu * fix(copilot): exclude retired models from blocks-metadata fallback menu
…angs (#5807) * fix(mcp): bound OAuth callback steps and log each phase to pinpoint hangs The MCP OAuth callback could hang indefinitely after burning state, with no error, no timeout, and no log — the request stalls somewhere between the burn and token persistence with no observable I/O. Wrap each awaited callback step (loadPreregisteredClient, mcpAuthGuarded, clearVerifier, discoverServerTools) with a per-step timeout so a stalled operation surfaces as a labeled error instead of hanging forever, and log start/done for every step, every guarded OAuth fetch phase (validate/request/read-body), and the token-exchange DB writes. The last start without a matching done names the exact stall point. * fix(mcp): widen callback step bounds and drop resolved IP from fetch logs - Raise mcpAuthGuarded step bound 60s->120s and discoverServerTools 30s->60s so a legitimately slow multi-leg exchange (each internal fetch is already 30s-bounded) can't be falsely reported as failed. - Log only a pinned boolean, not the resolved IP, so self-hosted private endpoint addresses don't reach log sinks. * fix(mcp): tolerate non-thenable returns in callback timedStep helper Promise.resolve(fn()) so a synchronously-returning (e.g. mocked) step can't throw on .catch — restores the callback route unit tests.
…r accounts and custom bots (#5800) * improvement(slack): merge slack_v2 auth into one credential picker for accounts and custom bots * improvement(slack): label the Sim app group in the merged credential picker * improvement(slack): move provider-specific picker copy and modal dispatch out of the credential selector
…d runners (#5808) * chore(ci): add CI_PROVIDER toggle between Blacksmith and GitHub-hosted runners * chore(ci): fail unrecognized CI_PROVIDER values over to GitHub-hosted runners
* improvement(mcp): let users re-open OAuth authorization anytime A server stuck on 'OAuth Authorization required' had no discoverable way to re-trigger the auth popup once it was closed or abandoned: the connect button lived only in the server detail view and hard-disabled while 'Connecting…', a state cleared by polling popup.closed (unreliable under COOP), so it could stay locked until the 10-minute safety timeout. - Add an 'Authorize'/'Reopen authorization' action to the server list row menu for unconnected OAuth servers, so re-auth is reachable without drilling in. - Make the detail-view button always clickable (reopens a fresh popup) instead of locking on an unverifiable 'Connecting…' state. * fix(mcp): guard OAuth start against double-click opening two popups Now that the connect button stays clickable, block re-entry while the /oauth/start request is in flight; cleared once it settles so a later reopen still starts a fresh flow. * test(mcp): cover OAuth popup double-click guard and reopen-after-settle * fix(mcp): kill prior popup poll before reopening so it can't clear the new flow Addresses a stale-poll race: an abandoned attempt's popup.closed interval could fire mid-reopen and clear 'Connecting…' for the fresh flow. * fix(mcp): retire prior OAuth flow's pending entry on reopen, not just its poll Drop the previous attempt's pending-flow map entry and safety timeout up front too, so a late BroadcastChannel result (settleFlow) can't clear the reopened flow's connecting state during the new /oauth/start await. * fix(mcp): preserve prior OAuth flow until reopen's start succeeds Reworks reopen concurrency so a superseded or failed reopen can't lose a real authorization: - Retire the prior flow only after the replacement /oauth/start succeeds; a failed reopen keeps it so its popup can still complete and be honored. - Guard the connecting-clear (settleFlow + popup poll) behind the in-flight start set, so a stale settle can't flicker the reopened flow's label. - Invalidate server queries on already_authorized so the UI reflects a server that authorized out from under the client. Adds a test for the already_authorized invalidation. * refactor(mcp): make OAuth connecting state deterministic via reference counting Replaces the scattered, race-prone stopConnecting/set-based tracking with a per-server attempt count: each start increments once and clears exactly once (fail, already_authorized, settle, or supersede), so the 'Connecting…' / 'Reopen authorization' label can never get stuck or flicker across concurrent reopens. Retire prior flows whenever a replacement start succeeds (including already_authorized); a failed reopen keeps the prior flow so it can still complete. Drops the unreliable popup.closed poll entirely — the label already invites reopening and the safety timeout guarantees clearing. Adds a test for the failed-reopen-preserves-prior-flow case.
- Pin ChipModalTabs to w-fit so the segmented pill always hugs its tabs instead of stretching full-width when dropped into a flex column - Migrate the mothership settings tab bar off a hand-rolled full-width underline bar onto ChipModalTabs - Fix undefined --border-secondary token (fell back to currentColor) in mothership -> --border, matching the admin settings precedent
…5816) * fix(mcp): don't blank the MCP tools page when tool discovery fails A tool-discovery error (one slow/failing server, e.g. a stalled transport) replaced the entire server list with an error banner. Gate the full-list replacement on serversError (the list genuinely failing to load) only; when the servers loaded, always render the list — each row already surfaces its own discovery state via toolsStateByServer, with a non-blocking notice above the list. Restores graceful degradation so one bad server can't hide the others. * fix(mcp): surface partial discovery failures per-row and in the notice When one server succeeds and another fails, the aggregate toolsError is suppressed (data exists), so the failure was hidden. Now: the notice renders on ANY per-server discovery error (not just all-fail), and each failed row surfaces its live discovery error instead of reading as '0 tools' before its stored status catches up.
…rd millisecond-tolerant (#5790) * fix(workflow-persistence): make persistMigratedBlocks' updated_at guard millisecond-tolerant * improvement(sockets): state improvements * address comments
…5811) * fix(custom-blocks): render and execute custom blocks as agent tools * fix(custom-blocks): address review findings on agent-tool execution * fix(custom-blocks): move executor tool id out of the custom-tool namespace
* feat(access-control): per-group chat-deploy auth modes + polish - Add a per-permission-group 'Auth modes chat deployments may use' control, mirroring the existing public-file-share auth-mode control, so admins can disable specific chat auth modes (Public, Password, ...) - Enforce it server-side on chat create/update via validateChatDeployAuth (ChatDeployAuthNotAllowedError -> 403), and filter the chat deploy UI's Access-control options to the allowed set (keeping the saved mode visible) - Remove the dead 'Template' deploy option (hideDeployTemplate had no consumer) - Shrink access-control block permission icons (size-[9px]) so they no longer touch their tile borders; normalize the tiles to size-[16px] * fix(access-control): gate copilot chat deploy + fix new-chat default auth - Enforce validateChatDeployAuth on the copilot executeDeployChat path (it called performChatDeploy directly, bypassing the HTTP-route gate) - Enforce on update only when the auth mode actually changes, so a grandfathered saved mode can be re-saved (e.g. title-only edits) - Chat deploy UI now grandfathers only the saved mode, not the unsaved 'public' default; snap a new chat off a disallowed default to the first allowed mode so it can't submit a value the server rejects * fix(access-control): keep a saved SSO chat mode selectable when SSO env is off Treat savedAuthType === 'sso' as SSO-enabled when building auth options so an existing SSO chat isn't dropped and silently downgraded by the snap effect. Mirrors the file-share modal. * improvement(access-control): require at least one auth mode when enabled Guard the chat-deploy and file-share auth-mode multiselects so the last allowed mode can't be removed while the feature is enabled — an empty allow-list would silently block every deployment/share. Disabling the feature entirely stays the job of the Hide Chat toggle / Public Sharing checkbox. * fix(access-control): never leave a new chat with zero selectable auth modes Surface SSO in the chat deploy options whenever the permission group explicitly allows it (not only when the env flag is on or the chat is saved as SSO), so a group whose only allowed mode is SSO can't produce an empty option set that strands a new chat on a disallowed default. Also drop a redundant trailing comment in the copilot deploy handler.
…rting at 16MB (#5810) * fix(pii): mask offloaded large payloads chunk-by-chunk and retry transient mask failures A block output past the 16MB inline materialization ceiling aborted the run before masking even started: the redaction path hydrated the whole offloaded value at once, and the pre-flight size assert fired on the manifest's total byteSize. Large-array manifests now page one stored chunk at a time (materialize -> mask -> re-store, rebuilt via the manifest writer with preview derived from masked items), so peak heap stays ~one chunk regardless of payload size. Single refs up to the 64MB durable cap hydrate with a raised budget and run serially outside the concurrency pool. Mask-batch chunk requests now retry transient failures (network errors, 408/429/5xx, honoring Retry-After) with jittered backoff, so a single ALB blip or Presidio pod restart no longer fails a whole payload's redaction. Nested-ref masking now runs the string pass before ref substitution, fixing a latent double-mask when a masked nested value shrinks back inline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ * fix(pii): retry runtime timeouts and socket closes in mask-batch chunks Verified end-to-end against a 26MB / 40k-record offloaded output: the chunk-wise path masks it in ~54s on a single local Presidio worker where the old path aborted at the 16MB ceiling. The exercise surfaced two more transient error shapes the retry classifier missed — runtime-level request timeouts (undici's default 300s headers timeout, Bun's TimeoutError) and mid-flight socket closes — both of which previously failed the whole payload's redaction on the first occurrence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ * fix(pii): gate the spaCy fast path on entities the loaded models can produce The registry's SpacyRecognizers claim every entity in Presidio's default NER-model mapping — including PHONE_NUMBER/AGE/ID/EMAIL, which exist for transformer de-identification backends and which no spaCy model can emit. The NER_ENTITIES derivation trusted that claim, so any request naming PHONE_NUMBER (present in nearly every redaction rule) silently forced the full spaCy pass and the regex-only fast path never fired. Intersect the claimed set with the entities the loaded models' actual NER labels map onto; the hard floor of core NER entities is unchanged, and a future backend that genuinely emits phone labels would re-gate automatically. Verified live: PHONE_NUMBER-only requests take nlp=skip with span parity against the full path, PERSON still forces NER, and a 26MB/40k-record block-output redaction with the realistic entity set runs entirely on the fast path (~3.2min vs ~15min projected full-NER on one worker). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ * fix(pii): hydrate oversized-chunk manifests serially, not just single refs Review finding: a manifest whose packer emitted a chunk past the inline ceiling (one item larger than the chunk target) hydrates that chunk with the raised 64MB budget inside the REF_CONCURRENCY pool, so several such manifests could hydrate oversized blobs concurrently — the exact heap scenario the serial path exists to prevent. The serial gate now covers any ref whose hydration can exceed the inline ceiling: oversized single refs and manifests containing an oversized chunk. Normally-chunked manifests stay pooled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ * fix(pii): serialize oversized hydrations globally across nested redaction passes Review finding: the serial gate was per-resolveReplacements invocation, so nested oversized refs discovered inside different pooled parents each got their own pool and could hydrate oversized blobs concurrently. A shared promise-chain gate now threads through the options from the entry points, and a reentrancy flag lets a gated ref's own nested oversized work run directly instead of deadlocking on the hold. Covered by a cross-parent max-in-flight assertion and a nested-oversized deadlock regression test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ * fix(pii): fail fast on a null mask-batch body; use sleep() in gate test A 200 response with a null JSON body threw TypeError on the data.masked read, which the retry classifier treats as transient — burning the full retry budget on a deterministic shape failure. Null-guard the body so it throws the non-retryable shape error immediately. Also swap the gate test's inline setTimeout promise for sleep() to satisfy check:utils, which failed CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Empirically: our client's first connect to these servers consistently hangs to our 30s timeout, then a retry connects in ~100ms (the server answers initialize in ~120ms from a normal client — proven via A/B). Treating that request timeout as a dead connection (isDeadConnectionError) retired the pooled connection, so the next discovery reconnected and paid the stall again — a connect/stall/ reconnect churn loop, and the cause of the intermittent 'Failed to discover' / disconnected states. A streamable-HTTP request timeout aborts only that request's own POST stream; the session stays healthy for the next request. So stop poisoning the lease on a timeout (matches the MCP SDK, OpenCode, and LibreChat, none of which retire a connection on a request timeout). Only a real transport close/404/reset retires.
…#5819) * diag(mcp): per-request phase logging on the streamable-HTTP transport Mirrors the OAuth guarded-fetch logging that isolated the /oauth/start body stall. Logs each transport request (host+method), the response headers with TTFB, and failures — so a first-connect stall shows whether it hangs before response headers (connect/request) or after (the SDK's stream read), which isolates the client-side first-connect stall we haven't yet root-caused. * diag(mcp): include error name/code in transport failure log Distinguishes abort vs connect-refused vs TLS vs timeout in the failure-phase data.
#5821) * fix(mcp): retire pooled connections after consecutive request timeouts Circuit breaker for the half-open-transport gap left by #5817: a lone timeout still keeps the session warm (retiring on every timeout caused the connect/stall/reconnect churn), but two consecutive timeouts with no healthy request in between retire the connection so a genuinely half-open transport can't serve repeated 30s failures until the liveness ping catches it. A healthy release resets the count. Matches the LibreChat pattern of counting connection-level failures rather than reacting to one. * fix(mcp): classify TimeoutError-named aborts as timeouts for the circuit breaker AbortSignal.timeout / undici surface a DOMException named TimeoutError whose message lacks 'timed out'; without this the breaker treated it as a healthy release and reset the streak. Adds name-based detection (incl. cause) + test.
…SearchHighlight into knowledge (#5822)
* fix(ci): make GitHub-hosted fallback actually build and test * fix(ci): drop unused build-args passthrough so the heap ceiling can't be overridden * chore(ci): trim comments to the non-obvious constraints * fix(ci): build the app image on a larger runner in GitHub mode
…rd (#5823) * fix(mcp): replace single-IP pinning with validate-at-connect SSRF guard Swaps the MCP transport + OAuth guard from pin-one-resolved-IP to the standard pattern (LibreChat getSSRFConnect): DNS resolves normally and EVERY socket connect filters the resolved addresses against the private/reserved blocklist, closing the validate-then-trust window a rebind could race and removing the non-standard mechanism implicated in the headers-then-no-body stalls (no reference MCP client pins IPs; a pinned attempt welded to a bad flow cannot escape, while retries rotate via resolver round-robin and succeed). Adversarially reviewed before shipping; both findings closed here: - Redirects are now followed manually with per-hop validation: an IP-literal redirect target (which bypasses ANY connect-time lookup - Node skips the custom lookup for numeric hosts) is checked explicitly, closing a metadata- endpoint redirect hole the old pin also had. - Custom request headers are dropped on cross-origin hops, so a redirect to a second attacker host cannot harvest configured auth headers. Policy gating unchanged: the guard activates exactly where the pin did (validateMcpServerSsrf non-null; allowlist mode / localhost-on-self-hosted stay unguarded). Non-MCP consumers of the pinned fetch are untouched. 397 tests incl. new rebinding, mixed-answer, IP-literal-redirect, and hop-cap coverage. * fix(mcp): restore IPv4 preference and harden the guarded redirect follower - Restore validateUrlWithDNS's prefer-IPv4 resolution (a stale working-tree hunk accidentally reverted #5798 for the still-pinned non-MCP consumers). - Validate the INITIAL url's IP-literal in followRedirectsGuarded, not just redirect hops, so the exported guard is self-contained. - Drop entity headers (content-length/type/encoding) when a 301/302/303 switches a POST to a bodyless GET, which undici would otherwise reject. - Lift method/headers/body/signal from a Request input instead of silently downgrading a guarded POST Request to a bare GET. * fix(mcp): annotate headers double-cast and cancel redirect body before throw paths - Add the missing double-cast-allowed annotation on the sanitized-headers cast (strict boundary audit). - Cancel the redirect response body before the hop-cap / blocked-target throws so those paths can't leave a socket checked out on the long-lived Agent. * fix(mcp): keep self-hosted private-resolving hosts unguarded (old-pin parity) A DNS alias resolving to loopback/private is only reachable on self-hosted, where the policy explicitly permits it; the guarded lookup would filter the address and strand the connect where the old pin connected. Both MCP gates (transport + OAuth guard) now route private/loopback resolutions over the unguarded path, same as the localhost carve-out. Test IPs moved off RFC-5737 TEST-NET (which is correctly classified reserved). * fix(mcp): pin (not skip) self-hosted private resolutions; refuse cross-origin body forwards - The private/loopback carve-out now keeps the LEGACY PIN to the validated address instead of falling back to unguarded fetch — preserving both the old behavior and its anti-rebinding property for self-hosted DNS aliases. - Cross-origin redirect hops now also refuse to forward a request body (307/308 preserve method+body; post-pin those redirects really dial the new origin, so an open redirect could exfiltrate OAuth client secrets). Bodyless cross-origin redirects still follow. Tests for both. * chore(mcp): true up stale pinned-era doc comments
…est (#5824) * fix(mcp): open the OAuth popup synchronously and bound the start request Two bugs behind 'pressed Connect/Reopen and nothing happened': - window.open ran AFTER awaiting /oauth/start, outside the browser's user activation, so the popup was silently blocked (worst on the add-server flow). Popup-first now: open about:blank synchronously in the click (named window, so a re-click focuses/reuses an existing authorization window), navigate it once the start returns; close it on failure/already_authorized; clear toast when genuinely blocked (and skip the request entirely). - /oauth/start had no client timeout, so a stalled start held the re-entrancy guard and the connecting label indefinitely. Bounded at 30s; on timeout the popup closes, the label resets, and retry is immediately available. * fix(mcp): harden popup-first reopen edges - Retire prior flows as soon as the named popup opens (the open already blanked any prior auth window; a failed start must not leave a windowless flow 'connecting' for the 10-minute safety timeout). - Check the COOP-fallback window.open result; when blocked, clear the state and toast instead of registering a windowless pending flow. - Feature-detect AbortSignal.timeout (Safari <16) with an AbortController fallback for the bounded /oauth/start. * fix(mcp): close the blank popup when post-start navigation fails * chore(mcp): correct connecting-count ordering comment
…istent (#5829) * improvement(mcp): make the OAuth experience visible and verbally consistent From a full UX-consistency audit of the MCP settings surfaces: - One name for one action: 'Authorize' / 'Reopen authorization' everywhere (list chip, row, detail) — was three different labels across surfaces. - The connecting state is now visible: the row subtitle shows a muted 'Waiting for authorization...' instead of continuing to shout the red 'OAuth authorization required' mid-flow. - The Authorize affordance is a visible chip on the list row (was buried in the overflow menu), so a blocked popup's 'retry' has an obvious target. - Removed the redundant aggregate discovery banner — every failing row already reports its specific error; two red messages for one failure. - The header Refresh chip no longer renders an error sentence as its label (short 'Failed'; the Status field carries the explanation). - Sentence-case sweep (Unnamed server, Not connected, Server name, Add MCP server / Edit MCP server, Add server, Test connection, Edit form, Delete MCP server), 'Search servers...' placeholder, row-subtitle/error text on the canonical tokens, and a Loading empty state instead of a blank flash. * fix(mcp): show stale-discovery failures, best-effort popup-close label clear, drop redundant branch - A failing latest discovery now shows its error even when cached tools exist (the stale tool count silently hid it). - Best-effort popup.closed poll clears only the 'Waiting for authorization...' label share; the flow entry stays registered so a completion that still arrives over the BroadcastChannel is honored (settleFlow skips the double-decrement). Under COOP misreport the worst case is an early label reset, never a dropped completion. - Remove the OAuth refresh-state branch made redundant by the 'Failed' change, plus its now-unused authType/error inputs.
…aming scroll (#5828) * improvement(mothership): stable thinking indicator and jump-free streaming scroll * fix(mothership): suppress shimmer over executing tool rows and seed chase interrupt baseline * fix(mothership): keep shimmer mounted through the slot collapse so it animates out * improvement(mothership): tighten transcript bottom padding, timed slot-exit latch, cleanup pass * fix(mothership): bridge hidden special-tag streaming with the shimmer * fix(mothership): hold sizer floor through reveal and reset chase deadline on park * improvement(mothership): swap actions into the thinking slot at settle, quicken the chase
) * feat(library): Best AI Agents for Data Extraction and RAG in 2026 * fix(library): correct CSV import claims, fix comparison link, drop duplicate FAQ body * fix(library): remove unsupported GDPR compliance claim * improvement(library): update connector source count to match registry (50+) --------- Co-authored-by: Sim Pi Agent <pi@sim.ai>
…#5833) * fix(mcp): follow tools/list pagination instead of silently truncating The SDK's listTools() returns a single page; a server that paginates via nextCursor was silently truncated to page one. Follow the cursor bounded by four independent budgets (50 pages / 1000 tools / 5 MB / 60s aggregate wall-clock) plus a repeated-cursor guard — a page cap alone can't stop a server returning a fresh cursor with no new tools. Partial results from earlier pages are kept when a later page fails; only a page-one failure throws. Matches the LibreChat capped-cursor-loop pattern; caps live in MCP_CLIENT_CONSTANTS. * fix(mcp): count UTF-8 bytes, keep empty partials, and log page-cap accurately Review fixes on the tools/list pagination loop: - Buffer.byteLength(..., 'utf8') instead of .length so non-ASCII schemas can't overshoot the 5 MB budget by counting UTF-16 code units. - Track pagesFetched (not tools.length) for the partial-success decision, so a valid-but-empty first page followed by a failing page returns [] instead of throwing and marking the server unhealthy. - Explicit reachedEnd/page-cap distinction so a natural finish on exactly MAX_PAGES isn't mislogged as truncated.
…ted (#5830) * feat(chat): soft-delete sidebar chats with restore from Recently Deleted * fix(chat): review round 1 — restore workspace authz, purge recheck, archived-list invalidation * test(chat): update SSE handler assertions for workspaceLists invalidation * fix(chat): bump updatedAt on restore, recheck retention cutoff in task cleanup * fix(chat): drop explicit feedback delete in task cleanup — chat FK cascade covers it * test(chat): cover restore route; guard legacy copilot delete from hard-deleting mothership chats * chore: revert unintended bun.lock drift from worktree install * fix(cleanup): recheck workflow archive cutoff on delete; export deletedAt in chat drain
|
Too many files changed for review. ( Bypass the limit by tagging |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Mothership sidebar chats move from hard delete to soft delete ( MCP OAuth callback steps are time-bounded and logged; settings UI surfaces per-server discovery errors, inline Authorize, and clearer copy without blanking the server list on tool fetch failure. PII fixes spaCy NER detection so entities like Mothership streaming UI refactors the turn-level thinking shimmer (contextual labels, fixed slot, actions swap on settle) and adds a scroll sizer floor to reduce jump while streaming. Smaller changes: Slack merged credential picker, 1Password Connect IPv4 pinning, model deprecated from Reviewed by Cursor Bugbot for commit 93fbf58. Configure here. |
…nsient failure (#5839) * fix(mcp): keep last-known-good tools instead of flashing red on a transient failure A connected server with cached tools was turning red 'Failed to discover' on a single transient discovery probe blip, even though the stored status stayed connected with tools — because useMcpToolsQuery dropped React Query's retained data on error, and the row's showDiscoveryError fired over a populated server. Now the aggregate keeps last-known-good tools across a failed refetch, and the row only hard-reds when there are genuinely no tools to show. A persistent failure still surfaces via the stored connectionStatus (gated by MAX_CONSECUTIVE_FAILURES), matching how Claude Code / LibreChat / OpenCode / VS Code handle a transient tools/list failure on a live connection. Validated against those clients' source + the MCP spec before changing; this supersedes the #5829 over-correction that showed the error even when tools existed. * fix(mcp): drop stale tools once a server is persistently failed, keep on transient Sharpens the last-known-good behavior with the transient-vs-persistent distinction the reference clients make: keep cached tools through a transient discovery failure (stored status still healthy) so a populated server doesn't blank, but drop them once the stored connectionStatus crosses its failure threshold to error/disconnected — so the shared aggregate the workflow editor consumes stops offering a dead server's stale tool schemas.
Uh oh!
There was an error while loading. Please reload this page.