Commit Graph
3719 Commits
Author SHA1 Message Date
Waleed 41fb86355d fix(webhooks): validate and pin EmailBison apiBaseUrl before outbound requests (#5415)
* fix(webhooks): validate and pin EmailBison apiBaseUrl before outbound requests

Route Email Bison webhook create/delete requests through the shared
DNS-validated, IP-pinned fetch used by the Teams and Slack webhook
providers instead of a raw fetch to the user-configured instance URL.

* fix(webhooks): restore NEXT_PUBLIC_APP_URL in emailbison tests, dedupe strict-delete warning log

Test env var mutation was never restored, risking cross-file leakage in
single-threaded vitest runs. Strict-mode deleteSubscription failures were
logged twice (once at the throw site with context, once generically by the
outer catch); the outer catch now skips its own log for errors already
logged at the throw site.
2026-07-04 15:37:09 -07:00
Waleed a1fbb5743d fix(stt): bound audio download response size (#5412)
* fix(stt): bound audio download response size

Cap the audioUrl download in the STT proxy route at the platform's standard 100MB file-size ceiling, matching the pattern already used by sharepoint/download-file and other external download routes. Classify size-limit rejections as a clean 413 instead of an unhandled 500.

* fix(stt): avoid double isPayloadSizeLimitError call in error handling
2026-07-04 15:36:55 -07:00
Waleed 1a371e51e9 fix(uploads): bound multipart body read in workspace-file and knowledge-document upload routes (#5413)
* fix(uploads): bound multipart body read in workspace-file and knowledge-document upload routes

* fix(uploads): avoid FormData-body stream race in bounded-read tests

* fix(uploads): share MAX_MULTIPART_OVERHEAD_BYTES constant across upload routes
2026-07-04 15:32:00 -07:00
Waleed b98c7c4136 fix(tools): bound download response size for drive/slack/onedrive (#5414)
Google Drive, Slack, and OneDrive download routes fetched provider file
content without a response size cap, unlike the SharePoint download route.
Add maxResponseBytes to each content fetch, reject Google Drive files whose
metadata size already exceeds the cap before starting the download, and map
the resulting size-limit error to a clean 413 response.
2026-07-04 15:31:21 -07:00
Waleed 04432b64a8 fix(copilot): validate credential-link URL scheme before rendering (#5416)
* fix(copilot): validate credential-link URL scheme before rendering

Only render the credential connect link as a clickable anchor when its
value resolves to an http(s) URL, reusing the isSafeHttpUrl helper
already used for chat file links.

* refactor(copilot): move isSafeHttpUrl to shared lib/core/utils/urls

Per Greptile's convention feedback: isSafeHttpUrl was consumed by both
chat and workspace/home but defined inside a feature-specific 'use client'
component. Move it alongside getBrowserOrigin (which it already depends
on) in lib/core/utils/urls.ts, matching this repo's shared-utility rule.
2026-07-04 15:19:08 -07:00
Theodore Li acf4afb1b5 fix(mothership): block subflow re-parenting in embedded workflow view (#5418) 2026-07-04 18:18:44 -04:00
Theodore Li 82eff5435d feat(custom-block): deploy a workflow as a reusable org-scoped block (#5407)
* feat(custom-block): deploy a workflow as a reusable org-scoped block

* fix(custom-block): reseed deploy form, guard duplicate publish, run child deployed

* test(custom-block): isolate custom-block rows fetch in execution-core test

* fix(custom-block): allow cross-workspace exec, org-scope authority, keep field ids, hide disabled

* feat(custom-block): run child under source owner's identity, workspace, and env

* fix(custom-block): bind publish authz to the source workflow's workspace

* fix(custom-block): gate edit/delete on source-workspace admin, not org admin

* chore(custom-block): rebaseline route count to 887 after staging merge

* fix(custom-block): sanitize failure output so it can't leak source workflow internals

* fix(custom-block): derive inputs and curated outputs from deployed state, not draft

* fix(custom-block): hide disabled blocks from the toolbar palette too

* fix(custom-block): bill nested + failed-run hosted cost; expose real inputs to the agent

* fix(custom-block): enforce enterprise + flag gate at every consumption path
2026-07-04 15:57:24 -04:00
Theodore Li 818fa00133 fix(mothership): stop chat perf decay from permanently-animated streamed messages (#5411)
* fix(mothership): stop chat perf decay from permanently-animated streamed messages

* fix(mothership): reset animation latches when a reused ChatContent gets replaced content

* fix(mothership): keep streaming parser on settled messages to kill the drain-swap flash

* fix(mothership): unify plain/special render branches to stop whole-message re-fade when options arrive

* improvement(mothership): hold render-phase animation latches in useState per updated hook rules

* fix(styling): translucent text-selection in form controls so field text stays readable

* improvement(styling): one lighter translucent text-selection color everywhere, no forced text color

* chore(styling): selection-muted tokens as 8-digit hex to match color token convention
2026-07-04 15:52:28 -04:00
Waleed 0249516cab fix(compare): swap LangChain logo, fix comparison-table horizontal scroll (#5409)
* fix(compare): swap LangChain logo, fix comparison-table horizontal scroll

Replace LangChainIcon's path with the official brand mark (light-blue
link icon) instead of the prior monochrome recreation.

The comparison table's grid cells were missing min-w-0, so a grid item
without it sizes to its content's max-content width instead of
respecting its column's fr track, pushing the whole table wider than
its container and forcing a horizontal scrollbar. Add min-w-0 to every
grid cell/header, and size the row-label column to minmax(140px,
max-content) instead of a guessed fr ratio, so it's exactly as wide as
its longest label ("Vetted first-party integrations") needs and no
wider, leaving the Sim/competitor value columns their full share.

* fix(compare): stacked mobile layout for the comparison table

Below sm (640px) a 3-column table has no room to be legible even with
the sticky label column, so switch to the standard responsive-table
pattern instead: each fact stacks as label -> Sim's value -> the
competitor's value, each value tagged with its product name since the
column headers are no longer directly above. Pure CSS (max-sm:/sm:
variants), no JS, keeping this a zero-hydration server component.

* fix(compare): fix SourceLink inline-anchor overflow, align table breakpoint

Root cause of the persistent table-overflow/mobile-scroll issues: SourceLink
renders a plain <a> with no explicit display, so it defaults to
'display: inline'. min-width and truncate are no-ops on inline elements, so
any fact with a source (nearly all of them) ignored its flex/grid parent's
width constraint and could force the whole row (and thus the whole table)
wider than intended, regardless of any container-level min-w-0/max-content
fix. Give the anchor 'block min-w-0' so width constraints and truncation
actually cascade down to the wrapped value.

Also aligns the table's mobile-stack breakpoint from an ad hoc sm (640px) to
lg (1024px), matching this route group's own tablet-and-below convention
(.claude/rules for the (landing) group), and fixes the stacked mobile cells
to override the cell's base items-center with items-stretch so the name tag
and value get a real full-width box to truncate within instead of shrinking
to their own content size with no boundary.

* fix(compare): add min-w-0 to ColumnHeader per Greptile review

ColumnHeader (the Sim/competitor logo header cells in the two fr columns)
still had default min-width: auto, so an unusually long competitor name
could size the header to its min-content width and push the grid wider
than its column allows, same root cause as the rows already fixed.
2026-07-03 20:58:13 -07:00
Waleed 759dddbf4c feat(billing): dedicated Credit usage page with date-range filter and CSV export (#5405)
* fix(billing): apportion per-row credit costs so they sum to the page total

Cursor Bugbot (medium): each row rounded its own dollar cost to
credits independently while the header total rounded the summed
dollars once — over enough rows those two roundings can visibly
disagree, the exact "line items don't add up to the total" class of
bug apportionCredits was already built to prevent (used by the trace
view / cost breakdown). Route now apportions each page's row credits
against that page's dollar sum instead of rounding rows independently.

Added a test with three sub-cent rows that would each independently
round to 0 credits (but sum to 1) to prove the reconciliation holds.

* fix(billing): dim stale credit usage rows while a new period loads

Cursor Bugbot (medium): keepPreviousData kept the prior period's rows
and total on screen while a newly selected period fetched, but the
dropdown label updated immediately — so during the transition the
displayed numbers were labeled under a period they didn't belong to.
Now reads isPlaceholderData (the standard TanStack Query signal for
"this data is a stale placeholder, not a fresh fetch for the current
key") and dims the list while it's true, matching the same flag
already used for this exact purpose in integration-skills-section.tsx.

* fix(billing): show "<1 credit" for rows apportioned to 0

Cursor Bugbot (low): with apportioned per-row credits, a row with a
real but sub-credit dollarCost can legitimately apportion to 0 credits
once a sibling row absorbs the shared rounding remainder — rendering a
flat "0 credits" reads as if nothing was charged, inconsistent with
formatCreditCost's "<1 credit" wording used elsewhere in billing.

Added dollarCost to the wire response (needed to distinguish a
genuinely free row from a rounded-to-zero one) and a small
formatRowCredits helper that only changes the label, not the
underlying creditCost number, so the page-total reconciliation from
the prior fix is unaffected.

* fix(audit-logs): fix broken Custom range picker, trim time-range presets

Custom range silently did nothing: the time-range trigger was a
ChipSelect (Radix DropdownMenu, modal by default), and selecting
"Custom range" opened the Calendar popover in the same tick the modal
menu began its close/focus-lock cleanup, trapping the popover
non-interactive. Swapped to ChipCombobox (Radix Popover, non-modal),
mirroring the already-working pattern in the main Logs page exactly.

Also trimmed the preset list from 11 to 8 entries (dropped Past 30
minutes/12 hours/14 days) so the menu fits without scrolling.

* feat(billing): dedicated Credit usage page with date-range filter and CSV export

Follow-up to #5391 per team feedback in Slack: move the credit usage
list out of the inline Billing section into its own page, redesign
rows to show source ("Chat", "Workflow: <name>") instead of a raw
model description + badge, and add real date-range filtering and
export.

- Billing settings now shows a compact glance (30-day total + a "View
  usage logs" link) instead of the full inline list.
- New /settings/billing/credit-usage page (sibling of [section],
  mirrors the secrets/[credentialId] detail-route pattern) with day
  presets (Today/7d/30d/All time) plus a working Custom range picker
  — the same ChipCombobox+Popover+Calendar wiring the audit-logs fix
  in this branch uses, not the broken ChipSelect pattern.
- Rows show the humanized source label, or "Workflow: <name>" for
  workflow-sourced events (new server-side workflow-name lookup,
  batched per page). Dropped the redundant badge and raw model
  description.
- CSV export of the currently-filtered logs via a new GET
  .../usage-logs/export route (mode: 'text' contract, synchronous
  single-response CSV — the dataset is a bounded per-user ledger, not
  a workspace-wide export, so no async job queue needed). Query-filter
  logic (date-range resolution, workflow-name lookup) is shared with
  the list route via shared.ts rather than duplicated.
- period/startDate/endDate live in the URL via a co-located
  search-params.ts; the list query keeps keepPreviousData +
  isPlaceholderData dimming during filter transitions, matching the
  behavior already shipped in #5391.

Verified live end-to-end: back link navigation, custom range picker
opens and applies, day presets, CSV export downloads and matches the
on-screen rows exactly (credits reconcile with the total), compact
Billing summary + link.

* refactor(billing): move workflow-name enrichment into getUserUsageLogs, dedup helpers

/simplify pass over the credit-usage-page branch (4 parallel review
angles: reuse, simplification, efficiency, altitude):

- getUserUsageLogs now LEFT JOINs workflow and returns workflowName
  directly (matching lib/logs/list-logs.ts's established pattern),
  eliminating the route-layer resolveWorkflowNames query that both the
  list and export routes previously ran independently.
- Added includeSummary (default true) to getUserUsageLogs so the
  export route's cursor loop can skip the cursor-independent
  SUM/GROUP BY aggregate it never reads — that aggregate was being
  recomputed on every page of a paginated export for no reason.
- Fixed an off-by-one in the export's pagination loop: `<=
  MAX_EXPORT_ROWS` let it fetch one more full page past the cap only
  to discard it; `< MAX_EXPORT_ROWS` with a shrinking per-page limit
  never overshoots.
- Deduplicated the SOURCE_LABELS map (was defined identically in both
  the page and the export route) into a shared, DB-free
  source-labels.ts both can import.
- Export route now builds CSV rows via lib/table/export-format.ts's
  toCsvRow/formatCsvValue instead of a hand-rolled escaper.
- Added formatApportionedCreditCost to conversion.ts so the page's row
  rendering shares its zero/sub-credit wording with formatCreditCost
  instead of re-deriving the same three-way branch.
- Replaced the generic requireStartDateForCustomPeriod<Schema> contract
  helper (nontrivial generic bound for a single four-line refine used
  at two call sites) with a plain shared error-options object.
- Removed the credit-usage page's dateRangeAppliedRef guard — a
  controlled Radix Popover never re-invokes onOpenChange in response
  to the parent's own setState call, so the guard was defending
  against a re-entrant close that can't happen.
- Added a modal prop to ChipSelect (forwarded to the underlying
  DropdownMenu, which already supported it) so a future call site that
  hits the same "modal select traps a same-tick Popover" bug the
  audit-logs Custom range fix worked around has a real fix available
  instead of having to swap components again.

Re-verified live end-to-end after the refactor: workflow-name
resolution, credit reconciliation, and CSV export all still correct.

* fix(billing): drop Dollar cost from the CSV export, strip inline comments

We only surface credits to the user, not the underlying dollar figure
— "Dollar cost" was the one place the export literally displayed a
dollar amount (the rest of the codebase uses dollarCost purely as an
internal signal to distinguish a sub-credit charge from a genuinely
free event, never rendered as a "$" value).

* fix(billing): export honors partial custom date range, surfaces truncation

Greptile (P1) and Cursor Bugbot independently caught the same bug:
handleExport only forwarded startDate/endDate when BOTH were truthy,
but the list query and both API contracts treat endDate as optional
for a custom period (defaults to now). A user landing on a bookmarked
?period=custom&startDate=... URL would see populated rows and an
enabled Export button, then get a 400 on click since the export
omitted the required startDate too. Fixed by forwarding each date
independently, matching the list query's existing behavior.

Also addressed Greptile's other two findings:
- The export route now sets X-Export-Truncated so a 5,000-row-capped
  download is visible to the user (a toast), not just a server log.
  Reading that header meant switching the trigger from a plain anchor
  navigation to fetch+blob — an anchor can't inspect the response
  before the browser commits to the download.
- resolveDateRange now throws explicitly when a custom period is
  missing startDate instead of silencing the null check with `as
  string`, which would have produced a silent Invalid Date if ever
  called without prior contract validation.

* fix(billing): remove the export's arbitrary row cap, fix a cursor pagination bug it exposed

A personal credit ledger doesn't have the same unbounded-growth problem
a workspace table does — capping the export at 5,000 rows just meant
long-tenured or high-usage accounts (exactly the ones most likely to
need a full export to reconcile a billing question) got silently
truncated. Replaced the cap with a 50,000-row circuit breaker that
should never fire in normal use (logged as an error, not a warning,
if it ever does) and bumped the page size from 500 to 1,000 to cut
round trips.

Removing the cap surfaced a real, pre-existing bug in
getUserUsageLogs's cursor pagination: a raw `sql` template embedded a
JS Date object directly as a bound parameter, which the postgres
driver can't serialize (unlike drizzle's typed gte/lte operators,
which already handle Date correctly elsewhere in the same function).
It only ever manifested past the first page, which nothing before
this export route's tight multi-page loop reliably exercised.
Replaced the raw sql template with drizzle's typed lt/eq/or/and
operators, matching the pattern already proven correct in this file.

Verified live: seeded 6,000 rows (past the old cap) and confirmed the
export downloads all of them in one request with credits reconciling
exactly against the total.

* perf(billing): skip the redundant cursor lookup when the caller already has it

The export loop holds the previous page's rows in memory, so its next
cursor's createdAt is already known — getUserUsageLogs was still
re-resolving it via an extra DB round trip every page regardless.
Added an optional cursorCreatedAt to skip that lookup when provided;
the list route's existing callers are unaffected since they don't
pass it. Verified live: zero cursor-lookup queries fired across a
3,500-row / 4-page export that previously issued one per page.

* fix(billing): apportion credits over the whole filtered set, not per page/call

Cursor Bugbot caught this: the list route apportioned each page's
rows against only that page's own dollar total, while the export
apportioned every exported row against the complete set's total.
Since apportionment depends on the full set, the same log could show
a different creditCost between the list and the export, or even
between two pages of the same "Load more" list — and the sum of every
loaded row could visibly drift from the "Total" header shown above
them once more than one page had loaded.

Extracted getUsageCreditsByLogId — a single, shared, whole-filter
apportionment lookup both routes now call instead of each computing
their own subset locally. The list route calls it once per page
request (same cost profile as the summary aggregate it already pays
for every page); the export calls it once before its pagination loop,
not per page, keeping the round-trip count this session's earlier fix
already reduced. Also extracted the condition-building shared by the
main query, the summary aggregate, and this new lookup into one
buildUsageLogConditions helper, removing a third copy of that logic.

Verified live: summed every row across 4 "Load more" pages and
confirmed it now matches the reported total exactly (previously could
drift), and confirmed the list and the export produce byte-identical
credit sequences for the same rows.

* fix(billing): make custom-range startDate/endDate nullable, not '' defaulted

startDate/endDate had no sensible static default (they're only ever
meaningful mid-custom-range), so defaulting them to '' via
.withDefault('') meant switching back to a preset left the URL
carrying startDate=&endDate= instead of dropping the params entirely.
Made them nullable (no .withDefault) instead, matching the identical
fields in the main Logs page's own search-params.ts. Verified live —
switching from a custom range back to a preset now clears both params
from the URL completely.

* feat(audit-logs): add CSV export, matching the Credit usage page pattern

Adds an Export chip to the top-right of the Audit Logs page (via
SettingsPanel's actions slot — the same header mechanism the Credit
usage page uses), downloading every audit log matching the current
search/type/date filters as CSV.

- New GET /api/audit-logs/export route: same session + enterprise
  admin/owner gating as the existing list route, reuses the shared
  buildFilterConditions/buildOrgScopeCondition/queryAuditLogs helpers
  (already using drizzle's typed operators for cursor pagination, not
  the raw-sql-with-embedded-Date pattern fixed elsewhere this
  session), and the same fetch+blob+X-Export-Truncated pattern the
  Credit usage export already established.
- Capped at 10,000 rows (not the 50,000 used for a personal credit
  ledger) — an org's audit trail can genuinely grow much larger than
  one user's usage history, so this is sized for "a reasonable audit
  review window," with truncation surfaced via a toast rather than
  silently dropped.
- Bumped the API-validation-contract audit's route-count baseline for
  the new route.

Verified live against a real enterprise org: switched to "All time,"
exported ~750 real audit log rows, confirmed formatting (quoted
descriptions, actor email fallback) and correct filter scoping.

* fix(billing): skip wasted credit apportionment on the summary fetch, block export during stale data

Cursor Bugbot caught two real issues:

1. The compact Billing summary glance (limit=1) only ever reads
   summary.totalCredits, but the list route unconditionally ran
   getUsageCreditsByLogId's whole-filter scan on every call including
   this one — pure wasted work for a caller that discards the result.
   Added an includeCredits query flag (default true, using the shared
   booleanQueryFlagSchema) so useUsageSummary can opt out; the main
   paginated view keeps it on since it genuinely needs per-row values.

2. Export stayed enabled while useUsageLogs held stale rows via
   keepPreviousData mid-filter-transition — a user could change the
   period/range and click Export before the new data loaded, exporting
   against the new filter while the table still showed the old one.
   Export is now also disabled while isPlaceholderData is true.

* fix(billing): deterministic apportionment order, block audit export during stale data

Cursor Bugbot caught two more real issues on the latest push:

1. Same stale-export bug as the earlier Credit usage fix, this time in
   Audit Logs: Export stayed enabled while useAuditLogs held prior
   rows via keepPreviousData, so it could export against a
   just-changed filter while the table still showed the old one. Now
   also disabled while isPlaceholderData is true.

2. getUsageCreditsByLogId had no ORDER BY before apportionCredits's
   largest-remainder tie-break, so which row absorbed a tied
   remainder credit depended on undefined Postgres row order — the
   same event's displayed credit could flip between calls (list vs.
   export, or even two successive requests). Added the same
   `orderBy(desc(createdAt), desc(id))` the main list query already
   uses, making the tie-break reproducible.

Verified live: 3 identically-costed rows produced the same tie-break
winner across 3 repeated requests (previously order-dependent).

* fix(billing): distinguish a failed summary fetch from zero usage

The compact Billing glance only branched on isPending, so once
useUsageSummary settled into an error state, totalCredits stayed
undefined and formatCreditsLabel(0) rendered "0 credits" — visually
identical to genuinely having no usage this period. Now shows the
same neutral "—" placeholder for isError as it already does for
isPending.

* fix(billing): gate the credit-usage page server-side for enterprise accounts

Greptile (P1) caught this: hiding the "View usage logs" link on the
Billing page for enterprise accounts doesn't stop direct navigation —
anyone with the URL (bookmark, shared link, browser history) could
still reach the full page and its CSV export, which enterprise
accounts were never supposed to see at all (billing is managed
out-of-band for them).

Added a server-side check in page.tsx before anything renders:
resolve the session, look up the highest-priority subscription, and
redirect to /settings/billing if it's enterprise — matching how
getHighestPrioritySubscription is already used elsewhere for
server-side plan checks, rather than relying on a client-side-only
conditional the way the Billing page's inline section does.

Also fixes loading.tsx: it was a Server Component (no directive)
passing a raw icon function reference into the client Chip component,
which fails RSC serialization. Added 'use client'.

Verified live in a real browser against both an enterprise account
(redirects to Billing before any credit-usage content renders) and a
non-enterprise account (reaches the page normally).
2026-07-03 19:37:17 -07:00
Theodore Li d5082013a2 fix(ui): surface silent validation and rejection errors across editors and modals (#5394)
* fix(ui): surface silent validation and rejection errors across editors and modals

* improvement(knowledge): toast chunk validation errors instead of inline footer text

* fix(tables): reject invalid date/number in expanded cell editor, dedupe invalid-input toasts

* fix(knowledge): toast every failed chunk validation attempt, replacing the previous toast

* fix(knowledge): dismiss stale validation toast once content validates

* revert(ui): drop chat identifier error rendering and profile-name toast
2026-07-03 19:13:09 -04:00
Waleed 88330b43bf fix(uploads): gate execution-context uploads behind write/admin permission (#5404)
Fallback multipart upload route (/api/files/upload) had no workspace
permission check for execution-context uploads, unlike the primary
presigned-upload route which requires write/admin. Mirror that gate
so both paths enforce the same access control.
2026-07-03 16:12:03 -07:00
Waleed afe3d32032 fix(mcp): pass SSRF-guarded fetch into OAuth callback token exchange (#5399)
Mirrors the same wiring already used by probe.ts and revoke.ts, so the
callback's token-exchange request goes through the same guarded fetch as
the rest of the OAuth flow.
2026-07-03 15:33:59 -07:00
Waleed 1574c20806 fix(mcp): pass SSRF-guarded fetch into OAuth start flow, matching probe/revoke (#5398)
Discovery and registration during the MCP OAuth start flow were using the
default global fetch. probe.ts and revoke.ts already route these calls
through createSsrfGuardedMcpFetch(); this brings the start route in line
with the same pattern.
2026-07-03 15:31:25 -07:00
Waleed ff8844c7aa fix(guardrails): authorize vertexCredential before use in hallucination validation (#5400)
* fix(guardrails): authorize vertexCredential before use in hallucination validation

The guardrails validate route now checks credential access via
authorizeCredentialUse before passing a caller-supplied vertexCredential
into hallucination validation, matching the existing pattern already used
in the providers route for the same field.

* fix(guardrails): gate vertexCredential authorization on the resolved provider

Only run the credential check when the model actually resolves to vertex,
matching the providers route's gating exactly, and drop a redundant
fallback now that workflowId is already guaranteed present at that point.
2026-07-03 15:20:44 -07:00
Waleed 6bc70cb149 fix(tables): verify workflow belongs to table's workspace before binding (#5397)
* fix(tables): verify workflow belongs to table's workspace before binding

Add a check that a table workflow group's workflowId resolves to an
active workflow in the table's own workspace, in both the create and
update handlers, before it is persisted.

* fix(tables): reorder JSDoc for mapWorkflowGroupError

Greptile flagged the JSDoc for mapWorkflowGroupError as orphaned after
validateWorkflowInWorkspace was inserted between the comment and the
function it documented. Move the comment back above its function.
2026-07-03 14:38:36 -07:00
Waleed fd98218e58 fix(gmail): strip CR/LF from header values before MIME assembly (#5395)
* fix(gmail): strip CR/LF from header values before MIME assembly

Adds sanitizeHeaderValue and applies it to to/cc/bcc/subject/
inReplyTo/references and the attachment filename in
buildSimpleEmailMessage and buildMimeMessage before they're placed
into MIME header lines.

* fix(gmail): sanitize attachment mimeType in Content-Type header

attachment.mimeType was written verbatim into the Content-Type header,
unlike the other header fields this PR sanitizes. Route it through the
same sanitizeHeaderValue helper for consistency.
2026-07-03 14:25:12 -07:00
Waleed 2e3574407b fix(chat): fail-safe to noindex if the deployment lookup errors (#5396)
generateMetadata queried the DB with no error handling, unlike the
identical query in api/chat/[identifier]/route.ts (which already wraps
it in try/catch). There's no error.tsx under app/(interfaces)/chat/ —
metadata resolution errors aren't caught by route-segment error
boundaries at all, only the root global-error.tsx — so a DB hiccup
during this lookup would take the whole page down to a generic error
page instead of just failing to determine indexability. Catches the
error, logs it, and defaults to noindex: if we can't confirm the
deployment is safely public, that's the correct SEO default anyway,
not a reason to crash the request.

Flagged by Cursor Bugbot on #5388 (already merged); this ships the fix
as a standalone follow-up since the underlying code is already live.
2026-07-03 14:23:46 -07:00
Waleed ca2a52ccf7 feat(billing): expose credit usage log in Billing settings (#5391)
* feat(billing): expose credit usage log in Billing settings

Add a "Credit usage" section under Billing, below Invoices, showing a
paginated, period-filterable list of individual credit-consuming
events (model, tool, and fixed charges) for every plan except
Enterprise. Wires the existing (previously unused) usage-logs backend
to a proper contract, React Query hook, and emcn-styled UI:

- getUsageLogsContract in contracts/user.ts, broadened the source enum
  to match the real usage_log schema
- Rewrote the route to use parseRequest per the API boundary rules
- useUsageLogs infinite-query hook, keyset-paginated
- CreditUsageSection: period dropdown, total-credits summary, row
  list with source badges, "Load more"
- Extracted formatCreditsLabel in conversion.ts as the shared
  formatter for already-converted integer credits

* fix(billing): move usage-log key factory out of the 'use client' boundary

Greptile P2: usageLogKeys lived in the 'use client' usage-logs.ts hook
file — importing it from a server component (e.g. a future prefetch)
would resolve to a client-reference stub and crash at build/SSR, the
same class of bug that hit tables' key factory before. Extracted to
hooks/queries/utils/usage-log-keys.ts, matching table-keys.ts and
folder-keys.ts.

Also renamed a shadowed `source` map param in the route to `sourceKey`
for clarity.

* chore(billing): dedupe the usage-log period literal type

The '1d' | '7d' | '30d' | 'all' union was hand-typed in three places
across usage-logs.ts and credit-usage-section.tsx. Extracted
usageLogPeriodSchema in the contract and derived UsageLogPeriod from
it, so the hook, the component, and the key factory all share one
definition instead of risking drift.

* improvement(billing): move credit usage period filter into the URL

/cleanup pass (nuqs rule, react-query-best-practices, emcn review):
- period was a plain useState, but it's exactly the kind of shareable
  list filter sim-url-state.md calls out for nuqs — migrated to
  billingParsers/useQueryStates so the selection deep-links, survives
  reload, and matches every sibling settings section (recently-deleted,
  inbox, teammates). Derives its literal values from
  usageLogPeriodSchema instead of a fourth copy of the same union.
- Removed an unjustified showSelectedCheck={false} on the period
  ChipDropdown — the one other usage in the codebase is a one-shot
  action menu with no persistent selection; this is a real filter and
  should show the check like the default intends.
- Added placeholderData: keepPreviousData to useUsageLogs — period is
  a variable query key, so without it switching periods flashed the
  loading empty-state instead of smoothly transitioning.

Verified live: deep-link with ?period=7d pre-selects and loads
correctly, switching periods updates the URL, and the selection
survives a hard reload.
2026-07-03 13:20:59 -07:00
Waleed 4fe372b583 improvement(seo): extract shared withFilteredNoindex helper (#5392)
The `{ ...base, ...(isFiltered && { robots: { index: false, follow: true } }) }`
faceted-navigation noindex pattern was duplicated verbatim across five
catalog pages (integrations, models, blog, careers, pricing). Extracted to
lib/landing/seo.ts alongside buildLandingMetadata, giving the pattern a
name and a single point of change. Pure refactor — verified byte-identical
rendered output (robots meta + canonical) on baseline and filtered variants
of all five pages before and after.
2026-07-03 13:18:08 -07:00
Waleed 8937005076 improvement(compare): tighten comparison-page copy, cut self-referential hedging (#5390)
* improvement(compare): tighten comparison-page copy, cut self-referential hedging

Across sim.ts and all 20 competitor profiles, remove phrasing that describes
a claim's own provenance/reliability (e.g. 'marketing claim', 'marketing
copy states', 'as of this check') in favor of stating the fact directly.
Also trims overlong run-on sentences into shorter, more direct ones.

No numbers, dates, comparisons, confidence levels, or sources changed.

* fix(compare): avoid accidental boolean-icon misparse from copy tightening

Three facts whose tightened wording started with a bare 'No ' now matched
the Yes/No boolean-icon convention (parseFactValue), collapsing a nuanced
'Partial' or plain descriptive sentence into a bare X icon. Rephrase with
'Not'/'not' so these render as full text again, matching pre-edit behavior.
2026-07-03 12:54:28 -07:00
Waleed 03e0e075e1 improvement(styling): consistent branded text-selection color app-wide (#5389)
* improvement(styling): use a consistent branded text-selection color app-wide

Previously no ::selection rule was defined anywhere, so text selection fell
back to the browser/OS default, which dims when a window loses focus and
made pages look inconsistent side by side. Adds one global rule using the
existing --selection token.

* fix(styling): resolve markdown-selection conflict, use --white token

Remove the local link/strikethrough ::selection color overrides in
rich-markdown-editor.css now that the global ::selection rule already
forces uniform white text on every selection. Also swap the literal
#ffffff for the existing --white design token.
2026-07-03 12:50:40 -07:00
Waleed 7ff4f17eb2 fix(seo): fix GSC indexing issues, remove unused academy/partners pages (#5388)
* fix(seo): fix GSC indexing issues, remove unused academy/partners pages

- robots.ts: unblock /chat/ (page-level noindex now gates gated/inactive
  deployments instead), drop the now-vestigial blog-tag/link-preview carve-out
- next.config.ts: add missing redirects for renamed integration slugs
  (sap-s-4hana, calcom), removed /partners, and removed /academy
- fix missing canonical/noindex on filtered catalog pages (integrations,
  models, blog, careers, pricing) causing GSC "duplicate, Google chose
  different canonical"
- standardize page titles to "Page | Sim, the AI Workspace" across the board
- remove the academy marketing pages and partner program page (content
  consolidated into docs.sim.ai/academy); drop the unused academy_certificate
  table via migration and strip the sandbox-mode plumbing from the workflow
  editor that only academy ever used

* fix(migrations): defer academy_certificate table drop to a follow-up PR

CI's expand/contract migration safety check correctly flagged this: the
academy_certificate DROP TABLE was bundled in the same PR as removing the
code that reads/writes it (the certificates API route had no feature-flag
guard of its own, so it was reachable independent of the marketing pages
being disabled). Dropping the table in the same deploy risks breaking any
pod still running the old code during a rolling deploy.

Restores the table/enum in schema.ts and the test mock, and removes the
0254 migration. The table drop should ship in its own PR once this one's
code removal is confirmed live.

* fix(seo): drop dead revalidate exports on searchParams-driven pages

Any Server Component in the route tree reading searchParams forces the
whole route to fully dynamic per-request rendering, which overrides ISR —
revalidate is a silent no-op once that happens. True on pricing/careers
because generateMetadata now parses searchParams directly, and was already
true on blog before this PR (its page body already read searchParams).
Flagged by Greptile on pricing; same root cause applies to all three.
2026-07-03 12:15:54 -07:00
Waleed e1b8200660 fix(sso): support skipping the OIDC UserInfo endpoint at registration (#5386)
* fix(sso): support skipping the OIDC UserInfo endpoint at registration

* fix(sso): cap OIDC discovery fetch at 10s to avoid stalling registration

* test(sso): default-mock discovery fetch so intent is explicit

* fix(sso): prefer client_secret_post and surface discovery failure reasons

* fix(sso): always resolve token auth method and skip SSRF-checking a discarded userInfoEndpoint
2026-07-03 12:09:22 -07:00
Waleed 331875b7fe fix(billing): stop showing "View all" when there are no more invoices (#5387)
* fix(billing): stop showing "View all" when there are no more invoices

Show fewer invoices (10) and derive hasMore from the finalized-invoice
count instead of Stripe's raw has_more, which counted drafts we filter
out client-side and could report more invoices than actually exist.

* chore(billing): move invoice pagination explanation into TSDoc

Extract collectFinalizedInvoices with a TSDoc comment explaining the
Stripe has_more/draft-filtering rationale, instead of an inline
comment block.

* fix(billing): don't hide View all when the page-cap is hit inconclusively

Greptile P1: if MAX_STRIPE_PAGES is exhausted while the finalized count
sits exactly at MAX_INVOICES and Stripe still has_more, hasMore was
silently returned as false. collectFinalizedInvoices now also returns
whether Stripe's cursor was still open at exit, and the route ORs that
into hasMore so the safety cap can never suppress "View all".

Also asserts starting_after cursor propagation across pages and adds a
test for the safety-cap-hit case.
2026-07-03 11:49:14 -07:00
Waleed 0cc290eeb7 feat(comparison): add Microsoft Copilot Studio, OpenClaw, Dust, CrewAI, and LangChain (#5384)
* feat(comparison): add Microsoft Copilot Studio, OpenClaw, Dust, CrewAI, and LangChain

- 5 new "Sim vs Competitor" profiles (now 20 total), each with ~58
  independently sourced facts, standout features, and limitations,
  researched against each vendor's own docs/pricing/GitHub
- New brand icons: MicrosoftCopilotIcon, OpenClawIcon, DustIcon,
  LangChainIcon, CrewAIIcon
- isWorkflowBuilder: false for OpenClaw, CrewAI, and LangChain since
  they're a personal agent runtime and code-first frameworks rather
  than visual workflow builders, so their FAQ asks a
  category-clarifying question instead of a peer feature-gap one
- Independent tone audit (no over-praising competitors, no unflattering
  Sim framing) and a fresh accuracy re-verification pass (50 highest-
  stakes facts across all 5 profiles, all confirmed against live
  sources) both came back clean

* fix(comparison): fix two more FAQ text-mangling bugs found during final audit

- lowercaseFirst only guarded against 2+ CONSECUTIVE leading capitals
  (acronyms like "AI"/"SSO"), so CamelCase brand names with a single
  leading capital (LangChain, OpenClaw, CrewAI) got their first letter
  wrongly lowercased ("langChain provides..."). Now checks for 2+
  uppercase letters anywhere in the leading word, which covers both
  acronyms and CamelCase brand names.
- parseFactValue and summarizeFact's boolean-prefix stripping only
  recognized "Yes:"/"No:" (colon), but "Yes, ..."/"No, ..." (comma) is
  an equally common phrasing already used across ~15 existing facts
  (stackai, pipedream, workato, zapier, etc.), so those facts kept
  their leading comma when stitched into an FAQ answer (e.g. "StackAI:
  , broad support..."). Both now accept either separator.

Found by systematically sweeping every "Sim vs X" FAQ answer across
all 20 competitor pages for garbled/mis-cased text, not just the 5
newly added ones.

* feat(comparison): add sub-workflow composition and loop-block facts

- Two new universal comparison facts across all 20 profiles:
  subWorkflows (calling a saved workflow as a reusable step inside
  another) and loopIteration (a dedicated sequential for-each/while
  loop container, distinct from concurrent Parallel execution)
- Both are real Sim capabilities (Workflow block, Loop block) verified
  directly against the codebase and docs.sim.ai
- Findings are genuinely mixed, not uniformly favorable: n8n, Zapier,
  Make, Workato, Retool, Power Automate, Gumloop, Vellum, Stack AI,
  Tines, Langflow, Flowise, Microsoft Copilot Studio, and LangChain all
  have some form of sub-workflow calling; Zapier and Gumloop's loop
  primitives run concurrently rather than sequentially (marked
  "Partial", not "No"); Pipedream, OpenAI AgentKit, Claude Cowork,
  CrewAI, Dust, and OpenClaw genuinely lack one or both

* feat(comparison): add third-party integration vetting fact

New universal comparison fact across all 20 profiles: thirdPartyVetting,
whether a platform's integrations/tools/skills come from a vetted
first-party catalog vs. an open marketplace where any third party can
publish executable code with lighter or no vendor security review.

Directly relevant given OpenClaw's ClawHub marketplace has documented
incidents (283 skills, ~7.1% of the registry, found leaking credentials;
24 accounts distributing 600+ malicious skills before scanning existed).

Findings are honest and mixed, not uniformly favorable: Gumloop, Retool,
and Tines are first-party-only like Sim (marked "Yes"); n8n, Zapier,
Make, Workato, and OpenAI AgentKit have partial vetting on an open or
semi-open ecosystem; Pipedream and OpenClaw are open marketplaces with
documented security incidents.

* fix(comparison): correct n8n supply-chain attack download count

Independently re-verified the cited Hacker News article: the primary
malicious package had 4,241 downloads listed (not "3,400 weekly" as
previously written, a number not actually supported by the source).

* fix(comparison): fix duplicated competitor name in first FAQ answer

Every competitor's oneLiner is already a complete "{Name} is ..."
sentence, so prepending "${name} is " before it was always redundant.
Before this session's lowercaseFirst fix, the duplication rendered in
mixed case ("Zapier is zapier is a cloud-based...") and was easy to
miss; the CamelCase-name fix made it fully literal and obvious
("CrewAI is CrewAI is..."), which is what Cursor Bugbot caught.

Fixed by using the oneLiner directly (via ensurePeriod) instead of
re-prepending the name. Verified fixed across all 20 pages via live
curl, not just the CrewAI case Cursor flagged.

* improvement(comparison): cite Sim's own docs alongside code for 4 facts

Added docs.sim.ai citations (Roles and Permissions, BYOK, Debugging
retrieval, Function block) as primary sources for rbac, byok,
kbChunkVisibility, and customCodeSteps, which previously cited only
GitHub source code with no user-facing documentation reference.
Verified all 4 doc URLs resolve (200).
2026-07-03 11:29:13 -07:00
Theodore Li de110e02eb improvement(tables): harden pagination for short pages and surface name-validation errors (#5351)
* improvement(tables): harden pagination for short pages and surface name-validation errors

* fix(tables): align getNextPageParam tests with count-based termination

* improvement(tables): lift the 10K-char per-string-cell limit
2026-07-03 13:46:38 -04:00
Waleed 4086750ddc feat(comparison): add Sim vs Competitor comparison pages (#5383)
* feat(comparison): add Sim vs Competitor comparison pages

- New /comparison hub + /comparison/[provider] detail pages for Sim vs
  16 competitors (n8n, Zapier, Make, Gumloop, Workato, Retool,
  Pipedream, OpenAI AgentKit, Tines, StackAI, Power Automate, Vellum,
  Claude Cowork, Langflow, Flowise), each with ~60 sourced, dated facts
  across platform, AI capabilities, integrations, pricing, security,
  observability, and support
- Data layer at lib/compare/data (types, per-competitor profiles) is
  UI-free and independently auditable
- BreadcrumbList, ItemList, and FAQPage JSON-LD per page; sitemap picks
  up all pages automatically via ALL_COMPETITORS
- Two new fact categories (parallel execution, Agent2Agent protocol)
  researched and sourced against every competitor's own docs

* improvement(comparison): neutralize tone across competitor and Sim fact copy

- Remove promotional/superlative adjectives applied to competitors
  (Zapier "one of the largest catalogs", Workato "notably wide",
  Retool "seamless"/"trusted solution", OpenAI "cutting-edge",
  Flowise "deep"/"mature"/"most widely adopted", Make "robust")
- Reword the few places Sim's own accurate limitations read as
  unflattering rather than neutral fact (dynamic tool use, model
  fallback, durability, async execution, support channels, tracing)
  without changing the underlying facts
- Restore the "Yes:"/"No:" value prefix on two Sim facts where it was
  accidentally dropped during rewording, since FactValue depends on
  that prefix to render the check/X status icon

* fix(comparison): address review findings from Greptile and Cursor Bugbot

- Fix parseFactValue regex to require a word boundary after Yes/No, so
  values like "Not documented"/"Not publicly documented" no longer get
  misread as a boolean "No" and render as neutral text again (Cursor)
- Reword the self-hosting FAQ question to drop the false presupposition
  that the competitor doesn't self-host, which was wrong for n8n,
  Langflow, and Flowise (Greptile)
- Rename isLastRow -> isNotLastRow in comparison-table.tsx to match
  what the variable actually computes (Greptile)
- Move critters to devDependencies; it's a next build -time-only CSS
  inliner, not needed at runtime (Greptile)
- Sitemap lastModified for comparison pages now matches the max(Sim,
  competitor) verified-date logic each page's own JSON-LD dateModified
  already uses, so the two never disagree (Cursor)

* fix(comparison): fix doubled Yes prefix and missing period in hub FAQ

- Two hub FAQ answers prepended "Yes." to fact values that already
  start with "Yes:", producing "Yes. Yes: ..." in visible copy and
  FAQPage JSON-LD. Export and reuse ensurePeriod instead of a hardcoded
  prefix.
- The integrations-count FAQ answer ran two sentences together with no
  period before "Combined with...". Fixed with the same ensurePeriod
  helper.

* improvement(comparison): remove redundant Key differences at a glance section

The 5 facts it previewed (self-hosting, environment promotion,
human-in-the-loop, pricing model, data residency) are the exact same
rows shown again immediately below in the full comparison table, so
the section read as pure repetition for a human scrolling past it.

* fix(comparison): strip Yes/No prefix before lowercasing in summarizeFact

summarizeFact fed the raw fact value through lowercaseFirst even when
it started with "Yes: "/"No: ", producing broken mid-sentence FAQ text
like "n8n: yes: many providers via dedicated Chat Model nodes." Strip
the boolean prefix first, matching Greptile's suggested fix.
2026-07-02 21:25:33 -07:00
Will Chen 3e710845da improvement(academy): set 2, pages for the new video wave (workflow embeds, 2K players, full sequencing) (#5382)
* academy: Chat section (intro, building) + Agents tool-calling and skills pages, sequenced in meta.json — pages for the four approved videos, blob src pattern, chapters offset by each recorded intro

* academy pages: Related documentation links point at real docs routes (mothership/agents/logs-debugging/deployment), not other academy videos

* academy: Tables — Workflow Columns page (combined-cut chapters, receipts pedagogy), sequenced after tables/intro

* academy set 2: five new pages (agents/block, agents/memory, knowledge-bases/connectors, tables/operations, files/object) + retimed chapters on the redone tables/files intros (recomposed/working-day cuts) + full meta.json sequencing — Chat · Agents(5) · Tables(3) · Files(2) · KB(2)

* academy pages: every page shows the VIDEO's workflow (new academy-video-workflows.ts registry — invoice intake, Qualify, memory, table ops, tools/skills agents, support-desk, content-agent) + plain pedagogical headings throughout (no poetry: 'The warm and cold split' → 'The same agent, with and without memory', 'From one pass to a loop' → 'What tools change', etc.); files/intro's embed swapped to the video's machine

* academy: all video players point at the 2K blob set (academy/<slug>.mp4) — 20 pages rewritten from academy-preview/*-light-with-intro, files/intro plays the new files-intro cut, workflows/logs gains its src (video now exists); use-case placeholders stay src-less (no videos yet)

* biome: format academy-video-workflows.ts

* fix (cursor/greptile): support-desk condition block uses branches + rows:[] (the renderer's real fields — conditions: was never read, so Urgent? rendered without if/else handles and branch edges dangled); bgColor aligned to the product condition example

* biome: format meta.json (CI lint:check)

* fix (cursor): Start exposes <start.input>, not <start.ticket>/<start.idea> — support-desk Triage and content-agent Writer message rows now match the product's Start output (same pattern as the file's other workflows)

* academy lesson material (CTO ask): FAQ on every lesson page + the index (grounded in the audited codebase facts: memory modes, the ten table ops, coerce/refuse, deploy surfaces + draft-vs-deployed, file parser formats; the FAQ component emits FAQPage JSON-LD for SEO); em-dashes 202 → 0 with hand-fixed splices; added copy where sparse (agent block placement, when-to-use-memory); SEO phrasing sprinkled naturally into FAQ answers ('visual platform for building AI workflows and agents', 'no-code', model names), not over-indexed

* fix: quote frontmatter descriptions that gained colons in the em-dash pass (unquoted YAML scalars with a second colon broke every page)
2026-07-02 18:35:59 -07:00
Waleed 4085a1ea07 fix(resume): fix click-blocking footer overlay + hardening on HITL resume page (#5381)
* fix(resume): fix click-blocking footer overlay + hardening on HITL resume page

- SupportFooter rendered position:absolute with no space reserved for it
  in InterfacesShell/AuthShell/file-share auth gate, silently overlapping
  and eating clicks on whatever content ended up in its ~50px footprint
  (on the resume page, the Resume Execution button itself)
- Applied the same fix to /invite and /f/[token], which shared the
  identical absolute-positioning pattern
- handleResume no longer fails silently on validation errors or
  unexpected exceptions
- getPauseContextDetail no longer duplicates a pause point's full
  response payload in the same API response
- Oversized HITL display-data values are now truncated in the resume
  page preview instead of rendering unbounded

* fix(resume): use consistent string for truncation-notice length

The object-branch truncation notice sliced from the prettified
JSON.stringify(value, null, 2) but reported the total against the
compact JSON.stringify(value).length, which could show a nonsensical
"5,000 of 4,800 characters shown" when the compact form is shorter
than the prettified slice. Derive both from the same string.

* fix(resume): show em dash for empty-string display data values

renderStructuredValuePreview only treated null/undefined as empty; an
empty string fell through to the plain-text branch and rendered as a
bordered, padded, contentless pill that reads as a stray UI element
(e.g. an unstyled toggle) in the Display Data table.
2026-07-02 15:11:13 -07:00
Waleed 577a7a2ef1 fix(landing): inset careers row hover, drop trusted-by section, gate contact submit (#5380) 2026-07-02 13:00:29 -07:00
Vikhyath Mondreti b4b666bfbe improvement(forking): fork time ux (#5348)
* improvement(forking): fork time ux

* add storage quota

* address comments

* merge latest staging

* address comments
2026-07-02 12:12:30 -07:00
Waleed 10b6bb33e9 feat(google-appsheet): add Google AppSheet integration (#5376)
* feat(google-appsheet): add Google AppSheet integration

- 4 tools (find/add/edit/delete rows) against the AppSheet Action API
- API key auth via Application Access Key (no OAuth/scopes needed)
- Block with operation dropdown, region selector, and Selector expression support
- Generated docs

* improvement(google-appsheet): harden response parsing, add wand config and skills

- Guard against empty/non-JSON AppSheet response bodies (Delete may return no body)
- Add wandConfig to the Selector field for AI-assisted expression generation
- Add 3 skills grounded in attested AppSheet/Zapier automation patterns
- Tighten json output descriptions to describe inner shape

* fix(google-appsheet): validate region against allow-list, encode appId, validate rows shape

- Reject unrecognized region values instead of interpolating them into the
  request host (a caller could otherwise redirect the Application Access
  Key to an arbitrary domain)
- URL-encode appId, not just tableName, in the Action endpoint path
- Reject non-array Rows input in tools.config.params instead of forwarding
  a single object to the AppSheet Action API
- Drop the mismatched json-object generationType on the rows wand config
  (that enricher appends "must start with { and end with }", which
  conflicts with the JSON-array shape the field expects)
- Add utils.test.ts covering region validation and response-body parsing

* docs(google-appsheet): add manual intro/getting-started section

Match the MANUAL-CONTENT convention used by other integration docs
(Airtable, Ahrefs, Google PageSpeed) — an overview of the service, what
the Sim integration lets agents do, and how to get an Application
Access Key.

* docs: sync generated integration docs with current source

Regenerate docs for integrations whose tools/blocks changed upstream
without a matching docs regen (ahrefs, algolia, amplitude, brex, clerk,
gong, hex, langsmith, loops, onepassword, sendgrid, sharepoint,
similarweb, supabase, tailscale, trello, vercel, wordpress), plus the
integrations.json catalog.
2026-07-02 11:58:30 -07:00
Waleed 06dd81150a fix(google-forms): fail-closed auth, legacy formId key fallback, idempotency (#5377)
- verifyAuth now rejects with 401 when no token is configured instead of
  silently allowing unauthenticated requests through
- formatInput falls back to the legacy formId providerConfig key (pre-#3141
  rename to triggerFormId) so old deployments keep working
- add extractIdempotencyId keyed on formId:responseId to dedupe retried
  Apps Script deliveries
2026-07-02 11:58:14 -07:00
Waleed f6b802e7cb fix(sendgrid): fix active field coercion, add pagination, tighten output typing (#5368)
* fix(sendgrid): fix active field coercion, add pagination, tighten output typing

- Fix active field for create_template_version being sent as the string
  "true"/"false" instead of the SendGrid-required int 0/1
- Add missing authMode: ApiKey on SendGridBlock
- Add pageToken/nextPageToken pagination support to list_templates and
  list_all_lists (SendGrid page_token cursor, parsed from _metadata.next)
- Fix nullable output fields to use ?? null / ?? [] with optional: true
  across get_contact, search_contacts, remove_contacts_from_list,
  create_template_version, add_contact, send_mail
- Remove dead data.templates fallback in list_templates (API only
  ever returns result)
- Remove unused UpdateContactParams/UpdateListParams/UpdateTemplateParams
  dead types; make CreateTemplateParams.generation optional to match
  actual tool behavior

* fix(sendgrid): address Cursor Bugbot findings on pagination and active coercion

- Gate listPageToken/templatePageToken remap on operation so a stale
  token from the other list operation can't override the intended one
- Fix active coercion to also treat a real boolean true (from a dynamic
  <Block.output> reference) as active, not just the dropdown string 'true'

* fix(sendgrid): coerce active to int at the tool layer too

Per Greptile: the block-level active coercion only covered the UI
path. A direct sendgrid_create_template_version tool invocation with
a boolean active would still send a raw boolean to SendGrid. Coerce
to 0/1 in the tool's own request body so both paths are correct.

* fix(sendgrid): always send page_size on list_templates

SendGrid's GET /v3/templates requires page_size on every request
(no server-side default) — omitting it errors. Default to 20 to
match our own documented default when the caller doesn't set one.

* fix(sendgrid): explicit false/'false' check for active flag

Per Cursor Bugbot: params.active ? 1 : 0 treated any truthy string
(including "false") as active. Extracted a toActiveFlag helper that
only treats real false or the string 'false' as inactive, everything
else (including unset) defaults to active — matches the tool's
documented default.

* fix(sendgrid): handle numeric 0 in toActiveFlag

Per Greptile: the block coerces active to a number (0/1) before
calling the tool, but toActiveFlag only checked for false/'false',
so the block's inactive selection (0) fell through to the
"active" branch. Check against an explicit inactive-values set
covering the boolean, string, and numeric forms.

* fix(sendgrid): nest add_contact custom fields under custom_fields

Pre-existing bug (predates this PR): custom fields were merged onto
the contact object as top-level sibling keys via safeAssign/Object.assign,
but SendGrid's PUT /v3/marketing/contacts requires them nested under a
custom_fields object. SendGrid silently drops unrecognized top-level
keys, so the documented customFields param never actually reached
SendGrid. Caught during a final adversarial re-verification pass
before merge.

* fix(sendgrid): document consistent page_size requirement for list_templates pagination

Per Cursor Bugbot: list_templates always defaults page_size to 20
when unset (required by SendGrid), so a follow-up pageToken-only
call after a first call with a larger pageSize would silently
shrink to 20 and desync page boundaries. This is inherent to a
stateless tool call (SendGrid requires page_size on every request,
and the tool has no way to remember the prior call's value), so
clarify via param description and UI placeholder that callers must
repeat the same pageSize across paginated calls.

* chore(api-validation): bump stale route-count ratchet baseline 883->884

Unrelated to the SendGrid work in this branch. staging's own HEAD
already has 884 compliant Zod-backed API routes (0 non-Zod), but this
ratchet baseline was never bumped when that route landed, so any PR
rebasing onto current staging fails check:api-validation:strict with
"route count increased from 883 to 884". All routes remain fully
Zod-backed; this is a mechanical counter update, not a policy change.

* fix(sendgrid): dedupe active coercion between block and tool

Per Cursor Bugbot: the block's pre-coercion only recognized the
dropdown string 'true' or boolean true as active, so a dynamic
reference producing numeric 1 or string '1' fell through to 0 and
silently created an inactive template version. Exported the tool's
toActiveFlag and reused it in the block instead of duplicating the
inactive-value logic, so both layers can no longer drift out of sync.
2026-07-02 11:16:07 -07:00
Waleed 11c7a36f11 feat(hex): expand API coverage, fix ID trimming, add cursor pagination (#5372)
* feat(hex): expand API coverage, fix ID trimming, add cursor pagination

- Add hex_update_collection, hex_create_group, hex_update_group,
  hex_delete_group, hex_deactivate_user tools + block wiring
- Add missing run_project fields (viewId, notifications) and
  get_project_runs runTriggerFilter
- Add after/before cursor pagination to list_projects, list_groups,
  list_data_connections, list_collections
- Add .trim() on all interpolated ID path params to guard against
  copy-paste whitespace

* fix(hex): guard JSON.parse on user-supplied array/object params

Wrap JSON.parse calls for memberUserIds, addUserIds, removeUserIds,
inputParams, and notifications in try/catch so malformed input throws
a clear error instead of an opaque parse exception.

* fix(hex): forward empty collection description on update

update_collection's params() mapping only copied collectionDescription
when truthy, so clearing it to an empty string in the UI never reached
the API. Untouched fields resolve to null (not undefined), so use a
loose null check to distinguish "cleared" from "never touched" without
sending description: null on every unrelated update.

* fix(hex): close remaining API coverage gaps found via raw OpenAPI spec

Pulled Hex's actual OpenAPI spec (static.hex.site/openapi.json) as
ground truth for a final verification pass:

- list_users was missing after/before cursor pagination and the
  userIds filter, which the spec confirms it supports (an earlier
  doc-summary pass had incorrectly flagged this as unverifiable)
- list_users response also exposes lastLoginDate per user, not
  previously surfaced
- list_projects was missing includeComponents, includeTrashed,
  creatorEmail, ownerEmail, collectionId, categories, sortBy, and
  sortDirection filters that the spec confirms are real query params

* fix(hex): trim remaining user-suppliable IDs for consistency

Trim collectionId (list_projects filter), viewId (run_project body),
and group member UUIDs (create_group/update_group) to match the
.trim() convention already applied to path-segment IDs elsewhere.

* fix(hex): validate parsed JSON is actually an array before iterating

categories, memberUserIds, addUserIds, and removeUserIds could parse
as valid JSON that isn't an array (e.g. an object), which would throw
an opaque "not iterable"/"map is not a function" error deeper in the
call. Validate Array.isArray after parsing and fail with a clear
message instead.

* fix(hex): validate array element types, send explicit ALL trigger filter

- notifications now gets the same Array.isArray guard already applied
  to categories/memberUserIds/addUserIds/removeUserIds
- member/category array elements are now validated as strings before
  .trim()/append, instead of crashing on non-string entries
- runTriggerFilter "All" option now sends the documented ALL enum
  value explicitly instead of relying on omission
2026-07-02 11:14:14 -07:00
Waleed 997740790d feat(fathom): add list meeting types tool and missing list-meetings filters (#5359)
* feat(fathom): add list meeting types tool and missing list-meetings filters

- Add fathom_list_meeting_types tool (GET /meeting_types)
- Add missing list-meetings params: includeHighlights, meetingType,
  calendarInviteesDomains, calendarInviteesDomainsType
- Add missing meeting response fields: meeting_type, meeting_url,
  shared_with, highlights
- Wire new operation and filters into the Fathom block

* fix(fathom): expose meeting_url/highlights in outputs, stop force-sending domain type default

- Add meeting_url and highlights to list_meetings outputs schema so
  they're addressable from downstream blocks (Greptile P1)
- Drop the forced 'all' default on calendarInviteesDomainsType so the
  filter is only sent when a user explicitly picks a value (Greptile P2)

* fix(fathom): never send calendar_invitees_domains_type=all to the API

Match the existing Fathom connector's guard (meetingType !== 'all') so
the request omits the param entirely when the value is the API's own
default, regardless of what the dropdown shows selected in the UI.

* fix(fathom): fully expose list_meetings meeting fields in outputs schema

transformResponse already returned meeting_title, scheduled/recording
times, recorded_by, calendar_invitees, default_summary, transcript,
action_items, and crm_matches, but outputs.meetings.items.properties
only documented a curated subset, leaving these fields unaddressable
from downstream workflow blocks. Complete the schema to match the
full Meeting object Fathom's API returns.

* fix(fathom): mark recording_id optional in list_meetings outputs

transformResponse maps recording_id as meeting.recording_id ?? null
and the response type already types it number | null; the outputs
schema now reflects that nullability.

* fix(fathom): mark calendar_invitees email optional in list_meetings outputs

Fathom's docs mark Invitee.email as nullable; the outputs schema now
reflects that instead of declaring it as always-present.
2026-07-02 11:04:17 -07:00
Waleed a8f27094f9 improvement(gong): validate integration against API docs, fix pagination/wandConfig consistency (#5361)
* improvement(gong): tighten pagination optionality and add wandConfig to ID list fields

- mark cursor output optional:true on aggregate_activity, interaction_stats, list_flows for consistency with list_calls
- add wandConfig to comma-separated ID fields (callIds, primaryUserIds, userIds, scorecardIds, reviewedUserIds) matching repo convention for CSV inputs

* fix(gong): request context data for get_extensive_calls

contentSelector.context/contextTiming were never set on the /v2/calls/extensive
request, so the documented context (CRM/external-system links) output was
silently always empty even though the field is declared in the tool's outputs.

* feat(gong): add data privacy erase and Engage flow prospect tools

- gong_purge_email_address / gong_purge_phone_number: POST /v2/data-privacy/erase-data-for-*,
  the write-half pairing with the existing lookup_email/lookup_phone read tools
- gong_assign_flow_prospects: POST /v2/flows/prospects/assign, enrolls CRM prospects into an
  Engage flow
- gong_get_prospect_flows: POST /v2/flows/prospects, looks up which flows a prospect is in

Field names verified against an OpenAPI-generator-produced client (cedricziel/gong-rs) whose
serde rename attributes mirror Gong's published spec, since Gong's interactive Swagger docs
require an authenticated session and can't be fetched directly.

* fix(gong): require human-entered target for data-erase tools

emailAddress/phoneNumber on the purge tools were user-or-llm, letting an
agent autonomously pick the erasure target for an irreversible operation
with no human confirmation. Match the user-only visibility already used
for credentials.
2026-07-02 10:57:09 -07:00
Waleed 4988ba621c feat(trello): expand tool coverage and fix API gaps (#5357)
* feat(trello): expand tool coverage and fix API gaps

- add delete card, remove label/member, update list, add/update checklist item, list members, and search tools
- add filter support to list lists/cards, since/before paging to get actions, member assignment on card creation
- promote move-to-list field out of advanced mode

* fix(trello): address review feedback on checklist item tooling

- idChecklist can be absent on Trello checkItem responses, so stop treating it as required
- validate state/name is provided before building the update-checklist-item request URL
- reject Update Checklist Item at the block level when neither State nor New Item Name is set
- add missing idOrganization field to search's board output schema
2026-07-02 10:55:42 -07:00
Waleed a08da86dff feat(wordpress): add category/tag CRUD tools, fix delete-status and dead-field bugs (#5360)
* feat(wordpress): add category/tag CRUD tools, fix delete-status and dead-field bugs

- Add wordpress_{get,update,delete}_category and _tag tools for full taxonomy parity
- Fix `deleted: data.deleted || true` always evaluating true across all 6 delete tools (posts/pages/media/comments/categories/tags)
- Remove dead `force` param from delete_media (endpoint always force-deletes; param had zero effect)
- Remove unwired `hideEmpty` block input
- Normalize search_content perPage/page visibility to user-or-llm for consistency

* fix(wordpress): use ?? instead of || for zero-valued numeric fields in delete_category/tag

count and parent can legitimately be 0 (empty term, top-level category); || was
dropping those values, same antipattern already fixed for `deleted` in this PR.
Flagged independently by Greptile and Cursor Bugbot.

* fix(wordpress): use ?? for zero-valued numeric fields across all delete tools

Same || antipattern already fixed for category/tag delete tools was still
present in delete_post/page/comment/media for id, author, featured_media,
menu_order, parent, post — all legitimately 0 in common cases (no featured
image, top-level page/comment). Found by a final independent validation pass.

* fix(wordpress): don't drop categoryParent=0 (root-level category) in block param mapping

Truthy check on params.categoryParent treated a resolved numeric 0 (root-level,
no parent) as unset. Flagged by Cursor Bugbot on create_category/update_category.

* fix(wordpress): don't clear category/tag description on update when field left blank

description used !== undefined (numeric-field convention) instead of a truthy
check (string-field convention used everywhere else in this codebase, e.g.
update_post/update_page excerpt), so an untouched empty description field
silently wiped existing text on every update. Flagged by Cursor Bugbot.

* fix(wordpress): fix search type/subtype mislabeling, complete I/O exposure gaps

- search_content.ts: type param was mislabeled with subtype's vocabulary
  (post/page/attachment); real WP type enum is post/term/post-format. Rewired
  the block's Content Type dropdown to map to subtype (which is what
  post/page/attachment actually filter), not type.
- Widened subBlock conditions so params already read by tools.config.params
  are actually reachable in the UI: commentPostId for list_comments,
  categories/tags for list_posts, parent for list_pages.
- Added missing subBlocks for tool params with no UI path: comment
  parent/authorName/authorEmail/authorUrl (create_comment), media description
  (upload_media), author filter (list_posts).
- Extended the ?? / !== undefined fix (already applied to categoryParent) to
  the same class of param across the block: featuredMedia, page parent,
  menuOrder, and the new commentParent/listAuthor mappings.
- Fixed featuredMedia/parent truthy-check inconsistency in create_post,
  update_post, create_page, update_page, create_category, create_comment
  body builders to match the !== undefined convention used elsewhere.

Found by an independent final validation pass across 3 parallel agents.

* fix(wordpress): keep searchType subBlock id to preserve saved-workflow compat

The type/subtype fix should only change which API param the field feeds
(subtype, not type) — renaming the subBlock id to searchSubtype broke
already-saved workflows with a search content-type filter set, since the
block would stop reading the old searchType key. Reverted the id rename,
kept the underlying subtype mapping fix. Flagged by Cursor Bugbot.

* fix(wordpress): remove invalid Attachment search subtype, fix listAuthor input type

- Search Content's "Content Type" dropdown offered Attachment, which maps to
  subtype=attachment. WP core's WP_REST_Post_Search_Handler explicitly
  excludes attachment from valid subtypes (media isn't searchable via
  /search) — selecting it guaranteed a 400 rest_invalid_param. Removed the
  option; only Post/Page (the only valid subtypes) remain.
- listAuthor was declared type: 'string' in the inputs catalog despite being
  Number()-coerced before use, inconsistent with every other ID-like field
  (postId, pageId, categoryId, commentParent, etc. are all 'number').

Found by an independent final pre-merge validation pass, requested before
merge to be certain of full API alignment.
2026-07-02 10:47:31 -07:00
Waleed 7a31871471 feat(clerk): expand Clerk integration with org, membership, moderation, and security tools (#5364)
* feat(clerk): expand Clerk integration with org, membership, moderation, and security tools

- fix 4 validate-integration warnings: missing .trim() on org/session IDs, incomplete session-status dropdown, missing list_users/list_organizations filter subBlocks
- add organization update/delete tools
- add organization membership CRUD (list, add, update role, remove)
- add organization invitation create/list
- add user ban/unban/lock/unlock and OAuth access token retrieval
- add allowlist/blocklist identifier management
- add JWT template list/get
- add actor token create/revoke (impersonation)
- add matching webhook triggers for session ended/removed/revoked, organization updated/deleted, and membership updated/deleted
- wire all 23 new tools into the block, tool registry, and trigger registry

* fix(clerk): I/O completeness fixes from final validation pass

- remove dead limit/offset params from list_blocklist_identifiers (Clerk API accepts zero params on this endpoint, verified across 6 spec versions)
- expose publicMetadata on OAuth access token output (was silently dropped)
- expose inviter email/first/last name (public_inviter_data) on organization invitation create/list outputs
- add missing orderBy param to list_organization_invitations
2026-07-02 10:47:08 -07:00
Waleed f33d325a88 fix(deps): bump echarts to 6.1.0 to patch XSS vulnerability (#5374)
Fixes GHSA-fgmj-fm8m-jvvx / CVE-2026-45249 — Lines series tooltip
rendering could execute raw HTML from series.data[i].name when no
custom tooltip.formatter is set.
2026-07-02 10:46:50 -07:00
Waleed 070f554047 feat(sharepoint): validate against Graph API and add delete/update/download tools (#5369)
* feat(sharepoint): validate against Graph API and add delete/update/download tools

- Fix bugs found validating existing tools against live Graph docs: malformed
  nested $expand syntax in get_list, reversed site-resolution precedence in
  create_list, unsanitized field fallback in add_list_items, missing URL
  encoding in read_page, and an incorrect root/serverRelativeUrl field shape
- Fix V1 block gaps vs V2: upload_file required flag, missing required on
  pageName/listDisplayName, wrong site-picker mimeType, dead subBlock refs
- Add 8 new tools within already-granted scopes for CRUD completion:
  delete_list_item, get_list_item, delete_page, update_page, publish_page,
  download_file, delete_file, get_drive_item
- Add opt-in stripAuthOnRedirect option to secureFetchWithPinnedIP so the
  SharePoint file-download route doesn't resend the bearer token to the
  preauthenticated redirect target (default off, no behavior change elsewhere)
- Dedupe read-only list-item field sanitization into a shared utils helper

* fix(sharepoint): encode siteId consistently and fix create_page precedence

- URL-encode siteId/groupId everywhere it's interpolated into a Graph
  request path (Graph site IDs like "host,guid,guid" contain commas that
  must be encoded) - addresses Cursor Bugbot findings on update_page,
  delete_page, publish_page, and applies the same fix consistently across
  every other sharepoint tool for consistency
- Fix create_page's site-resolution precedence (was siteSelector before
  siteId, inconsistent with every sibling tool)

* fix(sharepoint): escape HTML in page content and stop fallback from throwing

- create_page/update_page only escaped quotes when building innerHtml;
  add a shared escapeHtml() helper that also escapes &, <, > so page
  content with angle brackets/ampersands doesn't corrupt the canvas layout
- add_list_items' fields fallback (sanitized re-derivation when Graph's
  response omits fields) could throw on a malformed fallback input after
  the item was already created successfully; catch and fall back to
  undefined instead of failing an already-successful response

* fix(sharepoint): scope columnDefinitions->pageContent mapping to create_list

Both V1 and V2 tools.config.params mapped columnDefinitions onto pageContent
whenever columnDefinitions was truthy, with no operation check. Stale
list-column JSON left in block state after switching to create_page/
update_page would silently replace the user's intended page text. Gate
the mapping on operation === create_list (V1) / sharepoint_create_list (V2).

* fix(sharepoint): cap download-file content fetch at MAX_FILE_SIZE

Greptile flagged the content fetch as unbounded - a large file would
buffer entirely in memory before base64-encoding into the JSON response.
Pass maxResponseBytes: MAX_FILE_SIZE (100MB, same constant used by the
upload path) to secureFetchWithPinnedIP so oversized files reject early
with a clear PayloadSizeLimitError instead of exhausting memory.

* fix(sharepoint): close V1 block input/output gaps and encode upload URLs

Final validation pass (4 parallel audit agents against live Graph docs)
surfaced remaining gaps:

- apps/sim/app/api/tools/sharepoint/upload/route.ts: siteId/driveId were
  not encodeURIComponent-ed in the Graph upload URL, unlike every other
  sharepoint route/tool - same encoding-gap class fixed everywhere else
- read_page.ts: transformResponse recomputed siteId with a raw `||` in
  3 spots instead of the optionalTrim(...) || optionalTrim(...) || 'root'
  precedence used by request.url and every sibling tool
- SharepointBlock (V1, legacy/hidden-from-toolbar but still executable
  for existing saved workflows): listItemFields and listItemId were
  missing `required` for update_list despite the tool requiring them;
  maxPages/groupId/includeColumns/includeItems/nextPageUrl subBlocks
  were entirely absent, making those tool params unreachable from the
  UI; block-level outputs were missing site/pages/content/totalPages/
  nextPageUrl/lists/skippedFiles/skippedCount/errors even though the
  underlying tools return them - V2 already covered all of these
2026-07-02 10:46:35 -07:00
Waleed acece910b4 fix(supabase): remove non-functional SQL introspection, harden storage encoding, add missing endpoints (#5371)
* fix(supabase): remove non-functional SQL introspection path, harden storage URL encoding, add missing storage endpoints

- introspect.ts no longer attempts raw SQL via a nonexistent PostgREST
  RPC endpoint (always failed); now uses the OpenAPI-spec path directly
  with honest heuristic/best-effort documentation for PK/FK/index fields
- storage tools now trim + URL-encode bucket/path segments before
  building request URLs (download, list, get_public_url,
  create_signed_url, delete_bucket, delete, and the upload API route)
- storage_create_signed_url guards against a missing signedURL field
  in the response instead of silently building a broken URL
- add supabase_storage_create_signed_upload_url, supabase_storage_update_bucket,
  and supabase_storage_empty_bucket tools + block wiring
- change insert/upsert `data` param type from 'array' to 'json' to match
  actual accepted shapes (array or single object)
- bump tool versions to semver (1.0 -> 1.0.0)
- rewrite a BlockMeta template that implied unsupported Supabase Auth
  Admin user-provisioning

(cherry picked from commit 52b655acf59bace806c75c06b4b2cfaebe4b6781)

* fix(supabase): address review feedback on update-bucket, signed upload url, and introspect

- storage_update_bucket now fetches the bucket's current config first
  and only overrides isPublic/fileSizeLimit/allowedMimeTypes when the
  caller explicitly provides them, instead of silently forcing
  public: false on every update (the Storage API's PUT is a full
  replace, not a patch)
- storage_create_signed_upload_url and storage_empty_bucket now check
  response.ok before surfacing an error, so a non-2xx response reports
  Supabase's actual error instead of a misleading parse-side message
- introspect.ts drops the spurious ?select=* query param from the
  OpenAPI spec request (meaningless on the root spec endpoint)

(cherry picked from commit 557e4074594464262b2f631ca66272bf60f027f8)

* fix(supabase): tri-state bucket visibility on update, empty-string param coercion, introspect schema header

- introspect.ts now sends Accept-Profile when a schema param is given,
  matching the convention used by the other DB tools, so schema-scoped
  introspection actually reads from that schema's spec
- storage_update_bucket.ts treats an empty-string param the same as
  "not provided" (e.g. an untouched file size limit input no longer
  coerces to 0)
- the shared "Public Bucket" dropdown always sent an explicit true/false
  for storage_update_bucket (its default masked "not touched"), which
  could still flip a public bucket private; added a dedicated
  "Keep Current / True / False" control for the update operation so
  omitting a choice genuinely omits the isPublic override

(cherry picked from commit c80567b980e9a547b892a222cebee2bd03d89420)

* fix(supabase): check response.ok before parsing storage_create_signed_url

Matches the pattern already applied to the new signed-upload-url tool
this session — a non-2xx response now surfaces Supabase's actual error
message instead of the generic "did not return a signed URL path" one.

(cherry picked from commit 9f40427dd80ec21b4af1e85c9c8218fd961d6931)

* fix(supabase): correct download param semantics, echoed path field, and schema hint mismatch

Found by a second, per-tool parallel validation pass against live Supabase/PostgREST docs and source:

- storage_get_public_url.ts and storage_create_signed_url.ts sent
  download=true literally, which Supabase's Storage API treats as a
  filename override (renaming the downloaded file to "true") rather
  than a boolean flag; forcing a download while keeping the original
  filename requires an empty download= value
- storage_create_signed_url.ts also sent download in the POST body,
  which the sign endpoint ignores entirely — forcing download only
  works as a query param on the returned URL
- storage_create_signed_upload_url.ts read a `path` field from the API
  response that doesn't exist there; it now echoes the caller-supplied
  path, matching the official storage-js client
- introspect.ts's nullable heuristic didn't disclose that a NOT NULL
  column with a default is misreported as nullable (PostgREST omits it
  from the OpenAPI required list in that case); documented alongside
  the other already-disclosed heuristics, and removed a tautological
  schema-filter check left over from an earlier revision
- insert.ts/upsert.ts's `data` param reverted from 'json' back to
  'array': the 'json' type mapped to an LLM-facing JSON-schema type of
  'object', which contradicted the param's own description ("array of
  objects or a single object")
2026-07-02 10:45:53 -07:00
943ee27686 feat(langsmith): add run update, get run, and feedback tools (#5363)
* v0.6.29: login improvements, posthog telemetry (#4026)

* feat(posthog): Add tracking on mothership abort (#4023)

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(login): fix captcha headers for manual login  (#4025)

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* feat(langsmith): add run update, get run, and feedback tools

- add langsmith_update_run (PATCH /runs/{id}) to complete the create-then-patch tracing lifecycle
- add langsmith_get_run (GET /runs/{id}) to read a run back
- add langsmith_create_feedback (POST /feedback) to attach scores/corrections to runs
- wire all three into the LangSmith block, reusing shared subBlocks across operations
- fix feedback-capture template to use real feedback API instead of faking it via tagged runs
- switch manual Object.fromEntries filtering to filterUndefined per repo convention

* fix(langsmith): reject non-numeric feedback score instead of silently sending null

Number() on an invalid score string produces NaN, which serializes to
JSON null and would still be sent to LangSmith. Throw instead, matching
the existing parseJsonValue error pattern.

* fix(langsmith): harden error handling and validation on new run tools

- update_run, get_run, create_feedback now check response.ok before
  parsing/returning, matching the cloudwatch/zoom convention instead of
  silently returning success on a 4xx/404
- block outputs schema now exposes inputs/outputs for Get Run
- update_run requires at least one field to patch, matching the
  batch-ingest guard for post/patch

* fix(langsmith): align get_run/update_run outputs and narrow feedback score type

- get_run now also outputs runId (alias of id) so workflows can read
  the run identifier consistently across all operations on the block
- update_run now parses and surfaces the response message instead of
  discarding the body entirely, matching create_run's pattern
- narrow LangsmithCreateFeedbackParams.score to number — the tool
  param is declared as JSON-schema 'number' and the block's parseScore
  never produces a boolean, so the wider type was dead and misleading

* fix(langsmith): reject empty-string patch fields and empty PATCH bodies

- block mapper now normalizes blank name/end_time/status/error inputs
  to undefined instead of forwarding empty strings, which would clear
  those fields on the LangSmith run
- update_run tool now throws if the filtered PATCH body is empty,
  guarding direct/programmatic tool calls that bypass the block's own
  "at least one field" check

* fix(langsmith): normalize empty-string patch fields at the tool layer too

Direct/agent tool calls bypass the block's own emptyToUndefined guard,
so update_run now normalizes blank name/end_time/status/error itself
before filtering, matching the block-level fix.

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
2026-07-02 10:45:02 -07:00
Waleed a267a95809 fix(vercel): align integration with live Vercel REST API docs (#5370)
* fix(vercel): align integration with live Vercel REST API docs

- add missing teamId/slug scoping to 29 tools (deployments, checks, projects, env vars)
- add DNS SRV/HTTPS record support (nested srv/https objects) to create/update_dns_record
- add decrypt/gitBranch filters to get_env_vars, autoUpdate to rerequest_check
- add redirect param to create_alias, comment to create_dns_record
- add missing customNameservers/userId/teamId/transferStartedAt fields to list_domains output
- wire pagination filters (limit/since/until/search/app) into block subBlocks for list_deployments, list_teams, list_team_members, list_dns_records, list_project_domains
- wire previously-unexposed tool params into block UI: withGitRepoInfo, gitSource, forceNew, externalId, rerequestable, output, direction, follow
- fix duplicate projectId subBlock id collision between list_deployments and other project-scoped operations
- fix teamId scope field incorrectly hidden for check operations

* fix(vercel): address review findings on DNS/deployment param handling

- fix withGitRepoInfo 'No' dropdown sending withGitRepoInfo=false instead of omitting the param
- remove redundant duplicate 'Forward' option from eventsDirection dropdown
- fix mxPriority being silently dropped in update_dns_record when record type is left unset

* fix(vercel): fix pagination token type mismatch and clarify DNS update UX

- fix list_projects nextFrom being stored as a number despite string typing, which threw TypeError on .trim() when chained into a follow-up list_projects call
- clarify that the DNS update Value field has no effect on existing SRV/HTTPS records unless Record Type is explicitly reselected

* fix(vercel): fix zero-value SRV/HTTPS/MX numeric fields being dropped

- SRV weight/port/priority, HTTPS priority, and MX priority use truthy checks that silently drop legitimate 0 values; switch to explicit empty/null checks
- add missing MX Priority field to create_dns_record block UI (tool already required it for MX records but there was no way to set it)

* fix(vercel): fix stale cross-operation field leaks in block params

- fix list_deployments/create_deployment/update_check/list_team_members/update_dns_record/update_env_var conditionally spreading into keys that collide with unrelated operations' non-destructured literal subBlock fields (projectId, deploymentId, target, name, search)
- a stale value left over from switching operations in the UI would silently leak into the wrong tool call since the conditional spread only overrides when the current operation's own field has a value
- fix now always assigns these keys directly so they deterministically override any stale base value

* feat(vercel): close remaining input/output completeness gaps

- add missing slug (team-slug) param to 13 domain/DNS/alias tools that were skipped in an earlier fix pass, matching the pattern used everywhere else in this integration
- add rootDirectory, nodeVersion, devCommand params to create_project/update_project (verified against Vercel's live OpenAPI spec — high-value fields for monorepo support and runtime pinning)
- surface rootDirectory/nodeVersion in get_project/list_projects outputs, since the API already returns them
- wire all new fields into the block UI as advanced fields

* fix(vercel): fix remaining No->false truthy-string dropdown bugs

- checkRerequestable, checkAutoUpdate, envVarsDecrypt dropdowns used id: 'false' for their No option (a truthy non-empty string), causing the conditional spread to always fire and explicitly send false instead of omitting the param
- swept the whole file for this pattern; checkBlocking correctly keeps 'false' since it's a required field that's always sent directly, not conditionally

* fix(vercel): fix silent project-rename leak in update_project

- update_project had no dedicated rename field and just returned base directly, so a stale value from create_deployment's unrelated 'name' subBlock (Project Name for the deployment) could silently flow through and rename a project on update
- add a dedicated 'New Project Name' field for update_project, wired with a direct override so it always takes precedence over any stale leaked value
2026-07-02 10:39:09 -07:00
Waleed 0507acf2dd fix(amplitude): correct wire formats and add funnels/retention analytics (#5355)
* fix(amplitude): validate integration against API docs, add funnels/retention

- Fix Identify/Group Identify sending JSON instead of the form-urlencoded body Amplitude requires
- Fix Send Event using snake_case product_id/revenue_type instead of Amplitude's camelCase productId/revenueType
- Fix Get Revenue parsing a response shape that never matched the real Revenue LTV API
- Add EU data residency support across all tools
- Add missing filters/formula/segment params to Event Segmentation, groupBy/segment to Active Users and Revenue
- Add first_used/last_used to User Activity output, non_active/flow_hidden to List Events output
- Add real-time/hourly interval options to Event Segmentation
- Add Funnels and Retention tools for conversion and retention analysis
- Harden JSON-shape validation across funnels/segmentation/retention (fail loudly on malformed or partial input instead of silently degrading)
- Expose every tool output field (user_profile, send_event, user_search) on the block so nothing is unreachable downstream
- Update brand colors to current Amplitude guide

* fix(amplitude): validate retention brackets, require formula param, add segmentation 2nd group-by

- Retention now validates retentionBrackets as a JSON array and requires it when retentionMode is "bracket", matching the block UI's requirement
- Event Segmentation now throws if metric is "formula" but no formula is provided, matching the block UI's requirement
- Event Segmentation now supports a documented second group-by property via groupBy2/g2
- Corrected Get Active Users' group-by copy — Amplitude's docs don't document a second-property syntax for /api/2/users the way they do for segmentation's g2, so the field no longer overpromises "max two"
2026-07-02 10:25:59 -07:00
Waleed 59d6b8a62e fix(onepassword): validate integration against API docs, add file downloads (#5365)
* fix(onepassword): validate integration against API docs, add file downloads

- add onepassword_get_item_file tool + route for downloading item file
  attachments (SDK items.files.read / Connect files/{id}/content), backed
  by newly-exposed item.files metadata on get/create/replace/update item
- fix update_item JSON Patch applying array indices instead of 1Password's
  documented field-ID addressing (/fields/{fieldId}/...), which silently
  dropped field edits in Service Account mode
- fix Service Account mode's list-vaults/list-items filter to honor SCIM
  `eq` exact-match semantics instead of always substring-matching
- expand the create-item category dropdown from 9 to 19 real, creatable
  1Password categories (was missing SOFTWARE_LICENSE, EMAIL_ACCOUNT,
  MEMBERSHIP, PASSPORT, REWARD_PROGRAM, DRIVER_LICENSE, BANK_ACCOUNT,
  MEDICAL_RECORD, OUTDOOR_LICENSE, WIRELESS_ROUTER, SOCIAL_SECURITY_NUMBER)
- replace the block's single opaque `response: json` output with typed,
  per-operation output fields matching repo convention
- remove incorrect password-masking on the Vault ID field (not a secret)
- re-export tool types from the onepassword barrel

* fix(onepassword): honor SCIM attribute name in filter matcher

matchesFilter always compared against name/title regardless of the
attribute named in the eq expression, so `id eq "..."` incorrectly
matched against the display name instead of the id.

* fix(onepassword): close output-parity and doc-string gaps from final audit

- restore a deprecated no-op 'response' output so pre-existing saved
  workflows referencing it fail soft (empty) instead of hard-erroring
  now that per-operation outputs replace it
- add missing block outputs (urls, favorite, version, state,
  lastEditedBy) for get/create/replace/update item so all real
  FULL_ITEM fields are discoverable as <Block.field> references
- hide Connect Server credential fields for Resolve Secret (Service
  Account only) instead of leaving them selectable and silently ignored
- correct two doc-string enum lists that advertised values the API
  doesn't return (vault type TRANSFER, item state DELETED)

* fix(onepassword): fix silent data loss in update_item (Service Account mode)

update_item applied user JSON Patch ops (documented/typed against the
Connect-shaped vocabulary get_item returns: label/type/section.id)
directly onto the raw SDK item, whose vocabulary differs (title/
fieldType/sectionId, and SDK category enum strings vs Connect's
SCREAMING_SNAKE_CASE). Most patches beyond /title, /tags/-, and
/fields/{id}/value silently no-opped or could corrupt the item while
still reporting success.

Extracted the Connect->SDK item conversion already used by replace_item
into a shared connectItemToSdkItem helper. update_item now normalizes
the fetched item to Connect shape, applies patches to that, then
converts back before calling items.put() -- matching create/replace's
existing translation pattern.

Found via an adversarial final-verification pass that traced concrete
patch operations by hand against the SDK's actual field vocabulary.

* fix(onepassword): preserve field metadata and empty-title fallback

connectItemToSdkItem rebuilt every field as a bare object, dropping
SDK-only metadata (e.g. password-generation details) that a raw
patch/replace previously left untouched. Now merges onto the existing
SDK field by id before applying the translated properties, and only
starts fields bare when they're genuinely new.

Also restored the || (not ??) fallback on title to match replace_item's
prior behavior of treating an explicitly empty title as "not provided".
2026-07-02 10:23:48 -07:00
Waleed f658e6dc73 fix(tailscale): align tool coverage and outputs with the Tailscale API (#5366)
* fix(tailscale): align tool coverage and outputs with the Tailscale API

- fix list_users profilePicUrl field name (API returns lowercase, was always null)
- add nodeId, keyExpiryDisabled, expires to device outputs
- quote the If-Match header value on ACL updates per API spec
- add set_acl, expire_device_key, suspend_user, delete_user tools
- add wandConfig to dnsServers/searchPaths block fields
- expand BlockMeta skills/templates for ACL and key-expiry workflows

* fix(tailscale): remove phantom magicDNS field from list_dns_nameservers

The GET /tailnet/{tailnet}/dns/nameservers response only returns
{dns: string[]} per the API spec — magicDNS is not part of this
endpoint's response and was always silently false.

* fix(tailscale): extend ToolResponse in expire_device_key response type

Matches the pattern used by the other new tools in this PR
(delete_user, suspend_user).

* fix(tailscale): list_auth_keys now returns all tailnet keys

GET /tailnet/{tailnet}/keys silently scopes to the caller's own
keys unless all=true is passed, contradicting the tool's stated
purpose of listing all auth keys in the tailnet.
2026-07-02 10:15:06 -07:00
Waleed 5966e5cf5a feat(similarweb): add page views tool, fix paid referrals field mismatch (#5354)
* feat(similarweb): add page views tool, fix paid referrals field mismatch

- Add similarweb_page_views tool (Total Page Views, Desktop & Mobile)
- Fix paidReferrals in website overview: API Lite response key is
  literally "paid _referrals" (space before underscore), not caught
  by the existing fallback chain, so it always resolved to null
- Move startDate/endDate/mainDomainOnly to advanced mode

* fix(similarweb): accept both page_views key spellings in page views response

Cursor Bugbot and Greptile both flagged the response field as page_views by
convention, but SimilarWeb's own docs example response uses pages_views for
this specific endpoint. Accept both spellings defensively so the tool works
regardless of which is actually returned.
2026-07-02 10:12:53 -07:00