Skip to content

v0.7.64: snowflake, dynatrace, mintlify, files and security hardening, perf improvements - #6460

Merged
waleedlatif1 merged 65 commits into
mainfrom
staging
Aug 9, 2026
Merged

v0.7.64: snowflake, dynatrace, mintlify, files and security hardening, perf improvements#6460
waleedlatif1 merged 65 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

waleedlatif1 and others added 30 commits August 7, 2026 12:40
…6386)

* fix(jsm): accept the numeric pagination the JSM tools actually send

The JSM tools declare start/limit as type: 'number' and the block coerces
Max Results with Number.parseInt, but every /api/tools/jsm/* contract typed
them as z.string(). Any JSM read with pagination filled in 400'd before
reaching Atlassian, and get_queues 400'd unconditionally because the block
always sends includeCount as a boolean.

Normalize both shapes at the contract boundary, add the missing Start Index
block input, and route Max Results through the existing toOptionalInt helper
so a non-numeric entry no longer sends NaN.

* improvement(forking): widen the fork mapping target picker further

320px still clipped the longest secret keys the picker shows.

* fix(jsm): cap pagination at the documented int32 maximum

Addresses review: the schema claimed the int32 range but only floored at 0,
so values above 2147483647 were forwarded to Atlassian instead of being
rejected at Sim's boundary. Also restores the const tuple for the paginated
operation list and drops the widened ToolConfig from the test table.
* fix(providers): stop reporting an absent Ollama as an error

Ollama is optional and its URL falls back to a loopback default, so a deployment
that runs none refuses the probe on every poll — 10,068 of these in 14 days, the
single largest error stream in the app, all of them the same expected condition.

Report it the way the vLLM and LiteLLM routes already report an unconfigured
base URL, and skip the probe entirely on the hosted platform, which has no local
runtime to reach. An explicit OLLAMA_URL is still honoured everywhere, so a
self-hosted deployment behaves exactly as before — including the localhost
default that requires no configuration.

* fix(providers): keep an unreadable Ollama response out of the not-reachable path

The single catch covered the connection, the JSON read, and the schema parse, so a
server that answered but answered wrongly was filed as 'no Ollama here'. Scope the
quiet path to the connection itself and report an unusable response as the fault
it is.
…confirm modals (#6384)

* improvement(admin): move user row actions into an overflow menu with confirm modals

* fix(admin): surface password reset status outside the actions menu

* fix(admin): surface ban and role change errors inside the confirm modal

* fix(admin): reset the ban mutation when opening the confirm modal

* improvement(admin): simplify the user row actions after review passes

* fix(admin): show password reset progress while the request is in flight

* fix(admin): confirm the role change the admin chose, not the live row's inverse

* improvement(admin): align the role confirm and reset feedback with house patterns
* fix(chunkers): preserve FAQ prose in docs chunks

cleanContent deleted every FAQ section from the embedding index: the
multiline tag strip swallows an entire <FAQ items={[...]}/> block (it
matches from <FAQ to the first ">", often inside an answer string), and
the brace strip eats any surviving { question, answer } items — 637
Q&As across the docs never reached search, with mangled JSX fragments
embedded in their place. Consume FAQ blocks whole before the tag strip
and emit their question/answer text as plain prose, escape-aware so
braces and quotes inside answers survive. Tag and brace stripping are
otherwise unchanged — a corpus survey showed FAQ props are the only
place real page prose lives inside JSX syntax on searchable pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(chunkers): accept single-quoted FAQ items with trailing commas

session-policies.mdx and verified-domains.mdx write FAQ items with
single-quoted multiline values and trailing commas; the double-quote-only
item pattern matched nothing there, so the component consumer replaced
those whole FAQ blocks with a space. Capture either quote style
escape-aware (quotes of the other style inside a value are fine) and
allow the trailing comma; captured values keep their quotes and are
unquoted before unescaping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(chunkers): wrap the FAQ replace call for biome

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(files): reserve embedded-image space on direct file-view loads

An embedded image in a markdown file reshifted on every open: it loaded ~2.3s in with no
reserved box and shoved everything below it down (CLS ~0.17).

The intrinsic dimensions ARE stored server-side, but the image node view never read them at
render. useWorkspaceImageDimensionsAdapter read the active files list via
queryClient.getQueryData — non-reactively — so on a cold file-view load it returned null at
first render and, because the adapter identity was stable, never re-checked when the list
later resolved.

Read the list via a reactive useWorkspaceFiles subscription instead: the adapter re-runs the
image node view's memoized dimension read when the list resolves, so it reserves the box from
the stored dimensions before the (slower) image download finishes. The query key is shared, so
it dedupes with surrounding views. Gate it behind `enabled` (driven by the absence of a
caller-supplied contentSource) so the public share page — which passes a share token as
workspaceId — doesn't fire a 404.

Verified in a CLS harness: dims present -> 0 shift; dims absent -> 0.20.

* fix(files): stop the duplicate 'mention' extension-name warning

The @-mention menu extension and the mention node were both named 'mention', so TipTap logged
"Duplicate extension names found: ['mention']" on every editor (twice under the collaborative
placeholder + live pair). Rename the menu extension to 'mentionMenu' (the node keeps 'mention',
its persisted doc-node type) and move its editor.storage.mention -> editor.storage.mentionMenu.
* fix(secrets): preserve raw outputs with durable provenance

* improvement(provenance): cleanup boundary

* fix copy resources

* fix fork copies to work with provenance

* address comments

* fix
* feat(dynatrace): add the Dynatrace integration

Adds a Dynatrace block backed by 22 Environment API v2 tools, covering the
surfaces an observability workflow actually reaches for:

- Problems: list, get, close, list comments, add comment
- Metrics: query data points, list and get descriptors, ingest line protocol
- Entities: list, get, list entity types
- Events: list, get, ingest
- Logs: search, ingest
- SLOs: list, get
- Application Security: list and get security problems
- Audit log: read

Every request path, query parameter, and response mapping is taken from the
published Dynatrace API reference — no inferred fields. Auth is an access
token sent as `Authorization: Api-Token ...` against a user-supplied
environment URL, so SaaS, Managed, and environment ActiveGate all work.

Two details worth knowing:

`ingest_event` exposes Dynatrace's event timeout as `eventTimeout`, not
`timeout`. The tool transport reserves `params.timeout` for the HTTP request
deadline, so the obvious name would have silently retargeted the wrong knob.

`get_metric` encodes its path segment with `encodeDynatracePathSegment`
rather than `encodeURIComponent`, which leaves the `:` separators in metric
keys and transformation operators intact, matching the docs' own examples.

* fix(dynatrace): close the gaps a validation pass turned up

Three real defects and one usability gap, all found by auditing the tools
against the Dynatrace API reference a second time.

`ingest_logs` double-encoded its payload. `logs` is a `json` param, and a
`json` param arrives as a *string* whenever it comes from a long-input field
or an LLM tool call — only a block-to-block reference hands over a parsed
value. `JSON.stringify` on that string produced `"[{...}]"`, so Dynatrace
received a quoted string where it expected an array. The block hid this in
the UI path by pre-parsing, but the parse lived in `tools.config.params` and
*threw* on malformed input, and it never covered the direct tool-call path at
all. Both tools now normalize through the shared `parseJsonParam`, so the
tool is correct regardless of who calls it, and the block just forwards the
raw value. `ingest_event.properties` had the identical bug.

Path identifiers were not trimmed. A problem or entity ID pasted with a
trailing newline became `%0A` in the URL and 404'd with nothing to suggest
whitespace was the cause.

Errors dropped the part that matters. Dynatrace's ErrorEnvelope carries
`constraintViolations[]`, which names the offending selector or parameter;
the generic `nested-error-object` extractor returns only `error.message`
("Constraints violated."), and which extractor won was left to fallback
order. Adds a `dynatrace-errors` extractor that folds the violations into the
message and pins it on all 22 tools. It sits after `nested-error-object` in
the chain, which already matches this shape, so no other service's error
handling changes.

Adds 21 tests covering URL construction for SaaS/Managed/ActiveGate, cursor
pagination dropping sibling filters, identifier trimming, metric-key colon
preservation, both JSON-param paths, the `eventTimeout` -> `timeout` mapping,
EntityStub flattening, the audit log's dotted `dt.settings.*` keys, and the
204/200 split on log ingestion.

* docs(dynatrace): add the page intro, and pin every response key in tests

Adds a MANUAL-CONTENT:intro block to the generated integration page covering
what the block reaches, how to get an environment URL and a scoped token for
SaaS vs Managed, how selectors work, and how cursor pagination behaves.
Verified it survives `generate-docs.ts` byte-identically.

Also closes the last silent-failure gap the validation pass left open. A
wrong top-level response key does not throw — it maps to an empty array and
reads as "no results", which is indistinguishable from a genuinely empty
environment. Dynatrace is unusually easy to get wrong here: the SLO list
returns `slo` (singular) and the metric query returns `result` (singular).
Adds a table-driven test asserting the documented key for all ten list
endpoints plus the scalar keys of the ingest and single-entity responses.
Confirmed it bites by flipping `data.slo` to `data.slos` and watching only
that row fail.

* chore(dynatrace): type the shared param map as unknown

Review follow-up. `Record<string, any>` in the block's params builder dropped
compile-time checking from every operation's shared params; `unknown` is
enough here since the values flow straight into the tool param maps. Matches
.claude/rules/sim-typescript.md, which sibling blocks (Datadog, Grafana)
still violate.

* fix(dynatrace): stop three silent failures found in a final read-through

All three turn a failed call into something that looks like a successful
empty one, which is the worst shape for an observability integration — you
cannot tell "nothing is wrong" from "the call did not work".

`readJsonBody` swallowed any unparseable body and returned `{}`. A gateway
HTML page, a captive-portal interstitial, or a truncated payload therefore
mapped every field to null and read as "no problems found". Only genuinely
empty bodies are tolerated now (201 from add-comment, 204 from log ingest);
anything else that will not parse raises with a truncated preview.

`ingest_logs` sent `[]` when the payload was missing or empty. Dynatrace
answers 204 to that, so the tool reported `accepted: true` for a call that
shipped no logs. It now fails loudly instead.

`encodeDynatracePathSegment` percent-encoded the whole metric key and then
regex-unescaped `%3A` back to `:`. Same output, but it undoes the encoder's
work and hides the intent. Colons are structural in a metric key, so it now
splits on them, encodes each part, and rejoins — which says that directly.

Each fix has a test, and each test was confirmed to fail in isolation with
only its own fix reverted.
* feat(dynatrace): add the write and configuration surfaces

Takes the block from 22 operations to 47. The original PR shipped the read
paths plus a few ingests; this closes the gaps that made those reads
dead-end.

The one that was a real defect: security was read-only. The audit-vulnerabilities
skill promised "a remediation queue" and then gave you no way to act on it,
even though muting is the single most common triage action. Adds mute and
unmute, singly and in bulk, plus the remediation items behind a third-party
finding, plus the Attacks API so an exploited vulnerability can be traced to
the request that exploited it.

The rest, by how much they unblock:

- Custom tags (read/add/delete). Entity tags already drive every selector in
  the block; being able to write them closes a loop that was half open.
- Settings objects (schemas, list, get, create, update, delete). This is how
  maintenance windows, alerting profiles, and management zones are configured
  in modern Dynatrace, so "open a maintenance window before the deploy" was
  simply unreachable before. The value is a schema-defined blob, so the tool
  is honestly opaque rather than falsely typed; the docs tell you to mirror an
  existing object. Update and delete carry the updateToken so a concurrent
  change fails instead of being overwritten.
- Synthetic monitors and on-demand batch execution, which pairs with the
  deploy-marker tool to gate a release on a smoke test.
- Problem comment get/update/delete, and SLO create/update/delete, completing
  CRUD that was previously half-built.

Two structural notes. Synthetic monitors are the only endpoints still on
Environment API v1, so `buildDynatraceUrl` grew a v1 sibling and the shared
base-URL normalizer now strips either version; the query builder also learned
to repeat a param per value, which Synthetic's `tag` needs. And creating an
SLO returns 201 with an empty body and the new ID in the Location header, so
that tool reads the header rather than parsing nothing.

Deliberately excluded: the Grail/DQL query API. It is the long-term successor
to the deprecated logs/search endpoint, but it authenticates with a platform
token rather than an Api-Token, so it is a second auth path and belongs in
its own change.

* fix(dynatrace): drill the documented JSON shapes, and require the tag selector

Two problems, one found in review and one worth more than it was given.

The tag operations could run without an entity selector. All three tag tools
declare `entitySelector` required, but the shared block field was only marked
required for List Entities, so the block let a workflow reach those tools with
an invalid configuration and let Dynatrace do the rejecting. My own structural
auditor missed it because it only checked that *some* visible subBlock existed
for a required param, not that the specific one was required — that check is
now precise, and it confirms these three were the only instances across all 47
operations.

The larger one: outputs were declaring `type: 'json'` for shapes the API
reference documents in full. Thirty-five of them. The top-level entities were
mapped properly, but nested payloads — a problem's evidence and impact
analysis, a vulnerability's risk assessment and global counts, an attack's
attacker, request, entry point and exploited vulnerability, a remediation
item's assessment and mute state, the synthetic execution and failure records,
the metric ingest error envelope, the DQL translation — were passed through as
anonymous blobs. A downstream block could not reference `attacker.sourceIp`
without knowing to guess it. All of those now carry their fields.

What stays opaque is now only what genuinely is, and each says why in its
description: a settings object's schema-defined value, an entity's
type-dependent property bag and relationship keys, caller-supplied synthetic
metadata, an audit log's JSON patch, the undocumented partial-success body of
log ingestion, and the handful of security-detail shapes the reference names
without expanding.

* fix(dynatrace): make the synthetic enabled filter tri-state

Review catch. `enabled` on List Synthetic Monitors is a three-way filter —
enabled, disabled, or either — and I had it as a switch. The URL builder
deliberately serializes `false` (there is a test pinning that `evaluate=false`
survives), so leaving "Enabled Only" unchecked sent `enabled=false` and
returned only the disabled monitors: exactly backwards.

Made it a dropdown with Any / Enabled only / Disabled only, matching the
monitorType field directly above it, which had the same shape and already used
an empty-id "Any" option. The params mapper sends nothing for "Any".

Checked the other nine switches rather than assuming. None share the bug: for
each of them off genuinely means false, and false is Dynatrace's own default,
so serializing it is correct. A test now pins that list so the trap cannot be
re-introduced by converting one of them, alongside a test covering all three
states of the filter.
…6397)

* feat(workspaces): pin workspaces and widen the switcher to six rows

Show up to six workspaces in the switcher instead of three, keeping the
search input from six onward so it appears exactly when the list fills.

Pin workspaces to the top of the switcher via the existing row context
menu. Pins are per-user and global, so they live on the user's settings
row rather than in `pinned_item`, which scopes every row to one
workspace. They ride along on the /api/workspaces payload the switcher
already loads, so the server prefetch hydrates them and pinned-first
ordering never re-sorts after hydration.

Drop the seat/workspace-migration disclosure copy from both invitation
accept surfaces. The accept-time disclosure tokens are unchanged, so the
server still verifies the outcome hasn't shifted since the page loaded.

* fix(workspaces): serialize pin writes so a rapid toggle cannot be undone

Each write carries the whole pin list, so two overlapping requests that the
network delivered out of order left the earlier click as the stored state.
Chain them instead, and hold reconciliation until the last queued write
settles — refetching between two writes rendered the server's intermediate
state and bounced the row out of the pinned group and back.

* refactor(workspaces): store workspace pins in pinned_item, not user settings

Workspace pins were a jsonb array on the settings row, replaced wholesale on
every toggle. That shape is what forced the write serialization in 53ee94f:
two overlapping toggles each sent the entire list, so the one that landed last
won regardless of which the user clicked last.

pinned_item is the canonical pinning table and its resource_type is plain text
precisely so kinds can be added without a migration, so `workspace` joins it as
a sixth kind. A pin is now one row: pinning inserts, unpinning deletes, and two
toggles touch different rows and cannot overwrite each other. The serialization,
the outstanding-write counter, the settings column, and its migration all go
away, and deleting a workspace now cascades its pins.

Reads stay on the /api/workspaces payload — the switcher needs the pins *of*
every workspace, not the pins *inside* one — so the sidebar prefetch still
hydrates them and pinned-first ordering is correct on first paint.

* fix(workspaces): serialize same-workspace pin toggles and tolerate replays

Splitting pins into rows removed the lost-update race between *different*
workspaces but not the one on a single row: pin then unpin the same workspace
and the DELETE could overtake its INSERT, delete nothing, and leave the
workspace pinned. A mutation scope serializes them; TanStack runs onMutate
before the scope gate, so the optimistic update is still immediate.

Both duplicate-click replays now resolve to their end state rather than
erroring — a repeat pin answers 409, a repeat unpin 404, and each means the
row is already how the caller wants it. Rollback undoes its own toggle instead
of restoring a snapshot, so a sibling toggle's optimistic state survives.

Also: cap the switcher to the height Radix measured, since six rows can push
the footer actions off a short viewport with nothing able to scroll to them;
drop a dead pinned-item invalidation and a redundant ref; return the pin set
from the hook to match usePinnedIds; and exclude workspace pins from the
unscoped pinned-items listing, where they would read as a resource inside
themselves.

* fix(workspaces): hold pin reconciliation until nothing is still queued

The mutation scope serializes the writes, so an earlier toggle settles while a
later one is still waiting its turn. Invalidating there refetched the server's
intermediate state and bounced the row out of the pinned group and back before
the last write had even left the client.

* fix(workspaces): count outstanding pin toggles off the mutation cache

`hooks/queries/workspace.ts` has no 'use client' directive because server code
imports `workspaceKeys` during SSR, so the `useRef` counter added in 3dc924d
broke the production build — caught by CI, not by typecheck or tests.

`isMutating` answers the same question without a hook: `onSettled` runs before
the mutation leaves `pending`, so it counts itself, and anything above one means
a later toggle is still queued behind the scope.
…eedback modal (#6400)

#6241 deleted the --font-weight-* scale from globals.css and its fontWeight
mapping from tailwind.config.ts. font-medium jumped 440/480 -> 500,
font-semibold 500/550 -> 600, and body dropped 420 -> 400, so every existing
call site snapped a full step above a body that got lighter. #6291 fixed
packages/emcn only; the product call sites were left behind.

Strips the weight class from body, label, row, and heading text across
app/workspace, ee, workflow-renderer, the non-workspace route groups, and
components/ui/button.tsx, whose buttonVariants injected font-medium into every
consumer. Keeps it only where it steps up: markdown/prose bold and micro
avatar initials.

Also aligns the workflow-tree folder chevron to the sidebar section header
(14px, 150ms), and on the feedback modal drops the prompt line above the
Feedback field and re-homes Copy ID as a footer secondary action.
* fix(agent): overly broad check for secrets protection

* remove opaque input processing

* fix

* address comments

* fix
The pin sat inline before an always-reserved 18px options button, so a pinned
row's name lost ~18px of truncation budget — pinning visibly re-truncated the
name at the moment of the click, and hovering showed pin and options together.

Match the chat rows: one fixed 18px slot with both absolutely positioned, the
pin fading out as the button fades in. The trailing width is now constant, so
pinning cannot reflow the name. The options glyph moves to --text-icon, the
canonical icon token its new sibling already uses.
…6403)

* fix(mship): return the chat connect flow to the tab that started it

Connecting an integration from a chat credential chip opened OAuth in a
new tab and returned there, so the user landed on a second copy of the
app while the conversation they started from sat stale behind it.

The flow now runs in a popup and returns through a new self-closing page
at /oauth/chat-complete, which publishes its verdict to the shared
attempt record and closes. The chat tab picks that up over its storage
listener and updates in place, so it never navigates. A blocked popup
takes the same route in a new tab and still lands on the completion
page, so both paths share one verdict source.

That verdict is now the server's: reaching the completion page means
Better Auth routed the flow to its success callback. The previous check
diffed the workspace credential list, which reported failure whenever a
user re-authorized an account they had already linked -- that path
updates the account row and creates no new credential.

The lock is stricter than the label. A failure to create the credential
from its draft is swallowed server-side, so a flow can report success
with nothing in the workspace; the row stays retryable unless the
credential actually appears.

Also: the popup is named per attempt so sibling rows cannot renavigate
each other's window; a cross-origin connect URL keeps the anchor's
noopener instead of taking the popup path; the focus verifier reads the
attempt after its refetch so a verdict published mid-flight is not
overwritten; and the verifier treats a popup parked on a terminal page
(/oauth-error, the workspace error exit) as finished rather than
waiting on it forever.

* fix(mship): settle the connect row from the popup, not from focus alone

Addresses the review findings on the chat OAuth return leg.

- Watch the popup on an interval. A provider interstitial bouncing to the
  workspace root, a denied consent on /oauth-error, or a closed window all end
  the flow without publishing a verdict or firing any event in this tab, so the
  row waited forever. The focus handler also no longer consumes the away flag
  when it defers to a live popup.
- Focus an already-running popup on a repeat click instead of starting a rival
  attempt, which orphaned the first flow's verdict on an attempt id the row had
  stopped reading.
- Settle from the refetched credentials on the popup success path, so the row's
  lock is corroborated and a connected row stops being clickable.

Extracts the shared refetch-then-decide step into settleFromCredentials, used
by the focus handler, the popup watcher, and the success path.

* fix(mship): never read a disowned popup handle as a finished flow

A provider page with COOP same-origin disowns the popup, and the disowned
handle reports closed for a consent screen still running. The watcher took
that as an ending and published 'failed' against a live flow.

- Replace the boolean with a three-state observation. Only a same-origin
  terminal page counts as 'ended'; a closed-or-disowned handle is
  'unobservable' and publishes no verdict. Closing a popup hands focus back
  to this tab anyway, so the focus verification settles that case.
- Stop the interval before settling. The refetch leaves the status pending
  for its duration, so a running interval could fire again and resolve an
  attempt a retry had since replaced.
- Gate the success toast on a launched-attempt ref rather than the window
  handle, which the watcher clears before React applies the verdict.

* fix(oauth): keep the chat connect return leg in its opener's browsing context

/oauth/chat-complete runs as a popup but fell into the strict COOP rule, so
same-origin moved it into its own browsing-context group the moment it loaded
— disowning it from the tab that opened it. That is the documented cause of a
popup that is not reliably script-closable and whose opener sees window.closed
report true for a live window.

Matches it to its opener's same-origin-allow-popups instead, which is the
directive the platform provides for exactly this case.

* fix(oauth): keep every page an OAuth popup lands on observable to its opener

The popup watcher settles on a same-origin terminal page, but both entries in
OAUTH_POPUP_TERMINAL_PATHS were served strict same-origin COOP, which moves the
popup into its own browsing-context group. The opener could then neither read
its location nor trust window.closed, so the terminal-page branch could never
fire in production and a flow exiting through one of those pages left the row
waiting until the user happened to refocus the tab.

Serves /oauth-error and the /workspace root the same same-origin-allow-popups
their opener uses. The workspace root previously fell under the strict rule
while every /workspace/... route already got the permissive one.

* test(mship): cover the announcement surviving an early popup release

The success toast is gated on the launched-attempt ref rather than the window
handle; nothing pinned that. Adds the regression test, and trims the comment
duplication the fix left behind.

* fix(mship): bound the wait on a popup whose outcome became unobservable

A closed handle and a COOP-disowned one are indistinguishable, so the watcher
published no verdict for either and relied on the focus verification to settle
it. That recovers the normal case — closing a popup hands focus back — but not
one where the opener was never blurred, leaving the row waiting indefinitely.

Arms the same safety timeout the MCP OAuth popup uses for the same reason: past
it, the row decides from the credential list rather than waiting on a verdict
that is never going to arrive. Cleared as soon as a real verdict lands.

* fix(mship): survive a remount while an attempt is still pending

The unobservable deadline lived in the watcher effect's closure, so it was
armed only by the mount that launched the popup. The transcript virtualizes:
a row scrolled away mid-connect came back with no window handle and no blur
behind it, and nothing re-armed the bound.

Derives the deadline from the attempt's own requestedAt and arms it for any
pending attempt, so a remount inherits the time remaining rather than
restarting the clock or losing it. A demonstrably live popup still owns the
flow and is left to the watcher.

* fix(mship): bind a settle to its own attempt and keep the deadline armed

Two races the previous rounds left behind.

A settle read the attempt only after its refetch, so a retry landing during
that window was resolved by a run it never triggered — failing a replacement
whose popup was still going. The attempt id is now captured before the await
and the verdict only lands if it still matches; the status is still re-read
after, so a verdict published mid-refetch is not overwritten.

The safety deadline was one-shot. A consent screen that outlived it consumed
the timeout while still live, leaving nothing to catch the popup dying
unobservably afterwards. It now re-checks at the poll interval instead of
expiring against a live window.

* chore(mship): tighten the comments on the OAuth popup flow

Trims the COOP rationale in next.config.ts to the point, and condenses the
longest blocks in the connect hook without dropping the reasoning a reader
needs to keep the invariants.

---------

Co-authored-by: Waleed Latif <walif6@gmail.com>
…itation previews (#6415)

Two unrelated load-time fixes.

On a hard refresh of /files the folders painted first and the files a beat
later. Both are prefetched and hydrated together, so the files entry was not
reaching the client. Each read went to its own route over an internal HTTP
request; prefetchQuery swallows a rejection and shouldDehydrateQuery drops the
errored entry, so a failure there silently shipped a page with that list
missing, and the files read is the heavier of the two. Those two reads now call
the data layer. Note the staging logs show no errors from that route, so this
removes the failure mode without proving it was the one firing — the request it
drops from the render path, and the shape fix below, stand on their own.

listWorkspaceFilesWithShares is shared by the route and the prefetch and shapes
its result through the route contract's response schema. listWorkspaceFiles
returns contentUpdatedAt, which the schema neither declares nor passes through,
so the prefetch was caching a field a client fetch never has and that vanished
on the next refetch.

The reads carry no authorization of their own now that they bypass the route,
so the prefetch proves the viewer first. It reuses the layout's cached
host-context lookup rather than re-deriving the permission, so the gate costs no
extra queries.

Separately, GET /api/invitations computed join previews in a serial loop and
each preview issues up to three queries of its own, putting all of them on the
critical path of the workspace switcher opening. Bounded with
mapWithConcurrency; the mapper was already total, which is what that helper
requires.
* fix(tables): keep row context menu labels on one line

* improvement(emcn): ellipsize menu row labels instead of clipping them
The sidebar and its divider are fixed to the viewport, so at the end of the
page the footer was drawn over them and the lower part of the nav list became
unreachable.

FooterOverlapProbe publishes how far the footer reaches into the viewport as
`--docs-footer-overlap`. The sidebar reads it as `bottom`, so it keeps its full
height and slides up out of view as the footer arrives; the divider reads it too
but is shortened rather than slid, so it terminates on the footer's top border
instead of stopping short.

Measured against the viewport rather than the document on purpose: the value is
a constant 0 while the footer is off screen, so a content-height change higher up
the page cannot move the sidebar. Verified with Playwright at 1280x800 and
2000x1100 — expanding/collapsing an FAQ with the footer off screen moves the
sidebar 0px/0px and leaves the content column unchanged, and at the page bottom
the sidebar's bottom edge lands within ~1px of the footer's top.
…#6419)

The text and table bubble menus stayed pinned to a viewport position when the file
scrolled — clicking a table cell then scrolling left the toolbar floating over unrelated
content. TipTap v3's BubbleMenu reposition listener defaults to `window`, but the editor
scrolls inside an inner overflow container, so it never fired; the menu only moved when
the selection itself changed.

Pass the editor's scroll container as the BubbleMenu `scrollTarget` (a first-class TipTap
option) so it repositions with the selection, and enable Floating UI's `hide` middleware
so the menu hides once its anchored cell scrolls out of view. Share the anchor + options
through one `floating-anchor` helper so the two menus can't drift.

Removes the prior workarounds that fought this: the `strategy: 'fixed'` viewport-pin, the
resolveAnchor viewport-clamp branches, and the bubble menu's selection-keyed rect cache
(which froze the menu in place on scroll).

Verified in a harness: on scroll the menu delta matches the cell delta (follows), and it
hides once the cell leaves view.
…s auth types (#6426)

* fix(knowledge): apply knowledge-base access checks consistently across auth types

The tag-definitions route only ran its knowledge-base access check for browser
sessions, skipping it for internal JWT callers. Authorize on the acting user
for every auth type instead — read access for GET, write access for POST — and
require an acting user to be present, matching the sibling knowledge routes.

Thread the acting user through the KB tag schema enrichers so their request
carries the identity the route now authorizes.

* chore(tests): mark tag-definition test fixtures as const
…exports (#6427)

Check the acting user's workspace access before a function execution uses a
request-supplied workspaceId, and gate workspace file writes in the shared VFS
writer so every caller is covered by default.

Access is resolved once per request and threaded through the export path so the
added check does not re-query per output file.
* improvement(file-parsers): bound PDF text extraction

Extract page text through pdf.js's streaming API with page, character, and
wall-clock budgets instead of buffering the whole document, so extraction
memory stays bounded regardless of input. Release the document proxy when
done, and route output through sanitizeTextForUTF8 like the other parsers.

* fix(file-parsers): only flag truncation when PDF text is actually dropped
#6428)

Fold the internal- and polling-provider exclusions into
acceptsPathWebhookDelivery so the generic per-webhook path route has one
predicate deciding which providers it serves, instead of the route body
re-deriving it. Widen isPollingWebhookProvider to accept a nullable
provider, matching isInternalTriggerProvider, and drop the resulting
`?? ''` at both call sites.

Regression coverage is sourced from the trigger registries so a newly
added internal or polling trigger is covered automatically.
…6430)

The download name is the user's originalName, which only rejects path
separators, so a quote reached the quoted filename parameter unescaped and could
close it and append parameters of its own. An injected filename* is the one that
matters: RFC 6266 tells clients to prefer it, so it decides the name the file
lands under on disk regardless of what the UI showed.

- neutralize the quote, backslash and non-printable characters in the quoted
  parameter, and neutralize the semicolon there too since that fallback exists
  for clients liable to split parameters without honouring the quoting
- percent-encode the filename* ext-value fully, including the characters
  encodeURIComponent leaves raw — the apostrophe is the ext-value delimiter
- names that are already safe printable ASCII keep their exact previous header

Also stops a control character in a name from producing an invalid header value,
which previously made the download 500.
)

* improvement(files): harden the docx preview renderer

* fix(files): tighten the OOXML archive size guard against parser OOM

* fix(files): apply the per-entry OOXML cap to every part, not just XML names
@gitguardian

gitguardian Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
35897102 Triggered Generic High Entropy Secret 0aae736 apps/sim/executor/utils/resolved-secret-matcher.test.ts View secret
35897103 Triggered Generic Password 0aae736 apps/sim/executor/utils/resolved-secret-match-policy.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Large release touching auth/OAuth handoff, file parsing, webhooks, provenance, and workspace permissions—several security-sensitive boundaries changed alongside new third-party integrations.

Overview
v0.7.64 bundles new integrations (Dynatrace, Mintlify, Snowflake PAT), a broad files and untrusted-document hardening pass, provenance boundary cleanup, and assorted product and CI fixes.

Integrations & docs add large Dynatrace and Mintlify surfaces (tools, blocks, docs MDX) plus Snowflake; docs pick up vendor icons and integration nav entries.

Provenance guidance (skills/commands/validate-integration) replaces the centralized opaqueModelInput hard-rejection path with request.modelInput projection for proven serialized external model content, treats URLs/domains/IDs as ordinary request fields unless explicitly model-visible, and limits private provenance to inline bytes on authenticated internal routes—not storage keys or remote URLs.

Desktop Mothership OAuth now carries chatAttemptId through connect handoff IPC/handoff so chip-initiated connects complete on the same chat tab; chatAttemptId: null marks ordinary integration-page flows.

Docs site pins the sidebar with FooterOverlapProbe (--docs-footer-overlap) so the fixed sidebar and divider stop above the footer instead of running underneath it.

UI conventions add list/menu ordering rules (mirror sidebar/toolbar via shared constants like RESOURCE_MENU_ORDER) and related mothership menu ordering work.

Security & reliability (from the release notes in this train) include workspace-scoped file export permissions, bounded PDF/HTML/OOXML/HEIF parsing, webhook path-delivery centralization, MCP/API key policy alignment, AgentMail webhook fixes, CLI per-install secrets, knowledge-base access consistency, inbox disable atomicity, and copilot image decode bounds.

CI: CodeQL scheduled weekly (Monday) with cancel-in-progress on all events.

Reviewed by Cursor Bugbot for commit 3096de8. Configure here.

@github-actions github-actions Bot added the requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ Cross-repo companion check

One or more companion PRs aren't merged into main yet (aggregated across the feature PRs in this release). Merging this without them will leave copilot and sim out of sync — merge them in lockstep.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@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 high effort and found 1 potential issue.

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 3096de8. Configure here.

Comment thread apps/sim/blocks/blocks/dynatrace.ts
…mit (#6461)

The 1000-row and 1 MB bound-value caps duplicated the shared 10MB tool request
body gate on the same axis, at a lower threshold. Nothing fails between the two
thresholds: Snowflake's 1 MB guidance is a recommendation about metadata
retention rather than a hard limit, and statements above it still execute. The
row array is already resident before the statement is built, so the caps also
bounded no allocation the request body did not already bound.

These were removed once before in 624b737 and should have stayed removed.
…st the detail fields the tools map (#6463)

Unmute forwarded the shared muteReason dropdown's FALSE_POSITIVE default, but
Dynatrace accepts exactly one unmute reason, AFFECTED. The tool-level fallback
never fired because a truthy invalid reason was already supplied, so unmute
failed from the block unless the reason was changed by hand.

The vulnerability, problem, and attack detail endpoints omit every optional
property unless it is named in `fields`, so the descriptions, remediation
guidance, affected entities, root-cause evidence, and attacker details those
tools map were always null.
Comment thread apps/sim/lib/credentials/oauth-chat-attempt.ts Dismissed
* fix(pi): compact cloud event streams

* fix(pi): select bash for event pipeline

* address comments

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
…6468)

ConditionBlockHandler forwarded collectBlockData's full blockData — every
block output accumulated so far in the run — to function_execute on each
condition evaluation. The resolver already inlines every <block.field>
reference into the expression before the handler runs, so that payload was
never read; it only inflated the request body.

Inside a wide subflow one flat blockStates map holds every branch's outputs,
so a 91-branch parallel pushed the body past the 10MB cap and failed the
gate with "Request body size limit exceeded" even though the expression was
just a boolean compare. Per-value large-value offload does not catch this:
its threshold is 8MB for a single value, while this is an aggregate of many
medium ones.

Mirrors FunctionBlockHandler, which moved to blockData: {} in #4560 and left
the condition handler on the old path.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore(pi): organize mode implementations

* feat(pi): add plan mode

* fix(pi): preserve plan exploration timeout

* fix(pi): clean plan mode output

* fix(pi): stream only final plan content

* chore: trigger CI after retargeting

* fix(pi): compact plan mode event streams

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
… at build (#6471)

* fix(og): read OG fonts from the repo instead of fetching Google Fonts at build

The release build died prerendering an integration OG card with "No fonts are
loaded. At least one font is required to calculate the layout."

loadGoogleFont swallowed every failure and returned null, so a throttled fetch
produced an empty fonts array, and Satori requires at least one. Six routes
build OG images -- integrations/[slug] alone is 237 pages -- and each render
fetched two weights subsetted by &text=, a per-page URL no cache can reuse.
Several hundred uncacheable requests to one host from one CI egress IP across
parallel build workers, so a page losing that race was expected, not unlucky.
Mintlify was just whichever page drew the short straw; its description is 56
chars, unremarkable next to slack's 141.

Geist 400/500 now ship in public/brand/fonts and are read once at module scope,
per Next's ImageResponse guidance. .ttf because Satori accepts only ttf/otf/woff
-- the .woff2 already served to browsers cannot be reused. public/ needs no
outputFileTracingIncludes entry: the Dockerfile copies it into the runner, which
the force-dynamic share-token card needs since it renders per request.

Output is unchanged: rendering the same card with the full font and with the old
subset produces a byte-identical PNG (0 of 3,024,000 subpixels differ). Render
drops from ~74ms plus ~425ms of font fetching to ~74ms, and the share card no
longer makes two Google round trips per request.

* docs(og): record why process.cwd() is the app dir in the container

Review flagged the font path as invalid in the standalone image, reasoning
that the container starts at the monorepo root. It does -- but Next's generated
standalone server.js opens with process.chdir(__dirname), and that file ships
beside public/. Same reason content/ is read this way at runtime.
* Override

* Validation improvements

* remove from helm

* update helm

* Update Helm chart version from 1.6.0 to 1.5.2

sid wuz here

---------

Co-authored-by: Waleed <walif6@gmail.com>
…rations (#6474)

* feat(snowflake): credential-based auth, object pickers, and 9 new operations

Replace the per-block host + PAT fields with a Snowflake service-account
credential, move the credential picker to the top of the block, back the
object fields with metadata-only pickers, and add nine operations.

- credential: snowflake-service-account token service account (account host +
  programmatic access token), verified against the SQL API with the same
  headers the tools use
- selectors: database, schema, table, warehouse, execution role, file format
  and procedure pickers behind one /api/tools/snowflake/objects route
- new operations: unload_data, list_databases, list_schemas, list_tables,
  alter_warehouse, resume_task, suspend_task, list_query_history,
  list_copy_history

* fix(snowflake): migrate renamed subblock IDs and authenticate before parsing

- add SUBBLOCK_ID_MIGRATIONS entries so the renamed object fields map onto
  their pickers and the removed host/apiKey values are parked
- authenticate the caller before contract validation in the selector route,
  per the API route convention

* fix(snowflake): close unload-query breakouts, drop parked secrets, correct docs

- assertBalancedQuery now skips // line comments, $$ dollar quoting and rejects
  ambiguous nested block comments; each hid a paren that let an injected
  OVERWRITE = TRUE escape the derived table
- always emit OVERWRITE so an injected duplicate is rejected by Snowflake
  rather than silently replacing staged files
- _removed_ migration targets now drop the stored value instead of parking it
  under a dead key, where export scrubbing (which walks the block config) would
  never clear it
- 403 falls back to the shared invalid-credentials message, which names the
  network policy and SQL API causes Snowflake does not distinguish in the body
- correct the network-policy-by-user-type claim: only SERVICE_AGENT is exempt
- correct MAX_FILE_SIZE and errorOnly tool descriptions to match the fixed code

* fix(snowflake): stop untouched switches emitting clauses; retarget migration

- an untouched switch serializes as null, and advanced mode emits every
  advanced subblock, so alter_warehouse silently sent AUTO_RESUME = FALSE and
  permanently disabled auto-resume on the warehouse; normalize optional
  booleans to undefined in tools.config.params
- point the subblock migration at the advanced text members: a migrated block
  has no credential, so a picker cannot hydrate a stored name, and legacy
  fileFormat values were qualified while the picker lists bare names
- add the missing json-object wand type and scope the SQL wand prompt, which
  promised bindings that unload_data does not accept

* fix(migrations): sweep already-parked subblock values; align picker 403

- an earlier version of this migration renamed retired fields into _removed_*
  keys instead of deleting them, so deployed workflows still hold those values;
  they match no oldId, so a dedicated sweep clears them for every block type
- the picker now treats a Snowflake 403 like a 401: it means a network policy
  or a disabled SQL API, which the credential validator already reports as a
  credential problem rather than a bad request

* fix(wand): add json-array generation type for array-contract fields

The json-object reinforcement tells the model the response must start with {
and end with }, which fights any field whose contract is an array. Snowflake's
rows, matchColumns and procedureArguments all ask for arrays, so they were
being steered toward an object that the JSON parse would then reject.

Adds a sibling json-array type that strips fences the same way but reinforces
brackets, and points the three array fields at it. bindings and filters are
genuine objects and stay on json-object.

* fix(snowflake): unload a table, not an inline query

The COPY INTO grammar places the source immediately before its copy options, so
an inlined query sits one parenthesis from being able to rewrite them. Guarding
that means matching Snowflake's tokenizer exactly, and three successive versions
of the guard were each defeated: // line comments, $$ dollar quoting, and a bare
carriage return, which the scanner did not treat as a line terminator but
Snowflake does. Each fix was a guess at a lexer the public docs do not specify.

Removes the inline-query source instead of guessing a fourth time. A table name
goes through qualifiedIdentifier, which is provably safe. Exporting a query
result now means materializing it first — a view, or CREATE TABLE AS SELECT via
Execute SQL — which the tool description, the block skill and the docs all say.

Also from the final audit:
- optionalBoolean accepts the string forms a direct tool call delivers, matching
  the other boolean readers on this block, and its TSDoc no longer states the
  serializer rule backwards
- the five JSON editors declare language: 'json', so invalid JSON is caught
  inline instead of at execution
- bound the RESULT_SCAN read in SQL, not only by rows_per_resultset
- pin every migration target to a live subblock id, for all blocks
The paragraph under the token field ran 313 characters — three times the
typical provider's and 50% longer than the next worst — and pushed the account
host field down the modal. Its content (expiry, role restriction, the
network-policy requirement per user type) already lives on the setup guide the
modal links, and the network-policy cause is repeated in the rejection copy,
which is when someone actually needs it.
@waleedlatif1
waleedlatif1 merged commit e816933 into main Aug 9, 2026
45 of 46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants