Skip to content

v0.7.24: frontend perf improvements, md editor fixes, mailer fix, tools audit#5458

Open
waleedlatif1 wants to merge 33 commits into
mainfrom
staging
Open

v0.7.24: frontend perf improvements, md editor fixes, mailer fix, tools audit#5458
waleedlatif1 wants to merge 33 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

waleedlatif1 and others added 27 commits July 5, 2026 10:59
…heck (#5429)

hasWorkspaceAdminAccess + a separate getWorkspaceWithOwner call each
independently re-fetched the workspace row for the same (userId,
workspaceId) pair. Consolidated into a single checkWorkspaceAccess
call, matching the pattern the GET handler in this same file already
uses. access.canAdmin is logically identical to hasWorkspaceAdminAccess's
result (admin is the top PERMISSION_RANK, nothing else satisfies it) —
no behavior change, one fewer DB round-trip per publish request.
…Permissions thin wrappers of checkWorkspaceAccess (#5430)

hasWorkspaceAdminAccess and getUserEntityPermissions('workspace', ...)
each independently re-implemented the same getWorkspaceWithOwner +
getEffectiveWorkspacePermission fetch that checkWorkspaceAccess already
does — this is exactly the duplication that let the custom-blocks POST
handler drift into two separate DB round-trips for one permission
resolution (fixed separately). Centralizing all three onto one
implementation means a future logic change or bug fix only has to
happen once, and any future caller that (accidentally) calls two of
these functions together is now at least drawing from one consistent
definition of "effective permission" instead of two that could diverge.

Adds a `permission: PermissionType | null` field to the WorkspaceAccess
return shape so getUserEntityPermissions doesn't need a second
independent getEffectiveWorkspacePermission call to get the raw value.

No behavior change: verified analytically (canAdmin is exactly
permission === 'admin' per PERMISSION_RANK) and confirmed by an
independent security review focused on argument-order and
privilege-escalation risks in this kind of refactor. 616 existing
tests across lib/workspaces, lib/credentials, lib/invitations,
lib/copilot/vfs, and the affected API routes pass unmodified (plus 2
test assertions updated for the new additive field).
…nstant (#5427)

* refactor(react-query): hoist every staleTime into a named exported constant

Adds a repo-wide convention (documented in .claude/rules/sim-queries.md,
CLAUDE.md, and the react-query-best-practices skill/command) that every
staleTime value must come from a named exported constant instead of an
inline numeric literal. This prevents a server-side prefetch and its
client hook from independently duplicating the same duration and
drifting out of sync, which Greptile caught on PR #5426.

Applies the convention across all of hooks/queries/** and their
prefetch.ts consumers.

* refactor(react-query): use FOLDER_LIST_STALE_TIME in folders.ts hooks

useFolders and useFolderMap still used inline 60 * 1000 despite folders.ts
already exporting FOLDER_LIST_STALE_TIME — the same prefetch-drift gap this
refactor closes everywhere else. Same value, no behavior change.
…igation (#5428)

* improvement(sidebar): memoize workflow/folder rows for faster tab navigation

Switching between workspace tabs re-rendered every workflow and folder row in
the sidebar because the rows (and the shared export hooks they call) subscribed
to useParams, which re-renders on every navigation.

- Wrap WorkflowItem and FolderItem in React.memo (the only un-memoized leaf
  rows; every sibling row was already memoized).
- Decouple the rows from useParams: thread workspaceId as a stable prop from
  WorkflowList (matching the FileList convention), and expose the live active
  workflowId through a stable activeWorkflowIdRef on SidebarListContext, read
  only in delete callbacks — never during render.
- Refactor the three shared export hooks (used only by these two rows) to take
  workspaceId as a param instead of calling useParams internally.
- Stabilize handleWorkflowClick's identity via refs so the shared list context
  no longer changes identity on navigation.
- Lazy-init the drag-drop siblings Map ref.

On a tab switch only the two rows whose active state flips now re-render.

* fix(sidebar): add workspaceId to render-callback deps

renderWorkflowItem/renderFolderSection now pass workspaceId into the rows, so
they must list it as a dependency — otherwise a workspace switch that doesn't
also change workflowId would leave the callbacks closing over a stale
workspaceId (wrong-workspace deletes/exports).
…#5426)

* fix(sidebar): prefetch folders and workspace permissions on cold load

* refactor(sidebar): dedupe folder query and permission lookups from /simplify pass

* docs(workspaces): trim getWorkspacePermissionsForViewer tsdoc to match repo convention

* fix(folders): reference FOLDER_LIST_STALE_TIME constant instead of duplicating literal
…nts (#5434)

cc, attachments, and html are omitted from AgentMail's webhook payload
when empty rather than sent as empty arrays/null, but the zod schema
marked them required. Every inbound email without a CC recipient or
attachment (the common case) failed schema validation before a task
row was ever created, silently breaking Sim Mailer inbox ingestion
since the feature shipped. Renames from_ to from to match AgentMail's
documented field name.
* fix(analytics): apply runtime CSP to all landing pages, not just /

GTM/GA4 script-src and connect-src allowlist entries are gated behind
isHosted, which next.config.ts's build-time CSP bakes in from whatever
NEXT_PUBLIC_APP_URL was resolved at build/boot time. Only the homepage
route was overridden with the request-time CSP in proxy.ts, so every
other landing page (including /demo) silently lost the
googletagmanager.com/google-analytics.com allowlist and GTM never fired.

* style: drop inline comment from proxy CSP fix

* fix(analytics): apply runtime CSP to authenticated /invite pages too

handleInvitationRedirects returns NextResponse.next() for authenticated
users, bypassing the catch-all block entirely, so it never picked up
the runtime CSP fix in the same way. Same class of gap flagged by review.
…t, paste fidelity (#5438)

* feat(rich-md-editor): table row/col toolbar, raw HTML/footnote support, paste fidelity

- Add a table toolbar (add/delete row, add/delete column, toggle header, delete table) wired to stock @tiptap/extension-table commands
- Add verbatim snippet nodes for raw HTML blocks, HTML comments, footnotes (def + ref), and inline raw HTML tags — these no longer force the whole document read-only, and the raw source is directly editable in place
- Fix an upstream @tiptap/markdown footgun found while building this: a custom tokenizer with no explicit `start` callback corrupts the shared lexer and silently drops unrelated content elsewhere in the document
- Prefer the markdown parser over generic HTML→DOM paste mapping when the plaintext clipboard side looks like markdown, even if an HTML sibling is present
- Fix a stale select-all selection surviving a stream-settle content replace, which permanently highlighted every divider/image after a file regeneration

* fix(rich-md-editor): address review findings from initial PR round

- Collapse the stream-settle selection unconditionally, not only when setContent re-runs — the last streaming tick already syncs lastSyncedBodyRef to the final body, so the fix was previously skipped in the common streamed-content case (Cursor Bugbot)
- Support GFM footnote definition continuation lines (>=4-space indented, with blank lines between paragraphs) instead of truncating to just the opening line (Greptile P1)
- Fix the inline raw-HTML tokenizer to find the balanced closing tag by tracking nesting depth, instead of matching the first same-name closing tag — fixes corruption on nested same-tag elements like <span>outer <span>inner</span></span> (Greptile P1)
- Stop caching the table toolbar's anchor rect by selection key — the same cell can move on screen from scrolling alone with no selection change, and the cached rect went stale (Greptile P2)

* fix(rich-md-editor): fix CI type errors in raw-markdown-snippet.tsx

- parseMarkdown callbacks returned null on no-match, which @tiptap/core's MarkdownParseResult type doesn't permit — switch to returning [] (empty array), matching the same no-match convention MarkdownCodeBlock already uses, with identical runtime behavior
- Pin NodeViewContent's generic to 'span' (NodeViewContent<'span'> as='span'), since it defaults to 'div' and rejects other `as` values without an explicit type argument

Verified with a full `tsc --noEmit` (NODE_OPTIONS=--max-old-space-size=8192, matching CI) — 0 errors, where it previously failed the Next.js build's type-check step.
* 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

* fix(attachments): cross tenant security hardening

* address comments

* fix

* fix

* remove dead code

---------

Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Theodore Li <theo@sim.ai>
…ettings pages (#5439)

* improvement(inbox): align Sim Mailer settings page with design-system tokens

Replace literal pixel text-size classes (text-[12px]/[13px]/[14px]/[16px])
with the named Tailwind scale tokens across inbox.tsx, inbox-enable-toggle,
inbox-settings-tab, and inbox-task-list — pixel-identical, no visual change.
Swap the hand-rolled toggle-row span pair for the emcn Label component,
matching the toggle-row convention used elsewhere in settings.

* improvement(settings): extend design-system token migration across all settings pages

Replace literal pixel text-size classes with named Tailwind scale tokens
(text-caption/small/sm/base/md/lg) across every other settings page and
shared component (member-list, api-keys, mcp, billing, credential-sets,
workflow-mcp-servers, mothership, admin, byok, copilot, custom-tools,
settings-resource-row, and the ee/ access-control, data-retention,
whitelabeling, and shared setting-row components) — pixel-identical, no
visual change. Document the row title/subtitle token pairing and toggle-row
Label convention in sim-settings-pages.md and the add-settings-page skill's
audit checklist so future pages default to it.
…rency (#5436)

* perf(pii): mask offloaded refs in block outputs/input + parallelize + raise concurrency

- Block-output and input stages now hydrate → mask → re-store large-value refs
  (function/tool outputs are offloaded to refs before reaching the redactor, which
  treats refs as opaque) — fixes big block outputs coming back unredacted
- Parallelize large-value ref hydrate/mask/re-store: collect refs across the whole
  payload and process them with bounded concurrency instead of one sequential pass
  per key (PII_REF_CONCURRENCY, default 4)
- Raise mask-batch chunk concurrency default 4 -> 64 to saturate the load-balanced
  Presidio fleet behind the internal ALB (PII_MASK_CHUNK_CONCURRENCY, env-tunable)

* fix(pii): keep resolveReplacements mapper total (respect mapWithConcurrency contract)

- Catch per-ref errors inside the mapWithConcurrency mapper and rethrow the first
  after the pool drains, instead of letting a throwing mapper reject the pool
  (the util documents fn MUST NOT reject). Preserves fail-fast abort in throw mode.
- Add multi-ref throw-mode test: one of several refs failing aborts the redaction

* feat(pii): make route->Presidio chunk concurrency env-tunable (PII_SERVICE_CHUNK_CONCURRENCY)

Was hardcoded to 4; now env-tunable (default 4) so the inner route->Presidio
fan-out can scale with the fleet alongside the outer PII_MASK_CHUNK_CONCURRENCY.

* feat(pii): combined /redact + /redact_batch endpoint (one round-trip)

- Presidio: add /redact and /redact_batch (analyze + anonymize server-side, feeding
  analyzer results straight to the anonymizer, no dict round-trip). All existing
  endpoints kept, so old clients keep working during rollout.
- App: maskPIIBatch now uses /redact_batch (halves app<->service round-trips, no
  shipping text back up for anonymize). Falls back to the legacy analyze_batch +
  anonymize_batch pair on a 404 (older Presidio image), so the app is safe to deploy
  before or after the service in either order. Same length-check fail-closed guarantee.
…#5440)

* fix(rich-md-editor): fix raw-HTML-block fragmentation and styling

- RawHtmlBlock previously delegated to marked's built-in block-HTML tokenizer, which (per CommonMark's HTML-block-type-6 rule) stops at the first blank line. A real <details> with a paragraph inside — the common case — fragmented into a raw chip, an ordinary rendered paragraph, and a second raw chip, stranding real content in between.
- Fixed with a custom block-level tokenizer that scans to the tag's matching close (reusing the balanced open/close depth-tracking already built for nested inline HTML), spanning blank lines, restricted to CommonMark's own block-tag whitelist (details, div, table, section, etc.) so tags that can legitimately start a paragraph (em, a, span, code, kbd) are left untouched.
- Dropped the warning-colored tint on raw HTML/footnote blocks (color-mix with --warning read as an error state) in favor of the same neutral surface as existing code blocks — the hover badge alone now signals "this is raw, unrendered text".

* fix(rich-md-editor): fix indented HTML, quoted attributes, and code-mention edge cases

- CommonMark allows up to 3 leading spaces before a block-HTML opening line; the new block tokenizer required column 0, so an indented <details>/<div> still fragmented across blank lines. Fixed by splitting off the leading indent, matching against the rest, and stitching the indent back onto raw.
- The open-tag and balanced-close regexes stopped at the first `>`, even inside a quoted attribute value (e.g. data-example="a > b"), producing wrong match lengths and miscounting a same-tag mention inside the quoted value as a real nested tag. Replaced with an attribute-aware pattern that treats a full quoted value (including any interior >) as one unit.
- The balanced-tag scan couldn't distinguish real markup from a tag name mentioned inside inline code or a fenced code block. Now masks code regions (same-length filler, positions preserved) before scanning, so a properly-escaped mention (backticks or a fenced example) is never miscounted. A genuinely bare, unescaped mention outside code remains a known, inherent limitation of regex-based tag matching (shared by real HTML parsers given the same ambiguous input) — verified this can never lose data or hang, only reflow to a stable fixpoint on save.

* fix(rich-md-editor): fix blockquoted-fence masking and void-tag balance scan

- maskCodeRegions's fenced-code regex required fence markers at column 0, so a fence quoted inside a markdown blockquote (each line prefixed with `> `) wasn't recognized, leaving tag mentions inside it visible to the balance scanner. Extended the fence pattern to tolerate an optional blockquote prefix on both the opening and closing fence line.
- BLOCK_HTML_TAG_NAMES includes several void elements (link, meta, base, hr, ...) that have no closing tag at all. The block tokenizer only treated an explicit self-closing `/>` as complete, so a void tag without one would scan the rest of the document for a `</meta>` that will never appear, risking a false match on a later same-name mention. Now reuses the existing VOID_TAGS set (already used by the inline tokenizer) to treat these as complete right after the open tag. Also restored `hr` to the whitelist, which was accidentally dropped when transcribing marked's tag list.

* fix(rich-md-editor): tolerate an indented (non-blockquoted) fence in code masking

maskCodeRegions's fence pattern handled a blockquoted fence (`> \`\`\``) but still required the
fence marker at column 0 otherwise, missing CommonMark's independent up-to-3-space fence indent
tolerance this codebase already relies on elsewhere (FENCE_OPEN/FENCE_CLOSE in markdown-parse.ts).
An indented fence's tag-name mention could still end a whitelisted block early. Fixed by combining
both allowances in one prefix pattern (zero-or-more blockquote levels, each independently followed
by up to 3 more spaces of indent).

* fix(rich-md-editor): mask HTML comments in balanced-tag scan

A tag-name mention inside an HTML comment (e.g. `<!-- see </div> below -->`)
inside a whitelisted raw HTML block could still be matched by the balance
scan and end the block early, fragmenting it. maskCodeRegions already masked
fenced/inline code the same way; extend it to mask comments too.
Server rows derived "Loading..." from a workspace-wide isLoading/isFetching
aggregate instead of that server's own per-server query, so a row could sit
stuck on "Loading..." (or flicker back to it) whenever any other server's
tool query was fetching, only clearing after a full page reload.
…surface more entity fields (#5443)

* improvement(new-relic): validate integration against NerdGraph docs, surface more entity fields

- Audited all 4 New Relic tools (nrql_query, search_entities, get_entity,
  create_deployment_event) plus the block against live NerdGraph API docs;
  request/response shapes, auth, region endpoints, and GraphQL injection
  handling all confirmed correct.
- Added domain, reporting, alertSeverity, and tags to get_entity and
  search_entities outputs — stable Entity schema fields we weren't
  surfacing. Purely additive, no existing fields changed.
- Skipped NerdGraph's aiIssues/incidents API; New Relic marks it "unsafe
  experimental" and requires an opt-in header, not worth the fragility.

* fix(new-relic): normalize search_entities output, share entity normalization

search_entities passed raw NerdGraph entities straight through, so tags/
domain/reporting/alertSeverity could arrive as null/undefined at runtime
despite the non-nullable NewRelicEntity type contract — a .map() on a
null tags array would throw. get_entity already normalized these fields
correctly.

Extracted the normalization into a shared normalizeNewRelicEntity() in
utils.ts and reuse it from both tools, so the two can't drift again.

* fix(new-relic): gate alertSeverity behind required GraphQL interface fragments

alertSeverity isn't a direct field on New Relic's Entity/EntityOutline
types — it only exists on the AlertableEntity/AlertableEntityOutline
sub-interfaces, and NerdGraph rejects the query outright without the
inline fragment. get_entity.ts (entity(guid), returns Entity) now uses
`... on AlertableEntity { alertSeverity }`; search_entities.ts
(entitySearch, returns EntityOutline) now uses
`... on AlertableEntityOutline { alertSeverity }`. Confirmed against
New Relic's live NerdGraph entities API docs.
…ad/campaign lifecycle tools (#5448)

* improvement(instantly): validate integration against live API, add lead/campaign lifecycle tools

- Verify all existing Instantly tools against the live API v2 spec
- Add instantly_patch_lead, instantly_pause_campaign, instantly_delete_campaign
- Fix activate_campaign response mapping to surface the full campaign object
- Restore status/ai_sales_agent_id list_campaigns filters

* fix(instantly): guard against empty patch_lead request body

Follows the same pattern used in tools/langsmith/update_run.ts.
…ug (#5444)

* fix(cloudflare): align integration with live API, fix type-coercion bug

- fix update_zone_setting body builder: was blindly JSON.parse-ing every
  value, silently coercing scalar settings (e.g. min_tls_version "1.2")
  to the wrong type; now only parses object/array literals
- fix list_dns_records name/content/tag filters to use Cloudflare's
  exact-match dotted param names (name.exact/content.exact/tag.exact)
- remove auto_minify (never a real setting ID) and minify (deprecated
  and removed from the live zone-settings API in Aug 2024)
- remove dead jump_start param from create_zone (not part of the
  current POST /zones schema)
- fix block bgColor from #F5F6FA to Cloudflare's actual brand orange
  #F38020
- add missing secondary zone type option and plan.id sort option
- expand BlockMeta skills/templates

* fix(cloudflare): reject empty browser_cache_ttl value instead of coercing to 0

* fix(cloudflare): address Greptile review feedback

- use trimmed value consistently in update_zone_setting fallback paths
- document that tag_match has no effect with a single exact-match tag
  filter (Cloudflare's API only combines multiple tag conditions)

* fix(cloudflare): coerce non-string setting values before trim

Wand-generated or block-referenced values can arrive as a number at
runtime despite the declared string param type, which crashed the
body builder's .trim() call.

* fix(cloudflare): harden null/undefined value handling, fix stale tag description

- coerce null/undefined value to empty string instead of literal
  "null"/"undefined" strings before trimming
- fix stale block-level 'tag' input description left over from the
  comma-separated-tags -> exact-match-tag filter fix

* fix(cloudflare): pass through structured array/object values as-is

A block-referenced array/object value was being blindly stringified
before the JSON-shape check, so String([...]) comma-joined arrays and
String({...}) produced the literal "[object Object]" instead of the
JSON shape Cloudflare expects (e.g. for ciphers). Already-structured
values now pass straight through.

* revert(cloudflare): keep original block bgColor (#F5F6FA)
…gaps (#5442)

* improvement(microsoft-ad): add pagination support and fill BlockMeta gaps

Full validate-integration pass against live Microsoft Graph API docs. No
critical bugs found (endpoints, methods, params, and OAuth scopes were
already correct). Fixed the real gaps:

- List Users/Groups/Group Members had no way to page past the default
  Graph page size (100, max 999 via $top), which silently truncated
  results for the block's own audit/sweep templates. Added a nextLink
  input/output following the same convention as microsoft_dataverse.
- Create Group's visibility dropdown was missing HiddenMembership, a
  valid Microsoft 365-only value that can only be set at creation time.
- Rounded out BlockMeta skills (3 -> 5) with two more real, tool-grounded
  use cases: directory search and ad hoc group membership changes.

* fix(microsoft-ad): correct $search syntax and create-user response fields

Independent 4-agent re-audit against live Graph docs surfaced two real bugs
predating this PR:

- list_users/list_groups sent $search="<term>" with no property prefix.
  Graph requires the "property:value" form for directory-object search
  (e.g. "displayName:term" OR "mail:term") and 400s on a bare string.
- create_user's POST had no $select, so Graph's default create response
  omits department/accountEnabled even when submitted, making
  transformResponse report them back as null. Added the same $select used
  by list/get so the response reflects what was actually set.

Also tightened create_group's visibility description: only HiddenMembership
is create-only: Private/Public can still be changed after creation via
Update Group.

* fix(microsoft-ad): validate nextLink origin, allow nextLink-only pagination

Greptile and Cursor both flagged the same real issue: nextLink was passed
straight to fetch() as the request URL with no origin check, while the
OAuth bearer token was always attached. A crafted or prompt-injected
nextLink pointing outside graph.microsoft.com would exfiltrate the token.

Fix: reuse the existing assertGraphNextPageUrl/getGraphNextPageUrl helpers
from tools/sharepoint/utils (already the shared pattern for SharePoint,
OneDrive, Teams, Planner, Outlook, Excel Graph pagination) instead of a
bespoke unvalidated pass-through.

Cursor also caught that list_group_members required groupId even when
only nextLink was supplied for a later page. Relaxed groupId to optional
at the tool level, matching how sharepoint_get_list treats its analogous
listId param — the URL builder still throws a clear error if neither
groupId nor nextLink is given.

* fix(microsoft-ad): allow $search+$filter combo, escape backslashes, fix pagination UX

Second independent 4-agent re-audit of the final state (post security fix)
surfaced 3 more real issues:

- list_users/list_groups threw an error whenever $search and $filter were
  both supplied, claiming Graph doesn't support combining them. It does
  (AND semantics, documented) — the check was blocking valid, documented
  usage for no reason. Removed it.
- The $search term escaping only handled embedded double quotes, not
  backslashes, which Graph's own escaping rule also requires. Fixed the
  replace order (backslashes first, then quotes).
- list_group_members's Group ID field was still hard-required in the
  block UI for every operation including list_group_members, undermining
  the nextLink-only pagination path added earlier (the tool itself no
  longer requires it). Dropped list_group_members from the UI-required
  list, matching the tool's own conditional requirement — the runtime
  "Group ID is required" check still catches a genuinely empty call.
…verage (#5446)

* feat(pagerduty): validate integration + add 9 tools for deeper API coverage

- fix list tools' total field to null-default (matches PagerDuty spec, was wrong 0 default)
- add pagination offset across all list tools
- add incidentKey, resolution, urgencies, triggered status support
- add get_incident, get_service, list_escalation_policies, list_incident_alerts,
  list_schedules, list_users, merge_incidents, snooze_incident tools (REST API v2)
- add send_event tool (Events API v2) for trigger/acknowledge/resolve via routing key
- add 2 new BlockMeta skills grounded in documented PagerDuty workflows

* fix(pagerduty): address review findings on send_event/snooze/merge validation

- match required condition to visibility condition for eventSummary/eventSource (trigger-only)
- validate trigger payload has summary/source/severity before sending, throw descriptive error
- validate snooze duration is finite and within PagerDuty's 1-604800 range
- drop empty segments when splitting merge source incident IDs

* fix(pagerduty): reject empty merge sources, enforce dedupKey, require integer snooze duration

- merge_incidents: throw if source_incidents ends up empty after filtering blanks
- send_event: require dedupKey for acknowledge/resolve to match block's required condition
- snooze_incident: require an integer duration, not just a finite number

* fix(pagerduty): reject resolution note unless status is resolved

PagerDuty only accepts an incident's resolution field when status is
being set to resolved in the same request; sending it otherwise gets
rejected by the API with an opaque error.

* docs(pagerduty): mention triggered as a valid update_incident status
…endpoints (#5447)

* feat(ahrefs): validate integration, fix cents/column bugs, add 13 v3 endpoints

Audited the existing Ahrefs integration against live API v3 docs and fixed
real bugs: broken_backlinks selected the wrong column (http_code_target
instead of http_code), and keyword_overview/metrics/metrics_history returned
CPC/cost fields in USD cents without converting to USD. Also fixed the
top-pages mode dropdown missing the "exact" option.

Added 13 new tools covering previously-unsupported Ahrefs v3 endpoints:
Rank Tracker (overview, SERP overview, competitors overview, competitors
stats), Batch Analysis, Site Audit page explorer, four history/trend
endpoints (domain rating, metrics, referring domains, keywords), Related
Terms, Anchors, and Paid Pages.

Note: the CPC/cost unit fix silently shifts existing organicCost/paidCost/cpc
values by 100x for any workflow already consuming them (the old values were
wrong, in cents instead of dollars).

* fix(ahrefs): convert remaining cents-to-USD fields, drop unneeded fallback key

Rank Tracker SERP Overview's value field, Competitors Stats' trafficValue,
and Competitors Overview's nested competitor value were all left in USD
cents while every other monetary field in the integration converts to USD
- fixed for consistency with the rest of the integration.

Also drops the unverified `data.results` fallback in Batch Analysis; the
Ahrefs v3 docs confirm the response key is always `targets`.

* docs(ahrefs): regenerate docs to reflect cents-to-USD conversion fix

The generated docs page was stale after the previous commit converted
rank_tracker_serp_overview.value, rank_tracker_competitors_stats.trafficValue,
and rank_tracker_competitors_overview's nested value from cents to USD -
regenerating picks up the corrected field descriptions.

* fix(ahrefs): revert incorrect broken_backlinks column, fix missed top_pages conversion, revert unverified competitor value conversions

Independent re-verification against live Ahrefs v3 docs surfaced two real
regressions from the earlier fix rounds and one missed conversion:

- broken_backlinks: the earlier fix changed the selected column from
  http_code_target to http_code, but the docs say http_code is the
  *referring page's* status and http_code_target is the *broken target
  page's* status - the tool needs the latter (matches its own output
  description). Reverted to http_code_target.
- top_pages: value field was never divided by 100 despite the docs stating
  it's in USD cents and the output already claiming USD - fixed.
- rank_tracker_competitors_stats.trafficValue and the nested competitor.value
  in rank_tracker_competitors_overview were converted from cents to USD last
  round on a "match every other monetary field" assumption, but the live docs
  do not document these two fields as cents (unlike every field that was
  correctly converted). Reverted to passthrough and dropped the "(USD)"
  claim from their descriptions until Ahrefs documents the unit.

Also added the missing "exact" mode option to top_pages' mode param
description, matching every sibling tool.

* fix(ahrefs): convert rank tracker competitor value/trafficValue to USD

Both bots independently flagged these two fields as inconsistent with
every other monetary field in the integration, all 7 of which are
explicitly documented as USD cents. Neither field has explicit unit
documentation (one is undocumented as cents, the other's schema isn't
statically retrievable at all), but given the unanimous pattern across
every other verified field and no contrary evidence, converting for
consistency is the better bet than leaving them as an outlier.

* style(ahrefs): drop non-TSDoc inline comments introduced in this PR

Repo convention disallows non-TSDoc comments; removed the three
explanatory // comments this PR added next to cents-to-USD conversions
(keyword_overview, paid_pages, related_terms) - pre-existing comments
elsewhere in the file are untouched, out of scope for this PR.

* fix(ahrefs): default optional country to us consistently across all tools

paid_pages, metrics_history, keywords_history, and batch_analysis were the
only 4 of the 11 tools with an optional country param that didn't fall back
to "us" when omitted, unlike domain_rating, metrics, keyword_overview,
organic_keywords, organic_competitors, top_pages, and related_terms - all
of which default client-side. Aligned all four to the same convention so
direct tool/agent calls without an explicit country get the same behavior
regardless of which operation is used.

* fix(ahrefs): split shared date subBlock id for datetime-format operations

site_audit_page_explorer and rank_tracker_serp_overview both reused the
generic 'date' subBlock id with YYYY-MM-DDThh:mm:ss semantics, while every
other operation using that same id expects YYYY-MM-DD. Switching operations
without clearing the field could carry a stale wrong-format value into the
new operation's request. Split into distinct ids (crawlDate, asOfDate)
mapped back to each tool's date param in tools.config.params.
n8n's pricing standout feature implied a per-step pricing contrast that
doesn't hold up; reframe around the verified unlimited-seats difference.
* fix(comparison): correct false-contrast claims across competitor pages

Audited every Sim-vs-competitor page against Sim's own facts; fixed
standout/limitation claims that implicitly overstated a Sim gap that
doesn't actually exist, plus a few stale or under-sourced facts.

* fix(comparison): address review feedback on wording precision

Clarify the pipedream acquisition-status claim, fix circular OIDC
phrasing, correct a source/claim mismatch, and remove a stale SOC2
implication.

* fix(comparison): fix issues introduced by the previous fix pass

Second independent audit pass on the fixed pages caught a few new
problems from the edits themselves: a stale/mismatched source, an
internal contradiction, an unverified superlative, a still-dominant
false-contrast paragraph, a broken repo link, an editorializing note
leaking into shipped copy, and a duplicated/grammatically broken entry.

* fix(comparison): qualify self-reported Series B figure in value field

The confidence downgrade to 'estimated' wasn't reflected in the value
field itself, which still stated the Series B/total funding as plain fact.
* feat(attio): add attribute tools + fix API alignment gaps

- Add 4 new tools: attio_list_attributes, get_attribute, create_attribute, update_attribute
- Add missing completed_at field to task tools
- Fix note tags output to match Attio's actual response shape (workspace-member vs record tags)
- Fix attribute outputs missing is_default_value_enabled, default_value, relationship
- Fix create_comment sending both entry and thread_id (Attio requires exactly one)
- Wire attribute pagination + task sort into the block

* fix(attio): address review feedback on attribute tools

- Throw on invalid config JSON instead of silently overwriting with {}
  (create_attribute, update_attribute)
- create_attribute: omit config field entirely when not provided instead
  of always sending {}
- Add "Leave unchanged" option to Required/Unique dropdowns so
  update_attribute no longer clears existing constraints on unrelated
  field updates
- Fix stale sort param description on list_tasks (was missing
  completed_at:asc/desc variants)

* fix(attio): fix silent update-clobber bugs + complete comment mutual-exclusion

- create_comment: support all 3 of Attio's mutually-exclusive comment
  targets (thread_id, record, entry), not just thread_id/entry
- Fix update_task silently clearing is_completed on unrelated field
  updates (taskIsCompletedUpdate now defaults to "leave unchanged")
- Fix update_list silently resetting workspace_access to full-access on
  unrelated field updates (listWorkspaceAccessUpdate, same pattern)
- create_attribute: restore config:{} as always-required per Attio's
  schema (previously omitted it entirely, which is invalid on create)
- create_record/update_record/assert_record/list_records: throw on
  invalid values/filter/sorts JSON instead of silently substituting {}
  (was a silent data-loss risk on malformed input)
- get_task: drop stray Content-Type header on a GET request
- types.ts: remove two dead unreferenced interfaces, fix
  workspaceMemberAccess type (was string, is actually an array)

* fix(attio): gate operation-specific param mapping on current operation

Params like taskIsCompleted/listWorkspaceAccess/attributeIsMultiselect/
attributeIsArchived are only meant for one specific operation, but their
mapping wasn't checking params.operation. A stale value persisted in
block state from a prior operation selection (e.g. switching the
dropdown from create_task to update_task) could leak through and
override the "leave unchanged" default on the other operation.

* fix(attio): explicit comment target selector + attribute archived tri-state

- Add "Leave unchanged" default to attributeIsArchived dropdown, same
  pattern as isRequired/isUnique, so a stale archived flag from an
  earlier edit can't unarchive an attribute on an unrelated update
- create_comment: replace field-presence inference (which let stale
  record fields hijack a list-entry comment or vice versa) with an
  explicit commentTarget selector (List Entry / Record / Reply to
  Thread). Only the fields for the selected target are ever forwarded

* fix(attio): gate remaining unscoped param mappings + comment-target back-compat

- taskFilterCompleted (list_tasks filter) was mapped to isCompleted for
  every operation; gate it to list_tasks so it can't override the
  isCompleted value on an unrelated update_task call
- Generic threadId (get_thread) was unconditionally forwarded and
  create_comment prioritizes thread_id first, so a leftover threadId
  from configuring get_thread could silently hijack a comment meant for
  a list entry or record; gate it to get_thread
- Default commentTarget to the pre-existing behavior (entry, or thread
  if commentThreadId is set) when absent, so blocks saved before this
  field existed keep working instead of throwing

* fix(attio): gate every param mapping by its operation, eliminate stale-value class of bugs

The params() function reuses cleanParams keys (title, content, apiSlug,
filter, sorts, recordId, entryId, list, object, threadId, ...) across
several unrelated operation families. Every mapping was unconditional on
presence alone, so a stale value left in block state from a previously
selected operation could silently leak into an unrelated request and
overwrite the intended value (last-write-wins on a shared key).

Rewrote the whole function to gate every line by params.operation
against the exact operation set its subBlock's condition exposes it
under, closing this entire bug class in one pass instead of patching
individual instances as they were found in review.

Also split attributeIsRequired/attributeIsUnique into create-only
(default false) and update-only (tri-state, default "leave unchanged")
variants — the shared field let an explicit Yes/No choice from
create_attribute carry over and silently change constraints on an
unrelated update_attribute call.

* fix(attio): preserve legacy taskIsCompleted/listWorkspaceAccess on update

taskIsCompleted and listWorkspaceAccess pre-date this PR as fields
shared between create and update operations (confirmed present on
origin/staging). Splitting them into create/update variants earlier
this session meant existing saved blocks with a value stored under the
legacy field name would silently stop applying it on update_task /
update_list once the new -Update field (always undefined for old
blocks) took over. Fall back to the legacy field when the new field is
untouched, so old saved workflows keep behaving exactly as before.
…nment gaps (#5450)

* feat(aws): expand SES/STS/Secrets Manager tool coverage, fix API alignment gaps

- SES: add suppression list management, email identity CRUD, template
  update, configuration set creation, custom verification email (10
  new tools); fix silent httpsPolicy drop in create_configuration_set
  and unvalidated suppression reason enum in list_suppressed_destinations
- STS: add AssumeRoleWithWebIdentity and AssumeRoleWithSAML (unsigned,
  no static credentials required); extend assume_role with
  policyArns/tags/transitiveTagKeys session params
- Secrets Manager: add describe_secret, tag_resource, untag_resource,
  restore_secret, rotate_secret; fix list_secrets dropping
  rotation/version metadata fields; normalize tool versions to 1.0.0
  and alphabetize registry entries

All 31 tools verified param-by-param against live AWS API docs across
two independent audit passes.

* fix(aws): address review findings on SES config/identity and Secrets Manager rotation

- ses_create_configuration_set: validate suppressedReasons against
  BOUNCE/COMPLAINT enum before calling AWS (was silently reaching AWS
  as a generic 500 for bad values); tags now a proper Zod array schema
  instead of a string with route-side JSON.parse
- ses_create_email_identity: dkimSigningAttributes and tags now proper
  Zod object/array schemas instead of strings with route-side
  JSON.parse, matching the pattern used elsewhere (e.g. sts_assume_role
  tags, secrets_manager_tag_resource)
- secrets_manager_rotate_secret: reject automaticallyAfterDays and
  scheduleExpression when both are supplied — AWS RotationRules
  accepts only one
- sts createUnauthenticatedSTSClient: corrected a misleading comment
  claiming these calls are fully unsigned; the SDK still falls through
  its default credential provider chain

* fix(ses): correct json input types for tags/dkimSigningAttributes

The SES block declared the tags and dkimSigningAttributes block inputs
as 'string' instead of 'json', so the generic block executor never
parsed the JSON code-editor value before forwarding it — workflow runs
sent a raw JSON string where the contract now expects a structured
object/array, failing validation. Also corrected the corresponding
tool param TypeScript types, which were still typed as string | null.

* fix(ses): stop coercing switch string 'false' to true in create_configuration_set

Boolean('false') evaluates to true, so turning off the
reputationMetricsEnabled or sendingEnabled switch sent the opposite of
the user's choice to SES. Match the established === 'true' string
comparison pattern used elsewhere in the codebase.

* fix(sts): stop double-parsing assume_role session tags input

tags was declared as a 'json' block input, so the generic executor
JSON.parse'd it before the switch-case handler ran — but that handler
already converts the raw table-rows array (or a passthrough string)
into the JSON string the sts_assume_role contract expects. Declaring
it 'json' broke that conversion for non-string inputs. Reverted to
'string' so the handler's existing string/array disambiguation runs
on the untouched raw value.

* fix(sts): supply placeholder credentials to the unauthenticated client

createUnauthenticatedSTSClient omitted credentials entirely, so the
SDK's signing middleware fell through the default credential provider
chain and threw CredentialsProviderError before the request was sent
in any environment with no ambient AWS identity — even though
AssumeRoleWithWebIdentity/AssumeRoleWithSAML never check the
signature. Static placeholder credentials skip that resolution
without granting or requiring any real IAM identity.
…it (#5454)

* fix(microsoft-excel): clean up dead code found during integration audit

- Remove unreachable 'update' operation subblocks (Google Sheets
  copy-paste leftover — never a selectable dropdown option and not
  handled by the tool selector or any registered tool)
- Fix microsoft_excel_table_add to trim spreadsheetId and OData-escape
  tableName, matching every other tool in this file
- Drop phantom/dead type fields: Google-Sheets-only insertDataOption
  and responseValueRenderOption (never used), never-populated
  sheetId/sheetName/title/sheets metadata fields, unused rowIndex, and
  the unconfigurable majorDimension param (hardcoded to 'ROWS' now
  that it's inlined)

* fix(microsoft-excel): fix write output field mapping + OData escaping gaps

Found in an independent second-pass audit against the live Graph API
docs:

- microsoft_excel_write (v1) transformResponse read
  updatedRange/updatedRows/updatedColumns/updatedCells from the Graph
  response — none of these fields exist on the workbookRange resource
  (the real fields are address/rowCount/columnCount), so these four
  output fields were always undefined. Fixed to read the actual
  fields, matching what the v2 write tool already does correctly.
- read.ts and write.ts (v1 and v2) built worksheets('name') OData
  URLs with only encodeURIComponent, skipping escapeODataString — a
  worksheet name containing an apostrophe would produce a malformed
  request. Every other tool in this integration already escapes
  correctly; read/write were the outliers.
- write.ts (v1) also wasn't trimming spreadsheetId before use,
  inconsistent with every other tool here.
…tive paste tests, editor docs (#5455)

* fix(rich-markdown-editor): show the col-resize cursor on table column borders

prosemirror-tables toggles a `resize-cursor` class on the editor while the pointer is over a
column boundary, but there was no rule to change the cursor — the blue resize handle showed with
no cursor affordance. Add the scoped `col-resize` rule.

* test(rich-markdown-editor): exhaustive markdown paste coverage

Cover every rich construct (headings, marks, lists, task lists, blockquote, code block, image,
thematic break, table), markdown parsed despite an HTML sibling, multi-block order, read-only
rejection, and the defer/verbatim cases for non-markdown input.

* docs(editor): add rich markdown editor page

Document the inline rich markdown editor — formatting, structure, lists, tables, code blocks,
images, the slash menu, and markdown fidelity — with a rendered overview screenshot.

* test(rich-markdown-editor): scope paste tests to what MarkdownPaste actually gates

Inline-only marks (single-asterisk italic, ~~, single-backtick code) are intentionally not detected
by looksLikeMarkdown (single `*` would false-positive on e.g. `*args`); they route through the
Markdown extension's own paste path, not MarkdownPaste. Move them from the rich-render cases to the
defers-to-default cases so the suite tests the handler it names.
@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (330 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Building Building Preview, Comment Jul 7, 2026 2:42am

Request Review

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Removing credential sets and related OAuth/webhook paths is a breaking product change for Team/Enterprise email polling; file-context inference and large integration surface area need careful regression on auth, downloads, and workflow tools.

Overview
Removes credential sets (email polling groups) — deletes the /api/credential-sets/** surface (CRUD, invites, memberships), drops getCredentialsForCredentialSet and webhook sync on OAuth disconnect, and strips product/docs references (integrations, triggers, enterprise env vars, signup /credential-account/ invite flow). Trigger scaffolding no longer documents supportsCredentialSets on oauth inputs.

Hardens attachments and PII — file download/parse routes derive storage context from the object key instead of trusting client context / isExecutionFile. The PII service adds /redact and /redact_batch for analyze+anonymize in one call (aligned with execution redaction work in the release notes).

Performance and data-path refactors — folder listing moves to shared listFoldersForWorkspace; knowledge connector cron caps dispatches per tick, orders by nextSyncAt, and runs dispatches with bounded concurrency; custom-block publish reuses checkWorkspaceAccess instead of duplicate workspace/admin lookups.

Integration and AWS coverage — new internal routes and SDK helpers for Secrets Manager (describe, tag/untag, restore, rotate) and SES (identities, configuration sets, suppression list, templates, etc.); docs expanded for Ahrefs, Cloudflare, SES, STS, Secrets Manager. New Files → Editor doc page in the nav.

Engineering standards (no runtime behavior) — React Query rules now require named exported staleTime constants shared with server prefetch; settings-page rules add text-scale tokens and toggle-row Label patterns; trigger templates update resolveOAuthCredential arity.

Reviewed by Cursor Bugbot for commit e9a922d. Bugbot is set up for automated code reviews on this repo. Configure here.

* feat(pii): env-driven uvicorn worker count (PII_WORKERS)

Launch the Presidio service with --workers from the PII_WORKERS env var so one
image scales per task size (set PII_WORKERS = the task's vCPU count) without a
rebuild. `sh -c exec` expands the var while keeping uvicorn as PID 1 for clean
SIGTERM. Defaults to 1, so local/self-hosted is unchanged. Each worker loads the
spaCy models independently (~3.3GB measured), so task memory must be sized to
PII_WORKERS x ~3.3GB + overhead (set in infra alongside PII_WORKERS).

* harden(pii): quote PII_WORKERS expansion + raise healthcheck start-period for multi-worker

- Quote ${PII_WORKERS} so a malformed value fails uvicorn arg-parsing instead of
  being shell-interpreted (verified: '1; echo X' rejected as non-integer, X not run)
- Bump HEALTHCHECK start-period 180s -> 300s: N workers load the spaCy models in
  parallel, stretching cold start beyond the single-worker case

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 07a1224. Configure here.

Comment thread apps/sim/app/api/files/download/route.ts
.split(',')
.map((r) => r.trim())
.filter(Boolean) as SuppressionListReason[])
: null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SES config invalid suppression reasons

Low Severity

create-configuration-set splits suppressedReasons and casts tokens to SuppressionListReason without checking values. The new list-suppressed-destinations route rejects invalid reasons with 400, but this path can forward bad strings to the AWS API.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 07a1224. Configure here.

icecrasher321 and others added 2 commits July 6, 2026 18:49
… menu (#5459)

* fix(tables): resolve column ids to display names in enrichment edit sidebar

* feat(tables): scope group-header run menu to the active filter
* fix(images): fix stale images

* Fix comment
…nations (#5461)

startDate/endDate were passed through new Date(...) with no validity
check, so a malformed non-empty string became an Invalid Date and was
still forwarded to AWS, surfacing as a generic 500. The contract now
rejects unparseable date strings with a clear 400.
…5456)

* feat(textract): migrate to AWS SDK, add AnalyzeExpense and AnalyzeID

- Replace hand-rolled AWS SigV4 signing with @aws-sdk/client-textract, matching sibling AWS integrations (secrets_manager, s3, sts)
- Add Analyze Expense operation (invoice/receipt structured extraction) via AnalyzeExpense/StartExpenseAnalysis+GetExpenseAnalysis
- Add Analyze Identity Document operation (AnalyzeID) with optional back-of-ID page
- Add an operation selector to the textract_v2 block; defaults to the existing Analyze Document behavior for backward compatibility
- Add tests for tool body/response mapping and route-level AWS response normalization

* fix(textract): forward URL documents and fix ambiguous error status

- textract_analyze_expense/textract_analyze_id now fall back to filePath/filePathBack when the document input is a URL string rather than an uploaded file object, so advanced "File reference" URL inputs actually reach the API (Cursor Bugbot)
- mapTextractSdkError defaults to 500 (not 400) when the AWS SDK error has no HTTP status, since that implies a server-side/network failure rather than a bad request (Greptile)

* fix(textract): stop stale processingMode from hiding ID document fields

- Front-document fields (fileUpload/fileReference) are shared across all 3 operations; gate them with a values-aware condition so switching to Analyze Identity Document keeps them visible even if a stale processingMode='async' is left over from a previous operation
- S3 URI field now also requires operation !== 'analyze_id', since that operation never supports S3 input

* fix(textract): preserve first-page metadata across async pagination

pollTextractJob's merge callbacks spread only the latest page, dropping
any field (DocumentMetadata, model version) the first page had but a
follow-up NextToken page omits. Merge accumulated first so later pages
only override fields they actually return.

* fix(textract): pass through the real upstream status for filePath fetch failures

fetchDocumentBytes hardcoded 400 for any non-OK response from a document
URL, masking transient 5xx failures from the document host as client
errors and blocking tool-execution retries. Use the actual response
status instead.

* chore(textract): drop redundant inline comments
@waleedlatif1 waleedlatif1 requested a review from a team as a code owner July 7, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants