Skip to content

v0.7.29: GLM, ui and ux improvements#5567

Open
waleedlatif1 wants to merge 21 commits into
mainfrom
staging
Open

v0.7.29: GLM, ui and ux improvements#5567
waleedlatif1 wants to merge 21 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

waleedlatif1 and others added 6 commits July 10, 2026 09:08
…re format (#5559)

* fix(models): correct model catalog data and Gemini thinking-config wire format

- OpenAI: remove fabricated 'max' reasoning-effort value from gpt-5.6 family
- Anthropic: fix claude-sonnet-4-6 maxOutputTokens (64k -> 128k); remove 3 fully
  retired models (claude-opus-4-0, claude-sonnet-4-0, claude-3-haiku-20240307);
  fix budget_tokens/max_tokens clamp that could send budget_tokens >= max_tokens
  for claude-opus-4-1 at its default thinking level
- Google/Vertex: un-deprecate gemini-3-flash-preview (no shutdown date announced);
  add thinking capability to gemini-2.5-pro/flash/flash-lite (google + vertex)
- Fix gemini/core.ts to send thinkingBudget (not thinkingLevel) for Gemini
  2.5-series models, which reject thinkingLevel entirely - only Gemini 3.x
  supports it
- Bedrock: mark claude-opus-4-1 deprecated per AWS's own Bedrock lifecycle
  schedule (Legacy since Jul 8 2026, separate from Anthropic's direct-API date)

* fix(models): restore retired Claude entries as deprecated instead of removing

Greptile caught a real backward-compat regression: fully removing
claude-opus-4-0/claude-sonnet-4-0/claude-3-haiku-20240307 dropped them from
getHostedModels()/shouldBillModelUsage(), so saved workflows still referencing
them would fail on a missing-API-key error instead of Anthropic's actual
"model retired" error. deprecated:true isn't consumed by the model picker
(only copilot's serializer reads it), so restoring them this way costs
nothing on hiding from new selection while preserving hosted-key resolution
for existing references.

* fix(models): restore Sol-exclusive 'max' reasoning value; fix Gemini 2.5 disable-thinking gap

Found during a final per-model audit round with independent 2-3 source
verification on every changed model:

- gpt-5.6-sol: restore 'max' reasoning-effort value. Multiple independent
  sources (OpenAI's own model-guidance docs page, launch announcement, and
  press coverage) confirm 'max' is a real, newly-launched value exclusive to
  Sol - not fabricated as originally assessed. Terra and Luna correctly do
  NOT get 'max' (confirmed Sol-exclusive), so they're unchanged.
- gemini-2.5-flash / gemini-2.5-flash-lite (google + vertex): selecting
  'none' for thinking level was sending no thinkingConfig at all, which
  falls back to the API's dynamic default (thinking stays ON for flash) -
  not actually disabling it, even though both models explicitly support
  thinkingBudget:0. Now sends an explicit budget of 0 for these two models
  specifically (gemini-2.5-pro is correctly excluded - it cannot disable
  thinking at all, floor is 128 not 0).

* chore(models): tighten inline comments in anthropic/gemini core

Trim verbose multi-line comments to single concise lines and remove
duplication with the TSDoc already on the ANTHROPIC_MIN_BUDGET_TOKENS/
ANTHROPIC_THINKING_OUTPUT_HEADROOM constants. No behavior change - verified
against the full test suite (811 files, 11141 tests, all passing).
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* feat(providers): add NVIDIA NIM and Z.ai providers

- NVIDIA NIM (BYOK): Nemotron model family (70B/Ultra-253B/Super-49B v1.5,
  Nemotron-3 Nano/Super/Ultra) via integrate.api.nvidia.com's
  OpenAI-compatible API
- Z.ai (hosted): GLM model family (5.2 down to 4-32B) via api.z.ai's
  OpenAI-compatible API, bare glm-* model ids with no provider prefix,
  Sim-provided key rotation (ZAI_API_KEY_1/2/3) matching openai/anthropic/google
- Z.ai tool_choice is forced to 'auto' since the API only documents auto
  support; forced/none tool_choice from prepareToolsWithUsageControl is
  ignored with a warning log instead of being sent to the API

* fix(providers): scope zai model routing to the exact catalog

Drop the /^glm/ fallback pattern - it would overmatch any unrelated
self-hosted "glm-*" model (e.g. a custom vLLM/LiteLLM deployment) and
misroute it to Z.ai's hosted, Sim-billed key. Routing now relies solely
on the exact model-id match against zai's static catalog.

* fix(providers): wire thinking/reasoning_effort into Z.ai requests

request.thinkingLevel and request.reasoningEffort were computed but
never mapped onto the Z.ai payload, so the thinking toggle and GLM-5.2's
reasoning_effort control silently no-op'd and always ran on Z.ai's
server-side default instead of the user's selection.

* fix(providers): route Z.ai through the workspace BYOK resolver too

getApiKeyWithBYOK (the resolver actually used by workspace provider
runs, per providers/index.ts) only rotated server keys for
openai/anthropic/google/mistral, so GLM calls without a user apiKey
failed even with ZAI_API_KEY_1/2/3 configured — getApiKey in
providers/utils.ts had zai wired but that's not the codepath workspace
runs go through. Add zai to the hosted-check condition, and register it
as a BYOKProviderId so a workspace can also bring its own Z.ai key.

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(models): add latest Groq and Cerebras models, flag near-term retirements

- Groq: add qwen/qwen3.6-27b (Preview, $0.60/$3.00, 131k ctx) - live on
  console.groq.com/docs/models, not previously in the catalog
- Groq: mark meta-llama/llama-4-scout-17b-16e-instruct and qwen/qwen3-32b
  deprecated - both have an announced shutdown date of July 17, 2026 per
  Groq's own deprecations page
- Cerebras: add gemma-4-31b (Preview, $0.99/$1.49, 131k ctx/40k max output) -
  live on inference-docs.cerebras.ai, not previously in the catalog

Every field independently verified via 2+ live sources (provider docs +
pricing pages) before adding; no code changes needed since both providers
use generic OpenAI-compatible completions with no per-model special-casing.

* fix(models): add verified releaseDate for groq/qwen/qwen3.6-27b

Greptile correctly caught that omitting releaseDate causes this model to
sort last within the Groq section in the model picker (orderModelIdsByReleaseDate
treats a missing date as Number.NEGATIVE_INFINITY) - misleading since it's
actually the newest model in the lineup. Verified 2026-04-21 (Qwen's own
upstream release date, matching this repo's existing convention of using the
model creator's release date rather than a reseller-specific date) via
llm-stats.com, cross-checked against two other independent sources.
* fix(sidebar): fix rename input losing its selection on open

Radix's FocusScope defers close-time focus teardown to a setTimeout(0),
which can occasionally run after the rename input's own focus()/select()
and clobber the selection. Focus the input from onCloseAutoFocus instead,
which runs inside that same deferred teardown and always wins the race.

Affects workflow, folder, and workspace rename (all route through the
shared sidebar ContextMenu component).

* fix(sidebar): only refocus the rename input when Rename triggered the close

onCloseAutoFocus fires on every menu close, not just after selecting
Rename. Gate the refocus behind a ref set only when the Rename item
was selected, so closing the menu for an unrelated action (Delete,
Duplicate, ...) while an earlier rename is still live doesn't steal
focus back into it and delay its blur-save.
…5565)

* feat(landing): add HubSpot tracking script for hosted marketing site

- Loads the HubSpot loader in the landing route group only, gated by isHosted
- Not loaded for self-hosted/OSS deployments
- Adds the loader's companion scripts (analytics, form-tracking, banner) and their beacon hosts to CSP, verified against the actual scripts' network calls

* fix(landing): scope HubSpot CSP hosts to the landing route group only

Greptile flagged that the HubSpot script/connect hosts were added to the
shared CSP arrays used by every route, including /workspace, /login, and
/signup — even though the HubSpot loader only ever renders inside the
(landing) route group.

- Move the HubSpot hosts out of STATIC_SCRIPT_SRC/STATIC_CONNECT_SRC
- Add generateLandingRuntimeCSP(), which extends the shared runtime policy
  with the HubSpot hosts, mirroring the existing getChatEmbedCSPPolicy()
  pattern for route-scoped CSP variants
- Wire it into proxy.ts's catch-all branch, which is what actually serves
  the marketing/landing site; /workspace, /login, /signup keep the
  unmodified shared policy

* fix(landing): track HubSpot pageviews on client-side landing navigations

Cursor Bugbot flagged that the HubSpot loader only auto-fires a pageview
on the initial load. Since LandingLayout persists across client-side
navigations between landing routes, subsequent Link navigations never
told HubSpot about the route change, undercounting pageviews.

Add HubspotPageViewTracker, a small client component using the standard
Next.js App Router pattern (usePathname/useSearchParams in a Suspense
boundary) to push a manual pageview through HubSpot's _hsq queue on every
navigation after the first.

* simplify(landing): drop unnecessary Suspense from HubSpot pageview tracker

usePathname() alone doesn't require a Suspense boundary to preserve static
rendering — only useSearchParams() does. The tracker only needs the path,
not the query string, so drop useSearchParams and the Suspense wrapper
entirely. Simpler, and no risk to the landing site's static rendering/LCP.

* fix(landing): exclude non-landing routes from the landing CSP fallback

Greptile's second pass caught that proxy.ts's catch-all branch (which
serves generateLandingRuntimeCSP()) also handles several non-landing pages
that fall through the earlier explicit branches: /verify, /sso,
/reset-password (auth sub-pages), /resume/[workflowId] (interfaces),
/f/[token] (file shares), /playground, and the authenticated/callbackUrl
invite fallthrough. None of these render the HubSpot loader, so they
shouldn't get its CSP allowance either.

Add an explicit non-landing path prefix list and only fall back to
generateRuntimeCSP() (the tight policy) for those, keeping
generateLandingRuntimeCSP() for everything else in the catch-all.

* fix(landing): add /unsubscribe to non-landing paths, fix tracker remount bug

- Greptile correctly flagged /unsubscribe as another top-level page outside
  (landing) that reaches the CSP fallback branch; add it to the exclusion list
- Cursor caught that the per-mount useRef in HubspotPageViewTracker resets
  whenever LandingLayout remounts (e.g. leaving the landing site and coming
  back), but next/script dedupes the loader by id and won't re-fire the
  auto-tracked pageview on remount — so that return visit was silently
  dropped. Move the flag to module scope so it reflects the actual
  once-per-browser-session lifetime of the loader script, not per-mount

* fix(landing): track query-only landing navigations in HubSpot pageviews

Cursor caught that the tracker only depended on usePathname(), so
client-side navigations that change only the query string (blog/library
pagination, careers filters) never fired a pageview at all, and setPath
dropped the search string even when the path did change.

Add useSearchParams() back (the officially documented Next.js pattern for
tracking all route changes) and depend on the full path+query string.
Wrap the tracker in a local Suspense boundary, as required to keep the
route statically rendered — the fallback is null and the component
renders nothing, so this has no LCP/visual cost.

* test(proxy): add regression coverage for the non-landing path classifier

Exports isNonLandingPath and covers the exact prefix-boundary cases
(e.g. /f vs /ffoo, /resume vs /resumes) that the CSP routing fix depends
on, so this logic is verified by CI rather than my own ad-hoc checks.

* fix(landing): exclude /landing-preview from the landing CSP fallback

Greptile caught that /landing-preview calls notFound() in production
(see app/landing-preview/page.tsx) and its subroutes (marks-lab,
readme-tour-capture) don't render the (landing) layout either, so none of
them ever load the HubSpot tracker — but the CSP fallback was still
classifying the whole prefix as landing.

* chore(landing): remove the /landing-preview test scaffold

It was a temporary route for visual iteration (404s in production) — not
needed anymore, and Greptile had just flagged it as another route that
falsely inherited the landing CSP allowance. Removing it outright is
simpler than maintaining an exclusion for it.

Also removes SandboxWorkspacePermissionsProvider, which existed solely to
support the deleted readme-tour-capture page and has no other callers.

* refactor(landing): fix invalid const assertion, trim comments

- Fixed HUBSPOT_SCRIPT_SRC/HUBSPOT_CONNECT_SRC: 'as const' cannot wrap a
  ternary expression directly (TS1355) — caught by a full project typecheck
  I ran specifically to verify this PR, not by lint/tests alone. Rewrote
  using the same conditional-spread-inside-array-literal pattern already
  used by every other array in this file (e.g. STATIC_FRAME_SRC)
- Trimmed comments across all four touched files down to only the
  non-obvious 'why' (module-scope tracking flag, HubSpot CSP scoping
  rationale), matching this codebase's terse comment style elsewhere

* revert(landing): drop landing-scoped CSP, put HubSpot in the shared policy

Cursor caught a fundamental problem with the landing-scoped CSP: the
Content-Security-Policy header is fixed to the document's initial HTTP
response and is NOT re-applied on Next.js client-side (soft) navigation.

Both the landing navbar's ChipLink to /login and AuthShell's Link back to
/ are soft navigations (confirmed directly in the source, not assumed).
That means:
- /login -> / (soft nav): the browser keeps /login's CSP, which never
  allowed HubSpot hosts, so the loader gets silently blocked on landing.
- / -> /login (soft nav): the browser keeps the landing CSP, which is
  MORE permissive than /login's, undoing the tightening entirely.

A per-route CSP is fundamentally incompatible with this app's
client-side-routed navigation. Greptile's original 'CSP too broad on
/workspace' concern was valid in isolation, but the fix built across the
last several rounds doesn't actually work — it's neither reliably
tighter nor reliably functional, and each round's patch (exclusion list
entries, /landing-preview handling) was really just papering over that
core issue.

Revert to a single shared CSP for the whole app, matching exactly how
GTM/GA/ahrefs are already handled in this same file: HubSpot hosts land
in STATIC_SCRIPT_SRC/STATIC_CONNECT_SRC under the existing isHosted
gate. /workspace's CSP header technically allows origins it never
requests (same accepted tradeoff as GTM/GA), but the tracking script only
ever renders in the (landing) layout — matching the CSP scope Greptile
originally objected to. This is now provably correct because it can't
desync: proxy.ts, csp.ts, and proxy.test.ts are byte-for-byte identical
to origin/staging except for the HubSpot host list itself.

* fix(csp): drop overbroad *.hubspot.com connect-src wildcard

Greptile correctly flagged that *.hubspot.com is far broader than
anything the tracker actually needs — it covers HubSpot's entire product
surface (app, api, marketing), not just the tracking endpoints.

Re-checked my own network trace from earlier: the pageview beacon itself
is an image pixel (new Image() to track.hubspot.com/__pto.gif), which is
governed by img-src (already wide open to any https: origin), not
connect-src. The *.hubspot.com entry was an unverified guess for the
forms-API/banner fetch calls I couldn't pin down through minification —
removing it since I can't confirm what it was actually protecting,
keeping only the verified *.hscollectedforms.net entry.
…5566)

* fix(mcp): fix caret misalignment in Add MCP Server modal fields

The Server URL and Header fields render a transparent input under a
formatted overlay div for env-var highlighting. The overlay used
font-medium/font-sans but the real input didn't, so glyph widths
diverged and the native caret drifted from the visible text as you
typed.

* fix(mcp): loosen tool schema contract to accept valid JSON Schema shapes

discoverMcpToolsContract's property schema rejected legal JSON Schema
that real MCP servers can return: array-form `items` (tuple
validation) and non-primitive `enum` values. Any server exercising
either shape failed contract validation client-side and blanked the
entire MCP tools list.

* fix(mcp): only render dropdown UI for primitive-valued enums

The MCP dynamic-args dropdown stringifies enum members for its
labels/values. Now that the tool schema contract accepts
non-primitive enum members (object/array), routing those through the
dropdown would collapse distinct values to "[object Object]" and
submit that string as the tool argument. Gate the dropdown on
primitive-only enums; non-primitive enums fall through to the
existing type-based branching (the JSON long-input editor for
object/array types), which round-trips arbitrary JSON correctly.

* fix(mcp): route non-primitive enums to the JSON editor regardless of type

isPrimitiveEnum() correctly excluded object/array enum members from
the dropdown, but the fallback only reached the long-input JSON
editor when paramSchema.type was 'array'. An object-typed (or
untyped) param with a non-primitive enum fell through to the default
short-input, which stringifies via toString() and drops the
enum-membership guarantee entirely. Any non-primitive enum now routes
straight to long-input, independent of the declared type.

* chore(mcp): fold inline comment into the existing TSDoc block

* fix(mcp): serialize non-string values before displaying in the long-input editor

The long-input JSON editor received value={value || ''} unconditionally,
so an argument already holding a parsed object/array (loaded from the
block's JSON arguments field) rendered as "[object Object]" or a
comma-joined list instead of valid JSON, and saving would overwrite the
real value with that mangled text. Serialize non-string values with
JSON.stringify before display; onChange still stores the raw text the
user edits, unchanged.

* fix(mcp): parse JSON-typed long-input edits back into real values

The long-input editor's onChange always stored the raw typed text, so
a param whose schema requires an object/array/non-primitive-enum value
(e.g. entering {"mode":"strict"}) was persisted as a string, not the
actual JSON value — the MCP tool call could receive the wrong type.
requiresJsonValue() identifies these schemas; onChange now parses the
edited text back into the real value once it's valid JSON, falling
back to the raw string mid-edit so the controlled textarea keeps
reflecting in-progress keystrokes.
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 11, 2026 1:11am

Request Review

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Realtime edge and rename validation changes workflow persistence behavior and could drop edges or renames that previously saved; landing/docs changes are lower risk but touch high-visibility marketing surfaces.

Overview
Adds a make-interfaces-feel-better agent skill (with linked reference docs and a Claude symlink) for UI polish guidance, separate from runtime product code.

Realtime persistence now runs a shared filterEdgesForPersist pipeline on single-edge and batch-edge adds (and batch block+edge inserts): missing blocks, protected targets, annotation-only blocks, trigger targets, scope boundaries, duplicates, and cycles—aligned with @sim/workflow-types client rules. Block renames are skipped when getWorkflowBlockNameConflict would collide. Comments clarify that the workflow updatedAt update serializes concurrent persists per workflow.

Marketing / enterprise: shared landing-layout tokens, LandingHeroHeader, navbar glass/solid-top behavior on the landing scroll port, Enterprise nav links, and solutions-page variants (centered heroes, feature tiles, EnterpriseFeatureGrid for tablet reflow). New enterprise hero uses EnterprisePlatformLoop (live sidebar + new-chat stage + injectable HeroWorkflowStage invoice flow). Homepage hero refactored to the shared header; HeroWorkflowStage accepts custom blocks/edges/canvas.

Docs: manual intro sections on many integration MDX pages; BlockInfoCard picks black vs white icons from tile brightness (mirrors sim LIGHT_TILE_THRESHOLD); Context.dev doc trims removed actions; docs favicon swaps to embedded PNG; TikTokIcon added. Config: CONTEXT_DEV_API_KEY_* rotation vars in apps/sim/.env.example.

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

@waleedlatif1 waleedlatif1 changed the title v0.7.29: GLM, rename bug, ui improvements v0.7.29: GLM, ui and ux improvements Jul 10, 2026
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates model/provider support and cleans up several UI and landing paths. The main changes are:

  • Added NVIDIA NIM and Z.ai provider support.
  • Updated Gemini thinking-config handling and model catalog data.
  • Broadened MCP tool schema handling and dynamic argument editing.
  • Added hosted HubSpot page-view tracking for landing pages.
  • Removed temporary landing-preview routes and adjusted sidebar rename behavior.

Confidence Score: 4/5

The provider and MCP argument paths need fixes before merging.

  • Z.ai can skip required tool calls.
  • Z.ai structured-output requests can return JSON that does not match the requested schema.
  • MCP JSON argument editing can persist malformed values into execution inputs.
  • The HubSpot query forwarding issue is smaller but should be tightened.

apps/sim/providers/zai/index.ts; apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/mcp-dynamic-args/mcp-dynamic-args.tsx; apps/sim/app/(landing)/hubspot-page-view-tracker.tsx

Security Review

HubSpot tracking now sends full landing-page query strings to a third-party analytics service. Filtering to known-safe attribution params would reduce the chance of leaking tokens or personal data from shared URLs.

Important Files Changed

Filename Overview
apps/sim/providers/zai/index.ts Adds the Z.ai provider, but required tool calls can be downgraded to optional and structured-output schemas are not enforced.
apps/sim/providers/nvidia/index.ts Adds the NVIDIA NIM provider using the existing OpenAI-compatible provider flow.
apps/sim/providers/gemini/core.ts Switches Gemini 2.5 thinking requests to budget-based config while keeping Gemini 3 on thinking levels.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/mcp-dynamic-args/mcp-dynamic-args.tsx Improves MCP argument rendering for JSON-like schema values, but malformed JSON can now be persisted as an argument value.
apps/sim/lib/api/contracts/mcp.ts Broadens MCP schema contracts to accept tuple-style items and arbitrary enum members.
apps/sim/app/(landing)/hubspot-page-view-tracker.tsx Adds SPA navigation page-view tracking for HubSpot on hosted landing pages.
apps/sim/lib/core/security/csp.ts Adds hosted-only HubSpot script and connect sources to the CSP.

Reviews (1): Last reviewed commit: "fix(mcp): fix caret misalignment and too..." | Re-trigger Greptile

Comment thread apps/sim/providers/nvidia/index.ts Outdated
Comment thread apps/sim/providers/zai/index.ts
Comment thread apps/sim/providers/zai/index.ts
Comment thread apps/sim/app/(landing)/hubspot-page-view-tracker.tsx
* fix(pii): install CUDA torch on amd64 so GLiNER can run on GPU

The published pii image installed a CPU-only torch build, so GLiNER on the
ECS GPU fleet died at model load with "Attempting to deserialize object on a
CUDA device but torch.cuda.is_available() is False". The Dockerfile already
had a TORCH_INDEX_URL arg, but no CI job ever passed --build-arg, so every
image silently took the cpu default.

Select the wheel index from TARGETARCH instead: amd64 gets cu128, arm64 keeps
the cpu index (cu128 publishes no aarch64 wheel at 2.11.0, and no arm64 target
has a GPU). CUDA torch falls back to CPU when no GPU is present, so one image
still serves both the Fargate CPU tasks and the EC2 GPU tasks off the same tag
— no CI or CDK changes needed.

cu128 keeps sm_75, the compute capability of the fleet's T4s, and its CUDA 12.8
runtime needs driver >=525 via minor-version compatibility, which the ECS GPU
AMI satisfies. cu121 was not an option: that index stops at torch 2.5.1.

Verified in an amd64 build of the changed block:
  2.11.0+cu128  cuda=12.8  arch=sm_75 sm_80 sm_86 sm_90 sm_100 sm_120
arm64 still resolves to 2.11.0+cpu. A build-time assert now fails the image
if amd64 ever silently regresses to a cpu wheel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QHNEWVrh7k89m8Wtqzhs18

* fix(pii): assert torch CUDA state after every pip install

The check sat directly after the torch install, but requirements-gliner.txt
and requirements-dev.txt are installed afterwards and resolve against PyPI
with no torch pin, so a future gliner bump could swap the wheel that
torch_index selected without tripping the assert.

Neither file changes torch today (verified: torch is 2.11.0+cu128 both before
and after the gliner install), so this guards the invariant rather than fixing
a live regression. Moving it below the last pip install makes it certify the
torch that actually ships.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QHNEWVrh7k89m8Wtqzhs18

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/sim/providers/zai/index.ts
…NVIDIA/Z.ai (#5569)

* fix(providers): correct max-tokens param and add schema guidance for NVIDIA/Z.ai

- NVIDIA NIM and Z.ai both document max_tokens for output-length control,
  not OpenAI's newer max_completion_tokens - the latter was silently
  ignored by both vLLM-served NIM models and Z.ai's GLM models
- Z.ai has no json_schema response_format mode (only text/json_object),
  so structured-output requests now also inject the expected schema into
  the system prompt as best-effort guidance, since the request param
  alone can't enforce field names/types

* style(providers): replace inline comments with TSDoc, per CLAUDE.md

Consolidated the scattered narrative // comments in nvidia/index.ts and
zai/index.ts into a single TSDoc block per provider documenting the
provider-specific API quirks; removed the rest where they only restated
what the code already shows. Also dropped an inline comment on zai's
modelPatterns field in models.ts.

* fix(providers): scope Z.ai schema guidance to the response_format call only

- schemaGuidance now falls back to the bare responseFormat object when
  .schema is absent, matching the schema-or-format fallback used
  elsewhere in the codebase (was silently injecting nothing for callers
  that pass a bare JSON schema)
- guidance is now only appended to the messages sent alongside an
  actual response_format (the immediate call, or whichever pass
  deferResponseFormat applies it to) instead of every turn of an
  active tool loop, where it wrongly told the model to return final
  JSON instead of continuing to call tools
… of tool args (#5570)

* fix(mcp): route object-typed params to JSON editor, keep invalid drafts out of tool args

Two follow-up gaps from the earlier MCP schema fix:

- getInputType only routed array-typed and non-primitive-enum params to
  the long-input JSON editor; a plain object-typed param (no enum) fell
  through to the default short-input, which stores raw text via
  toString() and never round-trips a real object.

- The long-input onChange fell back to storing the raw typed text
  whenever JSON.parse failed (needed to keep the controlled textarea
  responsive mid-edit), but that meant an incomplete/invalid edit
  (e.g. `{"a":1` before the closing brace) could persist into the
  actual tool arguments. If executed in that state, the MCP execute
  route's array-coercion step would silently wrap the malformed string
  into a corrupted array instead of failing validation.

Invalid-edit text now lives in local `invalidJsonDrafts` state, keyed
by param name, instead of the real argument store — the textarea still
reflects every keystroke, but the persisted tool argument is always
either the last successfully parsed value or untouched. Drafts reset
when the selected tool changes.

* fix(mcp): reset invalid JSON drafts on schema change, not just tool change

The draft reset only fired when the selected tool id changed, so a
same-tool schema refresh (e.g. re-discovering tools from the live MCP
server) could leave a stale invalid draft displayed under a param name
whose shape had since changed. Key the reset off both the tool id and
a signature of the effective schema's properties, so any change to
what's actually being edited clears stale drafts.

* fix(mcp): key draft reset off both schema sources, not the resolved toolSchema

toolSchema resolves to cachedSchema || selectedToolConfig?.inputSchema,
so a live-only schema refresh (the discovered tool's inputSchema
changes but the cached _toolSchema snapshot doesn't) left the reset
key unchanged and could keep a stale invalid draft on screen. Track a
signature of each schema source independently so a change in either
one clears drafts, regardless of which source toolSchema resolves to.

* fix(mcp): sign whole schema for draft reset; invalidate drafts on external value changes

Two more follow-up gaps:

- schemaSignature only serialized schema.properties, so a same-tool
  refresh that changed only top-level fields like `required` (properties
  byte-identical) left the reset key unchanged. Sign the entire schema
  instead of cherry-picking fields, so nothing schema-level can be missed.

- A draft only reset on tool/schema change, so if the persisted argument
  changed for any other reason (undo/redo, a diff baseline switch, a
  collaborator's concurrent edit), the draft could keep shadowing the
  now-current value in the editor while execution used the real one.
  Drafts now carry a baseline signature of the value they were typed
  against; a draft only displays while that baseline still matches the
  live persisted value, so any external change makes it fall back to
  showing the real value instead of stale text.

* fix(mcp): restore comma-separated array input; stop spurious draft reset on tool load

Two more follow-up gaps:

- Holding every JSON.parse failure in a local draft blocked the
  documented comma-separated array shorthand (see the placeholder text)
  from ever reaching toolArgs, since plain comma-separated text is
  never valid JSON. Only an in-progress JSON array/object literal
  (starting with `[` or `{`) needs to stay in the draft until valid;
  plain array-typed text that isn't attempting JSON persists
  immediately as before, letting the execute route's existing
  comma-split/wrap coercion handle it as designed.

- draftResetKey always included the live selectedToolConfig schema
  signature, even when cachedSchema wins the `toolSchema` resolution.
  That segment flips from empty to populated the moment mcpTools
  finishes an unrelated async load, wiping in-progress drafts though
  neither the rendered schema nor the stored args changed. The live
  signature now only factors into the key when there's no cached
  snapshot for toolSchema to prefer.

* fix(mcp): reset drafts on genuine live schema refresh, not just its first load

Excluding the live schema signature whenever a cached snapshot exists
(the prior fix for a Cursor finding about mcpTools' initial load
spuriously wiping drafts) went too far the other way: a genuine
same-tool live schema refresh while a cached snapshot is still present
would no longer reset drafts either.

Track the live schema signature unconditionally, but only treat a
change as a real reset trigger when it goes from one non-empty
signature to a *different* non-empty one. The bare empty → non-empty
transition (mcpTools completing its initial fetch) is excluded, since
that's not a schema change; a populated → differently-populated
transition (an actual re-discovery) still resets drafts regardless of
whether a cached snapshot is present.

* fix(mcp): compare live schema against last-known-non-empty, not prior render

Comparing the live schema signature only against the immediately
preceding render's value meant a schema that dropped to empty and then
reappeared with different content was invisible to the reset check —
both the drop (X → '') and the reappearance ('' → Y) look like a bare
empty/non-empty transition, which was deliberately excluded to avoid
resetting on mcpTools' initial load. Track the last non-empty value
actually observed instead, so a transient empty gap no longer erases
the baseline: the schema reset now fires correctly when the tool
reappears with a genuinely different schema, while a real first-ever
load (no prior non-empty value at all) still doesn't spuriously reset.

* fix(mcp): re-baseline live schema tracker fresh on every tool switch

lastNonEmptyLiveSchemaSignature carried over across a tool switch
whenever the newly-selected tool's live schema hadn't loaded yet in
that same render (still empty). When it loaded a moment later, the
comparison was against the *previous* tool's signature, so the new
tool's first schema load could be misread as a "genuine refresh" and
wipe drafts the user had already started typing against the new tool.
A tool/cached-schema change now always re-baselines the live-schema
tracker to the new tool's current signature (even if still empty), so
the "same tool" refresh comparison never bleeds across tool switches.
* feat(tiktok): add TikTok integration

Adds TikTok as a full OAuth-based integration: provider registration
(with TikTok's comma-separated scope and client_key requirements),
9 tools covering profile info, video listing/querying, creator info,
direct video/photo posting (URL or file upload), inbox drafts, and
post status polling, plus the TikTok block, icon, and generated docs.

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

* fix(tiktok): add avatarFile output to Get User Info

Adds a file-typed avatarFile output (sourced from the largest available
avatar URL) alongside the existing string avatar fields, so the profile
picture can be materialized as a UserFile and chained into file-consuming
blocks (e.g. attached to an email), per PR review feedback.

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

* fix(tiktok): lower upload memory cap, drop redundant avatar string outputs

Cap the file-upload video buffer at 250MB instead of TikTok's 4GB ceiling —
relaying that much through this server's memory per request isn't safe
under concurrent load, and larger files can still go through the
PULL_FROM_URL path, which never buffers on our server. Also drop the
now-redundant avatarUrl/avatarUrl100/avatarLargeUrl string outputs from
Get User Info in favor of the file-typed avatarFile output alone, since
the feature is unreleased and the raw URL is still reachable via
avatarFile.url. Cover image URLs on List/Query Videos are confirmed to be
signed, expiring TikTok CDN links; left as strings (no file-output
conversion path exists for fields nested inside array items) but
documented the expiry behavior more clearly.

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

* chore(ci): bump API validation route-count baseline for TikTok publish-video route

The TikTok integration adds one new Zod-backed internal API route
(app/api/tools/tiktok/publish-video), which trips the route-count
ratchet in check-api-validation-contracts.ts. Bumping totalRoutes and
zodRoutes from 917 to 918 (nonZodRoutes stays 0) to acknowledge the
new route is properly validated.

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

* chore(tiktok): drop unused avatar_url_100 from default user fields

After removing the avatar string outputs, avatar_url_100 was still
requested from TikTok's user info endpoint but never surfaced anywhere.
Removed it from the default field list and the field descriptions, and
noted that avatar_url/avatar_large_url feed the avatarFile output.

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

* fix(tiktok): stop returning raw 'credential' subBlock id from tools.config.params

The block's params function built a local `credential` variable from
params.oauthCredential and returned it under the key `credential` in
every switch case. That literal token is the raw subBlock id, which is
deleted after canonical transformation into `oauthCredential` — the
blocks.test.ts canonical-param-validation suite flags any params
function that still references it.

It was also redundant: oauthCredential is already part of the base
resolved inputs, which the executor merges into the tool call before
config.params overrides are applied, so the OAuth token resolution
(which reads contextParams.oauthCredential) worked regardless. Removed
the explicit credential plumbing, matching the convention already used
by other OAuth blocks like dropbox.ts.

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

* fix(tiktok): send empty JSON body on Query Creator Info POST

query_creator_info had no request.body function, and
formatRequestParams() only attaches a body when tool.request.body is
defined at all — so despite sending Content-Type: application/json,
the request went out with no body whatsoever. Added body: () => ({}),
matching the convention already used by other parameterless-POST tools
in this codebase (Google Vault, Supabase, Square, Gmail, etc.).

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

* fix(tiktok): stop dropping valid zero values in optional numeric fields

cursor, photoCoverIndex, and videoCoverTimestampMs all used a truthy
check (params.x && {...}) to decide whether to include an optional
numeric override, which drops a legitimate 0 (first page has no
cursor issue aside, photoCoverIndex 0 is TikTok's own default cover
photo, and timestamp 0 is a valid first-frame cover). Switched to
explicit undefined/empty-string checks, matching the !== undefined
convention the underlying tools already use.

In today's resolution pipeline these fields always arrive as strings
(even chained block references get stringified by the template
resolver), and a non-empty string like "0" is truthy, so this wasn't
actively broken end-to-end - but it was relying on that subtlety
rather than being correct by construction, and was inconsistent with
the tools' own undefined checks.

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

* fix(tiktok): accept newline-separated video IDs in Query Videos

videoIds is a long-input (multiline textarea), the same widget used
for the newline-separated photoImages field on this block, but its
parser only split on commas. Entering one ID per line - the natural
pattern for a multiline field, and the one already used elsewhere on
this block - produced a single concatenated garbage string instead of
an array, so TikTok's query would fail or return nothing. Now splits
on commas or newlines, and updated the placeholder/description to
reflect both formats.

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

* feat(tiktok): add app-level webhook ingress and triggers

* fix(tiktok): only count actually queued webhook executions

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

* chore(tiktok): bump API validation baseline for staging merge

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

* cleanup code

* fix type issues

* misc code cleanup

* remove photos and add upload for videos

* move shared video output properties to types.ts so docs generation resolves them

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

* hide TikTok from toolbar and docs until the integration is ready to ship

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

* fix(ci): ratchet API validation baseline to 924 after staging merge

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

---------

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>
Comment thread apps/sim/app/api/tools/tiktok/publish-video/route.ts
Comment thread apps/sim/app/api/webhooks/trigger/[path]/route.ts
…ght cells (#5572)

* fix(enrichments): remove Icypeas providers and show Running on in-flight cells

* fix(enrichments): keep previous value visible while an enrichment cell reruns
…ust client-side (#5571)

* fix(workflow-edges): enforce edge/block validation server-side, not just client-side

Dragging a connection that creates a cycle correctly refused to render
client-side, but the cyclic edge was still queued for realtime persistence
and written to the DB, so it reappeared after refresh. Root cause: cycle
detection (and several other edge/block rules) only lived in the client
Zustand store and was never enforced by the realtime persistence layer,
which is the actual source of truth on reload.

- Move wouldCreateCycle, edge scope-boundary, annotation-only-block,
  duplicate-edge, and block-name-conflict checks into @sim/workflow-types
  so the client store, the collaborative queueing layer, and
  apps/realtime's DB write path all share one implementation
- Wire these into apps/realtime/database/operations.ts's edge-add and
  block-rename handlers as the authoritative gate
- Client-side behavior is unchanged (same call sites, same error messages,
  same rule ordering) — verified via existing + new test coverage

* fix(workflow-edges): address review findings on realtime edge validation

- Select triggerMode when fetching blocks for edge-add validation —
  isKnownWorkflowTriggerBlock checked block.triggerMode but the column
  was never fetched from the DB, so trigger-mode blocks could still
  receive an incoming edge (Cursor Bugbot)
- Make filterUniqueWorkflowEdges incremental, so two duplicate edges
  within the same BATCH_ADD_EDGES payload are also deduped instead of
  both surviving (Greptile)

* fix(workflow-edges): normalize empty-string handles in duplicate-edge check

filterUniqueWorkflowEdges compared handles with ??, so a `sourceHandle: ''`
edge wasn't recognized as a duplicate of an existing null-handle edge —
even though both get persisted as the same null value at insert time
(edge.sourceHandle || null). Falsy-coalesce in the comparison so '' and
null/undefined are treated as the same "no handle" state everywhere.
(Greptile)

* improvement(workflow-edges): dedup realtime edge-add validation, reuse block-name-conflict helper

/simplify pass on the already-merged-quality PR before sign-off:

- Extract filterEdgesForPersist in apps/realtime/src/database/operations.ts:
  the single-edge ADD and batch BATCH_ADD_EDGES handlers hand-inlined the
  same six-step validation pipeline (missing block, protected target,
  annotation-only, trigger-target, scope boundary, duplicate, cycle) and
  each independently re-fetched blocksById/existingEdgesForCycleCheck. Two
  copies of one rule in the same file was exactly the drift risk this PR
  otherwise closes across client/server. One shared helper now backs both.
  Net -63 lines despite the new shared function.
- Fix a real bug this surfaced: droppedCounts was keyed by the free-text,
  block-id-bearing scope-boundary message, so it could never aggregate
  across edges/runs. Now keyed by a stable 'scope boundary' reason.
- use-collaborative-workflow.ts's collaborativeUpdateBlockName still
  hand-rolled the empty/reserved/duplicate block-name-conflict checks this
  PR centralized as getWorkflowBlockNameConflict (already adopted by
  store.ts). Switched it to the shared helper, which also fixes a latent
  check-order mismatch between the two (this pre-check ran reserved before
  duplicate; the store's real gate — after this PR's own store.ts change —
  runs duplicate before reserved, so they could disagree on which toast a
  name that was both reserved and duplicate would surface).

* docs(workflow-edges): document the per-workflow write-serialization invariant

No behavior change. Address Greptile P1 on filterEdgesForPersist ('concurrent
duplicate writes can persist without a per-workflow write guard') by
documenting, at the actual mechanism, why the concern doesn't apply here:
persistWorkflowOperation's leading 'UPDATE workflow SET updatedAt ... WHERE
id = workflowId' already takes a row lock that serializes every operation
(including edge adds) for a given workflowId — a second concurrent call
blocks on that UPDATE until the first transaction commits or rolls back, so
the validate-then-insert sequence in filterEdgesForPersist can never
interleave across two writers on the same workflow.

Verified empirically, not just by reading: ran two concurrent transactions
against a throwaway local Postgres against the exact statement shape used
here (UPDATE the parent row, sleep to simulate the read/validate window,
insert, commit). The second transaction's UPDATE blocked for the full
duration of the first's transaction and only proceeded once the first
committed — confirming the row lock, not any application-level guard,
already provides the serialization Greptile flagged as missing.

Added a comment at the lock site (not a second, redundant advisory lock)
so a future change can't silently break this invariant by making the
UPDATE conditional/skippable as a perceived no-op optimization.

* fix(workflow-edges): validate edges in BATCH_ADD_BLOCKS before persisting

Real gap Cursor's PR summary flagged ('BATCH_ADD_BLOCKS edge inserts... not
fully covered by the new server pipeline'), confirmed by reading the code:
this handler bulk-inserted the edges from a block-paste/duplicate/import
payload directly into workflowEdges with zero validation — no missing-block,
protected-target, annotation-only, trigger-target, scope-boundary,
duplicate, or cycle check. A client sending edges through this operation
instead of BATCH_ADD_EDGES could bypass every rule this PR otherwise
enforces server-side, exactly the class of gap the PR exists to close.

Routes it through the same filterEdgesForPersist used by the other two
edge-add handlers. Runs after the block insert in this same handler, so the
shared helper's blocksById lookup also sees the blocks this same batch just
inserted (a transaction observes its own prior writes).
… link color under bold/italic/strikethrough/code (#5573)

* fix(rich-markdown-editor): stop drag/paste of an existing image from re-uploading a duplicate

Dragging an image block to reorder it, or copy-pasting an already-hosted image within the
editor, both re-uploaded the image as a brand-new file instead of reusing/moving the existing
one:

- Dragging an <img> to reorder it is a native HTML5 drag; browsers synthesize an image File
  into event.dataTransfer for it (the same mechanism that lets you drag a web image to your
  desktop), indistinguishable from a real external drop by dataTransfer contents alone. Our
  handleDrop treated that File as a genuinely new image, uploaded it, and inserted a duplicate
  node — while ProseMirror's own default move logic never got to run, so the original was left
  behind too. Fixed by checking `view.dragging` (ProseMirror's own signal that a drop follows a
  dragstart within this same view) and bailing out to let its default move logic run.

- The same browser behavior applies to copy-paste: selecting a rendered <img> already on the
  page and pressing Cmd+C puts BOTH `text/html` (the real node, with its real hosted src) AND a
  synthesized image File onto the clipboard. Our handlePaste preferred the File, re-uploading and
  inserting a new node rather than cloning the original (silently dropping width/href/title in
  the process). Fixed by preferring the HTML sibling — via the existing extractEmbeddedFileRef
  helper — whenever it already names one of our own hosted files.

* fix(rich-markdown-editor): fix link color lost under bold/italic/strikethrough/code

strong/em/del/s/code each set their own explicit `color` for the plain (no-link) case. Nested
inside a link, that explicit rule on the mark itself always wins over the color inherited from
the ancestor <a> — an inherited value never beats an element's own explicit rule, regardless of
how specific the ancestor's selector is. So an italic (or bold/struck-through/inline-code) link
rendered in the mark's plain-text color instead of the link's blue.

Adds an explicit `.rich-markdown-prose a <mark>` / `.rich-markdown-prose <mark> a` override,
covering both DOM nesting orders since ProseMirror's mark order (and so which nests outside the
other) depends on which was toggled first, not a fixed schema order. Only `color` is touched —
each mark's own font-weight/font-style/text-decoration/background composes normally underneath.

24 new tests load the real, shipped CSS into jsdom and assert against getComputedStyle for every
mark x both nesting directions x multi-mark stacks, plus regression guards that a mark with no
link keeps its own color and a link elsewhere in the doc doesn't bleed color into unrelated text.
Verified all 11 color-assertion tests fail against the pre-fix CSS.

* fix(rich-markdown-editor): fix real-world gaps in the image dupe-upload fix

Found while re-verifying the drag/paste dupe-upload fix against the actual rendered DOM before
trusting it:

- hasHostedImageHtml's predicate only recognized the *persisted* src shape
  (extractEmbeddedFileRef, e.g. /api/files/view/...). The DOM (and so a same-page copy's
  clipboard html) always contains resolveImageSrc's REWRITTEN inline-route URL instead
  (/api/workspaces/{id}/files/inline?key=.../?fileId=..., or the public-share equivalent) — a shape
  the fix never recognized, so it silently never engaged for a real browser copy. Added
  isInlineRouteSrc to also recognize it, verified end-to-end against the real resolveImageSrc
  output (not a hand-typed guess).
- The img-src regex only matched quoted attribute values; an unquoted src (valid HTML) fell
  through to the old re-upload path instead of being recognized as hosted.
- The paste bypass fired on ANY hosted image found in the html, even when the clipboard also
  offered additional image files — a genuinely mixed paste (the hosted image plus a separate new
  one) would have the new file silently dropped instead of uploaded. Narrowed to only bypass when
  exactly one image file is offered.
- The drop bypass checked view.dragging unconditionally, including for the plain-file swallow
  branch below it — a stale view.dragging (ProseMirror clears it up to ~50ms late via dragend when
  a prior internal drag was dropped outside the view) could suppress swallowing an unrelated
  non-image file drop (e.g. a PDF) in that window, letting it fall through to the browser default.
  Gated on images.length > 0 so staleness can only ever affect the image-specific path it exists
  for.

Extracted the paste/drop bypass decisions into shouldSkipPasteUpload/shouldSkipDropUpload so
they're unit-testable without mounting the full editor component.

* fix(rich-markdown-editor): fix the entire class of mark-color-vs-ambient-color bugs, not just links

Auditing every explicit `color` in this file for the same failure mode (an element's own explicit
color always wins over an inherited one, regardless of ancestor specificity) surfaced a second,
previously-unfixed instance: bold/italic text inside an h6 heading showed the brighter
--text-primary instead of h6's own intentionally dimmer --text-secondary, since strong/em hardcoded
--text-primary as their default.

strong/em's color was always redundant with the prose root's own default anyway — removing it
entirely (matching the highlight/`mark` rule's existing `color: inherit` convention in this same
file) lets normal CSS inheritance carry the correct color through from ANY ambient context, not
just links: a link's blue, h6's dimmer tone, or any future colored container this file doesn't
know about yet. `code` has the same redundant color, also removed.

del/s genuinely need their own dimmer default (distinct from the prose default), so they keep an
explicit color plus the link-color override — now the only mark that needs one, since strong/em/
code no longer set a competing color to override in the first place.

10 new tests cover every heading level x strong/em/code/del/s x link, including 2 that fail against
the pre-fix CSS (bold/italic and inline-code inside h6 both incorrectly showed --text-primary).

* fix(rich-markdown-editor): stop paste-clone from persisting the display-layer image URL

Cursor caught a real correctness bug in the paste/drop dupe-upload fix: bypassing to the editor's
DEFAULT html-based paste for an already-hosted image made it re-parse the clipboard html's <img
src>, which is resolveImageSrc's REWRITTEN *display* URL, not the real persisted one — baking that
display-only URL into the document. Public share, export, and referenced-by-doc tracking only
recognize the persisted shape, so the pasted image would silently vanish from all three.

Fixed by no longer letting default paste construct the node at all: findHostedImageAttrs walks the
CURRENT doc for an existing image node whose *resolved* src matches the clipboard html's, and
returns that node's real, persisted attrs (src, width, href, title — everything) to clone
ourselves. Falls through to a normal upload (always correct, just occasionally redundant) if no
match is found, rather than ever trusting the html's src directly.

Also merged the paste- and drop-specific skip checks into one shouldSkipFileUpload, and switched
the drop side off `view.dragging` entirely (Greptile: it can go briefly stale, up to ~50ms, when a
prior internal drag was dropped outside the view, which could suppress upload of an unrelated new
file dropped in that window) — now purely a function of what the current event's html/images
actually contain, which the drop's own default move logic (relocating the real node, never
re-parsing html) was never at risk from in the first place.
Wires all 22 context_dev tools into the same hosted-key mechanism as
Exa: CONTEXT_DEV_API_KEY_COUNT/1..N rotation, BYOK provider
registration, and hideWhenHosted on the block's API key field. Cost
is read directly from each response's reported credits_consumed
rather than estimated per endpoint.
…eature graphics (#5535)

* feat(landing): add enterprise link to navbar and footer

* feat(landing): redesign enterprise page with platform-loop hero and feature graphics

- New enterprise hero with animated platform loop (sidebar + home stage) and hero background
- Nine redesigned feature tiles under enterprise/components/feature-graphics with a shared monochrome design vocabulary, per-tile tones, and CSS-module animations
- Shared hero-header component and landing-layout constants; hero/platform/solutions pages aligned to the same layout system

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

* chore(dev): allow 127.0.0.1 dev origins and skip root redirect in dev

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

* chore(skills): install make-interfaces-feel-better skill

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

* cleanup(landing): enterprise redesign polish + asset compression

- Fix ChipLink chrome overrides in navbar/mobile-nav to use variant='border'
- Dedup elapsed-time reveal effects in enterprise-home-stage into one hook
- Remove unused FeatureGraphicNode component and barrel export
- Convert enterprise-hero-background.png (8.9MB lossless) to WebP q90 (1.07MB, near-lossless)
- Fix team-avatar-1/2/3.png mislabeling (actual JPEG bytes) to correct .jpg extension

* fix(landing): keep feature-tile graphics uncropped at small breakpoints

Feature tiles shrank their min-height on small screens while the tallest
vignettes (audit ledger, staging panel) still needed ~300px of visual slot,
cropping their tops against the slot's overflow-hidden. Tiles now hold one
440px min-height everywhere. The 3-up grid also collapses to two columns
below lg instead of md, and the fixed-width canvases (access graph,
standards seal, ops router) scale down on the narrow two-column band and
the 3-up row just past lg so outer labels and chips are never cut off.

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

* fix(landing): regroup enterprise feature cards into 4/4/2/2 at two-column band

Merge the four enterprise feature sections into one EnterpriseFeatureGrid so
the 640-1023px two-column layout fills 4/4/2/2 with no orphan empty cell;
lg+ and <sm layouts are unchanged via sm:max-lg order utilities.

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

---------

Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…), redesigned trigger (#5323)

* feat(slack): enable assistant-agent tools via assistant:write scope

Add assistant:write, app_mentions:read, and im:history to the Slack bot
OAuth scopes so the Set Assistant Status / Title / Suggested Prompts tools
(assistant.threads.*) work with users' existing Slack credentials — no new
app or credentials required. Restore the action_assistant trigger capability
(scope assistant:write) in the manifest generator.

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

* Add slack trigger

* fix channel picker in slack trigger

* improvement(slack-trigger): reorder app type, gate account to sim mode, add channel-id input

* fix(slack-trigger): drop unmapped events from filter, resolve oauth token for reaction text + file downloads

* fix(slack-trigger): empty operation selection fires nothing; resolve token via credential owner not execution actor

* fix(slack-trigger): ignore message edit/delete/system subtypes; prefer channel picker over stale manual ids

* feat(slack-trigger): single-event model with contextual filters and full event catalog

* fix(slack-trigger): apply event/channel/bot filters on custom-app path too

* fix(slack-trigger): don't drop edit/delete events when channel_type is absent

* feat(slack): reusable custom bot credentials, slack_v2 block, interactivity triggers

- Custom bot as a workspace service-account credential (set up once, shared
  ingest URL /api/webhooks/slack/custom/{credentialId}, reused across triggers
  and actions)
- slack_v2 action block: credential-based Custom Bot auth alongside Sim OAuth;
  v1 hidden from toolbar
- Interaction triggers (block_actions / view_submission) with optional
  action/callback id filter; settings.interactivity in generated manifests
- Setup wizard: name + description, full permissions by default with
  ChipDropdown customization; reconnect mode rotates secrets in place
- Centralized service-account token resolution (unknown provider fails loudly)
- Shared Slack webhook fan-out dispatcher for native + custom ingest routes

* chore(api-validation): bump route baseline to 924 after staging merge

* feat(slack): preview-gate slack_v2 and the custom-bot credential surfaces

slack_v2 (block + hosted slack_oauth trigger) ships preview: true — hidden
from all discovery until revealed via block-visibility AppConfig or
PREVIEW_BLOCKS. v1 stays toolbar-visible with the legacy slack_webhook
trigger until v2 GAs. The integrations-page custom-bot setup surface rides
the same flag via isHiddenUnder(slack_v2); placed instances, existing
credentials, and ingest/execution paths are never gated.

* fix(slack): v1 keeps slack_webhook trigger subblocks; handle object-form event channels

- v1 spread had been swapped to slack_oauth's trigger subblocks (shared with
  v2), leaving its slack_webhook deploy path without signing-secret config
  (Bugbot high). v1 now carries the legacy trigger set again; v2 swaps them
  for slack_oauth's.
- resolveSlackEventChannel reads channel.id for channel_created/channel_rename
  payloads, so channel filters no longer drop every rename event.

* fix(slack): default absent appType to custom at deploy; deactivate custom-bot webhooks on credential delete

- appType is hidden and seeded 'custom' by value(), which only covers
  editor-created blocks; defaultValue now persists it via buildProviderConfig
  and the deploy fallback flips to custom (the only exposed mode this ship)
- deleting a slack-custom-bot credential now also deactivates provider='slack'
  webhooks routed by that credential id, not just native slack_app rows

* fix(slack): resolve credential owner for deploy-time team_id lookup

A teammate deploying a trigger wired to a shared Slack credential isn't the
credential owner; refreshAccessTokenIfNeeded only loads tokens for the owning
user. Resolve the account owner first, mirroring the runtime formatInput path.

* chore(slack): reconcile staging merge

- nullable webhook.path coalesced at correlation/payload/tiktok boundaries
- slack dispatch delegates to staging's dispatchResolvedWebhookTarget
  (shared preprocess/deployment/filter/enqueue lifecycle), keeping the
  skip-reason diagnostics; route tests reworked around that seam
- api-validation route baseline 924 -> 926

* fix(slack): workspace-scope bot credentials at deploy; recreate webhooks on routing transitions

- a bot credential id is semi-public (embedded in Slack Request URLs), so the
  custom deploy branch now rejects credentials outside the workflow's workspace
- needsRecreation also compares path/routingKey, so a row from an older routing
  model can't survive redeploy as a stale delivery surface

* test(slack): pin fail-closed behavior for empty/missing event selection

* fix(slack): 409 on custom-bot name collision instead of silently returning the existing credential

The service-account dedupe matches on displayName, which defaults to the Slack
team name — shared by every bot in that workspace. A second unnamed bot create
returned the first credential as success, orphaning the new id already pasted
into the Slack Request URL. Same-id replays stay idempotent; different-id
collisions now fail loudly so the wizard prompts for a distinct name.

* fix(slack): reconnect surfaces Atlassian error codes and persists name/description edits

- PUT credential route now returns the Atlassian provider code (providerErrorCode
  -> code) so reconnect failures map to specific token/domain messages, matching create
- Google/Atlassian reconnect send + seed displayName/description (parity with Slack);
  edits are no longer silently discarded, and empty fields don't clobber existing values

* fix(slack): require bot name; propagate rotated bot_user_id to webhooks on reconnect

- the setup wizard now requires a bot name (canAdvance), so the credential name,
  manifest app name, and uniqueness key all use the user's choice instead of the
  shared Slack team-name fallback that collided for a second bot in one workspace
- reconnect that changes the bot user id (recreated Slack app) now updates the
  bot_user_id cached in each bound webhook's providerConfig, so reaction
  self-drop keeps working instead of letting the bot's own reactions re-enter

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit f4d47ed. Configure here.

Comment thread apps/realtime/src/database/operations.ts
…#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
…put required option (#5575)

* improvement(custom-blocks): usage visibility + type-to-confirm delete

* feat(custom-blocks): per-input required option

* improvement(custom-blocks): replace usage tab with delete-confirmation usage count

* fix(custom-blocks): escape LIKE wildcards in usage scan + fresh count on delete modal

* fix(custom-blocks): explicit ESCAPE clause on usage-scan LIKE prefilter
- add a Share chip (copy link / X / LinkedIn) to integration and
  model detail pages, matching the bordered secondary-pill chip
  already used for View docs / All {provider} models
- rework ShareButton to render as a Chip everywhere (blog, library,
  integrations, models) instead of a bespoke muted-text trigger, and
  switch its copy-link state to the shared useCopyToClipboard hook
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants