Commit Graph
678 Commits
Author SHA1 Message Date
Waleed 6d48444525 fix(docs): render native block icons instead of the two-letter fallback (#5981)
* fix(docs): render native block icons instead of the two-letter fallback

The Table and Logs pages (and every other native resource block) showed a
two-letter text fallback because the generated icon map never contained them.
Four separate causes in scripts/generate-docs.ts:

- The icon-map allowlist had drifted behind NATIVE_RESOURCE_BLOCK_TYPES, the
  set the docs writer uses. Key the exception off that set so the map cannot
  fall behind the pages that consume it.
- extractIconNameFromContent only matched identifiers ending in `Icon`, so
  Logs (`icon: Library`) resolved to nothing. Match any identifier, excluding
  bare JS literals.
- The map imported everything from `@/components/icons`, so an icon sourced
  from `@sim/emcn/icons` could not resolve. Imports are now grouped by the
  module each icon is actually imported from.
- Trigger-only pages (slack_app, twilio) and hand-written pages (a2a) had no
  entry at all. Seed provider icons from the trigger definitions.

Also fixes three regen bugs found while verifying the output:

- A comment reading "this becomes `hideFromToolbar: true`" in slack.ts was
  matched as the property itself, so a clean regen dropped Slack from the
  integrations catalog and reduced slack.mdx to a 29-line stub. Property
  probes now run against comment-stripped source.
- 16 hand-written *-service-account guides were unregistered, so the stale-doc
  cleanup deleted them on every regen. Registered them, and cleanup now refuses
  to delete any page holding MANUAL-CONTENT (this also restores the intros on
  file.mdx and twilio.mdx).
- Trigger outputs referenced as a constant (`outputs: SLACK_TRIGGER_OUTPUTS`)
  resolved to nothing, dropping whole Output tables. Constants and sibling
  modules now resolve, which also restores 319 lines on clickup.mdx.

Removes the language selector from the docs navbar.

Regenerated docs are included; remaining content deltas are tool-definition
drift since the last regen.

* improvement(docs): drop the preview-gated slack_app page, document managed_agent

- slack_oauth is reachable only through the preview-gated slack_v2 block, so
  documenting it published an unreleased surface under its own slack_app page.
  Triggers whose every hosting block sets `preview: true` are now excluded from
  the docs and the icon map. Triggers no block claims are untouched, so
  standalone webhook providers keep their pages.
- Adds the MANUAL-CONTENT intro to managed_agent.mdx, matching the other
  integration pages. Verified it survives a regen.

* fix(docs): stop truncating quoted descriptions, tighten the cleanup guard

Review findings from round 1.

- parseSubBlockObject read string properties with a single `['"]…[^'"]+…['"]`
  character class, which ends the match at the first quote of either kind. Any
  description holding an apostrophe inside a double-quoted string was cut
  mid-word ("Your app", "Found in your Zoom app"). Matches the opening quote to
  its own closing quote now, reusing the alternation the tool-description
  extractor already used. Restores full text across calendly, gmail,
  google_sheets, hubspot, intercom, whatsapp, and zoom.
- The stale-doc cleanup guard tested for a bare `MANUAL-CONTENT-START`
  substring, so a stray or unterminated marker would pin a stale page that has
  nothing recoverable. It now gates on what extractManualContent actually
  returns.
2026-07-27 14:07:25 -07:00
Bill LeoutsakosandBill Leoutsakos bd61603701 feat(tiktok): unhide integration (#5978)
* feat: unhide TikTok integration

* test: remove TikTok visibility assertion

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
2026-07-27 11:47:15 -07:00
Vikhyath Mondreti 8a2ae25d78 improvement(agent-streaming): add way to opt in for workflow executions (#5956) 2026-07-24 20:10:06 -07:00
Waleed 919a98d00f refactor(settings): fold verified domains into SSO, move group-detail state to nuqs, design-system cleanup (#5950)
* refactor(settings): fold verified domains into SSO and use shared primitives

Verified domains only gates SSO, so managing it on a separate page meant
discovering the requirement after filling out the whole IdP form and then
navigating away mid-setup. Move it into the SSO page as a section above the
provider config and drop the standalone page, its nav entry, and both route
branches.

Align the surfaces with the shared settings primitives rather than bespoke
chrome, matching whitelabeling/custom-blocks/access-control:
- SSO's local FormField (muted labels) is replaced by the shared SettingRow, so
  its fields read like every other settings page. SettingRow gains optional
  `optional` and `error` props to absorb what FormField did — additive, so
  existing consumers are untouched.
- The domains section is built from SettingsSection, SettingRow,
  SettingsResourceRow, and SettingsEmptyState instead of hand-rolled cards.

Also drop the redundant Upload/Change buttons in whitelabeling: the logo and
wordmark thumbnails were already clickable, so the button was a second control
for the same action. Remove still appears once an image is set.

* fix(settings): move group-detail view state to nuqs and clean up design-system drift

Access control's group detail kept its tab, three search boxes, and three status
filters in useState, so a `?group-id=` link always landed on General and a filter
was lost on reload — the parent already puts the group id in the URL. The three
tabs never render together, so search and status share one param each rather than
carrying three mutually-exclusive keys, and switching tabs resets both. Closing
the detail clears all three alongside group-id in one batched write, so nothing
lingers on the list URL.

Design-system fixes from a cleanup pass over the surfaces this branch touched:
- Restore accessible names lost when the whitelabeling Upload buttons were
  removed. The thumbnail is now the only click target, and it contained just an
  icon, so it announced as an unlabeled button; the icon-only Remove had the same
  problem. Both now carry aria-labels reflecting their state.
- Use Chip, not the legacy Button, for the domain actions — Button is ~26px
  against the 30px ChipInput beside it, so "Add domain" sat visibly short.
- SettingRow now uses the emcn Info component; a bare svg as Tooltip.Trigger was
  neither focusable nor nameable. It also stops re-specifying Label's own default
  styling.
- Use the new SettingRow error prop for the group name instead of a hand-rolled
  error paragraph, which is what the prop was added for.
- Hoist the block-category lookup out of a sort comparator, size-* over h/w, name
  the staleTime constants the rules require, and import RowActionsMenu from its
  barrel.

* fix(settings): alias the old /settings/domains path to SSO

Folding verified domains into the SSO page dropped /settings/domains, so
bookmarks and shared links 404'd instead of landing where domains now live. Both
alias maps already exist for exactly this (organization/'members',
subscription/'billing'); add domains -> sso to each.

* chore(settings): adopt ChipCopyInput, named staleTime constants, and a11y labels

* fix(settings): reset group detail params on open and drop issuer mono styling
2026-07-24 19:11:34 -07:00
Vikhyath Mondreti fe184d3695 improvement(whatsapp): validate + improve integration skill for file inputs/outputs (#5942)
* improvement(whatsapp): validate + improve integration skill for file inputs/outputs

* fix lint

* add whatsapp subblock migration
2026-07-24 16:14:27 -07:00
Vikhyath Mondreti 17d77795b4 feat(providers): prompt caching capability + usage-based cache pricing (#5922)
* improvement(providers): validation pass, and stream tool loop improvements

* remove deploy options correctly

* fix

* feat(providers): prompt caching capability and usage-based cache pricing

Replace the arbitrary cached-rate heuristic with a single cache-aware pricing
function, and add prompt caching as an opt-in capability for Anthropic.

Pricing: priceModelUsage in cost-policy.ts is now the only place cache
arithmetic happens. Provider adapters normalize their wire shape into
ModelUsage (input always excludes cache buckets); the pricing function never
branches on provider. This removes five divergent behaviors, including the
!!request.context heuristic that gave Router and Evaluator an unearned 10x
input discount, and the overwrite that silently billed Anthropic cache reads
and writes at zero. Also parses OpenAI cache_write_tokens, previously ignored.

Caching: Anthropic gets a capability-gated advanced switch that places
cache_control on the last tool and last system block; system is now always a
TextBlockParam array. OpenAI gets a stable per-block prompt_cache_key with no
UI, since its caching is automatic.

* fix(providers): route OpenAI and Gemini block cost through cache-aware pricing

Cache-aware pricing only reached trace segments. The billable block cost still
called calculateCost on the cache-inclusive prompt total, so OpenAI cache hits
and Gemini implicit-cache hits were charged at the full input rate and GPT-5.6+
cache writes went unbilled.

Both providers now accumulate cache buckets and price through priceModelUsage,
matching the Anthropic token convention where input excludes cache reads and
writes. Cached counts are clamped to the prompt total so an over-reporting
payload cannot bill more input than the request contained.

* fix(streaming): redact tool payloads on selected outputs in public chat

Redaction only ran on the empty-selection branch, but a deployment almost
always selects outputs, so it was dead in the case it exists for. Selecting
toolCalls streamed the raw arguments and results to a public chat client in a
chunk frame, and providerTiming carried thinking content the same way.

Both paths now extract from the sanitized block output rather than the raw log:
the streamed selected output, which is the reachable vector, and the final
envelope. Sanitizing the source rather than per selected path means a newly
selectable field cannot reopen the hole.

* refactor(providers): drop unreachable billing fallbacks

Every provider pricing helper took a policy parameter no caller passed. Worse
than dead: passing one would have double-applied the margin the central layer
already applies. Removed, so providers can only price at list.

Also removed guards that cannot fire. The central fallback normalized cache
buckets no provider can reach it with (all three that report cache usage price
themselves) and did so at a 1x write multiplier no vendor charges.
priceModelUsage re-validated token counts the adapter had already clamped, and
applyModelCostPolicy defaulted a required total field.

Validation now happens once, in the adapter that parses the vendor payload and
is the only layer that knows cache buckets are a subset of the prompt total.
2026-07-24 15:46:11 -07:00
Waleed 5a8d21904d fix(sso): surface DNS verification failures and the provider auto-append gotcha (#5931)
* fix(sso): surface DNS verification failures and the provider auto-append gotcha

Post-merge audit follow-ups for verified domains (#5909):

- The host field handed admins the FQDN `_sim-challenge.acme.com`. GoDaddy,
  Namecheap, Hover and most cPanel panels append the zone to whatever is typed,
  yielding `_sim-challenge.acme.com.acme.com` — the record looks right in their
  panel but never verifies, and our 422 tells them to wait 48 hours. Add a hint
  under the field (and a docs callout) telling those admins to enter just the
  label.
- DNS failures were logged at debug, but production log level is ERROR, so a
  blocked-egress or SERVFAIL condition was invisible to us and misreported to the
  admin as "record not found yet". Log infrastructure-class failures at warn with
  the DNS error code; keep the genuinely-absent codes at debug.
- Trim the joined TXT value before comparing: several DNS panels pad the stored
  string, which otherwise fails an exact match forever.
- Lower the resolver to 2s/1 try. c-ares multiplies timeout across servers and
  retries by ~7x, so the previous 5s/2-try config could block a verify request
  for ~35s when resolvers are unreachable.
- Cover `checkDomainTxtRecord` — the function that decides whether the gate opens
  had no tests. Adds exact match, chunk-joined value, match among unrelated
  records, padded value, near-miss, another org's token, absent record,
  infrastructure failure, and empty-response cases.

* fix(sso): log DNS infrastructure failures at error so prod actually surfaces them

Production's default minimum log level is ERROR, so the warn introduced in the
previous commit was still filtered out — the fault stayed invisible exactly as
before. A resolver failure that is not 'record absent' is a genuine
infrastructure error, so ERROR is both the visible and the honest severity.

* fix(sso): state the zone-removal rule instead of a wrong subdomain hint

The hint computed the bare label as the first segment of the challenge host, so
for a subdomain like eng.acme.com it advised entering `_sim-challenge` when the
host relative to the acme.com zone is `_sim-challenge.eng` — following it would
publish the record on the wrong name and verification would never succeed, the
exact failure the hint exists to prevent. Deriving the real zone needs the Public
Suffix List (acme.co.uk defeats naive label-stripping), so state the rule instead:
enter the host with the trailing zone removed. Docs show both the apex and the
subdomain form.
2026-07-24 11:32:27 -07:00
Waleed 70313cd2ad feat(providers): add Claude Opus 5 model (#5925)
* feat(providers): add Claude Opus 5 model

* fix(providers): route Opus 5 through adaptive thinking

Opus 5 supports only adaptive thinking (no extended thinking / budget_tokens),
same as Opus 4.8/4.7. Add opus-5 to supportsAdaptiveThinking so thinking
requests use thinking.type: adaptive instead of falling through to budget_tokens
extended thinking, which Opus 4.7+ rejects with a 400.

* docs(agent): regenerate agent-stream docs for Opus 5
2026-07-24 10:33:47 -07:00
Waleed 513292f17b feat(sso): DNS domain verification gating org SSO registration (#5909)
* feat(sso): DNS domain verification gating org SSO registration

Add org-scoped domain ownership verification (DNS TXT challenge) as the
security precondition for configuring SSO. Closes the first-come domain-claim
vuln where any org could wire another company's domain to its own IdP.

- New sso_domain table + migration 0266; existing org SSO domains are
  grandfathered as verified so live tenants are unaffected
- Verified-domains settings UI (enterprise-gated) with add/verify/remove
- Register route now requires a verified domain for org-scoped registration;
  personal SSO and already-grandfathered domains are unaffected
- Self-host register script writes the verified sso_domain row directly, so
  script-driven registration stays backwards compatible

* fix(sso): harden domain verification against concurrency + fix CI lint

Addresses review findings on state invariants under concurrent/failed writes:
- Add unique index on (organization_id, domain) so concurrent claims can't
  create duplicate pending rows; POST re-reads and stays idempotent on conflict
- Verify flips the row only if it's still the exact pending challenge checked
  (guards deletion/token-rotation mid-DNS-lookup) and maps the partial unique
  index violation to 409 instead of an unhandled 500
- Wrap the self-host script's provider write + verified-domain upsert in a
  transaction so a failed ownership write can't leave a provider committed
- Format 0266 snapshot/journal with biome (fixes @sim/db lint:check)

* fix(sso): re-check domain verification before provider write (TOCTOU)

The register gate checked the verified sso_domain row only at handler entry,
then ran OIDC discovery before writing the provider. A verified row removed
during that window could still complete registration. Extract the check into a
closure and call it both as an entry fast-fail and authoritatively right before
registerSSOProvider, alongside the existing domain-conflict re-check.

* fix(sso): stop rotating verification token on idempotent re-add

Re-adding a pending domain rotated its verification token, which invalidated a
TXT record the admin may have already published and — under two concurrent
re-adds — could return a token the racing write had already superseded, so the
admin's DNS record would never verify. Return the existing row unchanged
instead; the pending token is always shown in the UI, so it is never lost.

* fix(sso): close register TOCTOU with compensating delete + harden edges

Audit-driven hardening:
- Close the residual register TOCTOU: registerSSOProvider is create-only (throws
  if the providerId exists), so a compensating delete after the write is provably
  safe — it can only remove the just-created row. If verification was revoked
  during the write, roll the provider back and 403.
- Verify is now idempotent under concurrency: a same-org row already flipped to
  verified by a racing request returns 200, not a confusing 409.
- Grandfather backfill + self-host script now match normalizeSSODomain's dominant
  transforms (lower + trim + strip leading wildcard) so a non-canonical legacy
  domain can't miss the runtime gate's lookup. Prod backfill result is unchanged.
- Cleanup: drop dead default export, align card radius to sibling convention.

* fix(sso): redact domain tokens from non-admins + fix script stale-update

Round-5 review findings:
- GET /domains redacted the pending TXT verification token (a management
  secret) to any org member. Now only owner/admins read it; members see the
  list and status without it. Non-Enterprise orgs get an empty list (entitlement
  flag only), never the domains/tokens.
- Self-host script decided update-vs-insert from a read taken OUTSIDE the
  transaction; a provider deleted mid-flight made the UPDATE match zero rows
  silently while the verified-domain upsert still committed (orphaned domain).
  The decision now happens inside the transaction from the UPDATE's row count.

* docs(sso): drop unshipped enforce-SSO / auto-join copy from verified domains

Verified domains currently only gate SSO configuration. Remove the
forward-looking references to enforcing SSO and auto-joining members (deferred
to a later release) from the docs, settings copy, nav description, and schema
comment so we don't promise unshipped features.

* fix(sso): guard rollback to new providers only + Enterprise-gate domain removal

Round-6 review findings:
- The compensating provider rollback now only fires when the provider did not
  exist before this request (providerExistedBefore). registerSSOProvider is
  create-only today so reaching the rollback already implies a fresh create, but
  this makes the safety local and future-proof: if Better Auth ever allowed
  updating an existing provider, a revoked-verification rollback must not delete
  that pre-existing row.
- DELETE /domains now requires an Enterprise plan like add/list/verify, so all
  domain mutations share one entitlement (the UI already hides removal from
  non-Enterprise orgs). Adds a delete-route test.

* fix(sso): roll back the SSO provider by row id, not logical keys

The compensating rollback deleted by (providerId, orgId). providerId is unique,
so if this request's row were deleted and recreated by a concurrent registration
in the narrow window before the rollback, the logical-key delete would remove
that other request's provider. Delete by the primary-key id registerSSOProvider
returns instead, so only the exact row this request created is ever removed.

* chore(sso): final-review polish — trim script read, unify copy, doc migration edge

Cosmetic cleanup from a final 4-track adversarial review (no bugs found in the
new logic):
- Self-host script: narrow the pre-transaction existence read to select({ id })
  instead of SELECT * (it only feeds a log line now).
- Unify invalid-domain copy ("for example acme.com") and the verified-elsewhere
  409 wording ("is already verified by another organization") across routes.
- p-3 shorthand on the domain row card.
- Document the migration's rare two-orgs-share-a-domain grandfather behavior
  (login unaffected; validated no such duplicates in prod).

* fix(sso): apply attribute mapping + make SSO edit work; drop dead guard

Two pre-existing SSO bugs the final review surfaced (prod has one SSO org, RVW,
script-registered with the default mapping, so neither change affects it):

- Attribute mapping was passed at the top level of the register payload, which
  Better Auth ignores — it reads oidcConfig.mapping / samlConfig.mapping. Nest it
  so custom mappings actually apply. (Default mapping is unchanged, so existing
  logins are unaffected.)
- Editing an SSO provider was broken: registerSSOProvider is create-only and
  threw on the existing providerId → generic 500. Route now detects a provider
  the caller already owns and updates it via Better Auth's updateSSOProvider, and
  surfaces Better Auth's own error status/message instead of a blanket 500.

Also drops the now-unnecessary providerExistedBefore guard (the rollback deletes
by the created row's primary-key id and register is create-only) and the earlier
final-review polish (script read, unified copy, migration edge note).

Smoke-test SSO login + edit on staging before merge (auth-path change).

* fix(sso): require null org on personal-mode provider lookups (gate bypass)

The personal branch of both provider-ownership lookups keyed on
(providerId, userId) without requiring organizationId IS NULL. Because org
providers store userId = their creator and providerId is globally unique, an org
admin could send a personal-mode request (no orgId) — which skips the membership
check and the domain-verification gate — yet still match, and then via the new
update path move, their org's provider to an unverified domain. Add
isNull(organizationId) to the personal branch of both clauses so it can only
match a genuinely personal provider, matching the route's own isOwnedByCaller.

Found by an adversarial review of the update path added in 394bda9f7.

* fix(sso): script updates the observed provider by id, not providerId

Inside the registration transaction the script updated WHERE providerId — the
logical key. If the observed provider was deregistered and a replacement created
with the same providerId before the transaction ran, that update would clobber
the replacement's config and ownership. Update the specific observed row by its
primary-key id instead; if it's gone we insert, which fails cleanly on the
providerId unique constraint rather than overwriting the replacement.

* fix(sso): script upserts provider via delete-then-insert (no unique constraint)

sso_provider.provider_id is a plain (non-unique) index and prod holds legitimate
duplicates, so the previous "update by id, else insert" could create a duplicate
provider when the observed row was deregistered and replaced before the
transaction — the fallback insert would succeed. Delete every row for the
providerId then insert exactly one, inside the transaction, so the providerId
ends up as exactly this config atomically. Linked accounts key on the providerId
string (not the row id), so existing logins are unaffected.

* fix(sso): guard compensating-delete row id so rollback can't silently no-op

* chore(sso): regenerate migration as 0268 after merging staging

Staging landed migrations 0266/0267, colliding with our 0266. Removed our
migration, merged staging, and regenerated cleanly with drizzle-kit as
0268_sso_domain_verification (identical sso_domain table + indexes), then
re-appended the grandfather backfill. api-validation baseline reconciled to 973
(staging 970 + our 3 domain routes). Also make the register-route test's
registerSSOProvider mock return an id so the guarded compensating delete runs.

* refactor(sso): share normalizeSSODomain via @sim/utils so script matches gate

The self-host script canonicalized SSO domains with a minimal inline transform
(lower+trim+wildcard) that diverged from the app's full normalizeSSODomain
(protocol, port, path, trailing dot, email local part) — equivalent spellings
could store a different ownership key than the runtime gate looks up. Move
normalizeSSODomain into @sim/utils/sso-domain (a pure function) so the register
route, the domain-claim route, and the script all use the identical canonicalizer.
The script now skips the verified-domain record when SSO_DOMAIN isn't a valid
registrable domain instead of storing a malformed key.
2026-07-24 00:34:16 -07:00
Theodore Li 444c415a0b improvement(data-retention): docs for overrides + PII redaction, fix wedged saves (#5905)
* fix(data-retention): clamp sub-day retention values so saves aren't wedged

A stored value under 12 hours rounded to '0' days on load and was re-sent as 0, which the contract rejects (min 24) — blocking every save on the page, including unrelated fields.

Clamp hours->days into the contract's range on read, and throw instead of emitting 0/NaN on write.

* improvement(docs): rewrite data retention for workspace overrides + PII redaction

- Document PII redaction (Logs / Workflow input / Block outputs stages, entity types, languages, custom regex patterns)
- Replace the stale 'no per-workspace overrides' section with the retention-policies list and override inheritance
- Correct log retention (also covers background job logs) and soft deletion (adds Chat conversations, KB documents)
- Add PII + override screenshots, refresh the main one
2026-07-24 02:08:12 -04:00
Vikhyath Mondreti 7120cdc901 fix(providers): final regenerated stream must not re-call tools (empty chat answers) (#5915)
* fix(providers): stop the final regenerated stream from re-calling tools and clobbering the answer

After the silent tool loop settles, OpenAI Responses and Gemini re-issue a
streaming request purely to stream the answer as prose — but with tools still
attached and auto tool choice, a reasoning model can re-decide to call a tool
there. Streamed calls are never executed on this path, so the run ends with a
dead function call, an empty streamed answer, and the stream callback
clobbering the tool loop's settled text with '' (deployed chat rendered
{"content": ""}).

Force tool_choice 'none' / functionCallingConfig NONE on the regeneration and
keep the tool loop's settled answer whenever the stream ends without text.

* feat(openai): settled tool chips on the regenerated answer stream

The silent Responses tool loop has no live stream while tools run, so opted-in
consumers saw no tool chips at all for OpenAI. The loop now records each
executed call and prepends settled tool_call_start/end pairs (name + status
only) to the agent-events stream ahead of the regenerated answer. Runs without
a sink never see these events, so legacy output is unchanged.

* fix(providers): apply the regeneration guard fleet-wide

Audit of every silent tool loop for the same race fixed for OpenAI/Gemini
(final regenerated stream re-calls a tool that is never executed, ending with
an empty answer that clobbers the settled text):

- anthropic (both implementations): tool_choice {type:'none'} on the
  regeneration (tools must stay — history carries tool_use blocks) + keep the
  settled answer when the stream ends without text
- groq: was re-applying the ORIGINAL tool_choice, so forced-tool runs
  re-forced the tool on the regeneration — guaranteed dead call; now 'none'
  + fallback
- deepseek, mistral, cerebras, azure-openai (legacy chat path), openrouter,
  xai: 'auto' -> 'none' + fallback
- bedrock: fallback only — Bedrock's ToolChoice has no 'none' and toolConfig
  is required when history carries toolUse blocks

Already guarded (no change): meta, sakana, nvidia, vllm, litellm, baseten,
together, fireworks, kimi, zai, ollama.
2026-07-23 20:20:25 -07:00
6dcc65be89 feat(skills): add skill editors (#5705)
* feat(skills): permissions layer

* chore(db): drop skill_member migration 0261 for regeneration on latest staging

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

* feat(db): regenerate skill_member migration as 0262 on latest staging

Same DDL as the dropped 0261 (skill_member table, enums, indexes, skill.workspace_shared)
plus the hand-written write-user backfill, renumbered after staging's 0261.

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

* feat(db): regenerate skill_member migration as 0263 after staging merge

Staging claimed 0262 (strong_storm); same DDL plus the hand-written
write-user backfill, renumbered on the merged snapshot chain.

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

* chore(db): drop skill_member migration 0263 for regeneration on latest staging

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

* feat(db): regenerate skill_member migration as 0264 after staging merge

Staging claimed 0263 (workflow_fork_sync_excluded); same DDL plus the
hand-written write-user backfill, renumbered on the merged snapshot chain.

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

* make editing skills full page

* fix disclaimer

* edit access msg

* fix lint

* chore(db): drop skill_member migration 0264 for regeneration on latest staging

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

* feat(db): regenerate skill_member migration as 0265 after staging merge

Staging claimed 0264 (fat_ikaris); same DDL plus the hand-written
write-user backfill, renumbered on the merged snapshot chain.

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

* fix tests

* simplify system

* fix

* fix lint

* add mship skills docs

* chore(db): drop skill_member migration 0265 for regeneration on latest staging

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

* feat(db): regenerate skill_member migration as 0266 after staging merge

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

* fix(deps): override zod to 4.3.6 to dedupe nested copies breaking type-check

better-auth 1.6.23 and fumadocs-mdx resolve ^4.3.6 to a nested zod 4.4.3,
which makes @sim/auth's inferred betterAuth types non-portable (TS2883) and
split docs onto a second zod instance. Both ranges accept the repo-wide
pinned 4.3.6, so a single hoisted copy satisfies everything.

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

* fix lint

* feat(skills,tools): fullscreen skill create + shared custom tool editor

Moves the rich-markdown and custom-tool editing surfaces out of modals and
onto full-page surfaces, and collapses the duplicated chrome behind shared
components.

Skills
- Add /skills/new, a full-page create surface mirroring the skill detail page
  (CredentialDetailLayout + DetailSection + unsaved-changes guard). "Add to
  Sim" navigates there instead of opening a modal.
- Import moves to a header action (SkillImportButton) backed by a shared
  readSkillFile helper; the GitHub-URL import and its /api/skills/import route
  are removed.
- Skill name validation is now one shared validateSkillName, replacing three
  copies of the kebab-case rule and its messages.
- The skill editor roster renders through the shared MemberRow instead of
  re-deriving its identity block, with a locked role control and a lock-reason
  tooltip explaining inherited workspace-admin access.

Custom tools
- Extract the canvas modal's schema/code editors into a shared
  custom-tool-editor module (fields, wand generation, schema helpers), cutting
  custom-tool-modal.tsx by ~900 lines.
- Settings > Custom tools gains a full-page detail sub-view (SettingsPanel +
  SettingsSection + saveDiscardActions), deep-linkable via ?custom-tool-id.
  Rows are clickable; delete now lives only in the detail view.
- Replace legacy Button/Input/Badge/Label with the chip family, move chip-field
  chrome into CodeEditor behind an error prop, and delete its dead wand button.

Rich markdown field
- maxHeight is now opt-in: omit it on a page and the editor grows with its
  content so the page owns the only scrollbar. Modals pass explicit caps.
- The field variant drops to font-weight 400 to match adjacent chip fields.

* fix(skills): address review round on create navigation, 409 copy, and editor audit

- Skill create navigated using the first element of the upsert response, but
  that endpoint returns the caller's whole skill list (built-ins prepended) —
  match the new skill by its workspace-unique name instead.
- The suggested-skill 409 toast claimed the skill existed but was not shared
  and told the user to ask a skill admin. Every workspace member can already
  see and use every skill, so a 409 only means the name is taken.
- Adding an editor emitted the skill_shared event and SKILL_MEMBER_ADDED audit
  even when onConflictDoNothing skipped the insert on a concurrent add. Gate
  both on the insert actually returning a row.

* chore: format skills-resolver test import

* fix(skills,tools): audit fixes — autocomplete boundary, resize clipping, error routing

Two real regressions introduced while simplifying the extracted editor:

- The schema-param autocomplete's trigger was rewritten to match a trailing
  identifier, but the completion still split on separators. The two disagreed,
  so typing `data.ci` opened the menu and selecting replaced `data.ci` whole —
  eating the member-access prefix. Both now share one SCHEMA_PARAM_WORD regex.
- The uncapped markdown field measured its height only on value change while
  always setting overflow-hidden, so any width change that re-wrapped lines
  clipped the tail with no scrollbar to reach it. Now re-measures via
  ResizeObserver.

Also from the audit:
- Generation writes bypass the code field's change handler, so an open
  autocomplete stayed over a disabled streaming editor; close it on busy.
- Delete failures rendered in the Schema section's error slot on both custom
  tool surfaces; route them to a toast instead.
- Skill create navigated away while still dirty, stranding the unsaved-changes
  guard's history sentinel so Back landed on an empty create form.
- The Description field on skill create never received its error border.
- Drop a double-applied opacity-50 (the editor already dims when disabled), a
  dead try/catch around a non-throwing call that also shadowed the error prop,
  and a stale reference to a /tools page that does not exist.
- Docs still described the removed GitHub-URL import and the old Add Skill
  dialog; rewrite for the create page and file/paste import.

* feat(tools): read-only tool detail, create lands on the new tool, drop dead wand prompt API

- Viewers without edit rights could not open a custom tool at all, while the
  equivalent skill and custom-block surfaces both offer a read-only view. The
  detail page now takes `readOnly`: editors inert, no Save/Discard/Delete, no
  Generate. Creating still requires edit rights.
- Creating a tool bounced back to the list while creating a skill lands on the
  new skill. Tools now do the same. The upsert returns the workspace's whole
  tool list (newest first) rather than just the new row, so the id is matched
  by title instead of by index — the same trap that produced the skill-create
  navigation bug.
- Remove `openPrompt`/`closePrompt` from useWand. `closePrompt`'s last callers
  went away with the custom-tool-modal extraction and `openPrompt` had none
  before it; nothing reads `isPromptVisible` any more either.

* fix(tools): read-only editors, design-system wrench, skills-matching tool identity

- readOnly never reached the editors: the prop gated actions and Generate but
  the schema and code fields were still typable for viewers without edit rights.
  Wire disabled through both fields into CodeEditor.
- The row icon used lucide's Wrench (strokeWidth 2) where @sim/emcn/icons ships
  one drawn for this system (1.55, tuned viewBox), and it inherited body text
  colour instead of --text-icon. Swap it.
- Give the tool detail page the same identity heading as skill detail: tile,
  name, and description at the top left, instead of only a header title.
- Extract ResourceTile so the skills and tools tiles share one definition
  (SkillTile now composes it), and add an opt-in `iconFilled` to
  SettingsResourceRow so the tools list tile matches the skills gallery. Both
  default to today's behaviour for every existing consumer.

* fix(mentions): use the product's own glyph for every @ mention kind

The `@` menu and the inserted chip mapped kinds to arbitrary lucide icons —
`Sparkles` for a skill, a generic `File` for every file — while the rest of the
product has a settled glyph per resource. Mirror CHAT_CONTEXT_KIND_REGISTRY,
which Chat's `@` menu already renders from:

- skill now uses AgentSkillsIcon, the same glyph SkillTile shows everywhere
- workflow / folder / table / knowledge use the @sim/emcn/icons set the sidebar
  and the chat registry use
- file derives its icon from the filename extension, so a .pdf and a .csv are
  distinguishable, matching the file list and Chat's context chips
- integration keeps the block's brand icon from the registry

Also drop the generic placeholder. `kind` is untrusted — the node schema
defaults it to `''` and a hand-written `sim:` link can carry anything — but an
unrecognized kind now yields no icon instead of a meaningless box, which is what
the chat registry does. The menu already guarded a missing icon; the chip now
does too, so this cannot crash on a malformed link.

* chore(db): drop skill_member migration 0266 for regeneration on latest staging

* feat(db): regenerate skill_member migration as 0267 after staging merge

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Waleed Latif <walif6@gmail.com>
2026-07-23 19:52:00 -07:00
d24bc7eccb feat(agent-stream): thinking and tool streaming (#5671)
* feat(agent-stream): add agent-events thinking/tool streaming for chat and canvas

Ship the agent-events-v1 protocol with provider tool loops, dual-gated chat thinking, DeepSeek/Groq/OpenAI reasoning wiring, and ChatGPT-like thinking chrome.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): clear stuck streaming UI and format db snapshot

Biome was failing CI on migrations/meta/0261_snapshot.json. Also settle
assistant streaming/tool flags when SSE ends without a terminal frame,
without clobbering Stop's finalized content.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): satisfy biome format and import order

Auto-format the sim package for CI lint:check, and repair the Anthropic
streaming tool-loop payload after an unsafe delete-to-undefined rewrite.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): keep drained answer on abort and update migration journal test

Treat AbortError from reader.cancel as a cancelled pump result so soft-complete
retains answerText. Point the workspace storage migration journal assertion at
0261_chat_include_thinking.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(chat): keep Stop notice when server emits cancel error

Ignore terminal SSE error frames after the user aborts so
"Client cancelled request" cannot overwrite "Response stopped by user".

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(chat): ChatGPT-style thinking shimmer and stick-to-bottom scroll

Add left-to-right shimmer on live thinking label/body, keep scroll working by
shimmering an inner node, and follow the answer only while near the bottom.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): stop pump on client disconnect; soft-complete agents only

Abort the agent stream pump when the projected HTTP body is cancelled so
provider work does not continue after disconnect. Limit AbortError soft-success
to Agent blocks so Function/HTTP cancels still fail in logs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): persist includeThinking across pause snapshots

Paused chat runs with Include thinking enabled were dropping the flag when
serializing the pause snapshot, so resume always rebuilt streams without
thinking/tool SSE frames.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): keep drained answer text when stream times out

Persist pump answerText onto the streaming execution before throwing on
timeout, and carry that partial content into the failed block output so
logs match what the client already saw.

Co-authored-by: Cursor <cursoragent@cursor.com>

* improvement(chat): auto-collapse tools chrome when tool streaming ends

Match thinking UX: open while tools run, collapse when finished, and keep
the panel open only if the user manually reopens it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): settle canvas stream chrome on failure paths

Clear agentStreamActive and settle running tool chips when blocks error,
timeouts cancel runs, or execution ends without stream:done so the output
panel does not stay on live Thinking/Using tools chrome.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(lint): organize imports in terminal console store

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(agent-stream): mark open tools cancelled on HITL pause

Pause can interrupt a tool loop without tool end events; settling those
chips as success incorrectly showed unfinished tools as complete.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(db): drop branch-local 0261 migration ahead of staging merge

* chore(db): regenerate include_thinking migration as 0266 post staging merge

* fix(providers): resolve type errors in streaming tool loop call sites

* fix(agent-stream): gate agent events opt-in and correct provider loop behavior

- streamToolCalls and provider thinking requests now require run-level
  agentEvents opt-in (canvas on, chat dual-gated, API off) so existing
  runs keep pre-agent-events behavior exactly
- OpenAI reasoning summaries opt-in + strip-and-retry on unverified-org 400
- streaming loops run tool postProcess again (firecrawl/exa async results)
- bedrock live loop falls back to silent path for responseFormat
- deepseek: reasoning_content pass-back unconditional, 'none' sends disabled
- groq: x_groq.usage fallback, reasoning params gated, qwen none disables
- gemini: functionCall parts echoed verbatim, local ids only for events
- truncated turns (max_tokens/length) no longer execute partial tool calls
- MAX_TOOL_ITERATIONS exit flushes last turn text as final answer
- iterations reports actual model calls; shared loop plumbing extracted

* refactor(agent-stream): consolidate protocol, dedupe client/server plumbing, hygiene

- canonical ChatStreamFrame union + type guards consumed by server emitters
  and the chat client; stream_error restored to legacy log-only handling
- strip thinking/tool args from providerTiming on public final envelopes
- shared tool-chip lifecycle module for chat, canvas, and console store
- shared sink-to-execution-events forwarder replaces the copy-pasted
  adapter in the execute route and HITL manager; LIVE_ONLY event set shared
- stream:thinking payload field renamed data->text; canvas thinking batched
- abort reasons carried as AbortError DOMExceptions so raw fetch consumers
  classify correctly; thinking cap renamed to chars and scope-documented
- kimi wired for agent events like the other compat providers
- deleted dead exports/step-N comments; fixtures match real wire shapes;
  loop tests use explicit mocks instead of importOriginal

* test(agent-stream): cover the dual-gated execution path and typed abort reasons

- chat route tests assert agentEvents reaches executeWorkflow only when
  policy and protocol header agree
- execution-limits tests assert AbortError-typed reasons
- executor metadata type carries agentEvents

* fix(deploy-modal): align include-thinking spacing with the modal's 6.5px rhythm

* docs(agent-stream): autogenerate per-model thinking/tool stream support on the Agent block page

- capabilities.thinking.streamed ('full' | 'summary' | 'none') on models.ts,
  explicit for the Anthropic family where visibility varies per generation;
  getThinkingStreamVisibility exposes the derivation for docs and UI alike
- scripts/sync-agent-stream-docs.ts regenerates the support tables between
  markers in workflows/blocks/agent.mdx from the model registry and
  STREAMING_TOOL_CALL_PROVIDERS; --check fails on drift or missing metadata
- wired agent-stream-docs:check into CI next to the other sync gates

* feat(anthropic): request summarized thinking display for omitted-default Claude models

The newest Claude generations (Fable 5, Sonnet 5, Opus 4.8/4.7) default
thinking.display to omitted — empty thinking blocks, no deltas. On
agent-events runs Sim now opts back in with display: 'summarized', driven
by the registry's streamed metadata; legacy runs keep the exact
pre-agent-events request shape. Registry, generated docs, and the family
capability table updated accordingly.

* docs(skills): cover thinking.streamed and agent-stream docs sync in model skills

* chore(deps): upgrade @anthropic-ai/sdk to 0.114.0 and adopt official types

- adaptive thinking, display, and output_config are now SDK-typed; the only
  remaining custom payload field is output_format (beta-header structured
  outputs, which the SDK models as output_config.format instead)
- anthropic stream events narrow on the SDK's discriminated unions instead
  of anonymous casts; compat deltas type content/tool_calls from the OpenAI
  SDK with vendor reasoning fields as an explicit optional extension
- @sim/auth exposes an explicit VerifyAuth contract so its declarations no
  longer reference better-auth's nested zod instance (TS2883 under fresh
  install layouts); realtime consumer aligned
- docs app zod pinned to the repo's exact 4.3.6 so ai SDK types bind the
  same zod instance (docs type-check was latently broken)
- knowledge embedding tests made hermetic against local .env keys and
  hosted rotation fallback

* refactor(providers): replace legacy as-any stream casts with annotated typed casts

* refactor(providers): finish provider audit — remove dead byte-stream helper, annotate remaining legacy casts

Audit of all 26 providers for the agent-events feature confirmed every
streaming execution declares agent-events-v1 and every adapter emits
AgentStreamEvent objects. Cleanup from the audit: the unconsumed legacy
createOpenAICompatibleStream byte helper is deleted, and the remaining
streamResponse-as-any casts (xai, nvidia, kimi, meta, zai, sakana) are
annotated typed casts matching the groq/deepseek fix.

* feat(streaming): stream answer text live during tool loops via turn_end protocol

The live tool loops buffered all answer text per model turn (classification
of intermediate vs final is only known at turn end), so gated surfaces saw
thinking stream, then dead air with the thinking chrome stuck open, then the
whole answer at once.

Loops now emit text deltas live as `turn: 'pending'` plus a `turn_end`
event per turn. The pump buffers pending text and projects it to the byte
path (answerText/logs/memory/legacy clients) only on a final turn_end, so
all settled semantics are unchanged. Gated surfaces render the pending text
as it streams and reconcile with a reset when a turn resolves to tools:

- public chat: live `chunk` frames from the sink + dual-gated `chunk_reset`;
  byte-path frame emission is suppressed to avoid duplicates (kept for
  response-format transformed streams via clientStreamTransformed)
- canvas: forwarder emits live `stream:chunk` + `stream:chunk_reset`; the
  execute route and HITL resume readers stop re-emitting byte chunks; panel
  chat tracks per-block segments and replaces content on flush
- chat client: per-block text segments, chunk_reset handling, and thinking
  chrome now settles on tool start as well as first answer chunk

* fix(streaming): address validated review findings across provider gating and reset reconciliation

Three-reviewer pass over the branch, findings validated against staging:

- agent-handler forwards agentEvents to executeProviderRequest — the flag was
  computed but dropped in the field-by-field copy, so provider-side thinking
  requests (OpenAI summaries, Gemini includeThoughts, Anthropic summarized
  display) never activated on opted-in runs
- openai: restore summary:'auto' alongside explicit reasoning effort — staging
  always paired them; gating summary purely on agentEvents changed legacy
  payloads
- gemini: Gemini 2 + tools + responseFormat falls back to the silent path;
  the live loop never applied the deferred responseSchema for AUTO tools
- openai-compat loop: malformed tool-argument JSON fails the call instead of
  executing with defaulted {} args (staging parsed inside the execution try)
- openai-compat parser: a vendor id arriving after a synthesized start no
  longer renames the call (start/end ids stayed consistent)
- stream-pump: abort closes the byte projection so a drain blocked on
  backpressure cannot deadlock teardown
- chunk_reset removes the block from the client text order (deployed chat +
  panel chat) so a reset block re-registers at arrival position — fixes
  separator/order corruption when parallel blocks stream around a reset
- resume route echoes the negotiated X-Sim-Stream-Protocol response header
  (parity with the chat route); docs: [DONE] wire shape + final-vs-error
  terminal semantics corrected

* chore(deps): exempt pinned @anthropic-ai/sdk 0.114.0 from the release-age gate

CI's bun install --frozen-lockfile blocks 0.114.0 (published 2026-07-23,
younger than the 7-day supply-chain gate). The pin is exact and was vetted
for the agent-events streaming work; following the existing bunfig pattern,
the exclusion ages out on 2026-07-30 and should be dropped then.

* chore(providers): fix double-cast-allowed annotation placement for the strict boundary audit

The audit only recognizes the annotation on the line directly above the cast;
two annotations had drifted behind intervening code lines (groq stream params,
deepseek loop messages) and the OpenAI reasoning-summary widening cast was
never annotated. No behavior change.

* fix(chat): settle straggler tool chips as error when final reports failure

A failed run can still terminate with a `final` frame carrying success: false;
running chips previously settled green regardless of the outcome.

* fix(canvas): wire agent stream chrome into run-from-block

Run-from-block executions emit the same live stream:thinking/stream:tool
events as full runs but registered none of the handlers, so the terminal
never showed thinking or tool chips on that path. The per-run chrome
(batched thinking writes + tool chip lifecycle + settlement on stream done,
block error, and every terminal execution state) is extracted into a shared
createAgentStreamChrome factory consumed by both paths.

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
2026-07-23 19:39:03 -07:00
Waleed d48722a04e fix(helm): correct chart docs, examples, and dead config across the board (#5907)
* fix(helm): correct chart docs, examples, and dead config across the board

Audit-driven accuracy pass over the chart's entire documentation surface,
verified by rendering every example against the templates:

- migrations run as an init container on the app pod, not a Job — fix the
  README component list, troubleshooting commands, and sim-helm skill refs;
  drop the dead migrations-job NetworkPolicy ingress rule
- referenced-but-never-created resources: document the GKE ManagedCertificate
  creation (values-gcp), comment out the key-file Secret mount that stuck all
  pods in ContainerCreating (values-gcp), enable certManager for the postgres
  TLS issuerRef (values-production), add the cert-manager cluster-issuer
  annotation nginx needs (values-azure)
- values-external-db: networkPolicy.egress is a list, not a map (the map
  rendered an invalid manifest); fill schema-failing placeholder host/username
- realtime >1 replica requires REDIS_URL (Socket.IO Redis adapter) — default
  examples to 1 replica with the scaling note, and warn where autoscaling
  HPAs override replicaCount
- pod anti-affinity selectors matched nothing (simstudio vs sim name label)
- kubernetes.mdx: install commands were missing required CRON_SECRET and
  postgresql password (failed at template time), wrong deployment name in
  port-forward, stale version requirements, unsupported key-remapping claim
- remove unimplemented app.secrets.existingSecret.keys from values + schema;
  fix README PDB default, cronjob list, /metrics caveat, NOTES secret count,
  Azure-only StorageClass in generic examples, dead SOCKET_SERVER_URL and
  GOOGLE_CLOUD_* env, ESO apiVersion mismatch, and skill-reference drift
- bump chart to 1.0.1

* fix(helm): review round 1 — scoped example egress, in-tab secret note, copilot Job wording

- external-db example egress scopes to a placeholder database CIDR instead of
  to: [] (which allowed every destination on 5432, defeating the isolation
  the example teaches)
- kubernetes.mdx cloud tabs state explicitly that they reuse the variables
  generated in the Installation block
- Copilot migrations really do run as a Helm-hook Job — restore Job wording
  there (only the app migrations are an init container)

* fix(helm): template-sweep fixes — telemetry validity, ESO rollout checksums, dead passwordKey knob

- telemetry: memory_limiter gets the required check_interval (collector
  failed startup validation whenever telemetry.enabled=true); the jaeger
  exporter was removed from collector-contrib in v0.86 — export to Jaeger
  via its native OTLP endpoint instead (otlp/jaeger, default port 4317)
- app/realtime rollout checksums now hash the ExternalSecret manifest too,
  mirroring the copilot pattern — with ESO enabled the inline Secret renders
  empty, so remoteRefs changes never rolled the pods
- remove the unimplemented existingSecret.passwordKey knob (values, schema,
  README, dead helpers): nothing consumed it, and a non-default value
  silently produced a DATABASE_URL with an unexpandable placeholder; secrets
  must use the standard POSTGRES_PASSWORD / EXTERNAL_DB_PASSWORD keys
- drop the orphaned sim.migrations.labels helper (its only consumer was the
  dead NetworkPolicy rule removed earlier)
- helm test pod image resolves through sim.image so global.imageRegistry
  mirroring applies; NetworkPolicy realtime-ingress comment reflects actual
  traffic direction; smoke unittest suite loads the newly referenced
  external-secret template

* chore(helm): bump chart to 1.1.0 with upgrade notes

Removing (inert) documented values keys and changing the rollout-checksum
inputs is a values-surface change — per SemVer chart conventions that is
more than a patch. Adds an Upgrading section documenting the one-time pod
roll, the removed no-op keys, and the Jaeger-over-OTLP change.

* fix(helm): review round — external-db NP opt-in with real-CIDR-first flow, prod Jaeger OTLP endpoint

- external-db example ships networkPolicy disabled so a verbatim install
  always reaches the database; the scoped egress rule stays as the documented
  opt-in (set your CIDR first, then enable)
- values-production still pointed telemetry.jaeger at the legacy 14250
  collector port — now Jaeger's OTLP gRPC endpoint to match the otlp/jaeger
  exporter

* feat(helm): autoscaling.realtime.enabled toggle so examples can scale the app without unsafe realtime replicas

Cursor correctly flagged that comment-level warnings didn't stop a verbatim
production/external-db install from running the realtime HPA at minReplicas 2
without REDIS_URL (silent cross-pod event loss). Adds an opt-out toggle
(default true — existing deployments unchanged): the realtime HPA renders only
when autoscaling.realtime.enabled, and the realtime Deployment keeps
spec.replicas under its control when the HPA is excluded. The three
autoscaling examples set it false with the Redis rationale; README and
upgrade notes document the toggle.

* fix(helm): review round — whitelabeled realtime HPA opt-out, external-db isolation on with required CIDR in install flow

- values-whitelabeled now actually sets autoscaling.realtime.enabled: false
  (the earlier batch aborted before reaching this file — Cursor caught it)
- external-db keeps networkPolicy enabled (no isolation regression); the DB
  egress CIDR is marked REQUIRED and wired into both documented install
  commands via --set, so the copy-paste flow sets the real subnet in the same
  breath as the DB host

* fix(helm): use a syntactically valid example CIDR in the external-db install commands

An unreplaced <YOUR_DB_CIDR> literal fails Kubernetes CIDR validation and
aborts the install; every sibling placeholder in the same command (host,
username) installs fine and simply doesn't connect until replaced. The CIDR
now behaves the same way: valid example value (10.20.0.0/24), explicitly
marked as the operator's database subnet in both commands and the values
comment.

* fix(helm): remove text after continuation backslashes in external-db install commands

A trailing comment after the line-continuation backslash (and equally a
comment line spliced mid-command) breaks the shell command when copied —
the remaining --set overrides run as separate commands and required-value
validation fails. Both documented commands now reconstruct to bash-clean
multi-line invocations (verified with bash -n); the CIDR guidance lives in
the networkPolicy section comment.

* fix(docs): cloud-tab installs are alternatives via helm upgrade --install

Following Installation and then a cloud tab ran helm install twice for the
same release and failed on the second. The tabs now state they replace the
generic install and use helm upgrade --install, which is idempotent and also
converts an existing generic install to the cloud values.

* fix(docs): honest conversion caveat for cloud-tab upgrades over an existing install

The cloud values rename the bundled Postgres database to simstudio, which
Postgres only applies at first initialization — an in-place conversion of a
generic install would point DATABASE_URL at a nonexistent database. Document
the two safe paths: keep the original name via --set, or uninstall + delete
PVCs and install fresh.

* fix(helm): external-db header install command declares its secrets and includes CRON_SECRET

The primary documented command failed the chart's required-value validation
(missing app.env.CRON_SECRET with cronjobs default-on) and referenced an
undeclared DB_PASSWORD. It now shows the export lines for every variable it
uses and sets CRON_SECRET; both commands verified with bash -n.

* chore(helm): restore values.schema.json formatting — surgical deletions only

The earlier programmatic edit reformatted the whole file (~390 lines of
whitespace churn hiding the 12 real deleted lines). Re-applied the removal
of the dead keys/passwordKey properties as text-level deletions preserving
the original style.

* fix(helm+docs): explicit secret exports in external-db header; conversion must reuse original secrets

- all five export lines are written out (three were only named in a trailing
  comment, so a verbatim copy passed empty required values)
- the cloud-conversion caveat now leads with reusing the original secret
  values (helm get values) — a regenerated ENCRYPTION_KEY makes previously
  encrypted credentials undecryptable
2026-07-23 18:28:11 -07:00
Vikhyath Mondreti e737901b0b chore(blocks): rename webhook block (#5900) 2026-07-23 14:14:16 -07:00
Waleed 874e742a47 fix(connectors): purge archived/deleted source items in KB connectors (#5880)
* fix(confluence): exclude archived pages from KB connector listings so reconciliation purges them

* fix(connectors): purge archived/deleted source items across seven more KB connectors

The sync engine only purges a knowledge-base document when its source item is
absent from a full-sync listing, so any connector that keeps listing
archived/trashed/canceled items never drops them. An audit of all 51 connectors
found seven with this bug:

- asana: list only non-archived projects (the API returns both when `archived`
  is omitted), so tasks under archived projects stop being re-listed
- google-sheets: skip a spreadsheet Drive reports as trashed, which stays
  readable by id for 30 days before the Sheets call starts 404ing
- incidentio: exclude canceled incidents by default (cancelling is incident.io's
  documented stand-in for deletion), with an explicit opt-in to sync them
- outlook: exclude Deleted Items from the all-mail listing, which Graph
  otherwise includes
- servicenow: drop retired knowledge articles, which the Table API returns with
  no implicit state filter
- webflow: drop archived CMS items, which the staged items endpoint always
  returns and offers no way to filter
- youtube: drop playlist entries whose video was deleted or made private, which
  the API keeps returning as placeholder items

Every exclusion keys off an explicit non-current signal and fails open on a
missing field or a failed metadata read, since wrongly excluding a live item
would hard-delete it. Explicit user filter selections are still honoured
verbatim; the new defaults apply only when nothing is configured.

Also flag truncated listings as capped in asana, outlook, and servicenow. All
three silently cut a listing short at their configured item cap without setting
`syncContext.listingCapped`, so reconciliation read the untraversed tail as
deleted at the source and hard-deleted it.

* fix(asana): honour the pinned-project exception on the task rehydrate path

listDocuments deliberately keeps syncing a project the user pinned via the
`project` config field even once it is archived, but getDocument ignored
sourceConfig and applied the all-parents-archived exclusion unconditionally.
For a pinned archived project the listing kept emitting its tasks while every
hydration returned null, so new tasks were dropped as empty and already-indexed
ones were frozen at their last content.

isTaskUnderActiveProject now takes the pinned project gid and keeps any task
reachable through it, matching the listing exactly. The unpinned path is
unchanged and still fails open on missing/non-boolean archived values.

* fix(connectors): key removal on explicit source signals, never on absence

Follow-up to the connector purge fixes, from an independent audit.

YouTube inferred deletion from absence: a playlist entry whose id was missing
from a `videos.list` response was dropped, so a well-formed 200 that returned 49
of 50 requested ids hard-deleted the 50th. Playlist items instead carry a
documented `status.privacyStatus`, available as a free part on a call the
connector already makes, so the extra `videos.list` request is gone along with
its quota-failure and pagination-wedge risks. An item is now excluded only on an
explicit `private`; missing, empty, or unrecognized values keep it.

ServiceNow read every record through a guard requiring a string `sys_id`, but
the listing requests `sysparm_display_value=all`, under which every field —
`sys_id` included — comes back as `{display_value, value}`. The guard rejected
every record, so the retired-article filter was unreachable and the sys_id
object would have leaked into `externalId` and `title` had it not been. Records
are now read through the existing `rawValue` normalizer, which accepts both wire
shapes, and the fixtures use the shape the API actually returns.

Also: resolve the ServiceNow cap ambiguity with `X-Total-Count` so a table that
ends exactly on a page boundary is not read as truncated; stop the Google Sheets
comment claiming a purge the engine's zero-document guard prevents; and assert
the Outlook junk-mail invariant instead of comparing a constant to itself.

Document the behavior change: content archived, retired, or trashed at the
source is now removed from the knowledge base, and restoring it re-ingests it.
2026-07-22 22:34:48 -07:00
Waleed 2b5a92a3c8 feat(auth): org session policies — lifetime/idle limits, org-wide revocation (#5862)
* feat(auth): org session policies — lifetime/idle limits, org-wide revocation, cookie-cache versioning

* refactor(auth): consolidate session-policy clamp semantics, shared security-policy version module, canonical bounds, docs

* polish(session-policy): cleanup pass — muted field labels, spinner reset, state tracker, response-seeded baseline, comment trims

* fix(session-policy): govern member sessions by membership (closes revoke cookie-cache hole), normalize createdAt, remount on org switch, sync audit mock

* fix(session-policy): clamp pre-join sessions on invite acceptance, normalize expiresAt, sync unified nav test

* fix(session-policy): invalidate membership cache on removal/transfer, spare impersonator sessions in revoke-all, raise idle floor to 2x cookie window

* fix(session-policy): resolve governing org by membership only — activeOrganizationId goes stale across transfer/leave

* fix(session-policy): atomic policy save + eager clamp, asymmetric membership TTL, admin-add cache invalidation

* fix(session-policy): org-scoped cookie version string, atomic revoke delete+bump

* fix(session-policy): plan-gate effective policy so downgraded orgs stop enforcing automatically

* chore(session-policy): drop dead bumpSecurityPolicyVersion helper — call sites bump transactionally

* fix(session-policy): unify join paths on applySessionPolicyToNewMember; final audit polish (dead exports, response bound, test name)
2026-07-22 16:42:59 -07:00
WaleedandMarcus Chandra 8cce661a37 feat(api): proxyUrl for residential/custom proxy egress on the API block (#5867)
* feat(api): add proxyUrl for residential/custom proxy egress on the API block

The HTTP/API block egresses from the app runtime's fixed datacenter IPs via
secureFetchWithPinnedIP, so targets behind Cloudflare/WAF that block datacenter
IPs (e.g. state .gov license portals) return 403/429 even when the identical
request works from a browser. There was no way to route a request through a
residential/custom proxy.

Add an optional `proxyUrl` field (Advanced) to the API block. When set, the
request routes through the given http:// proxy so it egresses from that proxy's
IP.

Security:
- validateAndPinProxyUrl resolves the proxy host's DNS and blocks
  private/reserved/loopback IPs (same SSRF guard as target URLs), then pins the
  connection by rewriting the host to the resolved IP (creds/port preserved),
  closing the DNS-rebinding window.
- Restricted to the http: proxy scheme (https/socks rejected) so host pinning is
  safe without breaking TLS-to-proxy SNI.
- Target-IP pinning is intentionally bypassed when a proxy is active (the proxy
  resolves the target); target URL validation still runs.

Threaded block field -> http tool param -> formatRequestParams ->
executeToolRequest (validate + pin) -> secureFetchWithPinnedIP, which swaps its
pinned Node agent for HttpsProxyAgent/HttpProxyAgent (keyed off target protocol)
when proxyUrl is set.

* docs(api): document the Proxy URL advanced field and steer proxy credentials to env vars

* fix(api): reject loopback/private proxy hosts unconditionally, closing the self-hosted rebinding gap

* chore(api): tighten proxy-path inline comments

---------

Co-authored-by: Marcus Chandra <mzxchandra@gmail.com>
2026-07-22 16:24:31 -07:00
Theodore Li e3f9deb65e fix(slack): allow empty status to clear the assistant status indicator (#5827) 2026-07-21 20:25:04 -04:00
2c4d2091b8 feat(pi): add code review mode (#5577)
* feat(pi): add Cloud Code Review mode and rename Cloud to Cloud PR

Introduce a third Pi mode that reviews an existing GitHub PR in an E2B sandbox and posts a structured review with optional inline comments. Keep the stored cloud id for backward compatibility, extend github_create_pr_review for inline comments, and harden review submission against stale SHAs and invalid comment payloads.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(pi): cleanup code

* address comments

* address mor

* address comments

* update

---------

Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
2026-07-20 16:25:40 -07:00
Waleed 5eafa86992 feat(email): native Gmail API mail provider for GCP self-hosting (#5736)
* feat(email): native Gmail API mail provider for GCP self-hosting

Adds Gmail as a fifth transactional mail provider (Resend → SES → SMTP →
ACS → Gmail). GCP has no first-party SES/ACS equivalent, so the native
Google path is the Gmail API: a service account with domain-wide
delegation impersonates a Workspace sender (GMAIL_SENDER) and posts the
raw RFC 822 message (built via nodemailer's MailComposer — full parity
incl. attachments, replyTo, unsubscribe headers) to the media-upload
messages.send endpoint. The Workspace SMTP relay alternative is
documented against the existing SMTP provider.

* fix(email): review round 1 + audit hardening for Gmail provider

- a 2xx from Gmail with an empty/malformed body no longer surfaces as a send
  failure (the mailer's fallback chain would deliver the same email twice);
  covered by a regression test
- normalize bare-LF line endings in html/text bodies to CRLF (RFC 822) before
  composing the raw message
- add multi-recipient and text-only test cases
2026-07-17 12:55:21 -07:00
Waleed f6bb8e6d3e feat(storage): native Google Cloud Storage support for self-hosting (#5728)
* feat(storage): native Google Cloud Storage support for self-hosting

Adds GCS as a third object-storage backend with full parity with S3 and
Azure Blob: uploads, streaming downloads, deletes, head, V4 signed URLs
(single + batch), and browser/server multipart uploads via the GCS XML
API. Selection precedence is Azure Blob > S3 > GCS > local disk.

- new provider client at lib/uploads/providers/gcs (cached singleton,
  ADC/Workload Identity or inline GCS_CREDENTIALS_JSON auth)
- per-context GCS_*_BUCKET_NAME config wired through getStorageConfig
- shared getServeStoragePrefix() replaces hardcoded blob/s3 serve paths
- docs (object-storage, environment-variables), .env.example, helm
  values.yaml + values-gcp.yaml storage section

* fix(storage): review round 1 — GCS per-context bucket fallback + ETag quote normalization

- getGcsConfig falls back to the general bucket for every context (GCS bucket
  names are globally unique, so the S3-style sim-execution-files literal default
  would point at an unowned bucket; empty per-context buckets previously made
  uploads and downloads disagree)
- completeGcsMultipartUpload restores quotes on ETags stripped by the shared
  browser upload client before building the completion XML
- docs/.env.example/helm updated for the fallback behavior

* fix(storage): review round 2 — route chat authz and execution-URL detection through getStorageConfig

- getChatStorageConfig delegates to getStorageConfig('chat') (identical for
  S3/Azure, picks up the GCS general-bucket fallback instead of reading the
  raw chat config and rejecting valid chat files)
- parse route resolves the execution bucket via getStorageConfig('execution')
  for all providers, so GCS execution files in the fallback bucket are still
  recognized as our own objects

* fix(storage): validation pass — gcs serve-prefix parity in key parsers + CORS doc fix

- extractStorageKey, extractFilename, and extractEmbeddedFileRef now strip the
  gcs/ serve prefix like s3/ and blob/, so direct-uploaded files on GCS parse,
  delete, download, and embed correctly (previously only the serve route knew
  the prefix)
- file-download storageProvider union includes 'gcs'
- completeGcsMultipartUpload defensively rejects a 200 response carrying an
  XML error document
- docs: CORS example lists concrete x-goog-meta-* header names (GCS matches
  responseHeader entries exactly; wildcards are only supported for origin)
2026-07-17 00:15:14 -07:00
Vikhyath Mondreti 86e6e1d26c feat(forking): excluded workflows (#5727)
* feat(forking): excluded workflows

* improve sync preview
2026-07-16 20:23:26 -07:00
Theodore Li 1da346b703 feat(triggers): service-account credentials in the hubspot trigger + token-SA name-collision 409 (#5693) 2026-07-16 19:11:57 -04:00
8adeaab8a5 feat(gitlab): access, membership, and user-admin operations (#5710)
* feat(gitlab): add access, membership, and user-admin tools

Adds member, invitation, access-request, SAML group link, and user
administration tools to the GitLab integration. Resource-scoped ops work
against projects or groups; user-admin ops require an admin token. All tools
reuse the existing host/SSRF guard via getGitLabApiBase and add a shared
getGitLabResourcePath helper.

* feat(gitlab): wire access operations into the GitLab block

Adds the new operations to the block dropdown and tools access list, with a
named access-level dropdown (enum in, integer out), first-class expires_at,
a /members/all default (direct-only opt-in), resource-type selector, and
member_role_id passthrough.

* test(gitlab): cover access operations

Covers the access_level enum-to-integer coercion, the /members/all default vs
direct-only, the 409-duplicate-add soft success, invitation per-email error
handling, user-status-action response parsing, and getGitLabResourcePath.

* docs(gitlab): document access and membership operations

* Update apps/sim/blocks/blocks/gitlab.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix(gitlab): address review findings

- update_user now sends admin:false so the Administrator switch can demote
  (an untouched switch stays undefined and leaves the flag unchanged)
- expose the access-level dropdown for Update Invitation
- normalize comma-separated invite emails so spaced multi-email input works

* fix(gitlab): make update-invitation access level optional

Update Invitation now uses a dedicated dropdown that defaults to 'Leave
unchanged', so updating only the expiration no longer silently resets the
invitation's access level to Developer. The level is sent only when explicitly
chosen.

* fix(gitlab): validation pass — SAML provider param, member/invitation query filter, moderation user guard, registry order

* fix(gitlab): apply 10-agent validation findings across all 62 tools

- MR draft flag now applied via Draft: title prefix (GitLab has no draft body param)
- update_user only sends admin when a real boolean (untouched switch serialized null and could demote admins)
- add_member 409 soft-success now verified against the conflict body
- auto_merge sent alongside deprecated merge_when_pipeline_succeeds
- job log capped at 200k chars, file content at 1M chars, with truncated outputs
- MR diffs signal hasMore beyond 100 files
- wire dropped params: update_issue milestoneId, MR milestone/squash/removeSourceBranch, pipelines ref, tree ref, branches search, commits since/until/path/author, update_file lastCommitId, jobs includeRetried, create_user forceRandomPassword
- complete pipeline/job status enums, access-level enums, widen stale type unions
- guards: update_invitation requires a change; create_user requires a password strategy
- fix double-encoding trap in path descriptions; doc-accuracy touch-ups

* fix(gitlab): review round 1 — dedicated no-default access level for update member, expiration clearing via explicit empty string

* fix(gitlab): explicit Clear Expiration toggle for update member/invitation

* feat(gitlab): expose full documented API surface across tools and block

- membership: add-by-username, remove-member cleanup flags, list-member filters (user_ids/state/seat info), invite_source
- listings: search/visibility/owned/membership, assignee/milestone filters, source/target branch filters, per-domain order-by + sort direction
- CI: pipeline variables + spec:inputs, manual-job variables
- repo: commit authoring (start branch, author, execute flag), release tag message + asset links, cross-fork compare + unidiff, internal notes
- catalog: access-governance template + member-provisioning and access-request-audit skills
- hardening from 3-agent validation: declared release params, tolerate single-object asset links, NaN guard on assignee filter, null/scalar JSON rejection

* fix(gitlab): tri-state controls for update-op booleans (executable flag, MR squash/remove-source-branch)

---------

Co-authored-by: Marcus Chandra <mzxchandra@gmail.com>
Co-authored-by: mzxchandra <129460234+mzxchandra@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-07-16 12:00:12 -07:00
Waleed 68c4f28b77 feat(clickup): webhook triggers, hierarchy selectors, and Docs knowledge-base connector (#5708)
* feat(clickup): webhook triggers with auto-managed subscriptions + hierarchy selectors

* feat(clickup): KB connector (Docs v3), selector-route hardening, subblock migrations, registry-check regex fix

* test(clickup): webhook provider handler tests + redact create-response secret in error logs

* fix(clickup-connector): trim final page to maxDocs cap with precise listingCapped semantics

* chore(clickup): lint formatting

* fix(clickup): restore list-op location requiredness, split listSpaceId migration target, surface failed webhook rollback

* fix(clickup): clickup.lists selector accepts listSpaceId context like clickup.folders

* fix(clickup): integer-only maxDocs and location filters, depth-aware doc headings, most-specific-location hints
2026-07-16 10:17:02 -07:00
Waleed ff1d061897 feat(tools): Rocketlane integration — 64 tools across projects, tasks, phases, fields, time tracking, spaces, and invoices (#5709)
* feat(rocketlane): Rocketlane integration — 64 tools across projects, tasks, phases, fields, time tracking, spaces, and invoices

* fix(rocketlane): allow clearing optional fields on update, require a user reference for time-off and placeholder assignment

* chore(rocketlane): trigger fresh review round

* fix(rocketlane): require an owner reference when creating a project, matching the API contract
2026-07-16 10:10:19 -07:00
Waleed d7f0a110fb feat(credentials): client-credentials service accounts — Zoom, Box, Salesforce + Pipedrive API tokens (#5690)
Second service-account kind (follow-up to #5682): client-credential pairs
(client ID + secret + org identifier) that mint short-lived tokens at
execution — in-memory cache with ciphertext-fingerprint validation
(rotation-correct across instances), single-flight coalescing, 30s failure
memo, no refresh-token storage.

- Zoom Server-to-Server OAuth, Box Client Credentials Grant, and Salesforce
  client-credentials (integration user) are the first minters; Salesforce's
  live instance_url rides the existing instanceUrl plumbing so all 40 tools
  work unchanged
- Pipedrive lands as a token-paste provider with explicit authStyle
  threading: descriptor declares x-api-token, one shared header helper drives
  all 18 tools + both selector routes, OAuth Bearer behavior untouched
- SSRF-allowlisted Salesforce My Domain host (production/sandbox/developer
  partitioned domains); ENOTFOUND maps to site_not_found, EAI_AGAIN stays
  provider_unavailable; 408/429 from token endpoints never blamed on creds
- Create route forwards clientId/clientSecret/orgId to the builder, pinned by
  a route-level regression test
- Zoom user-scoped tools document that server-to-server tokens don't support
  'me'
- 4 setup-guide docs pages verified against current vendor flows (incl.
  Salesforce External Client Apps — the classic Connected App wizard is
  disabled by default since Spring '26); integrations sidebar gains a Service
  Accounts & API Keys section
2026-07-16 00:41:50 -07:00
Waleed 3a632936ab feat(clickup): ClickUp integration — 23 tools, OAuth + API-token auth, attachment upload (#5702)
* feat(clickup): add ClickUp integration with OAuth + API-token auth, 23 tools, block, and attachment upload

- 23 tools covering tasks (create/get/update/delete/list/search), comments
  (create/get/update/delete), attachment upload, tags, members, custom
  fields, and the workspace/space/folder/list hierarchy
- OAuth provider wiring (authorization-code flow, non-expiring tokens) plus
  clickup-service-account token-paste credential (personal pk_ API tokens),
  with a shared clickupAuthorizationHeader helper (pk_ tokens sent bare,
  OAuth tokens as Bearer)
- File upload follows the internal-route pattern: contract-validated
  /api/tools/clickup/upload-attachment builds the multipart form and
  returns UserFiles
- ClickUp block with per-operation subBlocks, canonical file param,
  BlockMeta templates/skills, and gradient brand icon
- Generated integration docs page + hand-written service-account guide

* fix(clickup): apply validation-audit fixes across tools, block, and upload route

- Map documented task fields that were dropped: markdown_description,
  subtasks, watchers, custom_fields, time_spent, folder, space — making
  the include_subtasks / include_markdown_description options observable
- Expand verified filters: assignees/tags/due-date ranges on get_tasks and
  search_tasks, include_closed on search_tasks; add due_date_time /
  start_date_time flags and update-task assignee add/remove
- Guard update_comment against an empty body and require comment text in
  the block; prefer markdown_content over content on create_list and make
  markdown reachable for lists in the UI
- Drop the unverified 'required' field from custom-field outputs; read
  both err and error keys from ClickUp error bodies; correct notify_all
  wording
- Upload route: 100MB size cap, shared attachment mapper with full
  documented response fields (version, thumbnails), base-URL constant

* fix(docs): restore clickup-service-account guide and shield it from doc generation

The generator prunes integration pages it does not derive from blocks;
add the hand-written ClickUp API-token guide to
HANDWRITTEN_INTEGRATION_DOCS so regeneration cannot delete it.

* fix(clickup): address review findings — dedupe catalog entries, config-time list parent validation, upload memory cap, unique icon gradient ids

- Remove duplicated clickup entries in docs meta.json and integrations.json
  introduced by a double docs regeneration
- Add a Location dropdown for Get Lists / Create List so the folder ID or
  space ID is conditionally required at configuration time instead of
  failing at run time
- Pass the 100MB cap into downloadServableFileFromStorage so oversized
  files abort during download instead of after full buffering
- Use useId()-derived SVG gradient ids for ClickUpIcon in both icon files

* chore(clickup): format integrations.json entry per biome

* improvement(clickup): final validation-pass refinements across tools and block

- create_task: add doc-backed sprint points param (parity with update)
- get_tasks/search_tasks: expose include_markdown_description
- update_task legacy numeric priority in list responses mapped instead of
  dropped; create_comment omits absent response fields instead of
  emitting sentinel ''/0 values
- order_by only sent when explicitly chosen (Default sentinel); comment
  text no longer UI-required for update_comment (resolve-only and
  assignee-only updates are valid per the tool contract, which still
  rejects an empty body)
- add_tag_to_task sends no request body per docs; upload tool tolerates
  non-JSON error responses

* fix(clickup): tolerate nested user wrapper in member mapping

The task/list member endpoints document a flat member object; accept the
workspace-members-style nested { user: {...} } wrapper as well so both
shapes map correctly.

* fix(clickup): map size-limit errors from download/compile to a 400 upload-size response

downloadServableFileFromStorage enforces maxBytes on both the raw download
and the resolved (compiled) artifact via PayloadSizeLimitError; catch it in
the route so oversized content returns the intended 400 instead of
bubbling to the generic 500 handler.

* feat(clickup): add custom field values, checklists, and time tracking (15 tools, 38 total)

- Set/remove custom field values on tasks (PUT/DELETE /task/{id}/field/{field_id});
  block value input parses JSON for structured field types, plain values pass through
- Checklist CRUD: create/rename/reorder/delete checklists and create/update/
  delete checklist items (assign, resolve, nest), mapped from the documented
  {checklist} response shape
- Time tracking: list entries in a date range (assignee/location filters,
  task-tag and location-name includes), create/update/delete entries, start/
  stop timers, and read the currently running timer; entries mapped from the
  documented data envelope with negative-duration running semantics
- Block gains 15 operations with conditionally-required fields, timestamp
  wand configs, tri-state billable/resolved dropdowns, and a single-location
  filter selector matching the API's one-location-filter rule

* fix(clickup): new-tools audit fixes — POST for set custom field value, tolerant time-entry envelopes, richer mappings

- Set Custom Field Value uses POST per the live reference OpenAPI (the
  llms mirror shows PUT; the reference console spec is authoritative)
- delete_time_entry maps the documented array envelope; create_time_entry
  tolerates both data-wrapped and flat echo bodies
- Time entries surface task_tags and task_location so the include switches
  are observable; checklists carry date_created
- Custom field value input parses any JSON literal (numbers, booleans,
  arrays, objects) and passes plain text through
- Update Time Entry supports duration edits; single-assignee time ops get
  their own field so a comma-separated list can't silently NaN out

* fix(clickup): send explicit date-time flags whenever a date is set

The due/start date-time switches previously only transmitted true; a
timed date could never be flipped back to date-only. The flag is now sent
as an explicit boolean whenever the corresponding date is provided and
omitted otherwise.

* improvement(clickup): final per-tool audit polish — checklist item children, tolerant comment date

- Checklist items surface the documented children array of nested item IDs
- create_comment tolerates a string-typed date in the response

* fix(clickup): reject empty update_task bodies with a clear local error, matching sibling update tools
2026-07-16 00:38:59 -07:00
Vikhyath Mondreti 54b35a4f0e improvement(deployments): bugfixes for run-block, airtable + external sub management (#5680)
* improvement(webhooks): external subscription management

* ui/ux

* remove test file

* fix tests

* address comments

* address comments

* update to grain v2 api

* improvement(grain): hide auto-registered webhook URL on v2 triggers

* Revert "improvement(grain): hide auto-registered webhook URL on v2 triggers"

This reverts commit c89660cc3e.

* address comments

* address comments

* address rollback

* fix grain v2

* fix more comments
2026-07-15 18:01:53 -07:00
Waleed 00a2f26fe5 feat(credentials): token-paste service accounts for 12 providers (#5682)
* feat(credentials): token-paste service accounts for 12 providers (HubSpot, Airtable, Notion, Asana, Attio, Linear, monday, Shopify, Webflow, Trello, Cal.com, Wealthbox)

* refactor(credentials): registry-dispatch service accounts + doc-verified validator hardening

- Migrate Google/Atlassian/Slack service-account branches to the same
  registry pattern as token-paste providers (builder + resolver maps,
  unified required-fields contract validation)
- Fix Shopify service-account store domain never reaching tool URL builders
- Linear: rate-limit 400s no longer mislabeled as invalid credentials;
  validation header matches runtime header rule
- Trello: server API key rejection no longer blamed on customer token
- Fleet-wide: network errors and non-JSON provider bodies map to
  provider_unavailable via shared fetchProvider/parseProviderJson
- HubSpot: drop regional-host displayName upgrade; Wealthbox: explicit 402;
  monday: provider-side GraphQL errors mapped correctly; Attio: null-body guard

* polish(credentials): ship-gate fixes, setup-guide docs, and lint pass for token service accounts

- 12 setup-guide docs pages (docs.sim.ai/integrations/<provider>-service-account)
  incl. the Trello authorize-link flow the integration depends on
- Attio: HTTP 400 on /v2/self maps to invalid_credentials (live-verified)
- monday: scan all GraphQL errors for provider-side codes; no empty audit ids
- Compile-time descriptor/validator lockstep (typed registry) and
  prototype-safe provider-id guard (Object.hasOwn)
- Shared errors.test.ts pinning fetchProvider/parseProviderJson/
  throwForProviderResponse guarantees for all validators
- Stale TSDoc updated after the registry migration; biome organizeImports pass

* docs(credentials): vendor-doc accuracy pass on service-account setup guides

34 corrections from a 12-agent audit against live official vendor docs:
current HubSpot Development-area nav, Notion connections rename, monday
Developer Center paths, Shopify legacy-vs-Dev-Dashboard token flows, and
hedged wording for claims vendors do not document (expiry, limits)

* improvement(credentials): vendor-accurate credential nouns on connect surfaces

'Add service account' only where the vendor actually has service accounts
(Google, Atlassian); token-paste providers now use their own vocabulary via a
connectNoun descriptor field — 'Add private app token' (HubSpot), 'Add API key'
(Attio/Linear/Cal.com), 'Add personal access token' (Airtable), etc. Docs page
titles updated to match; slugs and internal provider ids unchanged

* fix(credentials): classify auth-shaped Shopify GraphQL errors as invalid credentials

Shopify can reject invalid or revoked shpat_ tokens with HTTP 200 and a
GraphQL error body instead of a 401; those now map to invalid_credentials
instead of a provider-outage message

* fix(credentials): empirically-grounded HubSpot token verification

Live probing showed the documented access-token-info route returns a bare
404 for unrecognized tokens (ambiguous with a missing route), so 404/400
now falls back to the Account Information API, which answers with a JSON
401 for rejected tokens and 200/403 for live ones — verified against the
real endpoints

* fix(credentials): review-round-2 fixes for service-account edge cases

- Shopify tools prefer the credential-validated store domain over the
  block's auto-detected shopDomain (a store-bound token must hit its own store)
- Unknown non-empty service-account providerIds are rejected instead of
  silently persisting as google-service-account (empty stays the legacy
  Google fallback)
- Shopify/modal domain normalization strips URL paths ('https://x.myshopify.com/admin')
- monday: warning-class GraphQL errors no longer reject a token whose me
  data proves it authenticated

* chore(credentials): format shopify validator test

* fix(credentials): cold-review hardening pass

- Object.hasOwn guards on all provider-id registry lookups (crafted
  '__proto__'/'constructor' providerIds now 400 instead of 500) + regression test
- fetchProvider gets a 10s AbortSignal.timeout so a hung provider can't pin
  the create/reconnect request
- HubSpot: unexpected 403 on the token-info route defers to the account-info
  fallback instead of blaming the token
- Linear selector routes use the SDK apiKey option for lin_api_ keys (bare
  header parity with the tools sweep)
- Docs: UI steps aligned to the vendor-noun connect labels; HubSpot
  scope-propagation claims softened; Trello in-product-link promise corrected
2026-07-14 22:52:50 -07:00
Waleed 2e5b33c2db feat(community): replace Discord community links with Slack across app, docs, emails, and readme (#5653) 2026-07-13 17:15:03 -07:00
Waleed e7afe45b2d improvement(buffer): explicit mediaType override instead of guessing on ambiguous media (#5642)
* improvement(buffer): explicit mediaType override; error instead of guessing on ambiguous media

* fix(buffer): forward validated mediaType from routes into the media resolver

* fix(buffer): presign uploaded media for 7 days — Buffer fetches assets at publish time

* docs(buffer): document presign lifetime bound by signing credentials

* fix(buffer): always mint fresh presigned URLs from verified storage keys for media
2026-07-13 14:48:16 -07:00
Vikhyath Mondreti 2d41360807 improvement(concurrency): limits configurable, docs updates (#5640)
* improvement(concurrency): limits configurable, docs updates

* remove dead tests

* limits self hosted vars
2026-07-13 13:11:05 -07:00
Waleed e94e514c0a feat(flint): add Flint integration with agent task tools, block, and docs (#5641)
* feat(flint): add Flint integration with agent task tools, block, and docs

* fix(flint): forward explicit publish=false, guard missing taskId, align default params branch with tool fallback

* docs(flint): add manual intro section to integration docs page

* fix(flint): fail get_task on OK responses without a task ID

* fix(flint): drop json-object generation type so the wand emits the pages array
2026-07-13 13:06:47 -07:00
Waleed 04535ee33b feat(buffer): add Buffer integration with posts, channels, and ideas (#5637)
* feat(buffer): add Buffer integration with posts, channels, and ideas

* fix(buffer): return clear tool error when account lookup yields no account

* fix(buffer): default schedulingType server-side so basic-mode blocks never fail validation

* fix(buffer): probe Content-Type for extensionless media URLs so videos are not sent as images

* feat(buffer): add get_ideas and get_idea_groups tools, harden media URL classification

* fix(buffer): guard missing post on PostActionSuccess responses
2026-07-13 12:07:37 -07:00
Waleed b1bd2b5cab feat(gong): align tools with official API spec + 4 new tools (#5632)
* feat(gong): align tools with official API spec, add ask-anything, brief, unassign, and logs tools

- fix gong_list_flows: query param was flowEmailOwner (typo copied from Gong's endpoint prose); the API requires flowOwnerEmail, so every call failed
- create_call: drop phantom url output (API returns only requestId/callId) and make downloadMediaUrl optional per spec
- list_scorecards: refresh to current spec (numeric IDs, questionType/answerGuide/minRange/maxRange/answerOptions, reviewMethod)
- answered_scorecards: map selectedOptions on answers, correct score range description (1-50)
- surface requestId uniformly across all read tools; totalRecords no longer fabricated from page size
- normalize includeAvatars to a strict boolean param, uppercase aggregationPeriod, encode userId path param
- validate email/phone format before irreversible GDPR purge calls
- new tools: gong_ask_anything, gong_get_brief (AI entity Q&A/briefs), gong_unassign_flow_prospects, gong_get_logs

* fix(gong): require custom-range dates and gate param remapping by operation

- ask_anything/get_brief: entityFromDateTime/entityToDateTime now conditionally required (UI) and validated tool-side when timePeriod is CUSTOM_RANGE
- block params mapper: remaps are gated by the selected operation so stale values from previously configured operations can no longer overwrite fromDateTime/fromDate/workspaceId

* fix(gong): final spec-alignment pass, review fixes, and regenerated docs

- ask_anything/get_brief: send fromDateTime/toDateTime only for CUSTOM_RANGE; declare mcpResult brief section field
- unassign: dedicated optional unassignFlowId subblock so a stale assign-flow ID can never silently narrow an unassign to one flow
- get_logs: logType is a closed enum (AccessLog, UserActivityLog, UserCallPlay, ExternallySharedCallAccess, ExternallySharedCallPlay) - dropdown in block, enumerated in tool description
- get_folder_content: folderId optional per spec; get_call: encode callId path param
- list_trackers: drop saidInCallParts (absent from the spec's KeywordTracker schema)
- get_extensive_calls: correct declared interactionStats item shape to {name, value}
- block inputs: list all remapped subblock params; optional flags on nullable outputs; absolute types re-export
- regenerate Gong integration docs and integrations.json entries (29 operations)
2026-07-13 11:30:05 -07:00
Vikhyath Mondreti ef7c8e24b2 feat(platform): settings permissions, admin, billing attribution (#5545)
* fix(invites): preserve active organization for external access

Keep organization activation server-owned so failed membership checks cannot clear a valid session context.

* feat(admin, billing, settings): cleanup settings visibility, billing actor resolution, new admin routes

* address comments

* chore(db): reset pending migrations before staging merge

Remove locally generated migrations so they can be regenerated against the latest staging schema without preserving stale snapshots or numbering.

* regen migrations

* address comments

* chore(db): reset generated migrations before staging merge

Remove this branch's generated migrations so they can be regenerated against the latest staging schema with fresh numbering.

* upgrade global work

* fix lint

* address comments

* legacy callbacks correctness

* address comments

* update

* guardrail attribution
2026-07-13 00:53:49 -07:00
Waleed 8e7e2db35e feat(docs): update favicon, fix icon contrast, add integration intros (#5581)
* feat(docs): update favicon, fix icon contrast, add integration intros

- replace docs favicon/icon assets with new sim logo
- fix light-tile icon contrast in BlockInfoCard so icons like Daytona
  no longer render invisible (white-on-white); matches sim toolbar's
  brightness-based contrast logic
- add missing MANUAL-CONTENT-START:intro sections to 30 integration
  docs pages that lacked context/links

* chore(docs): normalize spacing from generate-docs pass

Ran the docs generator to verify our new intro sections survive
regeneration cleanly. It reformats the blank line before "## Usage
Instructions" to match every other manually-annotated page.

* fix(context-dev): remove prefetch and simplified-brand tools

- remove context_dev_prefetch_domain, context_dev_prefetch_by_email,
  and context_dev_get_brand_simplified tools and their block operation
  entries; these aren't meant for general use
- regenerate docs to drop their sections from context_dev.mdx
2026-07-10 17:12:41 -07:00
Waleed aa5b1d5569 fix(suggested-actions): swap unaudited filled icons for EMCN outline set (#5548)
* fix(suggested-actions): swap unaudited filled icons for EMCN outline set

Suggested-action template icons on the home page mixed filled/solid
icons in with the app's outline icon convention. Swapped them for
consistent outline icons and removed the unused filled Card icon.

- gmail.ts: Card -> ClipboardList
- clickhouse.ts, sftp.ts: Trash -> TrashOutline
- ssh.ts: TerminalWindow (emcn) -> SshTerminalIcon (moved to
  components/icons.tsx alongside the other block/brand icons)
- deleted unused packages/emcn/src/icons/card.tsx
- regenerated docs

* fix(docs): revert jira.mdx regen regression

generate-docs.ts is dropping the Configuration and generic-webhook
Output tables for Jira triggers even though the trigger schemas still
define those fields (caught by Greptile review). Unrelated to the
icon changes in this PR, so reverting jira.mdx to its prior content
rather than debugging the generator here.

* fix(build): remove last Card icon consumer in playground gallery

apps/sim/app/playground/page.tsx imported Card from the top-level
@sim/emcn barrel for the icon showcase grid, which I missed when
auditing @sim/emcn/icons consumers. Broke the production build after
card.tsx was deleted. Removed the import and its gallery entry.
Verified with a local `bun run build`.
2026-07-09 15:55:58 -07:00
Waleed 6e87d74a93 feat(jupyter): add Jupyter integration (contents, kernels, sessions) (#5527)
* feat(jupyter): add Jupyter integration (contents, kernels, sessions)

- 16 tools covering Contents, Kernels, Kernelspecs, and Sessions REST APIs
- File upload/download via UserFile, following the Box upload pattern
- Block with operation dropdown, token auth, and 8 catalog templates
- Registered tools + block, generated docs, bumped API validation baseline

* fix(jupyter): address Greptile review — SSRF guard, upload path ambiguity, silent notebook fallback

- Route uploads through validateUrlWithDNS + secureFetchWithPinnedIP (matches Grafana/1Password pattern) instead of a raw fetch to the user-supplied server URL
- Replace the upload path/trailing-slash heuristic with an unambiguous directory + filename split
- create_file no longer silently writes an empty notebook when notebook content is malformed JSON — it now errors clearly

* fix(jupyter): request content=1 when listing directory contents

Without it, Jupyter Server returns directory metadata with content:
null, so jupyter_list_contents always reported an empty items array.

* fix(jupyter): reject path-traversal segments in Jupyter content paths

encodeJupyterPath now rejects '.'/'..' segments across the whole path
(shared by all 16 tools, not just upload); the upload route returns a
clean 400 when it's hit.

* fix(jupyter): close remaining path-traversal and redirect-credential gaps

- extract the traversal check out of encodeJupyterPath into a shared
  assertion, and apply it to body-only path fields (rename newPath,
  copy copyFromPath, session path) that never flowed through URL
  encoding and so skipped the check
- pass stripAuthOnRedirect to the upload route's secureFetchWithPinnedIP
  call so a malicious Jupyter server can't redirect the PUT to another
  origin and receive the caller's token

* fix(jupyter): also reject percent-encoded traversal segments

A segment like %2e%2e wouldn't match the literal '..' check. Now
decodes each segment before comparing, in addition to the literal
check, so an already-encoded traversal attempt is caught too.

* fix(jupyter): route all 15 remaining tools through an internal proxy for HTTP/private-host support and no redirects

The generic external tool executor blocks plain-HTTP and non-localhost
private-IP hosts by default, so every non-upload Jupyter operation
could fail against typical self-hosted setups (LAN IP, docker
hostname, or even literal localhost on a hosted deployment) even
though the upload route worked via its own internal route.

Added /api/tools/jupyter/proxy (DNS-pinned, allowHttp, maxRedirects: 0)
that mirrors the upstream Jupyter response verbatim, matching the
established pattern for self-hosted-arbitrary-host integrations
(Grafana, 1Password) instead of the generic executor path. Each tool's
request block now posts to the proxy instead of building a direct
external URL; transformResponse and outputs are unchanged since the
proxy response mirrors upstream status/body exactly.

Also switches the upload route from stripAuthOnRedirect to
maxRedirects: 0 — stronger, since it stops the uploaded file body
(not just the token) from ever reaching a redirect target.

* fix(jupyter): validate proxy path at the trust boundary, reject path separators in upload filename

- The proxy route now independently validates the incoming path field
  for traversal segments instead of only relying on tool-side
  validation before the request reaches it — the route is a shared
  internal boundary, not something only our own tool code can call
- The upload route's fileName can come from an advanced override or
  the legacy fileContent path and could itself contain '/' or '\',
  silently nesting the upload deeper than the directory param
  specified. Now rejected outright before joining.

* fix(jupyter): decode the whole path before splitting, not per-already-split segment

A segment like foo%2f..%2fsecret has no literal slash, so splitting on
literal '/' first and decoding each piece in isolation treats it as
one opaque segment and never notices the '..' hiding behind the
encoded slash. Decode the full path once, then split and check every
segment the target server's own single URL-decode pass would see.
2026-07-08 19:35:57 -07:00
Vikhyath Mondreti 9404bda5c2 docs(forking): workspace forking for enterprises (#5520)
* docs(forking): workspace forking

* update comparison links
2026-07-08 16:00:36 -07:00
Theodore LiandClaude Opus 4.8 d30eb36d9a docs(enterprise): add Custom Blocks page (#5515)
* docs(enterprise): add Custom Blocks page

Document publishing a deployed workflow as a reusable org-wide block:
publishing flow, common uses, using a block, managing, and self-hosted
setup, with UI screenshots. Register in the enterprise sidebar and wire
the settings docsLink.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvLuS5o1eFjBBhEC4PytcA

* docs(enterprise): drop explicit Enterprise framing, note access control

The page's Enterprise placement is implicit, so remove the "Enterprise
feature" callout and plan mentions. Add that custom blocks can be
allowlisted per permission group via Access Control.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvLuS5o1eFjBBhEC4PytcA

* docs(enterprise): remove Self-hosted setup section from Custom Blocks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvLuS5o1eFjBBhEC4PytcA

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 17:36:45 -04:00
Waleed 767307177e fix(comparison): correct stale and unsourced claims on comparison pages (#5513)
* fix(comparison): correct stale and unsourced claims on comparison pages

- Remove stale "Templates" references from the LangChain page and
  related blog posts (feature was removed platform-wide)
- Re-verify every sourced claim across all comparison-page profiles
  (Sim's own facts and all 20 competitor profiles) against live
  sources; fix dead links, renamed products, outdated figures, and
  claims no longer supported by their citation
- Fix a stale sidebar permissions reference in the enterprise
  access-control docs

* fix(comparison): use consistent URL format for n8n AI Workflow Builder citation
2026-07-08 12:11:54 -07:00
Waleed 61ceedc5b9 fix(notion): align integration with live API docs, add block retrieval coverage (#5491)
* fix(notion): align integration with live API docs, add block retrieval coverage

- wire up notion_update_page in the block (was registered but unreachable — no dropdown option or subBlocks)
- fix legacy NotionBlock outputs to cover all 17 operations, not just content/metadata
- trim ID params (pageId, databaseId, parentId) before use in request URLs across 8 tool files
- remove content required:true on create_page (Notion allows title-only pages)
- remove dead unused NotionReadDatabaseParams interface
- add notion_retrieve_block/_v2 tool for GET /v1/blocks/{id}, filling a gap in block CRUD coverage

* fix(notion): second validation pass — pagination gaps, phantom outputs, dead types, regen docs

- add missing startCursor/start_cursor pagination param to notion_query_database and notion_search
- fix notion_search missing from the shared pageSize/startCursor field's operation condition in NotionBlock
- fix NotionV2Block title/url/created_time/last_edited_time outputs rendering unconditionally for every operation instead of only the ones that return them
- remove 153 lines of dead unused output-shape constants from types.ts
- regenerate integration docs

* fix(notion): expose retrieve-block outputs in legacy block picker

type/block/archived only conditioned on notion_update_block/notion_delete_block,
and has_children was missing entirely — Retrieve Block's payload was invisible
in the legacy NotionBlock output picker (Greptile P1 on PR #5491).

* fix(notion): document type-specific content gap on retrieve_block output

block.properties (BLOCK_OUTPUT_PROPERTIES) only covers common block fields,
not the type-specific sub-object (paragraph.rich_text, image.file, etc.) —
that varies per block type and isn't enumerable. Documented in the
description per Greptile P2 on PR #5491; no functional change, block: data
already carries the full object at runtime.
2026-07-07 13:57:33 -07:00
Waleed ed1492bcbd fix(google-vault): validate against live API docs, add matter/hold/saved-query CRUD coverage (#5482)
* fix(google-vault): validate integration against live API docs, add matter/hold/saved-query CRUD coverage

- Fix critical bug: create_matters_export sent the deprecated Query.searchMethod field (deprecated 2019, support ended 2020) instead of method, silently breaking account/org-unit scoped exports
- Fix duplicate matterId subBlock id (two definitions collided across operations)
- Add matter lifecycle: update, close, reopen, delete, undelete
- Add matter collaborator management: add/remove permissions
- Add export delete
- Add hold update, delete, add/remove held accounts
- Add saved query create/list/delete
- Bump tool versions to 1.0.0 to match repo convention
- Fix docsLink to point at docs.sim.ai instead of the vendor site
- All new endpoints are covered by the existing ediscovery + devstorage.read_only OAuth scopes (no new scopes requested)

* fix(google-vault): pageToken should be user-or-llm visibility, not hidden

Greptile review: hidden is reserved for framework-injected tokens; pageToken
in list_saved_queries.ts should be user-or-llm so an agent/user can pass it,
matching the pattern used elsewhere for tool-supplied pagination cursors.

* fix(google-vault): fail loudly instead of silently clearing hold scope on update

Cursor Bugbot: PUT holds/{id} replaces the full resource — a name/query-only
update with no accountEmails/orgUnitId would silently drop the hold's
custodian coverage. Now throws a clear error directing callers to resend the
scope or use add_held_accounts/remove_held_accounts for incremental changes.

* fix(google-vault): reject unscoped exports/saved-queries for non-MAIL corpus

Independent audit (parallel doc-verification pass): create_matters_export
and create_saved_query resolved Query.method to undefined when corpus
wasn't MAIL and no accountEmails/orgUnitId was given, silently sending an
invalid request (method is a required Query field) instead of a clear
error. Now throws with an actionable message before the request is sent.

* fix(google-vault): document full-replace semantics on hold update query filters

Cursor Bugbot: update_matters_holds only sets query.mailQuery/groupsQuery/
driveQuery when terms/date/shared-drive fields are provided, but the PUT
replaces the whole hold — omitting a previously-set filter clears it, not
leaves it unchanged. Filters are legitimately optional (a hold may have
none), so this can't be hard-required like scope; instead the tool and
field descriptions now explicitly state the full-replace behavior and
direct callers to resupply current values via Vault List Holds first.

* fix(google-vault): isolate stale-value-prone subblocks per operation

Cursor Bugbot: consolidating shared subblocks across operations left two
cross-contamination risks since a stale value from one operation stays in
block state until overwritten:

- accountEmails/orgUnitId are checked emails-first with silent either/or
  priority; sharing them across update_matters_holds and create_saved_query
  meant a leftover value from a different operation could silently override
  the intended scope. Gave both operations dedicated fields
  (updateHoldAccountEmails/updateHoldOrgUnitId, savedQueryAccountEmails/
  savedQueryOrgUnitId), remapped in tools.config.params.
- matterId presence alone switches google_vault_list_matters between
  list-all and single-get. Sharing it with every other operation meant a
  leftover matterId could silently turn "List Matters" into a single-matter
  fetch. Gave list_matters its own optional listMatterId field.

create_matters_holds/create_matters_export keep sharing accountEmails/
orgUnitId as before this PR (pre-existing behavior, not introduced here).

* fix(google-vault): isolate list-optional-id fields from required-elsewhere counterparts

Cursor Bugbot: exportId/holdId/savedQueryId were shared between their
respective list operation (optional filter) and update/delete/held-account
operations (required). A stale ID left over from a delete/update on the
same block instance would silently narrow the corresponding list operation
to a single-resource get instead of listing the collection. Gave each list
operation its own dedicated optional field (listExportId, listHoldId,
listSavedQueryId), remapped in tools.config.params — same pattern already
used for listMatterId.

* fix(google-vault): defensively order mutually-exclusive scope spreads

Greptile: savedQueryAccountEmails/savedQueryOrgUnitId spread after their
updateHold* counterparts, so if both were ever truthy at once the wrong
one would silently win. In practice they're mutually exclusive (each only
populated while its own single 'operation' value is selected, so at most
one pair is ever truthy), but reordering costs nothing and removes any
doubt about precedence.

* docs: regenerate integration docs

Reruns scripts/generate-docs.ts against the current block/tool/trigger
registry. Picks up google_vault's new operations plus everything else that
had landed on staging without a docs regen (bigquery, google_calendar,
google_maps, onedrive, microsoft_teams, gitlab, github, discord, dropbox,
and others), plus icon and integrations.json updates.
2026-07-07 12:53:50 -07:00
Waleed 1a87f1a83c fix(microsoft-planner): align with live Graph API docs, add plan CRUD + categories (#5486)
* fix(microsoft-planner): align with live Graph API docs, add plan CRUD + categories

- fix update_task never returning updated task (missing Prefer: return=representation)
- fix wrong @odata.type on task assignments (missing leading #)
- fix update_plan/update_plan_details/update_bucket/update_task_details crashing
  on 204 No Content responses to PATCH (Graph sometimes ignores Prefer header)
- add .trim() on path IDs across tools invoked outside the block
- add create_plan, update_plan, delete_plan, get_plan_details, update_plan_details
  tools (If-Match/etag handled correctly on update/delete)
- add appliedCategories support on create_task/update_task
- all new tools verified against Graph API Permissions tables to require only
  scopes we already request (Group.ReadWrite.All, Group.Read.All, Tasks.ReadWrite)
- regenerate integration docs

* fix(microsoft-planner): use Graph-specific error extractor consistently

7 tools were pinned to the generic 'nested-error-object' extractor
instead of MICROSOFT_GRAPH_ERRORS, losing Graph's inner-error detail
(e.g. ETag mismatch specifics) that sibling Microsoft integrations
(Excel, OneDrive, SharePoint) already surface correctly.

* fix(microsoft-planner): address Cursor/Greptile round-1 review findings

- fix empty priority/percentComplete string coercing to 0 (Number('')) at
  the block layer, which Planner treats as urgent/0% instead of leaving unset
- support removing applied categories on update via a "-category3" prefix,
  since Graph only clears a label when its key is explicitly set to false
- add missing MICROSOFT_GRAPH_ERRORS extractor to delete_plan/get_plan_details

* fix(microsoft-planner): don't return a stale etag on 204 update_task response

Graph's If-Match update changes the resource's etag even when it returns
204 No Content instead of the updated representation. Returning the
request's (now-stale) etag as if it were current would let a chained
update silently send a wrong If-Match and fail with 412. Return an empty
etag instead so update_task's own etag-required guard forces a re-fetch.
2026-07-07 12:14:38 -07:00
Vikhyath Mondreti 24ebba9acc chore(credential-sets): cleanup feature (#5460) 2026-07-06 18:49:06 -07:00
Waleed 719179258c improvement(rich-markdown-editor): table column-resize cursor, exhaustive 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.
2026-07-06 18:19:52 -07:00
Waleed fb3f95d5dc feat(aws): expand SES/STS/Secrets Manager tool coverage, fix API alignment 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.
2026-07-06 17:26:44 -07:00