v0.7.29: GLM, ui and ux improvements#5567
Conversation
…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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview Realtime persistence now runs a shared Marketing / enterprise: shared Docs: manual intro sections on many integration MDX pages; Reviewed by Cursor Bugbot for commit 7962236. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThis PR updates model/provider support and cleans up several UI and landing paths. The main changes are:
Confidence Score: 4/5The provider and MCP argument paths need fixes before merging.
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
|
| 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
* 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>
…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>
…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>
There was a problem hiding this comment.
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).
❌ 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.
…#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
…eel (#5587) * feat(branding): sim wordmark favicon/OG, docs footer parity, footer peel - replace apps/sim favicon and default OG image with the sim wordmark logo (OG image widened, logo kept at native size) - swap the docs navbar logo to the icon-only mark (no wordmark text) - add a scroll "peel" reveal effect to the landing footer using a sticky-positioned illustration, pure CSS, no scroll listeners - port the same footer (link directory + peel effect) to the docs app so both apps are visually consistent; add Academy to Resources - rebuild the docs OG image template to match the site's existing blog/library cover style (wordmark top-left, arrow top-right, title bottom-left), working around a Satori text-measurement bug that doubled the gap after certain words * fix(docs): correct OG font, mobile logo, and footer stacking - switch the docs OG image title font from Geist to the site's real brand font (Season Sans), instantiated as a static TTF weight since Satori can't parse WOFF2 or variable fonts; served from /static/ so the i18n proxy's matcher (which excludes static but not fonts) doesn't intercept it - fix DocsLayout's nav.title (fumadocs' own mobile menu slot) to show the wordmark instead of the icon mark - add an isolated stacking context + higher z-index to both the docs and sim app footers so fumadocs' sticky z-20 sidebar can't paint over the footer content or the peel reveal * fix(docs): match OG template exactly, fix gradient/origin bugs - recalibrate the OG image to the reference cover template's actual measured values: 1200x675 canvas (was 630), ~26px margins (was 56-64px), ink #525252 (was #3f3f3f), larger wordmark/arrow/title sizing — confirmed by direct pixel measurement of the reference cover.jpg, not estimation - fix SimLogoIcon/SimLogoFull's SVG gradient ids to be unique via useId() instead of a fixed string, so multiple instances on one page don't collide (Greptile P2) - fix SIM_SITE_URL to be a hardcoded sim.ai constant instead of deriving from NEXT_PUBLIC_APP_URL, which reflects wherever this deployment runs, not the fixed public marketing site (Greptile P1) * fix(docs): route Jira footer link to the docs guide, not sim.ai Every other integration in the footer's Integrations column links to its own docs.sim.ai guide; Jira was the only one pointing at the marketing site's landing page instead, despite docs having its own /integrations/jira guide. Matches the established pattern. * fix(docs): fix sidebar-divider grid regression, footer-peel path/positioning, OG sizing, and prune stray comments - #nd-docs-layout::before divider now spans the full grid explicitly (grid-row/grid-column: 1 / -1) instead of being auto-placed into a real content cell, which was pushing page content down - footer-peel.jpg moved under /static/landing/ (was 404ing behind the i18n proxy's non-static path matcher) and wrapped in a relative div so next/image's fill positioning is valid under the sticky container - OG route: corrected title font sizes and char-width ratio so long titles wrap to 2 lines instead of 3, and resized the corner arrow to match the reference cover template's proportions - swapped the icon-only desktop navbar logo back to the wordmark - removed stray non-TSDoc comments, folded into TSDoc where the explanation was worth keeping * fix(footer): remove sticky peel reveal, keep clean footer link directory The peel's "reveal window" relied on position: sticky bottom-detaching into a containing block whose extra height came from padding-bottom — that combination doesn't reliably work in WebKit/Safari (sticky never gets room to engage when the surplus height is padding rather than an explicit height or content), so the peel stayed permanently covered by the footer regardless of viewport size. Rather than carry that unreliable technique further, removing it entirely from both apps and reverting to the plain footer link directory.

Uh oh!
There was an error while loading. Please reload this page.