v0.6.88: mutex lock on oauth refresh, files export fix, hubspot trigger, search & replace UX, kb connectors multi-select, mcp negative cache#4717
Conversation
…flow (#4705) * improvement(hubspot): OAuth-native polling trigger replacing webhook flow * feat(hubspot): property autocomplete, multi-filter, property-changed, list-membership, pipeline/owner dropdowns * fix(hubspot): freeze cursor on failure + request full OAuth scopes * chore(api): bump API route baseline from 749 to 753 for HubSpot selector routes * fix(hubspot): make eventType required conditional on visibility * improvement(hubspot): align trigger name and longDescription with poll-trigger conventions * fix(hubspot): encodeURIComponent on search path segment for defense-in-depth * fix(hubspot): cursor-based seed for list_membership polling * fix(hubspot): Map-backed property snapshot + drop redundant filter parse
* improvement(search-replace): pass down to subblocks: * fix local lowercasing bug
#4710) * feat(mailer): add AWS SES and SMTP providers with auto-detect fallback * fix(mailer): cast SES options to bridge duplicate @aws-sdk type identities * fix(mailer): dedupe aws-sdk-sesv2, address review feedback - Force a single @aws-sdk/client-sesv2 install via root package.json overrides; @types/nodemailer pulled in a nested copy whose nominal class brand made the two SDK type identities incompatible, breaking the CI build. With one install the cast disappears. - Batch result message now reports successCount instead of sendable.length when entries are skipped, so "5 emails sent" no longer overstates delivery on partial failures. - SMTP provider now warns when SMTP_HOST is set without SMTP_PORT, and when only one of SMTP_USER/SMTP_PASS is set — both previously silent misconfigurations. - SMTP_SECURE schema is z.boolean() to match every other boolean in env.ts; runtime parsing is still handled by envBoolean. - Strip the verbose TSDoc comments I had added. * fix(mailer): exact sent counts in batch results, restore SES type cast - mergeBatchResults: data.count and the message now report only emails that were actually delivered, not skipped-unsubscribed ones (they returned success: true and inflated the count). Empty-sendable branch distinguishes "all unsubscribed" from "mixed skip/failure" so the message stops lying when some entries fail validation. - ses.ts: revert the package.json override approach (bun honors it locally but CI still installs a nested @types/nodemailer copy). Reinstate the `as unknown as` cast with a single-line WHY comment. * fix(mailer): annotate double-cast in ses provider for strict api-validation * fix(mailer): batch degrades isUnsubscribed errors to per-entry failures A transient DB error in isUnsubscribed used to abort the whole batch because the call sat outside the per-email try/catch in prepareBatch. Wrap the unsubscribe check inside the same catch so a rejection becomes a per-recipient failure, matching sendEmail's behavior. Lock it in with a regression test.
…ge extraction (#4711) * improvement(kb-connectors): multi-select fields + Slack bot/app message extraction Adds multi-value support to KB connector configuration fields and applies it across 8 connectors: Jira (projects), Confluence (spaces), Slack (channels), Microsoft Teams (channels), Google Calendar (calendars), Gmail (labels), Notion (databases), and Linear (teams + projects). Each connector emits byte-identical externalId for legacy single-value configs so existing rows reconcile in place via the sync engine's externalId-keyed matching. Framework changes: - ConnectorConfigField gains `multi?: boolean` - New `parseMultiValue` helper in @/connectors/utils - useConnectorConfigFields state model upgraded to string|string[] - ConnectorSelectorField renders Combobox in multi-select mode when `field.multi` - Add/edit connector modals handle array values end-to-end Per-connector specifics: - Jira: JQL `project in (...)` for 2+ keys, `project = X` for one - Confluence: routes through CQL `space in (...)` when multi; v2 fast path stays for single+no-label; also fixes selector returning space.id instead of space.key - Slack: loops per channel emitting one document each; extracts text from attachments and Block Kit blocks (incl. nested attachment.blocks where GitHub embeds PR bodies); contentHash bumped to slack-v2: to force one-time re-embed - Microsoft Teams: loops per channel within a single team - Google Calendar: compound cursor across calendars; single-calendar keeps legacy externalId/contentHash for zero-churn - Gmail: (label:A OR label:B) with quoted-form for labels with spaces - Notion: sequential walk via JSON compound cursor; single-DB keeps bare cursor - Linear: GraphQL IdComparator.in for multi, eq for single * fix(kb-connectors): valuesEqual treats legacy scalar as equal to multi-array Existing connectors created before multi-select store sourceConfig values as scalars (e.g. projectKey: "ENG"). With the field now declared multi: true, resolveSourceConfig returns an array (["ENG"]), and the original valuesEqual fell through to a strict reference comparison — falsely flagging unsaved changes on open and triggering an unnecessary string→array shape rewrite on save. valuesEqual now normalizes both sides to string[] via CSV-split when either is an array, so persisted scalar and in-memory array of the same content compare equal. Single-value (non-multi) fields keep strict string equality. * fix(kb-connectors): GCal externalId on config downgrade, Slack silent skip, valuesEqual order - google-calendar getDocument: derive isMultiCalendar from the externalId's `:` separator instead of the current config count. Prevents duplicates when a user downgrades from multi to single calendar — previously the returned doc lost its `calendarId:` prefix and was treated as a new row by the sync engine, orphaning the original. - slack listDocuments: throw on unresolvable channel instead of silently skipping. Matches MS Teams behaviour. Silent skip would let the sync engine orphan-delete the previously indexed channel content if a bot was removed or a channel was archived/renamed mid-life. - edit-connector-modal valuesEqual: order-insensitive comparison for multi- select arrays via Set membership. Multi-select UI doesn't guarantee insertion order matches the server-returned order, so `["A","B"]` vs `["B","A"]` would otherwise flag false unsaved changes. * chore(kb-connectors): use emptyValue() fallback in isFieldPopulated for consistency Behavior unchanged — isValuePopulated('') and isValuePopulated([]) both return false — but reading the field-typed fallback inline matches the convention used elsewhere in the hook (coerceForField, handleFieldChange, resolveSourceConfig). * fix(kb-connectors): Linear projects selector loads across all selected teams When the team selector is in multi-select mode, the basic-mode projects dropdown was passing only the first team ID into the linear.projects selector context (via readFirst in resolveDepValue), so projects from other selected teams were invisible. resolveDepValue now joins multi-value parents into a CSV string so dependent selectors receive every selected parent ID. The /api/tools/linear/projects route splits the CSV teamId, fetches projects from each team in parallel, and dedupes by project ID. Single-team configs pass through unchanged (`split(",")` on a bare ID yields a one-element array). The AND-of-filters semantics in buildIssuesQuery is intentional and matches standard GraphQL filter behavior — a user filtering on teams [A,B] and projects [X,Y] gets issues in (A or B) AND (X or Y). With this fix the project dropdown now shows every project under any selected team so the user can compose the right project set. * fix(gmail-connector): always wrap OR-containing custom query, not just unwrapped ones The previous check `!/^\(.*\)$/.test(trimmedCustom)` was supposed to avoid double-wrapping an already-parenthesized expression, but it false-positives on inputs like `(from:alice) OR (from:bob)` where the parens don't bracket the whole string. Those would skip wrapping and the top-level OR would bind across the preceding label / category / date filters instead of the custom clause. Always wrap when an OR is present — double-parens are a no-op in Gmail search syntax, so `((from:a OR from:b))` parses the same as `(from:a OR from:b)`. Simpler than walking parens depth and provably safe.
* improvement(mcp): per-server tool queries + negative cache so one slow server can't block the workspace Move MCP tool discovery off the workspace-aggregated `Promise.all` fan-out and onto per-server React Query keys, matching how Cursor and Claude Code render remote MCP. `useMcpToolsQuery` is now a `useQueries` combiner: each server has its own cache entry, its own loading state, and a slow neighbor never gates the others. Public shape stays compatible with existing consumers. Add a short-TTL negative cache: when `listTools` fails (timeout, connection error, etc.) we mark the server unhealthy for 30s so subsequent discovery calls short-circuit instead of re-paying the timeout. OAuth-required errors are exempt so re-auth retries immediately. Drop `LIST_TOOLS_TIMEOUT_MS` from 30s to 10s to bound the worst-case first failure. Invalidations are per-server where the action is per-server (OAuth popup, per-server SSE event, refresh, update, delete). Bulk operations stay workspace-broad. Adds tests for the negative-cache behavior and the OAuth exemption. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * improvement(mcp): in-flight dedup + 2min negative TTL + no refetch on window focus Three small follow-ups on top of per-server tool queries: - Coalesce concurrent `discoverServerTools(userId, serverId, workspaceId)` calls into a single upstream `tools/list`. Races between OAuth-callback cache priming, post-OAuth UI refetch, and multi-tab loads no longer double-fetch the same server. - Bump negative-cache TTL from 30s to 2 minutes. Cleared on listChanged, OAuth completion, manual refresh, and the next successful discovery, so this floor only matters for genuinely dead servers — drops their floor traffic by 4x. - Disable `refetchOnWindowFocus` on per-server tool queries. listChanged SSE + mutation invalidations already cover real schema changes; alt-tab no longer triggers N parallel `tools/list` calls. * fix(mcp): address bugbot/greptile review on per-server tool discovery - Workspace-scoped `mcpKeys.serverToolsWorkspace(workspaceId)` prefix for bulk invalidations (create-server, refresh-all, SSE workspace fallback, OAuth fallback). The previous `mcpKeys.serverTools()` prefix was global and invalidated every workspace's tools cache. - `useMcpToolsQuery` folds `useMcpServers().isLoading` into the aggregate `isLoading` so mounting no longer flashes an "empty tools" state during the servers-list fetch. Aggregate `error` is suppressed when any per-server query already returned data so one slow server can't blank out the others. - `useForceRefreshMcpTools` invalidates the per-server query keys of servers whose force refresh failed, so stale tools don't linger. - `DiscoveryOutcome` error variant carries the original error, restoring the OAuth-exemption check that `getErrorMessage(...)` previously erased. - `discoverServerTools(userId, serverId, workspaceId, forceRefresh = false)` now consults the positive + negative cache by default. Per-server React Query refetches hit the cache instead of re-paying the listTools timeout; callers that explicitly bypass cache (refresh route, OAuth callback, bulk POST refresh) pass `forceRefresh: true`. Negative-cache hits throw a typed `McpConnectionError` so the route layer can surface a fast 503. * update icons * chore(mcp): remove dead query keys and trim verbose comments - Drop unused `mcpKeys.tools()` / `mcpKeys.toolsList()` — replaced by per-server keys, no remaining callers. - Trim narrative comments to keep only the non-obvious "why" notes. * fix(mcp): map negative-cache cooldown error to HTTP 503 `McpConnectionError` thrown when a server is in cooldown previously fell through `categorizeError` to a generic 500. Cooldown is a transient-unavailability condition, so route it to 503. * test(mcp): cover cooldown error → 503 categorization * fix(mcp): address second-round bugbot review - useMcpToolsQuery serverIds: filter on enabled + workspaceId match. Disabled rows no longer trigger discover calls that get negative-cached, and keepPreviousData on useMcpServers no longer races a workspace switch into cross-workspace discover requests. - Aggregate skips per-server data when that server's latest refetch errored, so a broken server's last-known tools no longer linger in the workspace view while its card shows an error. - discoverServerTools failure path drops the positive cache alongside writing the negative-cache marker. A cache-respecting follow-up now fails fast via cooldown instead of returning stale tools from a now-broken server. - useMcpTools.refreshTools drops the dead forceRefresh param — the per-server queryFn always sends refresh=false, so the flag was never effective. Callers wanting cache-bypass should use useForceRefreshMcpTools. * chore(mcp): trim verbose comments * fix(mcp): third-round bugbot review - discoverTools failure path now drops the per-server positive cache alongside writing the negative-cache marker, matching discoverServerTools' behavior so a workspace-aggregate failure doesn't leave stale tools cached. - useForceRefreshMcpTools filters disabled and out-of-workspace rows before fan-out so disabled servers don't 404 → negative-cache themselves. - Remove unused useMcpServerTools export — the aggregate goes through useQueries directly, no external consumer exists. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview HubSpot integration shifts to an OAuth polling model in docs and adds new API selector endpoints to fetch HubSpot Download/export responses now RFC 5987-encode filenames by reusing UI/UX updates: knowledge-base connector config fields now support multi-select values end-to-end (state, selector field, change detection, submission), Linear project selection supports comma-separated multi-team IDs, MCP tool discovery routes can force refresh after OAuth/refresh actions, and workflow editor sub-blocks (code, conditions, checkbox list, combobox) receive improved search highlight propagation/rendering. Reviewed by Cursor Bugbot for commit 0c96964. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0c96964. Configure here.
* fix(oauth): follower last-chance read after poll deadline * test(oauth): exercise last-chance read in follower timeout test

Uh oh!
There was an error while loading. Please reload this page.