mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-19 09:30:49 +08:00
23bc210bf853d6300b345f30fbd5baaae1dc8df1
179
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
66ac015c4c |
fix(library): generate every post cover from one template (#5980)
* fix(library): generate every post cover from one template Three posts shipped an `ogImage` pointing at a file that was never committed, so the library index rendered broken images and their `og:image`, JSON-LD, and sitemap entries all 404'd. Several others were authored without the brand font loaded or with the title clipping off the bottom edge. Covers were hand-made per post with no generator, which is why they drifted. Adds `bun run library:covers`, rendering each cover from the post's frontmatter title using the reference template already encoded in the docs OG route, and regenerates all 20 so the grid is uniform. Line widths come from the font's real advance metrics rather than an average-glyph-width estimate: the template joins words with non-breaking spaces to dodge a Satori space-measurement bug, which leaves hyphens as the only fallback break points, so an under-measured line breaks mid-compound. Also drops six orphaned `cover.png` sources left over from the JPEG compression pass in #5528. * fix(library): re-render covers every run and add a sync check Covers are derived artifacts, so skipping outputs that already exist left an image showing the old title after a post's frontmatter `title` changed. Every run now re-renders from scratch; rendering is deterministic, so an unchanged title re-encodes to identical bytes and a full run stays a no-op in git. Replaces `--force` (now the default) with `--check`, which renders in memory and compares against the committed bytes without writing, so CI can catch both a stale cover and the missing-cover case that caused the original breakage. * fix(library): compare decoded pixels in the cover sync check Byte-equality on the mozjpeg output assumed portable encoder bytes. libvips/mozjpeg does not guarantee that across OS and CPU, so identical input can encode differently on a contributor's machine or a Linux CI runner and fail the check for no real reason — exactly where the check was meant to run. Decodes both images to greyscale and compares mean absolute difference instead, which discards encoder variance while still testing what the check is about. Measured on this cover set: re-encoding an identical render with a deliberately different encoder moves it ~0.26, a one-word title change moves it ~12; the threshold of 2 sits between them with ~8x margin. * fix(library): count redrawn pixels in the cover sync check Averaging the difference diluted a local edit across all 810,000 pixels. Changing a title's "2026" to "2027" moved the mean by 0.42 — under the tolerance that absorbed encoder noise — so the check passed a cover still showing the old year. Counts pixels that moved more than 48 greyscale levels instead. Measured on this cover set, that one-character edit redraws 2,559 pixels while three deliberately different encodes of an identical render (quality 60/70 without mozjpeg, quality 95 with) redraw none, so the count separates real drift from encoder variance in both directions. * fix(library): parse frontmatter with gray-matter and split oversized tokens Two issues in the cover generator, neither reachable from a current title. The hand-rolled frontmatter regex could disagree with `gray-matter`, which is what renders the page and its `og:title`. On a double-quoted escape or a block scalar the cover would have rendered a title the page never shows, with `--check` calling it in sync. Uses `gray-matter` directly so there is one parser. `wrapTitleLines` only breaks between space-separated words, so a token wider than the title box on its own stayed on an overflowing line, and the non-breaking spaces left Satori no recourse but to break it at a hyphen — the mid-compound break this layout exists to prevent. Oversized tokens now split here, at hyphens first and per-character only for something like a URL, and a font size is accepted only if every line measures within the box. All 20 covers re-render byte-identically, so neither change alters current output. |
||
|
|
6d48444525 |
fix(docs): render native block icons instead of the two-letter fallback (#5981)
* fix(docs): render native block icons instead of the two-letter fallback
The Table and Logs pages (and every other native resource block) showed a
two-letter text fallback because the generated icon map never contained them.
Four separate causes in scripts/generate-docs.ts:
- The icon-map allowlist had drifted behind NATIVE_RESOURCE_BLOCK_TYPES, the
set the docs writer uses. Key the exception off that set so the map cannot
fall behind the pages that consume it.
- extractIconNameFromContent only matched identifiers ending in `Icon`, so
Logs (`icon: Library`) resolved to nothing. Match any identifier, excluding
bare JS literals.
- The map imported everything from `@/components/icons`, so an icon sourced
from `@sim/emcn/icons` could not resolve. Imports are now grouped by the
module each icon is actually imported from.
- Trigger-only pages (slack_app, twilio) and hand-written pages (a2a) had no
entry at all. Seed provider icons from the trigger definitions.
Also fixes three regen bugs found while verifying the output:
- A comment reading "this becomes `hideFromToolbar: true`" in slack.ts was
matched as the property itself, so a clean regen dropped Slack from the
integrations catalog and reduced slack.mdx to a 29-line stub. Property
probes now run against comment-stripped source.
- 16 hand-written *-service-account guides were unregistered, so the stale-doc
cleanup deleted them on every regen. Registered them, and cleanup now refuses
to delete any page holding MANUAL-CONTENT (this also restores the intros on
file.mdx and twilio.mdx).
- Trigger outputs referenced as a constant (`outputs: SLACK_TRIGGER_OUTPUTS`)
resolved to nothing, dropping whole Output tables. Constants and sibling
modules now resolve, which also restores 319 lines on clickup.mdx.
Removes the language selector from the docs navbar.
Regenerated docs are included; remaining content deltas are tool-definition
drift since the last regen.
* improvement(docs): drop the preview-gated slack_app page, document managed_agent
- slack_oauth is reachable only through the preview-gated slack_v2 block, so
documenting it published an unreleased surface under its own slack_app page.
Triggers whose every hosting block sets `preview: true` are now excluded from
the docs and the icon map. Triggers no block claims are untouched, so
standalone webhook providers keep their pages.
- Adds the MANUAL-CONTENT intro to managed_agent.mdx, matching the other
integration pages. Verified it survives a regen.
* fix(docs): stop truncating quoted descriptions, tighten the cleanup guard
Review findings from round 1.
- parseSubBlockObject read string properties with a single `['"]…[^'"]+…['"]`
character class, which ends the match at the first quote of either kind. Any
description holding an apostrophe inside a double-quoted string was cut
mid-word ("Your app", "Found in your Zoom app"). Matches the opening quote to
its own closing quote now, reusing the alternation the tool-description
extractor already used. Restores full text across calendly, gmail,
google_sheets, hubspot, intercom, whatsapp, and zoom.
- The stale-doc cleanup guard tested for a bare `MANUAL-CONTENT-START`
substring, so a stray or unterminated marker would pin a stale page that has
nothing recoverable. It now gates on what extractManualContent actually
returns.
|
||
|
|
bd61603701 |
feat(tiktok): unhide integration (#5978)
* feat: unhide TikTok integration * test: remove TikTok visibility assertion --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local> |
||
|
|
1a4bfe4c59 |
fix(setup,compose): bundle Redis, fix socket reconnect, and harden the setup wizard (#5964)
* feat(compose,setup): bundle Redis, always configure it, fix lifecycle detection
Compose shipped no redis service at all — REDIS_URL was ${REDIS_URL:-} in both app and realtime, so every self-hosted stack ran without it. Storage silently falls back to PostgreSQL, but the pub/sub channels (live Chat task-status, table events) have no fallback, so live updates never arrived.
- compose (prod + local): add a redis:7-alpine service with a healthcheck, default REDIS_URL to redis://redis:6379, and make app/realtime depend on it being healthy. Not published to the host — only the containers need it, and binding 6379 would collide with a local Redis. An external REDIS_URL in root .env still overrides. Deliberately not written into root .env: doctor pings REDIS_URL from the host, and a compose-internal hostname would fail that probe the same way DATABASE_URL would.
- dev mode: configure Redis in quick too. Quick uses a new non-interactive ensureRedis (adopt whatever answers, else start the managed container, warn only if Docker is unavailable); custom keeps the ladder, with corrected copy — the old prompt claimed Redis was only for multi-replica.
- lifecycle: detect compose stacks via 'docker compose ls' instead of probing '-f <file> ps' in the working directory. Compose derives the project name from the directory it was started in, so the old probe found a stack only when run from the checkout that launched it (a globally linked sim never could) and listed the same stack once per candidate file. compose ls reports the real project and its config file, so one stack yields one install from anywhere; non-Sim projects are filtered by compose filename. Every compose op now runs in that stack's directory.
- lifecycle: distinguish 'Docker unreachable' from 'nothing installed'. With the daemon down, status reported containers as 'absent' and suggested re-running setup; it now says Docker is down and marks state unknown.
* fix(setup): don't start managed Postgres with a password the volume will ignore
POSTGRES_PASSWORD only applies when initdb runs on an empty data directory. The sim-postgres-data volume outlives its container (sim down keeps it, docker rm keeps it, and the wizard's own recreate path keeps it), and inspectManagedContainer recovers the password from the *container*, not the volume — so once the container is gone the password is unrecoverable.
Setup then generated a fresh password and ran against the initialized volume. Postgres kept its original password and rejected every connection with 'password authentication failed for user postgres', which surfaced as a misleading 'container did not become healthy'.
Detect an already-bootstrapped volume (PG_VERSION present) before choosing a password, and ask: supply the existing password, or delete the volume and start fresh (double-confirmed, since that destroys data). Refusing both fails with the exact docker volume rm command instead of looping.
* improvement(setup): default to Docker Compose and sharpen the run-mode copy
Compose was listed first but only preselected when Docker happened to be running — with Docker stopped the cursor sat on 'Local dev', steering people toward a source checkout when they wanted to run Sim. Compose mode calls ensureDocker(true), which offers to start Docker Desktop, so a stopped daemon is no reason to change the default.
Also tightens the hints to say what each mode is for: run bundled Sim (fastest way to start), work on Sim itself, test a production-style k8s deploy.
* fix(compose): point the browser socket at :3002 so it stops reconnecting
The stack publishes the app on 3000 and realtime on 3002 with no reverse proxy between them, but NEXT_PUBLIC_SOCKET_URL defaulted to empty — which tells the browser client to use the page origin. :3000/socket.io answers 308 (a Next redirect), not a Socket.IO handshake, so the client failed and retried forever. Default it to http://localhost:3002; a proxied deployment overrides it (or sets it empty to use the page origin).
Also give COPILOT_API_KEY and SIM_AGENT_API_URL empty defaults so every compose command stops printing 'variable is not set' warnings. The app already falls back to the prod copilot backend when SIM_AGENT_API_URL is blank.
* feat(setup): pass SIM_AGENT_API_URL through, and warn on a half-set mothership
Sim devs testing against a non-prod mothership export SIM_CLI_AUTH_ORIGIN so the Chat key is minted there, but nothing carried the matching backend URL into the install — the app kept defaulting to prod copilot, which rejects a staging key with 'Invalid API key'.
Persist SIM_AGENT_API_URL when it is exported, so later docker compose up / dev runs stay on that backend instead of reverting to prod once the shell is gone:
SIM_CLI_AUTH_ORIGIN=https://www.staging.sim.ai \
SIM_AGENT_API_URL=https://www.staging.copilot.sim.ai \
bun run setup
Setting only the auth origin is the trap, so that combination warns. Neither set is the self-hoster default and stays silent — no prompts, no flags.
* fix(setup): survive a vanished port owner, and stop flagging our own containers
Two failures from one compose re-run:
- 'Kill it for me' crashed setup with 'kill() failed: ESRCH: No such process'. The owner list is an lsof snapshot, so the process can exit before the signal lands — which is the outcome we wanted, not an error. ESRCH now counts as freed, EPERM warns that it must be stopped by hand, and anything else warns; the loop re-probes either way instead of aborting a setup that had already written .env.
- Compose mode demanded 3000/3002 be free even when this stack was the one holding them, so re-running setup against a running install reported its own realtime container as a blocker and offered to kill Docker's listener. 'docker compose up -d' reconciles its own containers, so skip the check when the project already has some. A foreign process is still caught, and a foreign container still surfaces as a bind error from compose.
* fix(csp,setup): permit the socket origin the client actually uses; encode DSN passwords
Review round on #5964:
- The socket reconnect was a CSP bug, not a URL bug. getSocketUrl() already falls back to localhost:3002 for a localhost page, but generateRuntimeCSP gated that same fallback on isDev — and compose runs NODE_ENV=production, so connect-src omitted ws://localhost:3002 and the browser blocked the handshake. Key the fallback on the app URL being localhost instead, mirroring getSocketUrl. Revert the compose NEXT_PUBLIC_SOCKET_URL default: an explicit value suppresses the page-origin fallback that reverse-proxied self-hosts depend on, and ':-' treats empty as unset so the documented escape hatch could not work either. LOCALHOST_HOSTNAMES is duplicated locally because csp.ts is loaded by next.config.ts before @/ aliases resolve.
- Percent-encode the password when building the Postgres DSN. A user-supplied password containing @ : / # does not merely re-parse to the wrong host — it fails to parse as a URL at all, so a correct password surfaced as a connection failure.
- Tell 'Postgres rejected this password' apart from 'Postgres never started'. On the keep-the-volume path a wrong password left a healthy server and the old generic 'container did not become healthy' error, which is the confusion this change set exists to remove.
Adds a CSP regression test for the unset-socket-URL production case; verified it fails against the previous condition.
* improvement(setup): make k8s mode end somewhere usable, and show install progress
Two things made k8s mode the least satisfying path.
The services are ClusterIP, so a successful install left nothing on :3000 — 'Sim is ready' was true about the cluster and useless to the user, who had to notice and run a port-forward by hand. Compose opens a browser and dev offers to start the server; k8s now offers the forward the same way and runs it in the foreground so Ctrl-C ends it. Realtime gets its own forward (kubectl takes one resource per invocation) or the editor socket fails; it is a child in the same process group, so the terminal's Ctrl-C reaches it, and it is killed explicitly when the app forward exits.
'helm --wait' then blocked for minutes with a single static spinner, so a slow image pull looked identical to a wedged install. Run helm asynchronously and poll the cluster, so the spinner reports '3/3 pods ready · 1 starting'. CronJob-owned pods are excluded: the chart schedules a lot of them (36 on a running cluster here) and they finish as Completed, which would swamp the count and make readiness jitter for reasons unrelated to the install. Restarting pods are surfaced too — a cold cluster restarts realtime while Postgres comes up, and a silent spinner made that look like nothing was happening.
* fix(setup): identify Sim compose projects by content, not filename
Cursor (High): composeInstalls treated any project whose config basename was docker-compose.prod.yml or docker-compose.local.yml as a Sim install. Those names are common, and sim reset runs 'compose down -v' — so a stranger's stack could have had its volumes destroyed.
I introduced that reach. The previous ROOT-scoped '-f' probe was implicitly safe because it could only ever see the project in this checkout; switching to a global 'compose ls' to find stacks started elsewhere means projects must be identified by content instead. Read the config file Docker recorded and require a Sim marker (the published app image, or the app Dockerfile this repo builds), so both the prod and local variants match while an unrelated file with the same name does not. An unreadable or since-deleted file is left unmanaged rather than assumed ours.
Verified against a decoy nginx compose file using our exact filename: ignored, while both real Sim compose files still match.
* fix(setup): scope the compose port skip to published ports; print both k8s forwards
Review round on #5964:
- ensureComposePortsFree skipped conflict handling whenever the project had any container running, so leftover db/redis (which publish neither app port) waved through a foreign process on :3000 — it then surfaced as a raw compose bind error instead of the prompt. Read the host ports the project actually publishes and skip only those; the remaining ports still get the full check. Reading from the containers rather than the file matters because what counts is what is bound right now.
- The post-install note and the skip path documented only the app forward, while offerPortForward runs two. Skipping the prompt or copying the printed command left the editor's socket dead — the exact failure this change set exists to fix. Both commands now come from one forwardCommands() helper, so what is printed and what is run cannot drift.
* fix(setup): one source for the k8s forwards, and surface a dead realtime forward
Third round on the same theme, so fix it at the root rather than at another call site.
- lifecycle's k8sReachHints (used by sim start/restart) still restated an app-only forward, recreating the dead editor socket the setup path had just been fixed for. forwardCommands is now exported and consumed there, so every place that tells a user how to reach a ClusterIP release derives it from one definition.
- The realtime forward was spawned with stdio ignored and never checked, so a busy :3002 or a missing service killed it silently while the app forward kept running — indistinguishable from success until the editor won't connect. Keep its stderr, warn on an exit we did not ask for, and stay quiet on the intentional kill.
* fix(setup): pin the compose project on every lifecycle op
composeInstalls records the real project name from 'compose ls' and status and the destructive confirms print it, but every op ran 'compose -f <file>' with only cwd set — so Compose re-derived the project from that directory. The derived name is frequently not the recorded one: a directory is lowercased and stripped of dots (Sim.Demo_Test derives simdemo_test), and an explicit -p or COMPOSE_PROJECT_NAME at creation diverges outright. stop/down/reset could therefore act on a different project than the one named in the confirm, and reset runs 'down -v'.
Route every op through composeArgs(), which pins '-p <recorded project>'. cwd stays, since the file's own relative paths still resolve against it. Verified with a stack started as -p pinned-name from a directory deriving simdemo_test: the old form found 0 of its containers, the pinned form finds them.
* fix(setup): warn on both halves of a mothership mismatch
mothershipOverride warned only when SIM_CLI_AUTH_ORIGIN was set without SIM_AGENT_API_URL, while its own copy said to set both or neither. The reverse is the same failure mirrored: with only SIM_AGENT_API_URL set, the Chat key is still minted against the default prod auth origin and then validated against the override, which rejects it — silently, which is exactly what this helper exists to prevent.
Warn on either asymmetry, and read the default origin from one constant shared with the handoff so the message can't claim an origin the code no longer uses.
* fix(setup): warn about a half-set mothership before minting the key
mothershipOverride ran two steps after promptCopilotKey, so a half-set override minted a key against one environment, stored it, and only then warned that the other environment would reject it. Worse on a re-run: promptCopilotKey offers to keep an existing COPILOT_API_KEY and defaults to yes, so the bad key survives.
Move the override ahead of the key prompt in both compose and dev, so the warning arrives while it can still change the outcome — the user can abort and set the missing half before anything is minted. Nothing in the override depends on the key, so the order is free.
|
||
|
|
19c3b6f47d |
feat(setup): setup wizard with browser-based Chat key handoff (#5911)
* feat(setup): setup wizard with browser-based Chat key handoff
Adds `bun run setup` and `bun run doctor` for local installs, and replaces
the wizard's paste-your-Chat-key step with a browser handoff that never puts
the key in a URL.
* improvement(setup): drop the paste-a-key fallback, simplify consent copy
The browser handoff is now the only path — the wizard waits on a spinner
instead of racing a paste prompt. Consent card leads with "Connect your
terminal" and moves the match-the-code disclaimer into the description.
* fix(setup): pin kube context, keep secrets out of argv, validate reused keys
Review findings from #5911:
- helm/kubectl now run against the validated context instead of the ambient one
- helm values are piped on stdin rather than passed as --set arguments
- ENCRYPTION_KEY/API_ENCRYPTION_KEY are checked for the 64-hex format the app
requires, not just length, so an unusable key is replaced rather than kept
- the managed Redis container's published port is read back instead of assumed
* refactor(copilot): one module for Chat API key operations
list/generate/delete each repeated the same /api/validate-key envelope in
their route. They now share callValidateKey in lib/copilot/server/api-keys.ts,
which also keeps the display masking server-side so the full key can only ever
leave at creation.
* improvement(setup): reuse shared helpers, parallelize probes, drop dead code
- PKCE verifier/state/pairing code now use generateSecureToken, generateRandomHex
and generateShortId instead of hand-rolled randomBytes; the pairing loop's
modulo was unbiased only because 256 % 32 == 0
- new sha256Base64Url in @sim/security/hash so both sides of the PKCE exchange
derive the challenge from one implementation
- isUsableSecret moved beside SECRET_KEYS so setup and doctor apply the same
rule; doctor previously passed a key setup would replace
- isTruthy narrowed to true/1, matching the app it claims to mirror — it accepted
yes/on, so a flag could read on in doctor and off in the app
- checkLive runs its five probes concurrently (~17s serial worst case)
- detection overlaps the banner animation instead of queueing behind it
- glyph.fail/glyph.warn at 13 sites that bypassed the constant; removed unused
prompter exports, a dead ENV_PATHS re-export, and an unused export keyword
* fix(setup): make doctor understand the compose env layout
Compose writes a single root .env (what docker-compose reads via env_file) but
the checks required the three per-app files, so a successful compose install was
followed by doctor printing three failures and exiting 1 — and the whole
coherence catalog was skipped because it keyed off apps/sim/.env existing.
Layout is now derived from what's on disk and every check consults it: file and
schema checks iterate the layout's targets, consistency reports skip when
there's only one file to mirror, and coherence/live read the layout's primary
file. The wizard's existing-config detection counts root for the same reason —
a compose install used to read as unconfigured and re-run from scratch.
* feat(cli-auth): device-authorization poll flow, drop the loopback listener
The CLI no longer binds a local port. It generates a request id + poll secret,
opens /cli/auth, and polls /api/cli/auth/poll over TLS while the user approves
in the browser — so the flow works over SSH and inside containers, where the
browser and terminal don't share a machine.
- approve stores the approval keyed by request id (session-authed, userId from
the session only); poll verifies the secret before an atomic claim, so an
observer of the semi-public request id can neither mint nor cancel it
- pairing code stays as the anti-phishing compare; no key ever crosses the
browser; done page just confirms
- removes the loopback listener, /token exchange, buildCliHandoffUrl, and
validateCliCallbackUrl (+ its tests) — nothing hands a key to a URL anymore
* fix(setup): reuse an existing managed Postgres container instead of colliding
A running sim-postgres fell through to `docker run --name sim-postgres` and died
on the name conflict; a stopped one failed with "no DATABASE_URL to reach it"
because the generated password only lived in the env files a fresh clone lacks.
Both facts are recoverable from Docker: the ladder now reads the published port
and password back via `docker inspect` and reuses the container (starting it if
stopped). A container that won't answer prompts before recreating, and never
drops the data volume silently.
* improvement(setup): audience-first run-mode hints
Each run mode now names who it's for — compose for self-hosting/evaluating, dev
for contributing to Sim, k8s for rehearsing a production deploy — with the live
detection state (Docker/kube/VM) appended.
* fix(cli-auth): retry a failed mint, port container/port fixes to Redis + k8s
Review findings from #5911:
- poll now reserves the mint with an atomic NX lock instead of deleting the
approval up front, so a failed mint (e.g. mothership blip) is retried by the
next poll instead of forcing a fresh browser approval; the lock still prevents
a double-mint and its TTL frees the slot if the caller dies
- setup reuses/recreates an unhealthy managed sim-redis instead of colliding on
the name (Redis has no data volume, so it removes and recreates without a prompt)
- k8s failure-path hints carry --context, matching the success-path hints, so a
changed ambient context can't send diagnostics to the wrong cluster
- compose port-free waits for a killed port to actually release before
re-checking; SIGKILL is async, so the immediate re-check re-saw the port
* fix(setup): harden mint cleanup, Windows browser, container detection, helm cwd
Review findings from #5911:
- a post-mint completeApproval failure no longer routes into releaseMint — the
mint lock now outlives the approval (shared TTL), so a cleanup blip can't leave
a re-mintable window and orphan a key; cleanup is best-effort after the key ships
- compose doctor --fix writes the feature-flag twin to the layout's primary env
(root .env on a compose install), not always apps/sim/.env
- Windows opens the browser via `cmd /c start "" <url>` — `start` is a shell
builtin, so spawning it directly ENOENT'd and the handoff never opened
- managed-container detection filters loosely and pins the exact name in code;
Docker's `name=^x$` anchor matches the internal `/x` form and often missed,
skipping the reuse branch
- the shared helm/kind run helper pins cwd to the repo root, matching helm test,
so `helm upgrade --install ./helm/sim` works from any working directory
* feat(chat-keys): standalone manage page, drop from settings nav, refresh README
- Add /account/settings/chat-keys — a linkable page to view, create, and revoke Chat API keys
- Remove Chat keys from the settings sidebar (account + unified nav) and its render branches
- README: replace Docker Compose + Manual Setup with the bun run setup wizard; drop the manual COPILOT_API_KEY step, point to the manage page
* fix(setup): per-key reason in the secret-replacement warning
Cursor: the warn hardcoded '64-character hex key', but only ENCRYPTION_KEY/API_ENCRYPTION_KEY require that — BETTER_AUTH_SECRET/INTERNAL_API_SECRET only need length >= 32. Use the existing secretRequirement(key) helper so each replaced key reports its actual requirement.
* fix(setup): compose doctor schema, cross-platform binary detection, quoted context hints
- Doctor: for the compose (root) env layout, require only the secrets compose has no interpolation default for (BETTER_AUTH_SECRET/ENCRYPTION_KEY/INTERNAL_API_SECRET). DATABASE_URL/BETTER_AUTH_URL/NEXT_PUBLIC_APP_URL come from docker-compose ${VAR:-default}, so a healthy compose install no longer fails doctor.
- Binary detection: use Bun.which instead of which (which is absent on Windows), so kubectl/helm/kind/docker resolve cross-platform.
- k8s diagnostic hints: POSIX-quote the kube-context so a context with whitespace/metacharacters can't break or inject into a copied command.
* fix(setup): quote kube-context in the helm uninstall tear-down hint too
The tear-down hint used --kube-context ${context} raw while the sibling kubectl hints already used shq(); a context with whitespace/metacharacters could break or inject into the copied command. All copyable k8s hints now go through shq(context).
* feat(setup): sim lifecycle CLI — start/stop/status/logs/down/reset
Turn the setup entry into a 'sim' command umbrella so there's one place to run everything, not scattered docker/bun commands. Adds a global bin (bun link) + a bun run sim fallback.
- Detects how you're running (compose file / managed dev containers / helm release) from disk + docker/helm state — no persisted mode. Ambiguous installs prompt.
- start/stop/restart/logs work per mode; down removes containers (volumes kept); reset archives .env + wipes managed data; both destructive verbs confirm first.
- status shows detected mode, container states, and app/realtime health.
- Wizard outro + README now point at the sim commands and the one-time bun link.
* feat(setup): 'bun run sim' is the primary entry; bare invocation prints help
- Lead usage/wizard-outro/README with 'bun run sim <cmd>' (works with zero PATH setup); global bare 'sim' via bun link is an optional upgrade, with the ~/.bun/bin PATH caveat spelled out (Homebrew's bun omits it).
- Bare 'sim' now prints help instead of launching the wizard; the wizard is 'sim setup'. The 'setup' npm script passes the keyword so 'bun run setup' is unchanged.
* fix(setup): quote the auth URL for cmd /c start on Windows
Cursor (High): cmd re-parses the command line and treats & in the query string as a command separator, so cmd /c start opened a URL truncated at the first &, breaking the key flow on win32 (the handoff URL always has request/challenge/pairing). Quote the URL and pass args verbatim so & stays literal.
* fix(setup): verify kube-context is really local; lengthen CLI handoff wait
- k8s: a context named like a local cluster (kind-*, docker-desktop) can actually point at a remote API server. Verify the server host is loopback/docker-internal before defaulting the 'use this context?' confirm to yes; otherwise warn and default to no, so generated secrets can't ship to a remote cluster on a blind Enter.
- cli-auth: bump the device-flow wait from 3 to 15 minutes so first-time users have time to sign up, wait for the email OTP, and approve before the terminal stops polling. The server-side approval record keeps its own short TTL, so a longer client wait only costs cheap rate-limited polls.
* fix(setup): only manage k8s lifecycle on a verified-local context
Greptile: sim down/reset used the ambient kube-context, so switching context after setup could uninstall a same-named sim-dev release from the wrong cluster. Gate k8sInstall on the same locality check the wizard uses (API server is loopback/docker-internal) via a shared isLocalKubeContext helper — the wizard only ever deploys locally, so a remote current-context is never treated as a Sim install.
* fix(setup): doctor skips placeholder secrets when seeding; reset names its target
- checks: the missing-file autofix copied shared keys from apps/sim/.env whenever truthy, including .env.example placeholders — doctor --fix could seed unusable secrets into realtime/db env files. Skip placeholders, matching autofixForMissing.
- lifecycle: reset now names the exact install (k8s context / compose file / dev containers) in its confirm, so a destructive reset can't silently hit the wrong same-named install after a context switch (down already names the context).
* fix(cli-auth): size the poll rate limit to the poll cadence; honor Retry-After
The poll route used the default public-IP bucket (10 burst, 5/min) but the CLI polls every 2s (30/min), so it 429'd within ~20s — worse behind a slow dev cold-compile. Give the endpoint a bucket matched to its cadence (60 burst, 60/min); it's not a brute-force surface (unknown request id returns pending, minting needs the 256-bit verifier). Also make the CLI honor Retry-After and back off on 429 so a shared-NAT per-IP limit degrades gracefully instead of hammering.
* fix(setup): check ports before starting the dev server, not just compose
Local dev auto-start spawned bun run dev:full with no port check, so it silently started a server that couldn't bind when 3000/3002 were already taken (e.g. another worktree's dev server). Extract compose's port-conflict resolver into a shared ensurePortsFree(ports) and run it before the dev start too — kill/recheck/leave, same as compose. Leaving the ports skips the auto-start with guidance instead of failing; compose still treats it as fatal.
* fix(setup): verify the kube cluster is reachable, not just local
A kubeconfig context can outlive its cluster — a kind cluster gets deleted or its Docker container stops (Docker/machine restart), but the context entry remains, pointing at a dead API-server port. The wizard checked the context looked local and handed it to helm, which failed with 'cluster unreachable'.
Add a clusterReachable() liveness probe: only offer the current context when it actually answers; if a local context is dead, fall through to the kind path. There, if kind still knows 'sim' but it's stopped, start its node containers and wait for the API; if it's gone, create fresh. Either way the user gets a working cluster instead of a cryptic helm failure.
* fix(helm): point appVersion at published image tags (v-prefixed, current)
The chart's appVersion was "0.6.73", but CI publishes GHCR tags with a v prefix (its release-commit regex captures v0.7.45). Since sim.image defaults every image tag to Chart.AppVersion, a default helm install requested ghcr.io/simstudioai/{simstudio,realtime,migrations}:0.6.73 — a tag that has never existed — so app and realtime sat in ImagePullBackOff and helm --wait failed with 'progress deadline exceeded'. Any self-hoster installing with default values hit this, not just the setup wizard.
Set appVersion to v0.7.45 (latest release on main; all three images verified present on ghcr) and bump the chart version to 1.1.1. Verified with helm lint, helm template (all images render as v0.7.45), and a live helm upgrade on a kind cluster where the new pods pull successfully while the old 0.6.73 pods remain in ImagePullBackOff.
* Revert "fix(helm): point appVersion at published image tags (v-prefixed, current)"
This reverts commit
|
||
|
|
fe184d3695 |
improvement(whatsapp): validate + improve integration skill for file inputs/outputs (#5942)
* improvement(whatsapp): validate + improve integration skill for file inputs/outputs * fix lint * add whatsapp subblock migration |
||
|
|
17d77795b4 |
feat(providers): prompt caching capability + usage-based cache pricing (#5922)
* improvement(providers): validation pass, and stream tool loop improvements * remove deploy options correctly * fix * feat(providers): prompt caching capability and usage-based cache pricing Replace the arbitrary cached-rate heuristic with a single cache-aware pricing function, and add prompt caching as an opt-in capability for Anthropic. Pricing: priceModelUsage in cost-policy.ts is now the only place cache arithmetic happens. Provider adapters normalize their wire shape into ModelUsage (input always excludes cache buckets); the pricing function never branches on provider. This removes five divergent behaviors, including the !!request.context heuristic that gave Router and Evaluator an unearned 10x input discount, and the overwrite that silently billed Anthropic cache reads and writes at zero. Also parses OpenAI cache_write_tokens, previously ignored. Caching: Anthropic gets a capability-gated advanced switch that places cache_control on the last tool and last system block; system is now always a TextBlockParam array. OpenAI gets a stable per-block prompt_cache_key with no UI, since its caching is automatic. * fix(providers): route OpenAI and Gemini block cost through cache-aware pricing Cache-aware pricing only reached trace segments. The billable block cost still called calculateCost on the cache-inclusive prompt total, so OpenAI cache hits and Gemini implicit-cache hits were charged at the full input rate and GPT-5.6+ cache writes went unbilled. Both providers now accumulate cache buckets and price through priceModelUsage, matching the Anthropic token convention where input excludes cache reads and writes. Cached counts are clamped to the prompt total so an over-reporting payload cannot bill more input than the request contained. * fix(streaming): redact tool payloads on selected outputs in public chat Redaction only ran on the empty-selection branch, but a deployment almost always selects outputs, so it was dead in the case it exists for. Selecting toolCalls streamed the raw arguments and results to a public chat client in a chunk frame, and providerTiming carried thinking content the same way. Both paths now extract from the sanitized block output rather than the raw log: the streamed selected output, which is the reachable vector, and the final envelope. Sanitizing the source rather than per selected path means a newly selectable field cannot reopen the hole. * refactor(providers): drop unreachable billing fallbacks Every provider pricing helper took a policy parameter no caller passed. Worse than dead: passing one would have double-applied the margin the central layer already applies. Removed, so providers can only price at list. Also removed guards that cannot fire. The central fallback normalized cache buckets no provider can reach it with (all three that report cache usage price themselves) and did so at a 1x write multiplier no vendor charges. priceModelUsage re-validated token counts the adapter had already clamped, and applyModelCostPolicy defaulted a required total field. Validation now happens once, in the adapter that parses the vendor payload and is the only layer that knows cache buckets are a subset of the prompt total. |
||
|
|
513292f17b |
feat(sso): DNS domain verification gating org SSO registration (#5909)
* feat(sso): DNS domain verification gating org SSO registration
Add org-scoped domain ownership verification (DNS TXT challenge) as the
security precondition for configuring SSO. Closes the first-come domain-claim
vuln where any org could wire another company's domain to its own IdP.
- New sso_domain table + migration 0266; existing org SSO domains are
grandfathered as verified so live tenants are unaffected
- Verified-domains settings UI (enterprise-gated) with add/verify/remove
- Register route now requires a verified domain for org-scoped registration;
personal SSO and already-grandfathered domains are unaffected
- Self-host register script writes the verified sso_domain row directly, so
script-driven registration stays backwards compatible
* fix(sso): harden domain verification against concurrency + fix CI lint
Addresses review findings on state invariants under concurrent/failed writes:
- Add unique index on (organization_id, domain) so concurrent claims can't
create duplicate pending rows; POST re-reads and stays idempotent on conflict
- Verify flips the row only if it's still the exact pending challenge checked
(guards deletion/token-rotation mid-DNS-lookup) and maps the partial unique
index violation to 409 instead of an unhandled 500
- Wrap the self-host script's provider write + verified-domain upsert in a
transaction so a failed ownership write can't leave a provider committed
- Format 0266 snapshot/journal with biome (fixes @sim/db lint:check)
* fix(sso): re-check domain verification before provider write (TOCTOU)
The register gate checked the verified sso_domain row only at handler entry,
then ran OIDC discovery before writing the provider. A verified row removed
during that window could still complete registration. Extract the check into a
closure and call it both as an entry fast-fail and authoritatively right before
registerSSOProvider, alongside the existing domain-conflict re-check.
* fix(sso): stop rotating verification token on idempotent re-add
Re-adding a pending domain rotated its verification token, which invalidated a
TXT record the admin may have already published and — under two concurrent
re-adds — could return a token the racing write had already superseded, so the
admin's DNS record would never verify. Return the existing row unchanged
instead; the pending token is always shown in the UI, so it is never lost.
* fix(sso): close register TOCTOU with compensating delete + harden edges
Audit-driven hardening:
- Close the residual register TOCTOU: registerSSOProvider is create-only (throws
if the providerId exists), so a compensating delete after the write is provably
safe — it can only remove the just-created row. If verification was revoked
during the write, roll the provider back and 403.
- Verify is now idempotent under concurrency: a same-org row already flipped to
verified by a racing request returns 200, not a confusing 409.
- Grandfather backfill + self-host script now match normalizeSSODomain's dominant
transforms (lower + trim + strip leading wildcard) so a non-canonical legacy
domain can't miss the runtime gate's lookup. Prod backfill result is unchanged.
- Cleanup: drop dead default export, align card radius to sibling convention.
* fix(sso): redact domain tokens from non-admins + fix script stale-update
Round-5 review findings:
- GET /domains redacted the pending TXT verification token (a management
secret) to any org member. Now only owner/admins read it; members see the
list and status without it. Non-Enterprise orgs get an empty list (entitlement
flag only), never the domains/tokens.
- Self-host script decided update-vs-insert from a read taken OUTSIDE the
transaction; a provider deleted mid-flight made the UPDATE match zero rows
silently while the verified-domain upsert still committed (orphaned domain).
The decision now happens inside the transaction from the UPDATE's row count.
* docs(sso): drop unshipped enforce-SSO / auto-join copy from verified domains
Verified domains currently only gate SSO configuration. Remove the
forward-looking references to enforcing SSO and auto-joining members (deferred
to a later release) from the docs, settings copy, nav description, and schema
comment so we don't promise unshipped features.
* fix(sso): guard rollback to new providers only + Enterprise-gate domain removal
Round-6 review findings:
- The compensating provider rollback now only fires when the provider did not
exist before this request (providerExistedBefore). registerSSOProvider is
create-only today so reaching the rollback already implies a fresh create, but
this makes the safety local and future-proof: if Better Auth ever allowed
updating an existing provider, a revoked-verification rollback must not delete
that pre-existing row.
- DELETE /domains now requires an Enterprise plan like add/list/verify, so all
domain mutations share one entitlement (the UI already hides removal from
non-Enterprise orgs). Adds a delete-route test.
* fix(sso): roll back the SSO provider by row id, not logical keys
The compensating rollback deleted by (providerId, orgId). providerId is unique,
so if this request's row were deleted and recreated by a concurrent registration
in the narrow window before the rollback, the logical-key delete would remove
that other request's provider. Delete by the primary-key id registerSSOProvider
returns instead, so only the exact row this request created is ever removed.
* chore(sso): final-review polish — trim script read, unify copy, doc migration edge
Cosmetic cleanup from a final 4-track adversarial review (no bugs found in the
new logic):
- Self-host script: narrow the pre-transaction existence read to select({ id })
instead of SELECT * (it only feeds a log line now).
- Unify invalid-domain copy ("for example acme.com") and the verified-elsewhere
409 wording ("is already verified by another organization") across routes.
- p-3 shorthand on the domain row card.
- Document the migration's rare two-orgs-share-a-domain grandfather behavior
(login unaffected; validated no such duplicates in prod).
* fix(sso): apply attribute mapping + make SSO edit work; drop dead guard
Two pre-existing SSO bugs the final review surfaced (prod has one SSO org, RVW,
script-registered with the default mapping, so neither change affects it):
- Attribute mapping was passed at the top level of the register payload, which
Better Auth ignores — it reads oidcConfig.mapping / samlConfig.mapping. Nest it
so custom mappings actually apply. (Default mapping is unchanged, so existing
logins are unaffected.)
- Editing an SSO provider was broken: registerSSOProvider is create-only and
threw on the existing providerId → generic 500. Route now detects a provider
the caller already owns and updates it via Better Auth's updateSSOProvider, and
surfaces Better Auth's own error status/message instead of a blanket 500.
Also drops the now-unnecessary providerExistedBefore guard (the rollback deletes
by the created row's primary-key id and register is create-only) and the earlier
final-review polish (script read, unified copy, migration edge note).
Smoke-test SSO login + edit on staging before merge (auth-path change).
* fix(sso): require null org on personal-mode provider lookups (gate bypass)
The personal branch of both provider-ownership lookups keyed on
(providerId, userId) without requiring organizationId IS NULL. Because org
providers store userId = their creator and providerId is globally unique, an org
admin could send a personal-mode request (no orgId) — which skips the membership
check and the domain-verification gate — yet still match, and then via the new
update path move, their org's provider to an unverified domain. Add
isNull(organizationId) to the personal branch of both clauses so it can only
match a genuinely personal provider, matching the route's own isOwnedByCaller.
Found by an adversarial review of the update path added in 394bda9f7.
* fix(sso): script updates the observed provider by id, not providerId
Inside the registration transaction the script updated WHERE providerId — the
logical key. If the observed provider was deregistered and a replacement created
with the same providerId before the transaction ran, that update would clobber
the replacement's config and ownership. Update the specific observed row by its
primary-key id instead; if it's gone we insert, which fails cleanly on the
providerId unique constraint rather than overwriting the replacement.
* fix(sso): script upserts provider via delete-then-insert (no unique constraint)
sso_provider.provider_id is a plain (non-unique) index and prod holds legitimate
duplicates, so the previous "update by id, else insert" could create a duplicate
provider when the observed row was deregistered and replaced before the
transaction — the fallback insert would succeed. Delete every row for the
providerId then insert exactly one, inside the transaction, so the providerId
ends up as exactly this config atomically. Linked accounts key on the providerId
string (not the row id), so existing logins are unaffected.
* fix(sso): guard compensating-delete row id so rollback can't silently no-op
* chore(sso): regenerate migration as 0268 after merging staging
Staging landed migrations 0266/0267, colliding with our 0266. Removed our
migration, merged staging, and regenerated cleanly with drizzle-kit as
0268_sso_domain_verification (identical sso_domain table + indexes), then
re-appended the grandfather backfill. api-validation baseline reconciled to 973
(staging 970 + our 3 domain routes). Also make the register-route test's
registerSSOProvider mock return an id so the guarded compensating delete runs.
* refactor(sso): share normalizeSSODomain via @sim/utils so script matches gate
The self-host script canonicalized SSO domains with a minimal inline transform
(lower+trim+wildcard) that diverged from the app's full normalizeSSODomain
(protocol, port, path, trailing dot, email local part) — equivalent spellings
could store a different ownership key than the runtime gate looks up. Move
normalizeSSODomain into @sim/utils/sso-domain (a pure function) so the register
route, the domain-claim route, and the script all use the identical canonicalizer.
The script now skips the verified-domain record when SSO_DOMAIN isn't a valid
registrable domain instead of storing a malformed key.
|
||
|
|
6dcc65be89 |
feat(skills): add skill editors (#5705)
* feat(skills): permissions layer * chore(db): drop skill_member migration 0261 for regeneration on latest staging Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(db): regenerate skill_member migration as 0262 on latest staging Same DDL as the dropped 0261 (skill_member table, enums, indexes, skill.workspace_shared) plus the hand-written write-user backfill, renumbered after staging's 0261. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(db): regenerate skill_member migration as 0263 after staging merge Staging claimed 0262 (strong_storm); same DDL plus the hand-written write-user backfill, renumbered on the merged snapshot chain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(db): drop skill_member migration 0263 for regeneration on latest staging Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(db): regenerate skill_member migration as 0264 after staging merge Staging claimed 0263 (workflow_fork_sync_excluded); same DDL plus the hand-written write-user backfill, renumbered on the merged snapshot chain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * make editing skills full page * fix disclaimer * edit access msg * fix lint * chore(db): drop skill_member migration 0264 for regeneration on latest staging Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(db): regenerate skill_member migration as 0265 after staging merge Staging claimed 0264 (fat_ikaris); same DDL plus the hand-written write-user backfill, renumbered on the merged snapshot chain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix tests * simplify system * fix * fix lint * add mship skills docs * chore(db): drop skill_member migration 0265 for regeneration on latest staging Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(db): regenerate skill_member migration as 0266 after staging merge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(deps): override zod to 4.3.6 to dedupe nested copies breaking type-check better-auth 1.6.23 and fumadocs-mdx resolve ^4.3.6 to a nested zod 4.4.3, which makes @sim/auth's inferred betterAuth types non-portable (TS2883) and split docs onto a second zod instance. Both ranges accept the repo-wide pinned 4.3.6, so a single hoisted copy satisfies everything. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix lint * feat(skills,tools): fullscreen skill create + shared custom tool editor Moves the rich-markdown and custom-tool editing surfaces out of modals and onto full-page surfaces, and collapses the duplicated chrome behind shared components. Skills - Add /skills/new, a full-page create surface mirroring the skill detail page (CredentialDetailLayout + DetailSection + unsaved-changes guard). "Add to Sim" navigates there instead of opening a modal. - Import moves to a header action (SkillImportButton) backed by a shared readSkillFile helper; the GitHub-URL import and its /api/skills/import route are removed. - Skill name validation is now one shared validateSkillName, replacing three copies of the kebab-case rule and its messages. - The skill editor roster renders through the shared MemberRow instead of re-deriving its identity block, with a locked role control and a lock-reason tooltip explaining inherited workspace-admin access. Custom tools - Extract the canvas modal's schema/code editors into a shared custom-tool-editor module (fields, wand generation, schema helpers), cutting custom-tool-modal.tsx by ~900 lines. - Settings > Custom tools gains a full-page detail sub-view (SettingsPanel + SettingsSection + saveDiscardActions), deep-linkable via ?custom-tool-id. Rows are clickable; delete now lives only in the detail view. - Replace legacy Button/Input/Badge/Label with the chip family, move chip-field chrome into CodeEditor behind an error prop, and delete its dead wand button. Rich markdown field - maxHeight is now opt-in: omit it on a page and the editor grows with its content so the page owns the only scrollbar. Modals pass explicit caps. - The field variant drops to font-weight 400 to match adjacent chip fields. * fix(skills): address review round on create navigation, 409 copy, and editor audit - Skill create navigated using the first element of the upsert response, but that endpoint returns the caller's whole skill list (built-ins prepended) — match the new skill by its workspace-unique name instead. - The suggested-skill 409 toast claimed the skill existed but was not shared and told the user to ask a skill admin. Every workspace member can already see and use every skill, so a 409 only means the name is taken. - Adding an editor emitted the skill_shared event and SKILL_MEMBER_ADDED audit even when onConflictDoNothing skipped the insert on a concurrent add. Gate both on the insert actually returning a row. * chore: format skills-resolver test import * fix(skills,tools): audit fixes — autocomplete boundary, resize clipping, error routing Two real regressions introduced while simplifying the extracted editor: - The schema-param autocomplete's trigger was rewritten to match a trailing identifier, but the completion still split on separators. The two disagreed, so typing `data.ci` opened the menu and selecting replaced `data.ci` whole — eating the member-access prefix. Both now share one SCHEMA_PARAM_WORD regex. - The uncapped markdown field measured its height only on value change while always setting overflow-hidden, so any width change that re-wrapped lines clipped the tail with no scrollbar to reach it. Now re-measures via ResizeObserver. Also from the audit: - Generation writes bypass the code field's change handler, so an open autocomplete stayed over a disabled streaming editor; close it on busy. - Delete failures rendered in the Schema section's error slot on both custom tool surfaces; route them to a toast instead. - Skill create navigated away while still dirty, stranding the unsaved-changes guard's history sentinel so Back landed on an empty create form. - The Description field on skill create never received its error border. - Drop a double-applied opacity-50 (the editor already dims when disabled), a dead try/catch around a non-throwing call that also shadowed the error prop, and a stale reference to a /tools page that does not exist. - Docs still described the removed GitHub-URL import and the old Add Skill dialog; rewrite for the create page and file/paste import. * feat(tools): read-only tool detail, create lands on the new tool, drop dead wand prompt API - Viewers without edit rights could not open a custom tool at all, while the equivalent skill and custom-block surfaces both offer a read-only view. The detail page now takes `readOnly`: editors inert, no Save/Discard/Delete, no Generate. Creating still requires edit rights. - Creating a tool bounced back to the list while creating a skill lands on the new skill. Tools now do the same. The upsert returns the workspace's whole tool list (newest first) rather than just the new row, so the id is matched by title instead of by index — the same trap that produced the skill-create navigation bug. - Remove `openPrompt`/`closePrompt` from useWand. `closePrompt`'s last callers went away with the custom-tool-modal extraction and `openPrompt` had none before it; nothing reads `isPromptVisible` any more either. * fix(tools): read-only editors, design-system wrench, skills-matching tool identity - readOnly never reached the editors: the prop gated actions and Generate but the schema and code fields were still typable for viewers without edit rights. Wire disabled through both fields into CodeEditor. - The row icon used lucide's Wrench (strokeWidth 2) where @sim/emcn/icons ships one drawn for this system (1.55, tuned viewBox), and it inherited body text colour instead of --text-icon. Swap it. - Give the tool detail page the same identity heading as skill detail: tile, name, and description at the top left, instead of only a header title. - Extract ResourceTile so the skills and tools tiles share one definition (SkillTile now composes it), and add an opt-in `iconFilled` to SettingsResourceRow so the tools list tile matches the skills gallery. Both default to today's behaviour for every existing consumer. * fix(mentions): use the product's own glyph for every @ mention kind The `@` menu and the inserted chip mapped kinds to arbitrary lucide icons — `Sparkles` for a skill, a generic `File` for every file — while the rest of the product has a settled glyph per resource. Mirror CHAT_CONTEXT_KIND_REGISTRY, which Chat's `@` menu already renders from: - skill now uses AgentSkillsIcon, the same glyph SkillTile shows everywhere - workflow / folder / table / knowledge use the @sim/emcn/icons set the sidebar and the chat registry use - file derives its icon from the filename extension, so a .pdf and a .csv are distinguishable, matching the file list and Chat's context chips - integration keeps the block's brand icon from the registry Also drop the generic placeholder. `kind` is untrusted — the node schema defaults it to `''` and a hand-written `sim:` link can carry anything — but an unrecognized kind now yields no icon instead of a meaningless box, which is what the chat registry does. The menu already guarded a missing icon; the chip now does too, so this cannot crash on a malformed link. * chore(db): drop skill_member migration 0266 for regeneration on latest staging * feat(db): regenerate skill_member migration as 0267 after staging merge --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Waleed Latif <walif6@gmail.com> |
||
|
|
d24bc7eccb |
feat(agent-stream): thinking and tool streaming (#5671)
* feat(agent-stream): add agent-events thinking/tool streaming for chat and canvas Ship the agent-events-v1 protocol with provider tool loops, dual-gated chat thinking, DeepSeek/Groq/OpenAI reasoning wiring, and ChatGPT-like thinking chrome. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): clear stuck streaming UI and format db snapshot Biome was failing CI on migrations/meta/0261_snapshot.json. Also settle assistant streaming/tool flags when SSE ends without a terminal frame, without clobbering Stop's finalized content. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): satisfy biome format and import order Auto-format the sim package for CI lint:check, and repair the Anthropic streaming tool-loop payload after an unsafe delete-to-undefined rewrite. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): keep drained answer on abort and update migration journal test Treat AbortError from reader.cancel as a cancelled pump result so soft-complete retains answerText. Point the workspace storage migration journal assertion at 0261_chat_include_thinking. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(chat): keep Stop notice when server emits cancel error Ignore terminal SSE error frames after the user aborts so "Client cancelled request" cannot overwrite "Response stopped by user". Co-authored-by: Cursor <cursoragent@cursor.com> * improvement(chat): ChatGPT-style thinking shimmer and stick-to-bottom scroll Add left-to-right shimmer on live thinking label/body, keep scroll working by shimmering an inner node, and follow the answer only while near the bottom. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): stop pump on client disconnect; soft-complete agents only Abort the agent stream pump when the projected HTTP body is cancelled so provider work does not continue after disconnect. Limit AbortError soft-success to Agent blocks so Function/HTTP cancels still fail in logs. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): persist includeThinking across pause snapshots Paused chat runs with Include thinking enabled were dropping the flag when serializing the pause snapshot, so resume always rebuilt streams without thinking/tool SSE frames. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): keep drained answer text when stream times out Persist pump answerText onto the streaming execution before throwing on timeout, and carry that partial content into the failed block output so logs match what the client already saw. Co-authored-by: Cursor <cursoragent@cursor.com> * improvement(chat): auto-collapse tools chrome when tool streaming ends Match thinking UX: open while tools run, collapse when finished, and keep the panel open only if the user manually reopens it. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): settle canvas stream chrome on failure paths Clear agentStreamActive and settle running tool chips when blocks error, timeouts cancel runs, or execution ends without stream:done so the output panel does not stay on live Thinking/Using tools chrome. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lint): organize imports in terminal console store Co-authored-by: Cursor <cursoragent@cursor.com> * fix(agent-stream): mark open tools cancelled on HITL pause Pause can interrupt a tool loop without tool end events; settling those chips as success incorrectly showed unfinished tools as complete. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(db): drop branch-local 0261 migration ahead of staging merge * chore(db): regenerate include_thinking migration as 0266 post staging merge * fix(providers): resolve type errors in streaming tool loop call sites * fix(agent-stream): gate agent events opt-in and correct provider loop behavior - streamToolCalls and provider thinking requests now require run-level agentEvents opt-in (canvas on, chat dual-gated, API off) so existing runs keep pre-agent-events behavior exactly - OpenAI reasoning summaries opt-in + strip-and-retry on unverified-org 400 - streaming loops run tool postProcess again (firecrawl/exa async results) - bedrock live loop falls back to silent path for responseFormat - deepseek: reasoning_content pass-back unconditional, 'none' sends disabled - groq: x_groq.usage fallback, reasoning params gated, qwen none disables - gemini: functionCall parts echoed verbatim, local ids only for events - truncated turns (max_tokens/length) no longer execute partial tool calls - MAX_TOOL_ITERATIONS exit flushes last turn text as final answer - iterations reports actual model calls; shared loop plumbing extracted * refactor(agent-stream): consolidate protocol, dedupe client/server plumbing, hygiene - canonical ChatStreamFrame union + type guards consumed by server emitters and the chat client; stream_error restored to legacy log-only handling - strip thinking/tool args from providerTiming on public final envelopes - shared tool-chip lifecycle module for chat, canvas, and console store - shared sink-to-execution-events forwarder replaces the copy-pasted adapter in the execute route and HITL manager; LIVE_ONLY event set shared - stream:thinking payload field renamed data->text; canvas thinking batched - abort reasons carried as AbortError DOMExceptions so raw fetch consumers classify correctly; thinking cap renamed to chars and scope-documented - kimi wired for agent events like the other compat providers - deleted dead exports/step-N comments; fixtures match real wire shapes; loop tests use explicit mocks instead of importOriginal * test(agent-stream): cover the dual-gated execution path and typed abort reasons - chat route tests assert agentEvents reaches executeWorkflow only when policy and protocol header agree - execution-limits tests assert AbortError-typed reasons - executor metadata type carries agentEvents * fix(deploy-modal): align include-thinking spacing with the modal's 6.5px rhythm * docs(agent-stream): autogenerate per-model thinking/tool stream support on the Agent block page - capabilities.thinking.streamed ('full' | 'summary' | 'none') on models.ts, explicit for the Anthropic family where visibility varies per generation; getThinkingStreamVisibility exposes the derivation for docs and UI alike - scripts/sync-agent-stream-docs.ts regenerates the support tables between markers in workflows/blocks/agent.mdx from the model registry and STREAMING_TOOL_CALL_PROVIDERS; --check fails on drift or missing metadata - wired agent-stream-docs:check into CI next to the other sync gates * feat(anthropic): request summarized thinking display for omitted-default Claude models The newest Claude generations (Fable 5, Sonnet 5, Opus 4.8/4.7) default thinking.display to omitted — empty thinking blocks, no deltas. On agent-events runs Sim now opts back in with display: 'summarized', driven by the registry's streamed metadata; legacy runs keep the exact pre-agent-events request shape. Registry, generated docs, and the family capability table updated accordingly. * docs(skills): cover thinking.streamed and agent-stream docs sync in model skills * chore(deps): upgrade @anthropic-ai/sdk to 0.114.0 and adopt official types - adaptive thinking, display, and output_config are now SDK-typed; the only remaining custom payload field is output_format (beta-header structured outputs, which the SDK models as output_config.format instead) - anthropic stream events narrow on the SDK's discriminated unions instead of anonymous casts; compat deltas type content/tool_calls from the OpenAI SDK with vendor reasoning fields as an explicit optional extension - @sim/auth exposes an explicit VerifyAuth contract so its declarations no longer reference better-auth's nested zod instance (TS2883 under fresh install layouts); realtime consumer aligned - docs app zod pinned to the repo's exact 4.3.6 so ai SDK types bind the same zod instance (docs type-check was latently broken) - knowledge embedding tests made hermetic against local .env keys and hosted rotation fallback * refactor(providers): replace legacy as-any stream casts with annotated typed casts * refactor(providers): finish provider audit — remove dead byte-stream helper, annotate remaining legacy casts Audit of all 26 providers for the agent-events feature confirmed every streaming execution declares agent-events-v1 and every adapter emits AgentStreamEvent objects. Cleanup from the audit: the unconsumed legacy createOpenAICompatibleStream byte helper is deleted, and the remaining streamResponse-as-any casts (xai, nvidia, kimi, meta, zai, sakana) are annotated typed casts matching the groq/deepseek fix. * feat(streaming): stream answer text live during tool loops via turn_end protocol The live tool loops buffered all answer text per model turn (classification of intermediate vs final is only known at turn end), so gated surfaces saw thinking stream, then dead air with the thinking chrome stuck open, then the whole answer at once. Loops now emit text deltas live as `turn: 'pending'` plus a `turn_end` event per turn. The pump buffers pending text and projects it to the byte path (answerText/logs/memory/legacy clients) only on a final turn_end, so all settled semantics are unchanged. Gated surfaces render the pending text as it streams and reconcile with a reset when a turn resolves to tools: - public chat: live `chunk` frames from the sink + dual-gated `chunk_reset`; byte-path frame emission is suppressed to avoid duplicates (kept for response-format transformed streams via clientStreamTransformed) - canvas: forwarder emits live `stream:chunk` + `stream:chunk_reset`; the execute route and HITL resume readers stop re-emitting byte chunks; panel chat tracks per-block segments and replaces content on flush - chat client: per-block text segments, chunk_reset handling, and thinking chrome now settles on tool start as well as first answer chunk * fix(streaming): address validated review findings across provider gating and reset reconciliation Three-reviewer pass over the branch, findings validated against staging: - agent-handler forwards agentEvents to executeProviderRequest — the flag was computed but dropped in the field-by-field copy, so provider-side thinking requests (OpenAI summaries, Gemini includeThoughts, Anthropic summarized display) never activated on opted-in runs - openai: restore summary:'auto' alongside explicit reasoning effort — staging always paired them; gating summary purely on agentEvents changed legacy payloads - gemini: Gemini 2 + tools + responseFormat falls back to the silent path; the live loop never applied the deferred responseSchema for AUTO tools - openai-compat loop: malformed tool-argument JSON fails the call instead of executing with defaulted {} args (staging parsed inside the execution try) - openai-compat parser: a vendor id arriving after a synthesized start no longer renames the call (start/end ids stayed consistent) - stream-pump: abort closes the byte projection so a drain blocked on backpressure cannot deadlock teardown - chunk_reset removes the block from the client text order (deployed chat + panel chat) so a reset block re-registers at arrival position — fixes separator/order corruption when parallel blocks stream around a reset - resume route echoes the negotiated X-Sim-Stream-Protocol response header (parity with the chat route); docs: [DONE] wire shape + final-vs-error terminal semantics corrected * chore(deps): exempt pinned @anthropic-ai/sdk 0.114.0 from the release-age gate CI's bun install --frozen-lockfile blocks 0.114.0 (published 2026-07-23, younger than the 7-day supply-chain gate). The pin is exact and was vetted for the agent-events streaming work; following the existing bunfig pattern, the exclusion ages out on 2026-07-30 and should be dropped then. * chore(providers): fix double-cast-allowed annotation placement for the strict boundary audit The audit only recognizes the annotation on the line directly above the cast; two annotations had drifted behind intervening code lines (groq stream params, deepseek loop messages) and the OpenAI reasoning-summary widening cast was never annotated. No behavior change. * fix(chat): settle straggler tool chips as error when final reports failure A failed run can still terminate with a `final` frame carrying success: false; running chips previously settled green regardless of the outcome. * fix(canvas): wire agent stream chrome into run-from-block Run-from-block executions emit the same live stream:thinking/stream:tool events as full runs but registered none of the handlers, so the terminal never showed thinking or tool chips on that path. The per-run chrome (batched thinking writes + tool chip lifecycle + settlement on stream done, block error, and every terminal execution state) is extracted into a shared createAgentStreamChrome factory consumed by both paths. --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> |
||
|
|
2b5a92a3c8 |
feat(auth): org session policies — lifetime/idle limits, org-wide revocation (#5862)
* feat(auth): org session policies — lifetime/idle limits, org-wide revocation, cookie-cache versioning * refactor(auth): consolidate session-policy clamp semantics, shared security-policy version module, canonical bounds, docs * polish(session-policy): cleanup pass — muted field labels, spinner reset, state tracker, response-seeded baseline, comment trims * fix(session-policy): govern member sessions by membership (closes revoke cookie-cache hole), normalize createdAt, remount on org switch, sync audit mock * fix(session-policy): clamp pre-join sessions on invite acceptance, normalize expiresAt, sync unified nav test * fix(session-policy): invalidate membership cache on removal/transfer, spare impersonator sessions in revoke-all, raise idle floor to 2x cookie window * fix(session-policy): resolve governing org by membership only — activeOrganizationId goes stale across transfer/leave * fix(session-policy): atomic policy save + eager clamp, asymmetric membership TTL, admin-add cache invalidation * fix(session-policy): org-scoped cookie version string, atomic revoke delete+bump * fix(session-policy): plan-gate effective policy so downgraded orgs stop enforcing automatically * chore(session-policy): drop dead bumpSecurityPolicyVersion helper — call sites bump transactionally * fix(session-policy): unify join paths on applySessionPolicyToNewMember; final audit polish (dead exports, response bound, test name) |
||
|
|
6f33a9485b |
feat(workflows): IDE-style reference viewer for workflows (#5854)
* feat(workflows): add IDE-style reference viewer for workflows
Adds a "Show references" viewer so you can see how workflows connect:
which workflows call a given workflow ("Used by") and which workflows it
calls ("Uses"), rendered as recursive, clickable trees.
- Opened via Cmd/Ctrl+click on a sidebar workflow row and a "Show
references" context-menu item.
- Resolves references through both the workflow / workflow_input blocks
(reusing isWorkflowBlockType) and published custom blocks
(custom_block_* -> source workflow), scoped to the workspace.
- Builds the whole workspace reference graph once from live workflow_blocks
state; cycle-safe DFS marks A->B->A loops as (cycle) leaves.
- Contract-bound GET /api/workflows/[id]/references with workspace-level
authz; React Query hook gated to fetch only when the modal opens.
- Unit tests for the pure graph/tree logic (cycles, self-refs, dangling
drop, custom-block + workflow_input resolution) and route tests
(401/400/403/200).
* fix(workflows): correct reference resolution for active mode, cycles, cache, and graph size
Addresses review findings on the reference viewer:
- Resolve the workflow-block child via resolveActiveCanonicalValue (the
shared SOT) instead of basic-first `||`, so an advanced-mode block whose
old basic workflowId value lingers resolves to the active manual value.
- Keep self-references (A -> A) and render them as a cycle leaf instead of
dropping the edge, matching the cycle-safe viewer's purpose.
- Set the references query staleTime to 0 so reopening the always-mounted
modal refetches live editor state instead of serving a stale cached graph.
- Bound converging paths: a node already expanded elsewhere in the tree is
emitted once more as a plain leaf (edge stays visible) rather than
re-expanded, so a densely reconverging graph can't grow exponentially.
* improvement(workflows): align reference viewer auth, coverage, and UI with platform conventions
- authorize via authorizeWorkflowByWorkspacePermission and derive the
workspace server-side (404/403 semantics; drops the client-supplied
workspaceId query param from the contract, hook, and modal)
- add workflow-tool call edges: workflow_input tools inside tool-input
sub-blocks now appear in both trees; non-call selector shapes stay
deliberately excluded (documented against remap-internal-ids)
- restore native cmd/ctrl+click open-in-new-tab on sidebar workflow rows;
references stay reachable from the context menu
- mount ReferencesModal on demand per row, deleting the prevIsOpen reset,
the enabled knob, and the staleTime-0 workaround (now 30s)
- align the tree with design tokens (--text-icon, --surface-hover, px-4
text gutter) and drop the hardcoded brand hex
- escape LIKE wildcards in the custom_block_ prefix match; import
MAX_CALL_CHAIN_DEPTH instead of mirroring it; remove dead fallbacks,
the duplicate not-found scan, and the redundant custom-block row map
* improvement(workflows): final polish on the reference viewer
- drop the vestigial isOpen prop (conditional mount owns visibility)
- unify on the emcn Workflow icon in tree rows
- inline the static className and derive nodes without an annotation
- remove one restating test comment
* fix(workflows): resolve tool references by active canonical mode and keep the reference cache live
- workflow_input tools inside tool-input now resolve basic/advanced via the
index-scoped canonicalModes override, mirroring execution (Cursor finding)
- staleTime back to 0: no mutation invalidates this key, so a reopen must
background-refetch; on-demand mounting keeps the cached tree painting
instantly (Greptile P1)
- modal header uses the em-dash label-entity convention; tree items carry
aria-level instead of a static aria-selected
* fix(workflows): cover legacy workflow-typed tools and retry depth-truncated expansions
- toolInputCallees matches both workflow tool type spellings via
isWorkflowBlockType and passes the tool's own type as the legacy
canonicalModes fallback, matching providers/utils resolution
- a depth-capped expansion no longer poisons the expanded set, so a
shallower path re-expands the node in full (Cursor finding)
- the allowed-but-workspaceless auth branch now returns 403, not the
authz result's 200
- tests: legacy tool type + per-tool index-scope isolation, diamond
re-expansion with a real subtree, depth ceiling, shallow-path retry
---------
Co-authored-by: Marcus Chandra <mzxchandra@gmail.com>
|
||
|
|
93fbf584a2 |
feat(chat): soft-delete sidebar chats with restore from Recently Deleted (#5830)
* feat(chat): soft-delete sidebar chats with restore from Recently Deleted * fix(chat): review round 1 — restore workspace authz, purge recheck, archived-list invalidation * test(chat): update SSE handler assertions for workspaceLists invalidation * fix(chat): bump updatedAt on restore, recheck retention cutoff in task cleanup * fix(chat): drop explicit feedback delete in task cleanup — chat FK cascade covers it * test(chat): cover restore route; guard legacy copilot delete from hard-deleting mothership chats * chore: revert unintended bun.lock drift from worktree install * fix(cleanup): recheck workflow archive cutoff on delete; export deletedAt in chat drain |
||
|
|
dd0e736d52 |
feat(managed-agents): add Claude Managed Agents workflow block (#5778)
* feat(managed-agents): add Claude Managed Agents workflow block
* improvement(managed-agents): complete session inputs/outputs; trim block templates
- add memory instructions + file mount_path inputs; bound metadata (16 pairs)
- surface cumulative token usage (inputTokens/outputTokens) as outputs
- validate the full session-create schema against live docs
- remove BlockMeta templates
* improvement(managed-agents): select a Claude Platform credential instead of BYOK
- register Claude Platform as a token-paste service-account credential (descriptor + validator)
- add no-OAuth OAUTH_PROVIDERS entry; generalize the shared credential picker with a 'service-account' kind
- block: oauth-input credential picker + dependsOn dropdowns; list route resolves the key server-side (audit-logged)
- run via directExecution with the executor-injected key; drop the internal run route
- remove the interim claude-platform BYOK provider
* fix(managed-agents): harden reconnect loop; fix credential-picker regression
- drive completion off terminal events + authoritative session status (drop the fragile busy-clock)
- drain full event history so a long session's tail is never cut off
- skip idless events in catch-up; require an id before replying to custom_tool_use
- gate the shared credential-selector service-account lookup on credentialKind and use the non-throwing helper (was crashing multi-service OAuth pickers)
- audit-log the list route's credential access; refresh stale tool docs
* fix(managed-agents): address review round 2
- don't complete on idle status while a requires_action is still outstanding
- vaults: combobox → dropdown so multiSelect actually attaches multiple vaults
- auto-select a freshly-pasted service-account credential (onCreated through the connect modal)
* fix(managed-agents): propagate cancellation, fix reconnect ordering, classify advanced fields
- Thread the executor abort signal into directExecution (additive ToolConfig
change) so a cancelled workflow stops the session immediately; best-effort
user.interrupt releases the Anthropic session past cancel/wall-clock cap
- Recompute the requires_action pending state from the chronological history
so an older agent.message recovered on catch-up can't clear a newer pause
- Retry a custom-tool error reply that failed to send instead of stranding the
session (mark the event seen only once handled)
- Mark optional fields (vaults, memory, files, metadata) mode: advanced
- Title the session with the workflow id for Claude-console traceability
* fix(managed-agents): stop leaked sessions on all give-up paths; harden event ordering and normalizers
- Interrupt on sendUserMessage failure and on the reconnect-cap exit, matching
the abort/wall-clock paths, so no give-up path leaves a session running
- Order the events list by processed_at (list page order isn't guaranteed
chronological) so catch-up accumulates text and reads the latest lifecycle
event correctly regardless of API sort
- Don't reset reconnect backoff on a failing custom-tool reply (stays unseen
for retry) — prevents a no-delay reconnect storm
- Treat only end_turn as a complete status_idle event; an unspecified idle
defers to the sawActivity-gated status check (no empty completion pre-turn)
- normalizeFiles parses a JSON-stringified table instead of dropping it;
normalizeStringList returns [] on malformed JSON; metadata keeps scalar values
- Declare the injected accessToken as a hidden param for convention parity
* fix(managed-agents): interrupt the session on a mid-run stream/API failure
The non-abort error catch path returned without stopping the session, so a
mid-run network/API failure could leave the Anthropic session running against
the workspace key. Interrupt on that path too — completing the invariant that
every give-up exit (abort, cap, send failure, reconnect cap, stream error)
stops the session. Still fails fast; no retry added.
* fix(managed-agents): skip idless stream previews; resolve service-account provider icons
- Skip idless events in the live SSE handler (mirroring catch-up). event_start/
event_delta previews carry no id, are never deduped, and final text always
arrives as a persisted id-bearing agent.message — so appending previews could
double the block's content output
- Map serviceAccountProviderId to its base provider in PROVIDER_ID_TO_BASE_PROVIDER
so parseProvider resolves 'claude-platform-service-account' to 'claude-platform'
(a two-segment base the hyphen split can't recover), fixing the credential-row
icon falling back to the generic external-link glyph
* fix(managed-agents): keep idless terminals and preserve live pending state
Refine the round-6 idless-event fix, which was too broad:
- Process idless events again (revert the blanket stream skip) so an idless
session.status_idle(end_turn)/session.error delivered only on the live stream
still registers as terminal instead of reconnecting to a timeout. Only the
agent.message TEXT append is now id-gated, so preview text still can't double
- When catch-up history has no lifecycle event, restore the live-observed
requires_action state instead of trusting a stale older agent.message that
cleared it — prevents a false completion with partial output while a tool
result is still pending
* fix(managed-agents): route memory via metadata on self-hosted environments
Live API testing revealed self-hosted environments reject the `resources`
array with a 400 ("resources are not supported with self-hosted
environments"), so the prior universal-resources[] payload would have failed
any self-hosted session that attached a memory store or files.
Restore env-type-aware routing: resolve the environment's config.type via a new
getEnvironmentType() before session create, and for self_hosted send the memory
store through metadata.memory_store_ids/memory_access (the worker consumes it)
and drop file attachments. Cloud environments keep resources[]. Verified end-to-
end against the live Managed Agents API (cloud resources[] 200, self-hosted
metadata 200, self-hosted resources[] 400).
* feat(managed-agents): environment-type selector; hide cloud-only fields on self-hosted
Collapse what #5769 split into two blocks into one, natively:
- Add an Environment type selector (Cloud / Self-hosted) that filters the
environment list to the matching type and gates cloud-only fields
- Memory store, memory access/instructions, and files are cloud-only (self-
hosted rejects the resources[] attach — verified live, 400) and are now hidden
on self-hosted instead of silently dropped. A self-hosted worker that uses a
memory store reads its id from a Metadata key the author sets explicitly
- Expose each environment's config.type on the list options so the picker can
filter by mode; pass the selected type as a routing hint (server still re-
resolves the authoritative type via getEnvironmentType)
* fix(managed-agents): track requires_action by processed_at, not history position
Persisted history can lag the live stream, so the last lifecycle event in the
history array may be OLDER than a requires_action the stream already observed.
Deriving the pending state from history position (findLastLifecycleEvent) could
then clear a newer pause and let an idle snapshot complete a still-waiting
session with partial text.
Track requires_action from the NEWEST lifecycle event by processed_at across
both the live stream and catch-up, so an older/lagging event can never override
a newer pause. Removes the pendingBeforeCatchup snapshot and history-position
recompute. New test covers lagging history holding only an older running event.
* fix(managed-agents): treat missing processed_at as oldest, not newest
A lifecycle event without processed_at mapped to +Infinity, poisoning the
high-water mark: once seen, no later timestamped event could update the pending
state (at >= Infinity always false), stranding a pause or clearing one wrongly.
Map missing/unparseable processed_at to -Infinity so an untimestamped lifecycle
event can never outrank a timestamped one in either direction — it neither
blocks later real events nor clears a timestamped requires_action. Persisted
lifecycle events always carry processed_at; this is purely defensive. New test
covers a stray untimestamped running not clearing a timestamped pause.
|
||
|
|
7e6aeb2b7a |
feat(chat): favicon external links with secure link-preview tooltips (#5734)
* feat(chat): favicon external links with secure link-preview tooltips * fix(link-preview): address review findings - allow http fetches to match advertised http(s) link support - fix meta content regex to handle apostrophes and either quote delimiter - hash Redis cache keys so sensitive URLs are not stored verbatim - add per-user rate limit to the outbound-fetching route - render siteName-only previews instead of falling back to the URL * fix(link-preview): redact full URLs from failure logs * improvement(link-preview): render-time preview fetch, cheerio parsing, cleanup pass - fetch previews when links render (emcn tooltip shows instantly — hover prefetch had no delay to race); tooltip reads the warmed cache, eliminating the URL-then-preview flash - parse OG metadata with cheerio (already used server-side) instead of hand-rolled regexes + entity decoding, fixing double-decode and quote-handling classes - drop the no-longer-needed prefetch hook; remove dead side prop on Tooltip.Content - extract ExternalLink to a sibling module per component-size guidelines; fix TSDoc placement * fix(link-preview): https-only previews and full-document parsing - drop allowHttp: plain-http fetches would reach the URL validator's self-host loopback exception; previews are now explicitly https-only on both server (early null) and client (query never fires for http) - parse the full capped document instead of truncating at the first <body> substring, which could match inside head scripts/comments and drop metadata * improvement(chat): render mailto links as plain text |
||
|
|
86e6e1d26c |
feat(forking): excluded workflows (#5727)
* feat(forking): excluded workflows * improve sync preview |
||
|
|
f03b4337fa |
feat(mothership): mixture of models, search agent, persistent subagents, fork chat, inline questions (mothership v0.8) (#5410)
* feat(scout): add scout agent * fix(contracts): update contracts to include scout agent * feat(copilot): search agent (research+scout merge) + read-only table/KB tool handlers Mirrors mothership dev f90f9b05: - regenerated tool-catalog/tool-schemas mirrors (search trigger replaces research + scout; QueryUserTable / SearchKnowledgeBase entries) - queryUserTableServerTool / searchKnowledgeBaseServerTool: read-only wrappers delegating to the full user_table / knowledge_base handlers with hard operation allowlists (and outputPath export rejection on query_user_table) - display maps: 'search' agent label/title/icon added; research + scout entries retained so historical transcripts keep rendering - Search.id replaces Research.id in LONG_RUNNING_TOOL_IDS (it inherits research's long crawls) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(copilot): run_code compute-only handler; docs lint fix Mirrors mothership dev db60da94: run_code is the compute-only variant of function_execute for the search agent — same sandbox and inputs, no outputs.files / outputTable, so it cannot create or overwrite workspace resources. Wrapper handler hard-rejects the write vectors and delegates to executeFunctionExecute; run_code is deliberately absent from OUTPUT_PATH_TOOLS and the table output post-processor, so the name gating blocks writes even for leaked args. Added to LONG_RUNNING_TOOL_IDS, display title/icon maps, and the regenerated catalog/schema mirrors. Also removes two ineffective biome suppression comments in the docs workflow-preview (the rule doesn't fire in the docs app config). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(copilot): failed tool calls must surface their error in terminal data A failed handler result that carried a defined-but-empty output (the app-tool executor's 'Tool not found' ships output: {}) won the priority race in getToolCallTerminalData, so the resume payload's data — the only thing the model reads — was a bare {} with the error text dropped. The search agent retried run_code 20+ times blind against a stale server because every failure rendered as empty instead of 'Tool not found'. Failed calls now always carry error in their terminal data: merged into object outputs, wrapped alongside non-object outputs, preserved when the output already has an error field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(chat): render inline question tags from the agent in chat * fix(chat): let inert multi-step questions browse all prompts * improvement(chat): guard question answer formatting against sparse arrays * chore(copilot): drop user_memory from generated contracts and tool display Companion to mothership 8ae32e97 (user_memory tool removed — the feature no longer exists). Regenerates the mothership contract mirrors via generate-mship-contracts.ts, which also picks up the pending telemetry contract additions (gen_ai.agent.name labels, llm.client.context_tokens, llm.client.compactions, llm.request.compaction_trigger, llm.compaction.pause, gen_ai.usage.context_tokens), and removes the user_memory display title. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * improvement(chat): answered question card becomes the user turn; two select types only UI ordering: answering a question card no longer echoes a duplicate user bubble. The combined answer still goes on the wire as a user message, but the chat pairs it back to its card (strict 'Prompt — Answer' match, now uniform for single questions too) and renders the card as the answered recap — the card IS the user turn, and the next assistant message streams below it. The pairing is derived from the transcript, so live and reloaded renders are identical; a dismissed card followed by an unrelated typed message does not match and renders normally. Messages ending with a question card also drop the copy/thumbs actions row — the card is an input surface, not a reactable assistant turn. Question types are now single_select and multi_select only: text is removed (the free-text 'Something else' row covers it) and confirm collapses into single_select with Yes/No options. multi_select rows toggle with a check and the free-text row's arrow submits the step; answers are comma-joined labels plus any typed entry. Agent-supplied catch-all options ('Other', 'Something else', 'None of the above') are stripped at parse — the card always provides its own free-text row; a question left with no real options is invalid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * improvement(chat): question cards are single_select only Removes multi_select (and its toggle/check UI). The card is one shape: pick one option or type into the always-present 'Something else' row. Catch-all stripping and the transcript pairing/recap behavior are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * improvement(chat): bring back multi_select question cards Re-adds multi_select with a reworked interaction: option rows carry real checkboxes (emcn Checkbox chrome) instead of numbers and arrows, an option-styled Submit row confirms the step, and the "Something else" row reads as a plain option until clicked — then it becomes the focused text box, auto-checks, and can be unchecked without losing the typed text (blur with nothing typed reverts it). single_select behavior, catch-all stripping, and the transcript pairing/recap format are unchanged; multi_select answers are the checked labels comma-joined. * chore(copilot): regenerate mothership contract mirror (chat blob span attrs) * chore(copilot): regenerate mothership contract mirror (chat blob metrics) * feat(secrets): make output of generate api key a secret * feat(cli): add mkdir, mv, cp to mship tool set * feat(fork-chat): add fork chat to mothership * fix(fork-chat): fix messageid handling in fork chat * feat(credentials): agent-initiated oauth credential reconnect (#5488) * feat(credentials): agent-initiated oauth credential reconnect * fix(credentials): address reconnect review findings * improvement(credentials): log when connect draft name lookups degrade * fix(conflicts): remove migration * fix(conflicts): fix conflicts * fix(fork-chat): add migrations back * fix(ci): fix lint * fix(ci): fix bad import * fix(vfs): fix 500 char limit in vfs for skills and custom tools * feat(copilot): gate user skills to explicit slash-attach (#5536) Stop the mothership from adopting a workspace user-skill on its own: - Remove the load_user_skill tool and its three payload callers (chat payload, mothership execute route, inbox executor); delete lib/mothership/skills.ts + its test. Skills no longer autoload as the agent's own instructions. - Rename the workspace "## Skills" inventory to "## Agent Block Skills — NOT FOR YOU" with a one-line guardrail so a skill's description (e.g. "respond like a pirate") is not treated as an instruction. Skills reach the model as behavior only via explicit /-attach. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lots-of-things): lots of things * feat(subagents): add persistent subagents * fix(copilot): let edit_workflow set knowledge-base tag filters, and stop it clearing them (#5546) * fix(copilot): persist KB tag subblocks as JSON strings from edit_workflow The edit_workflow tool normalizes array-with-id subblocks (via normalizeArrayWithIds) but only re-stringifies the keys listed in JSON_STRING_SUBBLOCK_KEYS. `tagFilters` (knowledge-tag-filters) and `documentTags` (document-tag-entry) were missing, so agent-authored tag filters were stored as raw JSON arrays while those UI components read their value with JSON.parse (expecting a string). The result: an agent edit to a Knowledge block's tag filter persisted correctly but rendered as an empty filter in the editor (JSON.parse on an array throws -> []). - Add `tagFilters` and `documentTags` to JSON_STRING_SUBBLOCK_KEYS so edit_workflow stores them in the same shape the UI writes. - Make both components' parsers tolerate an already-parsed array on read, self-healing values already persisted in the broken (array) shape. Search execution was unaffected (parseTagFilters accepts arrays), so the value was never lost — only the editor render and round-trip were broken. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(copilot): expose KB tag definitions in VFS meta.json Surface each knowledge base's defined tags (displayName -> tagSlot) inline in its meta.json via serializeKBMeta, loaded in one batched query (loadKbTagDefinitions), so the agent can bind a knowledge-tag filter to a real tag slot instead of guessing a tag name it cannot otherwise see. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): stringify KB tag subblocks on the nested-node edit path The nested-node merge path normalized array-with-id subblocks but never re-serialized the JSON_STRING_SUBBLOCK_KEYS, so editing a block nested in a loop/parallel container still persisted tagFilters/documentTags (and conditions/routes) as raw arrays -- the exact shape the subblock components cannot JSON.parse. Route all four write paths through a single normalizeSubblockValue helper so the normalize and re-stringify steps cannot drift apart again, and extract the duplicated string-or-array read logic into parseJsonArrayValue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(copilot): tighten subblock serialization helpers Derive KbTagDefinitionSummary from the canonical TagDefinition instead of restating its fields, make parseJsonArrayValue generic so callers drop their `as T[]` casts, and unexport the three builders helpers that no longer have consumers outside the module now that normalizeSubblockValue fronts them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): stop stripping tagFilters/documentTags from the agent's workflow view sanitizeForCopilot dropped `tagFilters` and `documentTags` from the workflow state the agent reads (workflows/{name}/state.json), while edit_workflow is allowed to write both. The field was therefore write-only: on a follow-up edit the agent read back an absent field, concluded no filter was set, and cleared the user's tag filter. The redaction was introduced for workflow *export* (#1628) and is already enforced there by sanitizeWorkflowForSharing's key list. The duplicate in the copilot-only sanitizeSubBlocks was redundant for export and destructive for the agent. Removes it and pins the contract with a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): reject malformed KB tag values instead of clearing the filter `knowledge-tag-filters` and `document-tag-entry` had no arm in the `edit_workflow` input validator, so they fell through to the pass-through default. Any non-array value the agent supplied -- a double-encoded JSON string, an object, an unparseable string -- reached `normalizeSubblockValue`, where `normalizeArrayWithIds` coerces unparseable input to `[]`. The write path then persisted `"[]"` over the tag filter the user had configured. `condition-input` and `router-input` already guard against exactly this and return an actionable error to the model. Extend that arm to cover the two KB subblock types. It keys on subblock type, so the unrelated `tagFilters` short-input on the Algolia block is unaffected. `null`/`undefined` and empty arrays still clear the field, so intentional clears keep working. Also wrap `loadKbTagDefinitions` in try/catch. Tag definitions are an optional meta.json enrichment, but the query ran inside the top-level `Promise.all`, so a transient failure would reject the entire workspace VFS materialize and leave the agent unable to read any file. Now it degrades to a meta.json without tag definitions, matching the sibling materializers. Adds regression tests for both, plus the first tests for `parseJsonArrayValue`, the helper that keeps pre-fix raw-array rows readable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(copilot): collapse duplicate JSON-array parsing in edit-workflow builders `normalizeArrayWithIds` and `normalizeConditionRouterIds` each hand-rolled the same "accept a raw array or the JSON string these subblocks persist" parse. Extract `parseJsonArray`, which returns null when the value is neither, so each caller keeps its own distinct fallback: `[]` for the former, the untouched original value for the latter. Behavior-preserving. An empty array is truthy, so `[]` and `"[]"` still parse through rather than hitting either fallback. `validation.ts` has a third copy, but `builders.ts` already imports from it, so sharing the helper across the two would introduce an import cycle. Left as is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(copilot): specify tag name and legal operators in KB meta.json `tagDefinitions` exposed `displayName`, but a `tagFilters` entry must carry the key `tagName`. An entry written with `displayName` passes validation and persists, then filters nothing -- a silent failure. Rename the field at the serializer boundary; the DB column is untouched. Also emit the operators legal for each tag's `fieldType`, reusing `getOperatorsForFieldType`. `between` is valid for number and date but not for text or boolean, and the agent has no way to infer that. An unrecognized fieldType yields an empty list rather than throwing. Still unspecified, and deliberately out of scope: a filter entry's value key is `tagValue` (but `value` on documentTags), and `between` needs `valueTo`. Those describe the subblock entry shape, not the knowledge base, so meta.json is the wrong place for them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(copilot): pass a nullish subblock clear through instead of serializing "[]" `validateValueForSubBlockType` accepts null as an explicit clear, but `normalizeSubblockValue` then ran it through `normalizeArrayWithIds`, which coerces any non-array to `[]`, and persisted the string "[]". No data is lost either way -- "[]" and an absent field both mean "no filters". But it left the field present when the caller asked for it to be unset, so `sanitizeForCopilot` showed the agent an empty filter rather than an absent one, contradicting the absent-means-unset invariant the sanitizer documents. It also made Algolia's `if (params.tagFilters)` see a set value, since "[]" is truthy. An explicitly empty array still serializes to "[]" -- clearing with a value is distinct from clearing by omission. Reported by Cursor Bugbot on #5546. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(changes): huge changes * fix(subagents): lanes * fix(mship): transcript stuff * fix(subagents): thinking lanes * fix(superagent): fix superagent tools and checkpoints * fix(scope): scope subagent tools * fix(lint): fix lint * chore(db): regenerate workspace_files.message_id migration as 0260 on staging base * fix(superagent): fix superagent integration tools * improvement(questions): make something else a placeholder * chore(copilot): regenerate mothership contract mirror after staging rebase * feat(mship): add external mcps to mship * fix(ci): fix dev build * fix(stream): show thinking text * fix(ci): force redeploy * fix(mothership): keep chat forks outside workspace storage billing Preserve the product invariant that Mothership chat files are not charged as workspace file storage after the billing storage merge. * fix(uploads): restore listWorkspaceFiles throwOnError option dropped in rebase * fix(subagent-streaming): remove italics * fix(mothership): treat subagent lanes closed by subagent_end as settled so the between-steps thinking indicator isn't suppressed * fix(ui): thinking loader and rool names * fix(ui): add file * fix(thinking): show thinking during subagents * fix(chat): drop dead thinking-channel ternary after lanes skip thinking blocks * fix(thinking): remove thinking text * improvement(function execute): add timeout to function execute and stop showing text in subagents * fix(subagents): hide thinking text * fix(ff): move ff to go * improvement(superagent): nuke superagent * feat(main-agent): superagent into main agent * chore(db): regenerate workspace_files.message_id migration as 0262 on staging base * fix(credentials): restore reconnect params on shared createConnectDraft * fix(migrations): rebase with staging --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu> Co-authored-by: Justin Blumencranz <96924014+j15z@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> |
||
|
|
68c4f28b77 |
feat(clickup): webhook triggers, hierarchy selectors, and Docs knowledge-base connector (#5708)
* feat(clickup): webhook triggers with auto-managed subscriptions + hierarchy selectors * feat(clickup): KB connector (Docs v3), selector-route hardening, subblock migrations, registry-check regex fix * test(clickup): webhook provider handler tests + redact create-response secret in error logs * fix(clickup-connector): trim final page to maxDocs cap with precise listingCapped semantics * chore(clickup): lint formatting * fix(clickup): restore list-op location requiredness, split listSpaceId migration target, surface failed webhook rollback * fix(clickup): clickup.lists selector accepts listSpaceId context like clickup.folders * fix(clickup): integer-only maxDocs and location filters, depth-aware doc headings, most-specific-location hints |
||
|
|
3a632936ab |
feat(clickup): ClickUp integration — 23 tools, OAuth + API-token auth, attachment upload (#5702)
* feat(clickup): add ClickUp integration with OAuth + API-token auth, 23 tools, block, and attachment upload
- 23 tools covering tasks (create/get/update/delete/list/search), comments
(create/get/update/delete), attachment upload, tags, members, custom
fields, and the workspace/space/folder/list hierarchy
- OAuth provider wiring (authorization-code flow, non-expiring tokens) plus
clickup-service-account token-paste credential (personal pk_ API tokens),
with a shared clickupAuthorizationHeader helper (pk_ tokens sent bare,
OAuth tokens as Bearer)
- File upload follows the internal-route pattern: contract-validated
/api/tools/clickup/upload-attachment builds the multipart form and
returns UserFiles
- ClickUp block with per-operation subBlocks, canonical file param,
BlockMeta templates/skills, and gradient brand icon
- Generated integration docs page + hand-written service-account guide
* fix(clickup): apply validation-audit fixes across tools, block, and upload route
- Map documented task fields that were dropped: markdown_description,
subtasks, watchers, custom_fields, time_spent, folder, space — making
the include_subtasks / include_markdown_description options observable
- Expand verified filters: assignees/tags/due-date ranges on get_tasks and
search_tasks, include_closed on search_tasks; add due_date_time /
start_date_time flags and update-task assignee add/remove
- Guard update_comment against an empty body and require comment text in
the block; prefer markdown_content over content on create_list and make
markdown reachable for lists in the UI
- Drop the unverified 'required' field from custom-field outputs; read
both err and error keys from ClickUp error bodies; correct notify_all
wording
- Upload route: 100MB size cap, shared attachment mapper with full
documented response fields (version, thumbnails), base-URL constant
* fix(docs): restore clickup-service-account guide and shield it from doc generation
The generator prunes integration pages it does not derive from blocks;
add the hand-written ClickUp API-token guide to
HANDWRITTEN_INTEGRATION_DOCS so regeneration cannot delete it.
* fix(clickup): address review findings — dedupe catalog entries, config-time list parent validation, upload memory cap, unique icon gradient ids
- Remove duplicated clickup entries in docs meta.json and integrations.json
introduced by a double docs regeneration
- Add a Location dropdown for Get Lists / Create List so the folder ID or
space ID is conditionally required at configuration time instead of
failing at run time
- Pass the 100MB cap into downloadServableFileFromStorage so oversized
files abort during download instead of after full buffering
- Use useId()-derived SVG gradient ids for ClickUpIcon in both icon files
* chore(clickup): format integrations.json entry per biome
* improvement(clickup): final validation-pass refinements across tools and block
- create_task: add doc-backed sprint points param (parity with update)
- get_tasks/search_tasks: expose include_markdown_description
- update_task legacy numeric priority in list responses mapped instead of
dropped; create_comment omits absent response fields instead of
emitting sentinel ''/0 values
- order_by only sent when explicitly chosen (Default sentinel); comment
text no longer UI-required for update_comment (resolve-only and
assignee-only updates are valid per the tool contract, which still
rejects an empty body)
- add_tag_to_task sends no request body per docs; upload tool tolerates
non-JSON error responses
* fix(clickup): tolerate nested user wrapper in member mapping
The task/list member endpoints document a flat member object; accept the
workspace-members-style nested { user: {...} } wrapper as well so both
shapes map correctly.
* fix(clickup): map size-limit errors from download/compile to a 400 upload-size response
downloadServableFileFromStorage enforces maxBytes on both the raw download
and the resolved (compiled) artifact via PayloadSizeLimitError; catch it in
the route so oversized content returns the intended 400 instead of
bubbling to the generic 500 handler.
* feat(clickup): add custom field values, checklists, and time tracking (15 tools, 38 total)
- Set/remove custom field values on tasks (PUT/DELETE /task/{id}/field/{field_id});
block value input parses JSON for structured field types, plain values pass through
- Checklist CRUD: create/rename/reorder/delete checklists and create/update/
delete checklist items (assign, resolve, nest), mapped from the documented
{checklist} response shape
- Time tracking: list entries in a date range (assignee/location filters,
task-tag and location-name includes), create/update/delete entries, start/
stop timers, and read the currently running timer; entries mapped from the
documented data envelope with negative-duration running semantics
- Block gains 15 operations with conditionally-required fields, timestamp
wand configs, tri-state billable/resolved dropdowns, and a single-location
filter selector matching the API's one-location-filter rule
* fix(clickup): new-tools audit fixes — POST for set custom field value, tolerant time-entry envelopes, richer mappings
- Set Custom Field Value uses POST per the live reference OpenAPI (the
llms mirror shows PUT; the reference console spec is authoritative)
- delete_time_entry maps the documented array envelope; create_time_entry
tolerates both data-wrapped and flat echo bodies
- Time entries surface task_tags and task_location so the include switches
are observable; checklists carry date_created
- Custom field value input parses any JSON literal (numbers, booleans,
arrays, objects) and passes plain text through
- Update Time Entry supports duration edits; single-assignee time ops get
their own field so a comma-separated list can't silently NaN out
* fix(clickup): send explicit date-time flags whenever a date is set
The due/start date-time switches previously only transmitted true; a
timed date could never be flipped back to date-only. The flag is now sent
as an explicit boolean whenever the corresponding date is provided and
omitted otherwise.
* improvement(clickup): final per-tool audit polish — checklist item children, tolerant comment date
- Checklist items surface the documented children array of nested item IDs
- create_comment tolerates a string-typed date in the response
* fix(clickup): reject empty update_task bodies with a clear local error, matching sibling update tools
|
||
|
|
54b35a4f0e |
improvement(deployments): bugfixes for run-block, airtable + external sub management (#5680)
* improvement(webhooks): external subscription management
* ui/ux
* remove test file
* fix tests
* address comments
* address comments
* update to grain v2 api
* improvement(grain): hide auto-registered webhook URL on v2 triggers
* Revert "improvement(grain): hide auto-registered webhook URL on v2 triggers"
This reverts commit
|
||
|
|
e2ea49ea7e |
feat(instagram): add Instagram integration (#5568)
* feat(instagram): add Instagram Login OAuth, tools, and block * feat(instagram): add Gmail-style media uploads for publish ops Resolve UserFiles to Meta-fetchable presigned HTTPS URLs (600s TTL) via internal publish routes, and fix OAuth scope storage plus connect-draft wiring so Instagram Login publishing is testable end-to-end. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(instagram): simplify messaging tools to direct requests, clean up types * fix(instagram): parallelize carousel child polling, enforce 2-10 items, extend poll window and insights periods, use canonical user_id in OAuth callback * fix(instagram): resolve user id from user_id only, accept numeric user_id Co-authored-by: Cursor <cursoragent@cursor.com> * fix(instagram): use form/query params for publish and comment endpoints, request message timestamps explicitly Co-authored-by: Cursor <cursoragent@cursor.com> * fix(instagram): normalize Graph ID outputs to strings so downstream .trim() calls are safe Co-authored-by: Cursor <cursoragent@cursor.com> * style(instagram): use brand gradient tile for the block icon Match the official Instagram look by filling the tile with the orange–pink–purple radial gradient so the white camera glyph sits on a full-bleed brand background. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(instagram): tighten publish defaults and cloud-storage upload UX Default Reel share-to-feed to Yes, drop unused media fields params, share publish transform helpers, and warn when cloud storage is missing for Meta-fetchable uploads. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(instagram): fail closed when cloud storage status is unknown Treat loading/error as blocked for requiresCloudStorage uploads, show the warning once the check finishes, and disable selecting local workspace files Meta cannot fetch. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(instagram): proactively refresh long-lived tokens before expiry Meta only allows refreshing still-valid Instagram tokens, so refresh within 14 days of expiry (after the 24h age gate) instead of waiting until after accessTokenExpiresAt. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(oauth): restore TikTok clientIdParamName JSDoc after merge The staging merge dropped the opening /** on ProviderAuthConfig.clientIdParamName, which broke TypeScript parse in CI. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(api): Zod-contract storage-status and ratchet validation baseline Wire /api/files/storage-status through a shared route contract so the strict API validation audit stays at zero non-Zod routes after the new Instagram cloud-storage check. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(instagram): match Gmail advanced media placeholders Drop public-URL paste hints from advanced fields and the cloud-storage banner so the UI mirrors Gmail attachments. Co-authored-by: Cursor <cursoragent@cursor.com> * code review + hide from toolbar * address comments * fix(instagram): drop hidden Instagram from OAuth catalog pin test Instagram is hideFromToolbar so it is excluded from integrations.json; the pinned slug map must not expect it until the block is visible again. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(instagram): validate client ID before creating connect draft Avoid orphan pending credential drafts when INSTAGRAM_CLIENT_ID is missing, matching the Shopify authorize ordering. 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> |
||
|
|
66d1e61beb |
feat(skills): canonicalize skills to a single source with generated .claude/.cursor projections (#5609)
* improvement(cleanup-skill): parallelize analysis, apply fixes sequentially * improvement(cleanup-skill): add comment-reduction pass; mirror 6 missing skills into .claude/commands * fix(cleanup-skill): substitute parsed scope into analysis passes instead of literal <scope> * fix(cleanup-skill): parse fix token anywhere; preserve pass labels through convergence for ordered apply * fix(cleanup-skill): apply Step 1 proposals content-anchored, re-derive when a prior pass invalidated the snippet * fix(babysit-skill): correct garbled --reverse explanation across all three copies * fix(skills): propagate parallel cleanup to cursor/agents copies; disambiguate babysit /ship refs in claude copy * fix(skills): port url-state + comment passes to cursor/agents; clarify converge pass-label ordering * feat(skills): canonicalize skills under .agents/skills with generated .claude/.cursor projections Establish .agents/skills/<name>/SKILL.md as the single source of truth (latest content reconciled per skill from the three drifted copies), and generate the .claude/commands and .cursor/commands projections from it via scripts/sync-skills.ts. Adds skills:sync/skills:check, a CI gate, a pre-commit regen hook, and CONTRIBUTING docs. Structurally fixes prior drift (e.g. abbreviated .claude ship -> full ship). * fix(skills): strip leaked XML tags from skill tails; clarify lint:check has no per-file target Removes stray </content>/</invoke> markup that leaked into add-block, add-connector, add-hosted-key canonical skills, and reword the cleanup skill's lint step to note bun run lint:check runs repo-wide via turbo (no per-path API). Projections regenerated via skills:sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QjefwescJoHZ6zcc3C17FR * fix(add-block-skill): restore unknown-output stop in Final Validation Re-add the "if any tool outputs are still unknown, tell the user instead of guessing block outputs" step that was dropped when Final Validation step 5 became the BlockMeta template check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QjefwescJoHZ6zcc3C17FR --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
04535ee33b |
feat(buffer): add Buffer integration with posts, channels, and ideas (#5637)
* feat(buffer): add Buffer integration with posts, channels, and ideas * fix(buffer): return clear tool error when account lookup yields no account * fix(buffer): default schedulingType server-side so basic-mode blocks never fail validation * fix(buffer): probe Content-Type for extensionless media URLs so videos are not sent as images * feat(buffer): add get_ideas and get_idea_groups tools, harden media URL classification * fix(buffer): guard missing post on PostActionSuccess responses |
||
|
|
ef7c8e24b2 |
feat(platform): settings permissions, admin, billing attribution (#5545)
* fix(invites): preserve active organization for external access Keep organization activation server-owned so failed membership checks cannot clear a valid session context. * feat(admin, billing, settings): cleanup settings visibility, billing actor resolution, new admin routes * address comments * chore(db): reset pending migrations before staging merge Remove locally generated migrations so they can be regenerated against the latest staging schema without preserving stale snapshots or numbering. * regen migrations * address comments * chore(db): reset generated migrations before staging merge Remove this branch's generated migrations so they can be regenerated against the latest staging schema with fresh numbering. * upgrade global work * fix lint * address comments * legacy callbacks correctness * address comments * update * guardrail attribution |
||
|
|
7962236719 |
improvement(custom-blocks): hardened delete with usage count + per-input 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 |
||
|
|
f4d47ed826 |
feat(slack): reusable custom bot credentials, slack_v2 block (preview), 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> |
||
|
|
5d3809a0e3 |
feat(tiktok): add tiktok trigger, block (#5504)
* 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> |
||
|
|
5b7513f15d |
feat(blocks): add block visibility gating (preview blocks + AppConfig reveals) (#5526)
* fix(deps): install xlsx from @e965/xlsx npm mirror The dependency was pinned to a direct tarball on cdn.sheetjs.com, which now returns 403 (Cloudflare bot-challenge) to automated clients, breaking bun install in CI. npm's own xlsx is frozen at 0.18.5, so switch to the @e965/xlsx mirror which republishes the identical 0.20.3 CDN build to the npm registry. No code changes needed — all imports use bare 'xlsx'. * feat(blocks): add block visibility gating (preview blocks + AppConfig reveals) * fix(blocks): reset visibility to fail-closed empty state on workspace switch * fix(blocks): carry kill-switch entries across workspace-switch visibility resets * chore(deps): revert stray local xlsx-mirror commit (keep staging's pinned source) * chore(skills): rename gate-block skill to add-block-preview |
||
|
|
6e87d74a93 |
feat(jupyter): add Jupyter integration (contents, kernels, sessions) (#5527)
* feat(jupyter): add Jupyter integration (contents, kernels, sessions) - 16 tools covering Contents, Kernels, Kernelspecs, and Sessions REST APIs - File upload/download via UserFile, following the Box upload pattern - Block with operation dropdown, token auth, and 8 catalog templates - Registered tools + block, generated docs, bumped API validation baseline * fix(jupyter): address Greptile review — SSRF guard, upload path ambiguity, silent notebook fallback - Route uploads through validateUrlWithDNS + secureFetchWithPinnedIP (matches Grafana/1Password pattern) instead of a raw fetch to the user-supplied server URL - Replace the upload path/trailing-slash heuristic with an unambiguous directory + filename split - create_file no longer silently writes an empty notebook when notebook content is malformed JSON — it now errors clearly * fix(jupyter): request content=1 when listing directory contents Without it, Jupyter Server returns directory metadata with content: null, so jupyter_list_contents always reported an empty items array. * fix(jupyter): reject path-traversal segments in Jupyter content paths encodeJupyterPath now rejects '.'/'..' segments across the whole path (shared by all 16 tools, not just upload); the upload route returns a clean 400 when it's hit. * fix(jupyter): close remaining path-traversal and redirect-credential gaps - extract the traversal check out of encodeJupyterPath into a shared assertion, and apply it to body-only path fields (rename newPath, copy copyFromPath, session path) that never flowed through URL encoding and so skipped the check - pass stripAuthOnRedirect to the upload route's secureFetchWithPinnedIP call so a malicious Jupyter server can't redirect the PUT to another origin and receive the caller's token * fix(jupyter): also reject percent-encoded traversal segments A segment like %2e%2e wouldn't match the literal '..' check. Now decodes each segment before comparing, in addition to the literal check, so an already-encoded traversal attempt is caught too. * fix(jupyter): route all 15 remaining tools through an internal proxy for HTTP/private-host support and no redirects The generic external tool executor blocks plain-HTTP and non-localhost private-IP hosts by default, so every non-upload Jupyter operation could fail against typical self-hosted setups (LAN IP, docker hostname, or even literal localhost on a hosted deployment) even though the upload route worked via its own internal route. Added /api/tools/jupyter/proxy (DNS-pinned, allowHttp, maxRedirects: 0) that mirrors the upstream Jupyter response verbatim, matching the established pattern for self-hosted-arbitrary-host integrations (Grafana, 1Password) instead of the generic executor path. Each tool's request block now posts to the proxy instead of building a direct external URL; transformResponse and outputs are unchanged since the proxy response mirrors upstream status/body exactly. Also switches the upload route from stripAuthOnRedirect to maxRedirects: 0 — stronger, since it stops the uploaded file body (not just the token) from ever reaching a redirect target. * fix(jupyter): validate proxy path at the trust boundary, reject path separators in upload filename - The proxy route now independently validates the incoming path field for traversal segments instead of only relying on tool-side validation before the request reaches it — the route is a shared internal boundary, not something only our own tool code can call - The upload route's fileName can come from an advanced override or the legacy fileContent path and could itself contain '/' or '\', silently nesting the upload deeper than the directory param specified. Now rejected outright before joining. * fix(jupyter): decode the whole path before splitting, not per-already-split segment A segment like foo%2f..%2fsecret has no literal slash, so splitting on literal '/' first and decoding each piece in isolation treats it as one opaque segment and never notices the '..' hiding behind the encoded slash. Decode the full path once, then split and check every segment the target server's own single URL-decode pass would see. |
||
|
|
4a80374c03 |
improvement(forking): unlink, settings page migration, deployed chats, public apis, external mcp servers/tools, special subblocks (#5505)
* bad checkpoint * stash * add unlink, move UI to settings pages * fix edge cases * remove dead code * remove migration 0255 ahead of staging merge (regenerated after) * regenerate workflow_mcp_server enum migration on top of staging (0257) * fix tests * more tests * address comments * consolidate fork migrations into 0257 (enum value + activity metadata indexes) * acquire MCP server locks before reads in attachment reconcile (TOCTOU) * move into ee folder + ui perm gates * use randomInt from @sim/utils/random for chat identifier suffix --------- Co-authored-by: Siddharth Ganesan <siddharthganesan@gmail.com> |
||
|
|
9d34fbea1b |
refactor(utils): consolidate duplicated helpers onto @sim/utils (#5509)
* refactor(utils): consolidate duplicated helpers onto @sim/utils Replaces ~90 hand-rolled reimplementations of error-message extraction, postgres error-code checks, sleep, Math.random, retry/backoff, object filtering/omission, noop, string truncation, date/time formatting, email normalization, and plain-object type guards with the shared @sim/utils exports. Wires check:utils into CI (test-build.yml) so these patterns don't regress. * fix(test): mock @sim/utils/random instead of Math.random in schedule-execute tests The schedules/execute route previously used Math.random() for jitter delay; this consolidation PR switched it to randomInt() from @sim/utils/random, which is backed by crypto.getRandomValues() rather than Math.random(). The route.test.ts spies on Math.random() no longer had any effect, so jitter became real random delay instead of the deterministic 0ms the tests expect, causing intermittent 10s timeouts in CI. * fix(retry): preserve uncapped Retry-After comparison in tools/index.ts parseRetryAfter() caps its return value at 30s by default. tools/index.ts compares the parsed Retry-After against a caller-configured maxDelayMs to decide whether to skip a retry entirely -- capping before that comparison silently defeats the skip check whenever maxDelayMs is configured above 30s, since a Retry-After between 30s and maxDelayMs would incorrectly look "within limits" and get retried instead of skipped (caught by Cursor Bugbot). Added an optional maxMs param (default unchanged) so tools/index.ts can request the raw, uncapped value for its own comparison while backoffWithJitter still clamps the actual sleep duration to maxDelayMs. Added a regression test covering maxDelayMs > 30s. * fix(utils): fall back to Intl-resolved abbreviation for unmapped timezones getTimezoneAbbreviation only covered 9 hardcoded IANA zones and returned the raw IANA string for everything else, degrading schedule descriptions for zones like Europe/Berlin or America/Toronto (caught by Greptile). The deleted local implementation in schedules/utils.ts resolved any valid IANA timezone generically via Intl.DateTimeFormat's short timeZoneName. Restore that as a fallback so only genuinely invalid timezone strings return themselves unchanged. |
||
|
|
8fcce5100c |
fix(aws): align cloudwatch, cloudformation, athena, codepipeline with live API docs (#5483)
* fix(aws): align cloudwatch, cloudformation, athena, codepipeline with live API docs - cloudwatch: fix list_metrics pagination (wasn't draining pages past 500), add MaxRecords cap validation to describe_alarms; add describe_alarm_history, filter_log_events, put_log_group_retention - cloudformation: fix get_template missing TemplateStage param, fix invalid ModuleTag in block metadata; add full stack lifecycle tools (create/update/delete/cancel_update_stack, create/describe/execute_change_set, get_template_summary) - athena: fix missing .trim() on query/named-query ID fields; add delete_named_query, batch_get_query_execution, list_databases, list_table_metadata - codepipeline: fix missing rollbackMetadata field in list_pipeline_executions response; add get_pipeline, list_action_executions, disable/enable_stage_transition * fix(aws): require template on cloudformation change-sets, bump route-count baseline - cloudformation create_change_set now rejects requests missing both templateBody and usePreviousTemplate, matching update_stack (Cursor Bugbot finding) - bump check:api-validation route-count baseline 906->917 to reflect the 19 new fully contract-bound routes added in this PR (0 boundary violations) * fix(aws): address Greptile round-1 review findings - cloudwatch describe_alarm_history: always request both MetricAlarm and CompositeAlarm types, even when alarmName is provided (was silently returning empty history for composite alarms queried by name) - cloudformation: add validateAwsRegion refinement to region field on the 7 new write-path contracts (update/delete/cancel-update-stack, create/describe/execute-change-set, get-template-summary), matching the pattern already used elsewhere - cloudformation: destroy the AWS SDK client in a finally block on the same 7 new write routes, matching the pattern used by every other new route in this PR * fix(aws/cloudformation): add missing region validation and client cleanup to create-stack - Add validateAwsRegion refinement to create-stack contract (completes P2 fix from Greptile review) - Wrap AWS SDK call in try/finally with client.destroy() (completes P2 fix from Greptile review) - Aligns create-stack with the pattern used across all 7 other new CloudFormation routes Co-authored-by: Waleed <waleedlatif1@users.noreply.github.com> * fix(aws): final validation pass — bound missing limits, close consistency gaps - cloudformation create_stack: add missing validateAwsRegion refine and client.destroy() finally block, matching sibling write routes - cloudwatch get_metric_statistics: cap statistics array at AWS's 5-item limit - cloudwatch get_log_events: cap limit at AWS's 10,000-record max - athena batch_get_query_execution: surface engineExecutionTimeInMillis/queryPlanningTimeInMillis/queryQueueTimeInMillis, matching the sibling get_query_execution tool's Statistics mapping * fix(aws/athena): destroy AWS SDK client on the 4 new athena routes Cursor Bugbot finding: batch_get_query_execution, delete_named_query, list_databases, and list_table_metadata created an AthenaClient but never called client.destroy(), unlike every other new route in this PR. Wrapped each in try/finally to match. * fix(aws/athena): add validateAwsRegion refinement to the 4 new contracts Greptile finding: delete_named_query, batch_get_query_execution, list_databases, and list_table_metadata accepted any non-empty string for region, unlike every other new contract in this PR. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Waleed <waleedlatif1@users.noreply.github.com> |
||
|
|
b686111082 |
feat(textract): migrate to AWS SDK, add AnalyzeExpense and AnalyzeID (#5456)
* feat(textract): migrate to AWS SDK, add AnalyzeExpense and AnalyzeID - Replace hand-rolled AWS SigV4 signing with @aws-sdk/client-textract, matching sibling AWS integrations (secrets_manager, s3, sts) - Add Analyze Expense operation (invoice/receipt structured extraction) via AnalyzeExpense/StartExpenseAnalysis+GetExpenseAnalysis - Add Analyze Identity Document operation (AnalyzeID) with optional back-of-ID page - Add an operation selector to the textract_v2 block; defaults to the existing Analyze Document behavior for backward compatibility - Add tests for tool body/response mapping and route-level AWS response normalization * fix(textract): forward URL documents and fix ambiguous error status - textract_analyze_expense/textract_analyze_id now fall back to filePath/filePathBack when the document input is a URL string rather than an uploaded file object, so advanced "File reference" URL inputs actually reach the API (Cursor Bugbot) - mapTextractSdkError defaults to 500 (not 400) when the AWS SDK error has no HTTP status, since that implies a server-side/network failure rather than a bad request (Greptile) * fix(textract): stop stale processingMode from hiding ID document fields - Front-document fields (fileUpload/fileReference) are shared across all 3 operations; gate them with a values-aware condition so switching to Analyze Identity Document keeps them visible even if a stale processingMode='async' is left over from a previous operation - S3 URI field now also requires operation !== 'analyze_id', since that operation never supports S3 input * fix(textract): preserve first-page metadata across async pagination pollTextractJob's merge callbacks spread only the latest page, dropping any field (DocumentMetadata, model version) the first page had but a follow-up NextToken page omits. Merge accumulated first so later pages only override fields they actually return. * fix(textract): pass through the real upstream status for filePath fetch failures fetchDocumentBytes hardcoded 400 for any non-OK response from a document URL, masking transient 5xx failures from the document host as client errors and blocking tool-execution retries. Use the actual response status instead. * chore(textract): drop redundant inline comments |
||
|
|
24ebba9acc | chore(credential-sets): cleanup feature (#5460) | ||
|
|
fb3f95d5dc |
feat(aws): expand SES/STS/Secrets Manager tool coverage, fix API alignment gaps (#5450)
* feat(aws): expand SES/STS/Secrets Manager tool coverage, fix API alignment gaps
- SES: add suppression list management, email identity CRUD, template
update, configuration set creation, custom verification email (10
new tools); fix silent httpsPolicy drop in create_configuration_set
and unvalidated suppression reason enum in list_suppressed_destinations
- STS: add AssumeRoleWithWebIdentity and AssumeRoleWithSAML (unsigned,
no static credentials required); extend assume_role with
policyArns/tags/transitiveTagKeys session params
- Secrets Manager: add describe_secret, tag_resource, untag_resource,
restore_secret, rotate_secret; fix list_secrets dropping
rotation/version metadata fields; normalize tool versions to 1.0.0
and alphabetize registry entries
All 31 tools verified param-by-param against live AWS API docs across
two independent audit passes.
* fix(aws): address review findings on SES config/identity and Secrets Manager rotation
- ses_create_configuration_set: validate suppressedReasons against
BOUNCE/COMPLAINT enum before calling AWS (was silently reaching AWS
as a generic 500 for bad values); tags now a proper Zod array schema
instead of a string with route-side JSON.parse
- ses_create_email_identity: dkimSigningAttributes and tags now proper
Zod object/array schemas instead of strings with route-side
JSON.parse, matching the pattern used elsewhere (e.g. sts_assume_role
tags, secrets_manager_tag_resource)
- secrets_manager_rotate_secret: reject automaticallyAfterDays and
scheduleExpression when both are supplied — AWS RotationRules
accepts only one
- sts createUnauthenticatedSTSClient: corrected a misleading comment
claiming these calls are fully unsigned; the SDK still falls through
its default credential provider chain
* fix(ses): correct json input types for tags/dkimSigningAttributes
The SES block declared the tags and dkimSigningAttributes block inputs
as 'string' instead of 'json', so the generic block executor never
parsed the JSON code-editor value before forwarding it — workflow runs
sent a raw JSON string where the contract now expects a structured
object/array, failing validation. Also corrected the corresponding
tool param TypeScript types, which were still typed as string | null.
* fix(ses): stop coercing switch string 'false' to true in create_configuration_set
Boolean('false') evaluates to true, so turning off the
reputationMetricsEnabled or sendingEnabled switch sent the opposite of
the user's choice to SES. Match the established === 'true' string
comparison pattern used elsewhere in the codebase.
* fix(sts): stop double-parsing assume_role session tags input
tags was declared as a 'json' block input, so the generic executor
JSON.parse'd it before the switch-case handler ran — but that handler
already converts the raw table-rows array (or a passthrough string)
into the JSON string the sts_assume_role contract expects. Declaring
it 'json' broke that conversion for non-string inputs. Reverted to
'string' so the handler's existing string/array disambiguation runs
on the untouched raw value.
* fix(sts): supply placeholder credentials to the unauthenticated client
createUnauthenticatedSTSClient omitted credentials entirely, so the
SDK's signing middleware fell through the default credential provider
chain and threw CredentialsProviderError before the request was sent
in any environment with no ambient AWS identity — even though
AssumeRoleWithWebIdentity/AssumeRoleWithSAML never check the
signature. Static placeholder credentials skip that resolution
without granting or requiring any real IAM identity.
|
||
|
|
82eff5435d |
feat(custom-block): deploy a workflow as a reusable org-scoped block (#5407)
* feat(custom-block): deploy a workflow as a reusable org-scoped block * fix(custom-block): reseed deploy form, guard duplicate publish, run child deployed * test(custom-block): isolate custom-block rows fetch in execution-core test * fix(custom-block): allow cross-workspace exec, org-scope authority, keep field ids, hide disabled * feat(custom-block): run child under source owner's identity, workspace, and env * fix(custom-block): bind publish authz to the source workflow's workspace * fix(custom-block): gate edit/delete on source-workspace admin, not org admin * chore(custom-block): rebaseline route count to 887 after staging merge * fix(custom-block): sanitize failure output so it can't leak source workflow internals * fix(custom-block): derive inputs and curated outputs from deployed state, not draft * fix(custom-block): hide disabled blocks from the toolbar palette too * fix(custom-block): bill nested + failed-run hosted cost; expose real inputs to the agent * fix(custom-block): enforce enterprise + flag gate at every consumption path |
||
|
|
759dddbf4c |
feat(billing): dedicated Credit usage page with date-range filter and CSV export (#5405)
* fix(billing): apportion per-row credit costs so they sum to the page total Cursor Bugbot (medium): each row rounded its own dollar cost to credits independently while the header total rounded the summed dollars once — over enough rows those two roundings can visibly disagree, the exact "line items don't add up to the total" class of bug apportionCredits was already built to prevent (used by the trace view / cost breakdown). Route now apportions each page's row credits against that page's dollar sum instead of rounding rows independently. Added a test with three sub-cent rows that would each independently round to 0 credits (but sum to 1) to prove the reconciliation holds. * fix(billing): dim stale credit usage rows while a new period loads Cursor Bugbot (medium): keepPreviousData kept the prior period's rows and total on screen while a newly selected period fetched, but the dropdown label updated immediately — so during the transition the displayed numbers were labeled under a period they didn't belong to. Now reads isPlaceholderData (the standard TanStack Query signal for "this data is a stale placeholder, not a fresh fetch for the current key") and dims the list while it's true, matching the same flag already used for this exact purpose in integration-skills-section.tsx. * fix(billing): show "<1 credit" for rows apportioned to 0 Cursor Bugbot (low): with apportioned per-row credits, a row with a real but sub-credit dollarCost can legitimately apportion to 0 credits once a sibling row absorbs the shared rounding remainder — rendering a flat "0 credits" reads as if nothing was charged, inconsistent with formatCreditCost's "<1 credit" wording used elsewhere in billing. Added dollarCost to the wire response (needed to distinguish a genuinely free row from a rounded-to-zero one) and a small formatRowCredits helper that only changes the label, not the underlying creditCost number, so the page-total reconciliation from the prior fix is unaffected. * fix(audit-logs): fix broken Custom range picker, trim time-range presets Custom range silently did nothing: the time-range trigger was a ChipSelect (Radix DropdownMenu, modal by default), and selecting "Custom range" opened the Calendar popover in the same tick the modal menu began its close/focus-lock cleanup, trapping the popover non-interactive. Swapped to ChipCombobox (Radix Popover, non-modal), mirroring the already-working pattern in the main Logs page exactly. Also trimmed the preset list from 11 to 8 entries (dropped Past 30 minutes/12 hours/14 days) so the menu fits without scrolling. * feat(billing): dedicated Credit usage page with date-range filter and CSV export Follow-up to #5391 per team feedback in Slack: move the credit usage list out of the inline Billing section into its own page, redesign rows to show source ("Chat", "Workflow: <name>") instead of a raw model description + badge, and add real date-range filtering and export. - Billing settings now shows a compact glance (30-day total + a "View usage logs" link) instead of the full inline list. - New /settings/billing/credit-usage page (sibling of [section], mirrors the secrets/[credentialId] detail-route pattern) with day presets (Today/7d/30d/All time) plus a working Custom range picker — the same ChipCombobox+Popover+Calendar wiring the audit-logs fix in this branch uses, not the broken ChipSelect pattern. - Rows show the humanized source label, or "Workflow: <name>" for workflow-sourced events (new server-side workflow-name lookup, batched per page). Dropped the redundant badge and raw model description. - CSV export of the currently-filtered logs via a new GET .../usage-logs/export route (mode: 'text' contract, synchronous single-response CSV — the dataset is a bounded per-user ledger, not a workspace-wide export, so no async job queue needed). Query-filter logic (date-range resolution, workflow-name lookup) is shared with the list route via shared.ts rather than duplicated. - period/startDate/endDate live in the URL via a co-located search-params.ts; the list query keeps keepPreviousData + isPlaceholderData dimming during filter transitions, matching the behavior already shipped in #5391. Verified live end-to-end: back link navigation, custom range picker opens and applies, day presets, CSV export downloads and matches the on-screen rows exactly (credits reconcile with the total), compact Billing summary + link. * refactor(billing): move workflow-name enrichment into getUserUsageLogs, dedup helpers /simplify pass over the credit-usage-page branch (4 parallel review angles: reuse, simplification, efficiency, altitude): - getUserUsageLogs now LEFT JOINs workflow and returns workflowName directly (matching lib/logs/list-logs.ts's established pattern), eliminating the route-layer resolveWorkflowNames query that both the list and export routes previously ran independently. - Added includeSummary (default true) to getUserUsageLogs so the export route's cursor loop can skip the cursor-independent SUM/GROUP BY aggregate it never reads — that aggregate was being recomputed on every page of a paginated export for no reason. - Fixed an off-by-one in the export's pagination loop: `<= MAX_EXPORT_ROWS` let it fetch one more full page past the cap only to discard it; `< MAX_EXPORT_ROWS` with a shrinking per-page limit never overshoots. - Deduplicated the SOURCE_LABELS map (was defined identically in both the page and the export route) into a shared, DB-free source-labels.ts both can import. - Export route now builds CSV rows via lib/table/export-format.ts's toCsvRow/formatCsvValue instead of a hand-rolled escaper. - Added formatApportionedCreditCost to conversion.ts so the page's row rendering shares its zero/sub-credit wording with formatCreditCost instead of re-deriving the same three-way branch. - Replaced the generic requireStartDateForCustomPeriod<Schema> contract helper (nontrivial generic bound for a single four-line refine used at two call sites) with a plain shared error-options object. - Removed the credit-usage page's dateRangeAppliedRef guard — a controlled Radix Popover never re-invokes onOpenChange in response to the parent's own setState call, so the guard was defending against a re-entrant close that can't happen. - Added a modal prop to ChipSelect (forwarded to the underlying DropdownMenu, which already supported it) so a future call site that hits the same "modal select traps a same-tick Popover" bug the audit-logs Custom range fix worked around has a real fix available instead of having to swap components again. Re-verified live end-to-end after the refactor: workflow-name resolution, credit reconciliation, and CSV export all still correct. * fix(billing): drop Dollar cost from the CSV export, strip inline comments We only surface credits to the user, not the underlying dollar figure — "Dollar cost" was the one place the export literally displayed a dollar amount (the rest of the codebase uses dollarCost purely as an internal signal to distinguish a sub-credit charge from a genuinely free event, never rendered as a "$" value). * fix(billing): export honors partial custom date range, surfaces truncation Greptile (P1) and Cursor Bugbot independently caught the same bug: handleExport only forwarded startDate/endDate when BOTH were truthy, but the list query and both API contracts treat endDate as optional for a custom period (defaults to now). A user landing on a bookmarked ?period=custom&startDate=... URL would see populated rows and an enabled Export button, then get a 400 on click since the export omitted the required startDate too. Fixed by forwarding each date independently, matching the list query's existing behavior. Also addressed Greptile's other two findings: - The export route now sets X-Export-Truncated so a 5,000-row-capped download is visible to the user (a toast), not just a server log. Reading that header meant switching the trigger from a plain anchor navigation to fetch+blob — an anchor can't inspect the response before the browser commits to the download. - resolveDateRange now throws explicitly when a custom period is missing startDate instead of silencing the null check with `as string`, which would have produced a silent Invalid Date if ever called without prior contract validation. * fix(billing): remove the export's arbitrary row cap, fix a cursor pagination bug it exposed A personal credit ledger doesn't have the same unbounded-growth problem a workspace table does — capping the export at 5,000 rows just meant long-tenured or high-usage accounts (exactly the ones most likely to need a full export to reconcile a billing question) got silently truncated. Replaced the cap with a 50,000-row circuit breaker that should never fire in normal use (logged as an error, not a warning, if it ever does) and bumped the page size from 500 to 1,000 to cut round trips. Removing the cap surfaced a real, pre-existing bug in getUserUsageLogs's cursor pagination: a raw `sql` template embedded a JS Date object directly as a bound parameter, which the postgres driver can't serialize (unlike drizzle's typed gte/lte operators, which already handle Date correctly elsewhere in the same function). It only ever manifested past the first page, which nothing before this export route's tight multi-page loop reliably exercised. Replaced the raw sql template with drizzle's typed lt/eq/or/and operators, matching the pattern already proven correct in this file. Verified live: seeded 6,000 rows (past the old cap) and confirmed the export downloads all of them in one request with credits reconciling exactly against the total. * perf(billing): skip the redundant cursor lookup when the caller already has it The export loop holds the previous page's rows in memory, so its next cursor's createdAt is already known — getUserUsageLogs was still re-resolving it via an extra DB round trip every page regardless. Added an optional cursorCreatedAt to skip that lookup when provided; the list route's existing callers are unaffected since they don't pass it. Verified live: zero cursor-lookup queries fired across a 3,500-row / 4-page export that previously issued one per page. * fix(billing): apportion credits over the whole filtered set, not per page/call Cursor Bugbot caught this: the list route apportioned each page's rows against only that page's own dollar total, while the export apportioned every exported row against the complete set's total. Since apportionment depends on the full set, the same log could show a different creditCost between the list and the export, or even between two pages of the same "Load more" list — and the sum of every loaded row could visibly drift from the "Total" header shown above them once more than one page had loaded. Extracted getUsageCreditsByLogId — a single, shared, whole-filter apportionment lookup both routes now call instead of each computing their own subset locally. The list route calls it once per page request (same cost profile as the summary aggregate it already pays for every page); the export calls it once before its pagination loop, not per page, keeping the round-trip count this session's earlier fix already reduced. Also extracted the condition-building shared by the main query, the summary aggregate, and this new lookup into one buildUsageLogConditions helper, removing a third copy of that logic. Verified live: summed every row across 4 "Load more" pages and confirmed it now matches the reported total exactly (previously could drift), and confirmed the list and the export produce byte-identical credit sequences for the same rows. * fix(billing): make custom-range startDate/endDate nullable, not '' defaulted startDate/endDate had no sensible static default (they're only ever meaningful mid-custom-range), so defaulting them to '' via .withDefault('') meant switching back to a preset left the URL carrying startDate=&endDate= instead of dropping the params entirely. Made them nullable (no .withDefault) instead, matching the identical fields in the main Logs page's own search-params.ts. Verified live — switching from a custom range back to a preset now clears both params from the URL completely. * feat(audit-logs): add CSV export, matching the Credit usage page pattern Adds an Export chip to the top-right of the Audit Logs page (via SettingsPanel's actions slot — the same header mechanism the Credit usage page uses), downloading every audit log matching the current search/type/date filters as CSV. - New GET /api/audit-logs/export route: same session + enterprise admin/owner gating as the existing list route, reuses the shared buildFilterConditions/buildOrgScopeCondition/queryAuditLogs helpers (already using drizzle's typed operators for cursor pagination, not the raw-sql-with-embedded-Date pattern fixed elsewhere this session), and the same fetch+blob+X-Export-Truncated pattern the Credit usage export already established. - Capped at 10,000 rows (not the 50,000 used for a personal credit ledger) — an org's audit trail can genuinely grow much larger than one user's usage history, so this is sized for "a reasonable audit review window," with truncation surfaced via a toast rather than silently dropped. - Bumped the API-validation-contract audit's route-count baseline for the new route. Verified live against a real enterprise org: switched to "All time," exported ~750 real audit log rows, confirmed formatting (quoted descriptions, actor email fallback) and correct filter scoping. * fix(billing): skip wasted credit apportionment on the summary fetch, block export during stale data Cursor Bugbot caught two real issues: 1. The compact Billing summary glance (limit=1) only ever reads summary.totalCredits, but the list route unconditionally ran getUsageCreditsByLogId's whole-filter scan on every call including this one — pure wasted work for a caller that discards the result. Added an includeCredits query flag (default true, using the shared booleanQueryFlagSchema) so useUsageSummary can opt out; the main paginated view keeps it on since it genuinely needs per-row values. 2. Export stayed enabled while useUsageLogs held stale rows via keepPreviousData mid-filter-transition — a user could change the period/range and click Export before the new data loaded, exporting against the new filter while the table still showed the old one. Export is now also disabled while isPlaceholderData is true. * fix(billing): deterministic apportionment order, block audit export during stale data Cursor Bugbot caught two more real issues on the latest push: 1. Same stale-export bug as the earlier Credit usage fix, this time in Audit Logs: Export stayed enabled while useAuditLogs held prior rows via keepPreviousData, so it could export against a just-changed filter while the table still showed the old one. Now also disabled while isPlaceholderData is true. 2. getUsageCreditsByLogId had no ORDER BY before apportionCredits's largest-remainder tie-break, so which row absorbed a tied remainder credit depended on undefined Postgres row order — the same event's displayed credit could flip between calls (list vs. export, or even two successive requests). Added the same `orderBy(desc(createdAt), desc(id))` the main list query already uses, making the tie-break reproducible. Verified live: 3 identically-costed rows produced the same tie-break winner across 3 repeated requests (previously order-dependent). * fix(billing): distinguish a failed summary fetch from zero usage The compact Billing glance only branched on isPending, so once useUsageSummary settled into an error state, totalCredits stayed undefined and formatCreditsLabel(0) rendered "0 credits" — visually identical to genuinely having no usage this period. Now shows the same neutral "—" placeholder for isError as it already does for isPending. * fix(billing): gate the credit-usage page server-side for enterprise accounts Greptile (P1) caught this: hiding the "View usage logs" link on the Billing page for enterprise accounts doesn't stop direct navigation — anyone with the URL (bookmark, shared link, browser history) could still reach the full page and its CSV export, which enterprise accounts were never supposed to see at all (billing is managed out-of-band for them). Added a server-side check in page.tsx before anything renders: resolve the session, look up the highest-priority subscription, and redirect to /settings/billing if it's enterprise — matching how getHighestPrioritySubscription is already used elsewhere for server-side plan checks, rather than relying on a client-side-only conditional the way the Billing page's inline section does. Also fixes loading.tsx: it was a Server Component (no directive) passing a raw icon function reference into the client Chip component, which fails RSC serialization. Added 'use client'. Verified live in a real browser against both an enterprise account (redirects to Billing before any credit-usage content renders) and a non-enterprise account (reaches the page normally). |
||
|
|
b4b666bfbe |
improvement(forking): fork time ux (#5348)
* improvement(forking): fork time ux * add storage quota * address comments * merge latest staging * address comments |
||
|
|
3d1e8c4f84 |
chore(ci): bump API contract boundary audit route-count baseline to 884 (#5375)
The pinned totalRoutes/zodRoutes baseline (883) went stale after a legitimate new Zod-contract-compliant route merged without the required ratchet bump, breaking check:api-validation:strict on staging HEAD itself. Actual count is 884 total / 884 zod / 0 non-zod routes — a pure headcount update, no policy violation. |
||
|
|
59d6b8a62e |
fix(onepassword): validate integration against API docs, add file downloads (#5365)
* fix(onepassword): validate integration against API docs, add file downloads
- add onepassword_get_item_file tool + route for downloading item file
attachments (SDK items.files.read / Connect files/{id}/content), backed
by newly-exposed item.files metadata on get/create/replace/update item
- fix update_item JSON Patch applying array indices instead of 1Password's
documented field-ID addressing (/fields/{fieldId}/...), which silently
dropped field edits in Service Account mode
- fix Service Account mode's list-vaults/list-items filter to honor SCIM
`eq` exact-match semantics instead of always substring-matching
- expand the create-item category dropdown from 9 to 19 real, creatable
1Password categories (was missing SOFTWARE_LICENSE, EMAIL_ACCOUNT,
MEMBERSHIP, PASSPORT, REWARD_PROGRAM, DRIVER_LICENSE, BANK_ACCOUNT,
MEDICAL_RECORD, OUTDOOR_LICENSE, WIRELESS_ROUTER, SOCIAL_SECURITY_NUMBER)
- replace the block's single opaque `response: json` output with typed,
per-operation output fields matching repo convention
- remove incorrect password-masking on the Vault ID field (not a secret)
- re-export tool types from the onepassword barrel
* fix(onepassword): honor SCIM attribute name in filter matcher
matchesFilter always compared against name/title regardless of the
attribute named in the eq expression, so `id eq "..."` incorrectly
matched against the display name instead of the id.
* fix(onepassword): close output-parity and doc-string gaps from final audit
- restore a deprecated no-op 'response' output so pre-existing saved
workflows referencing it fail soft (empty) instead of hard-erroring
now that per-operation outputs replace it
- add missing block outputs (urls, favorite, version, state,
lastEditedBy) for get/create/replace/update item so all real
FULL_ITEM fields are discoverable as <Block.field> references
- hide Connect Server credential fields for Resolve Secret (Service
Account only) instead of leaving them selectable and silently ignored
- correct two doc-string enum lists that advertised values the API
doesn't return (vault type TRANSFER, item state DELETED)
* fix(onepassword): fix silent data loss in update_item (Service Account mode)
update_item applied user JSON Patch ops (documented/typed against the
Connect-shaped vocabulary get_item returns: label/type/section.id)
directly onto the raw SDK item, whose vocabulary differs (title/
fieldType/sectionId, and SDK category enum strings vs Connect's
SCREAMING_SNAKE_CASE). Most patches beyond /title, /tags/-, and
/fields/{id}/value silently no-opped or could corrupt the item while
still reporting success.
Extracted the Connect->SDK item conversion already used by replace_item
into a shared connectItemToSdkItem helper. update_item now normalizes
the fetched item to Connect shape, applies patches to that, then
converts back before calling items.put() -- matching create/replace's
existing translation pattern.
Found via an adversarial final-verification pass that traced concrete
patch operations by hand against the SDK's actual field vocabulary.
* fix(onepassword): preserve field metadata and empty-title fallback
connectItemToSdkItem rebuilt every field as a bare object, dropping
SDK-only metadata (e.g. password-generation details) that a raw
patch/replace previously left untouched. Now merges onto the existing
SDK field by id before applying the translated properties, and only
starts fields bare when they're genuinely new.
Also restored the || (not ??) fallback on title to match replace_item's
prior behavior of treating an explicitly empty title as "not provided".
|
||
|
|
507cee1187 |
fix(integrations): repair corrupt icons, backfill missing block metas, restore scroll on back-nav (#5342)
* fix(integrations): repair corrupt icons, backfill missing block metas, restore scroll on back-nav - Restore 7 brand icons (Google, Outlook, MongoDB, Postgres, OpenRouter, Groq, Cerebras) whose SVG path data was corrupted by a past bulk reformat, flooding the integrations page console with <path> parse errors; add a check:icon-paths CI gate that validates every icon d attribute (operand counts + arc flags). - Backfill BlockMeta (tags/url/templates/skills) for postgresql, mysql, ssh, sftp, smtp — previously catalog integrations with empty detail pages; add an integration meta-coverage CI check so every catalog block must have a meta. - Add scroll-position restoration for the integrations index/detail inner scroll containers so browser Back returns to where you were. - Remove the error digest pill from the shared workspace ErrorShell (kept in logs, dropped from UI). * fix(integrations): make scroll restoration robust — value-based echo detection + Back/Forward-only gate Addresses review: replace the racy programmatic-scroll flag with value comparison (a restore's echo equals lastApplied and is ignored, so a stuck flag can never drop the first user scroll or overwrite the saved target), and gate restoration on popstate history traversals so fresh push navigations open at the top instead of jumping mid-list. TSDoc-only comments. * fix(ci): attribute icon-path errors for export-const icons too Greptile review: iconNameAt only matched 'export function', so a malformed path inside an 'export const XxxIcon = (...)' arrow-function icon would be misattributed to the preceding function-declared icon. Match both forms (mirrors check-bare-icons indexIconBodies). |
||
|
|
af53eda5ac |
feat(landing): reintroduce /contact page styled like /demo (#5315)
* feat(landing): reintroduce /contact page styled like /demo - Restore the /contact page (removed in #5181) with a two-column layout mirroring /demo: value prop + trusted-by logos on the left, a message form card on the right, on the platform light tokens and chip components - Restore the contact contract, /api/contact route (rate-limit, honeypot, Turnstile, help-inbox notification + visitor confirmation), now fully contract-bound via parseRequest - Add a useSubmitContact React Query mutation hook - Link Contact from the footer Resources column and add it to the sitemap * fix(contact): server-authoritative captcha + review fixes - Make captcha server-authoritative: drop the client-trusted captchaUnavailable flag; a valid Turnstile token is the only way past the stricter fallback bucket, so callers can't opt out of the challenge - Re-execute the Turnstile widget on every submit (incl. after expiry) instead of falling into the no-captcha path once the token expires - Reset the pre-submit gate on mutation settle so rapid double-clicks can't fire a duplicate /api/contact request - Map only feature_request to its email type; every other topic resolves to a General Inquiry confirmation so support requests aren't labeled bug reports - Drop the confirmation-email promise from the success copy (it's best-effort) - Collapse the duplicated no-captcha rate-limit branch; hoist shared response constants; read the Turnstile site key as a module constant * fix(contact): drop redundant Turnstile hostname pin The Turnstile site key is already domain-bound in Cloudflare, so pinning expectedHostname to the marketing SITE_URL (www.sim.ai) only rejected valid tokens issued on self-hosted, preview, and apex-vs-www hosts. Remove the pin and rely on Cloudflare's own domain binding. * fix(contact): fail closed on the no-captcha rate-limit backstop checkRateLimitDirect fails open on limiter-storage errors so a limiter outage never takes down normal traffic. But the contact route's no-captcha bucket is the only throttle on token-less submits, so a fail-open there let uncaptcha'd requests reach the email path unthrottled during an outage. - Add an opt-in { failClosed } option to checkRateLimitDirect; default behavior (fail open) is unchanged - Use failClosed on the contact no-captcha backstop so an unenforceable limit rejects instead of admitting - Cover both fail-open and fail-closed paths with tests * refactor(contact): TSDoc over inline comments Move the captcha-design rationale into the route handler's TSDoc and drop the inline body/JSX comments, per the project's TSDoc-only comment convention. |
||
|
|
ca34301d7f |
fix(mailer): permissions entitlements for enabling/disabling (#5312)
* v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li <theo@sim.ai> * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha * fix(mailer): permissions entitlements for enabling/disabling * fix lifecycle for agentmail infra --------- Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Theodore Li <theodoreqili@gmail.com> Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com> Co-authored-by: Theodore Li <theo@sim.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ad19f7fc40 |
improvement(landing): refine hero and mothership visuals (#5181)
* stash * feat(landing): mothership feature stages + pre-footer CTA Tell-then-show landing: the Mothership section defines the five capabilities (Mothership · Pod · Formation · Dispatch · Return); the Features section now shows each as a real Sim UI callout floating over a static, edge-faded platform backdrop (Linear's "callout over a faded platform" pattern). - FeatureStage template: copy + masked static LandingPreview + elevated callout - LandingPreview: static autoplay=false snapshots with per-stage view/workflowId - Callouts: Mothership chat, model picker, parallel-agents Formation graph, deploy targets, logs table - Pre-footer CTA set over the Mothership render; removed the old capabilities grid Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): reusable platform-page + solutions-page layouts and routes Add config-driven, padding-safe layouts consumed by route pages: - platform-page: hero (shared CTA) + centered logos + N card rows (3|4), JSON-LD, single <h1>, server-only; Workflows route as first consumer. - solutions-page: structural mirror (kept separate to diverge later); IT, Engineering, Finance, Compliance, HR routes under /solutions. - Hoist shared LandingShell/HeroCta/Logos to components/ (top-level = shared); refactor hero to consume them. - Restructure all of (landing) to the workspace folder-per-component convention (each component in its own folder + index.ts barrel). * refactor(landing): convert hero-visual CSS-module keyframes to Tailwind Move the hero-visual + stage-home keyframe animations out of CSS modules into tailwind.config (matching the existing dash-animation pattern) and delete both module.css files. Components now use animate-hero-* utilities + arbitrary properties for the per-element delays, SVG stroke draw, and gradient shimmer; reduced-motion preserved via motion-reduce: variants. Upgrade the shimmer's hardcoded #b4b4b4 to the --text-subtle token. brand-tokens.module.css is intentionally kept: it reassigns --surface-*/ --text-* token VALUES via a doubled-class selector for specificity over .light, which Tailwind utilities cannot express. * refactor(landing): move brand palette from CSS module into LandingShell Replace brand-tokens.module.css with a BRAND_TOKENS constant of Tailwind arbitrary-property utilities applied on the LandingShell wrapper, so the brand hex lives in the component, not a stylesheet. They emit in the utilities layer and override .light (@layer base) by cascade order — verified the brand --text-primary (#121212) wins over .light (#1a1a1a). No more .module.css files remain in the landing. * chore(landing): remove Testimonials from the home page for now Drop <Testimonials /> from the landing composition (component kept for re-adding later). * feat(landing): hero send→loader→workflow animation + landing WIP Hero visual: clicking send zooms into the button, morphs the disc into the gooey thinking loader (held, then cycling), slides it straight across to a phrase indicator with the camera following (no zoom-out), then zooms back out as the reply types and the chat morphs into the GitHub→Agent→Jira workflow. The chat card holds a fixed size through the zoomed scene and the greeting reserves its space, so nothing drifts; the user bubble reveals only on zoom-out. Loader ink tweens dark→gradient via the thinking-loader stop-color/flood-color transition. Also folds in in-progress landing work: knowledge + integrations feature callouts, CTA chat, mothership + line-glyph, wordmark tweak; removes the ethos and testimonials sections. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): responsive pass for iPad + mobile Make the landing page fully responsive while keeping the desktop layout byte-identical (desktop classes stay the unprefixed baseline; smaller screens layer max-* overrides on top). - Navbar: hide desktop clusters below lg, add MobileNav hamburger sheet (scroll-lock, Escape/tap close, reduced-motion aware) - Hero: collapse the absolute split (visual + logos) to a stacked column below xl so iPad-landscape avoids the headline/visual collision - Mothership: 4-col grid steps to 2 (tablet) then 1 (phone) - Features: drop the floating callout below md, show the un-masked backdrop preview full-width - CTA + Footer: scale type/padding; footer 7-col steps to 3 then 2 - Document the breakpoint strategy in the landing CLAUDE.md Also includes the in-progress mothership goo/iso brand marks and the marks-lab preview route the section depends on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): align hero visual panel to text + logos extent Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(landing): delay hero user bubble until card finishes expanding The grey user bubble's fade-in raced the card's upward grow on send. Hold the bubble's reveal until after the parent-driven grow settles so the card expands fully before the bubble appears. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(landing): isolate new landing — remove dead old-folder code + --landing-* coupling - Delete dead (landing) auth-modal (a duplicate of (home)'s, still on the old --landing-* / dark tokens) — its removal drops the new landing's last styling tie to the old landing. - Delete 5 merge-orphaned, zero-consumer callouts (deploy-callout, mothership-chat-callout, mothership-chat-preview, workflow-graph-preview, model-picker-preview). - Relocate the one live preview (logs-table-preview) into its consumer features/components/ + add a barrel; dissolve the owner-less feature-callouts/ shell. - Fix stale --landing-bg-surface reference in landing-preview-mount. (landing) now has zero (home) imports and zero --landing-* token usage. * refactor(landing): token-map hex, fix a11y/SEO, align structure Styling (within (landing)): - Replace ~90 hardcoded hex colors with the in-scope brand tokens they already equal (--surface-*/--text-*/--border*); divider edges -> --border, field/card edges -> --border-1. Delete the redundant C color-palette mirrors in the landing-preview home/sidebar and route them through tokens. - Convert static inline SVG styles (display:block/outline:none) to Tailwind. - 6 un-tokenizable hexes remain (dark send-button fills, status-green dot) — no brand token exists; left as-is. a11y / SEO: - Decorative mothership goo/iso marks: role='img'+aria-label -> aria-hidden. - Preview chrome titles <h1> -> <span> (kills duplicate client-only H1s). - sitemap.ts: add /workflows and the five /solutions/* routes. Structure: - Folder the bare logo-mark/mobile-nav leaves + barrels; complete the navbar components barrel and consolidate navbar.tsx to a single barrel import. * style(landing): restore taller hero panel with border-shadow chip chrome - Revert the right visual panel to the previous full-height framing (top-8 bottom-8) — hero text (pt-[112px]) and the logos panel are untouched, so their positions and spacing are unchanged. - Apply the canonical border-shadow chip surface: --surface-2 fill + the shared chipBorderShadowRing (1px hairline ring + soft drop shadow) from emcn, the documented chrome for a landing media panel. * feat(landing): swap Volvo for thinkproject and reposition hero logos - Replace Volvo with the thinkproject wordmark (official SVG, tagline/descriptor cropped out, all paths unified to --text-primary #1a1a1a; aspect 6.01). - Reorder the shared 6-logo set so the 3x2 hero grid reads: Rivian|VW (top-left), eXp Realty (top-center), Russell (top-right); Artie (bottom-left), thinkproject (bottom-center), Mobile Health (bottom-right). - Enlarge Rivian|VW a touch (height 15 -> 17, same aspect). - eXp Realty, Artie, Russell, Mobile Health, Rivian|VW all retained. * style(landing): size hero description with the type scale (text-lg) Replace the arbitrary text-[20px]/text-[16px] on the hero description with named scale tokens — text-lg (18px) desktop, text-md (16px) on phones — a touch smaller and the canonical lead size (1.2x the platform's 15px base). * style(landing): hero headline "for AI automations" with break after "agent" Replace "solving automations" with the higher-intent "AI automations" and move the line break after "agent" so "for AI automations." sits on the second line. * style(landing): unify CTA radius and box hero logos in cards - HeroCta email bar: rounded-[13px] -> rounded-lg, so the bar, the inset Book-a-demo chip, the Sign-up chip, and the navbar chips all share one radius. - Hero logos: box each wordmark in a bordered --surface-1 card (platform card chrome: rounded-lg + --border-1, 100px tall) on a responsive 3-up grid (2-up on phones) at a consistent gap-5 rhythm. Wide marks scale to fit (max-w-full h-auto). The platform/solutions 'row' layout stays bare wordmarks. * style(landing): concentric CTA bar radius + tighter logo cards - HeroCta email bar back to rounded-[13px] (= inner chip 8px + ~5px inset) so the Book-a-demo chip's right corners nest concentrically inside the bar. - Logo cards: smaller and tighter — h-20 (80px), px-4, gap-3 (12px, the product UI card-grid rhythm). * style(landing): restore 100px logo cards, scale icons down 15% The 80px cards read too wide-for-their-height. Restore h-[100px] (keeping the tighter gap-3/px-4) and instead shrink the wordmarks to 0.85x their optical size in the grid via GRID_ICON_SCALE — row layout unchanged. * style(landing): match sign-up radius to email bar + shrink logo icons - Sign-up chip overridden to the email bar's rounded-[13px], so the two hero CTAs share one corner radius. - Logo icons: GRID_ICON_SCALE 0.85 -> 0.65 and card padding px-4 -> px-2; card dimensions (h-[100px], gap-3) unchanged. * style(landing): shrink hero logo cards Cards read massive — too tall (100px) and stretched to fill the panel. Drop to h-16 (64px), cap width at w-[150px], and make the grid w-fit so it hugs the cards instead of stretching. gap-3 and the 0.65 icon scale unchanged. * style(landing): upscale hero logo cards ~25% Cards read too small. Bump all dimensions: h-16->h-20 (80px), w-[150px]->w-[180px], px-2->px-3, and icon scale 0.65->0.8. Grid stays content-hugging at gap-3. * style(landing): taller logo cards, larger icons, reorder top row - Card height h-20 -> h-[88px] (width w-[180px] unchanged), icon scale 0.8 -> 0.85. - Top row reordered: eXp (left), Russell (center), Rivian|VW (right). * style(landing): more card height, swap top-row Rivian/eXp back - Card height h-[88px] -> h-24 (96px); width unchanged. - Top row: Rivian|VW (left), Russell (center), eXp (right). * feat(landing): add "Trusted by technical teams at" label above hero logos Top-left, gap-3 above the logo grid (matching the grid rhythm); text-sm (navbar text size) in --text-muted (the label token). * style(landing): recolor logos to --text-body, match label gap to hero rhythm - Recolor all six customer logo SVGs to #3b3b3b (--text-body light value), so they match the Sim navbar wordmark's color. Landing is light-only, so the hardcoded value always equals var(--text-body). - Trusted-by label gap gap-3 -> gap-[22px] (the hero's description->CTA spacing). * style(landing): scale hero CTA down a hair, drop radius to the nav chip's Sign-up read too round. Take the bar + Sign-up to h-[40px] / rounded-lg (8px, the navbar chip radius), and keep the inset Book-a-demo concentric: h-[2em] + rounded (4px) with a 4px inset (8 = 4 + 4). * style(landing): round Book-a-demo to rounded-md to match the bar curve rounded (4px) read too square next to the bar's rounded-lg (8px). Bump to rounded-md (6px) — echoes the bar's curvature, still inside the 4px inset. * style(landing): match Book-a-demo proportions to the navbar chip Restore h-[2.143em] (the chip's 30/14 height ratio); with px-[0.571em] (its 8/14 padding ratio) and the 16px label, Book-a-demo now shares the navbar chip's exact height/padding/text proportions. * style(landing): equal inset around Book-a-demo (h-[30px]) Button was h-[2.143em] (34.3px) -> only ~1.9px top/bottom vs 4px right inside the bar's 38px inner box (40px minus the 1px border). Drop to h-[30px] (the nav chip height) so it centers to an equal 4px inset on top, bottom, and right. * style(landing): enlarge Book-a-demo to h-[32px], tighten inset to 3px h-[30px] read too small/airy in the bar. Bump to h-[32px] and pr-[4px] -> pr-[3px] so the inset is an equal, snugger 3px on top, bottom, and right. * style(landing): lift hero logos off the bottom again (pb-20) Restore the 80px bottom padding so the logos rest 112px above the section bottom (mirroring the hero text's 112px top) instead of sitting flush with the visual panel's bottom. max-xl:pb-0 keeps the stacked layout tight. * improvement(landing): refine hero and mothership visuals * fix(landing): cap hero fold height so it doesn't stretch on huge monitors The section was min-h-[calc(100vh-62px)], so on very tall displays both absolute panels (top-8 bottom-8) stretched — the visual panel grew gigantic and the bottom-anchored logos sank to the very bottom. Cap the fold at 960px via h-[min(calc(100vh-62px),960px)] (min-height can't be capped by max-height): the whole hero stops growing, panels/logos stay proportioned like a large laptop, and the next section just starts below. Laptops (<=16in) are unaffected; max-xl:h-auto keeps the stacked layout below xl. * refactor(landing): session cleanup — DRY CTA label, drop dead grayscale Final tidy after this session's hero/CTA/logo iteration: - hero-cta: extract the duplicated 16px label knob (px-[0.571em] + text-[16px] + font-size:inherit) into a single CTA_LABEL constant, matching the 'single knob' the TSDoc already describes — used by both Book-a-demo and Sign-up. - logos: remove the grayscale filter (now a no-op — all wordmarks were recolored to a single #3b3b3b), inline the single-use LOGO_GAP_X, and flatten the nested cn() into plain layout ternaries (dropping the now-unused cn import). * improvement(landing): animate mothership illustrations * style(landing): solid-ink branding + hero cursor/loader polish Branding: drop the bespoke BRAND_TOKENS palette and bottom-reveal from LandingShell (use the platform's own light tokens); re-ink the wordmark, logo-mark, and hero loader from the gradient+glow to a solid --text-body so the marks read as one ink with the nav text. Add a `shimmer` prop to ThinkingLoader for a static --text-body label, and stroke the squeeze arcs with the shared gradient. Hero visual: the cursor now enters from below the field and chases the send button live through the zoom (retimed beats, no arrive-then-wait); the greeting fades in gently instead of shimmer-revealing; the click ring becomes a press-dip (hero-cursor-press replaces hero-click-ring and hero-greeting-reveal). Extract BlockHandles so the morphed GitHub card carries a real edge handle in scene space; seed the compose card at its true height; pop the sent bubble in immediately. * improvement(landing): update feature iso-marks to perfected geometry Re-author the four Mothership iso-mark illustrations (Integrate, Ingest, Build, Monitor) on the refined isometric geometry, keeping the existing animation vocabulary intact: hover line-draw plus per-mark auto-motion (integrate float, ingest pulse, monitor panel-separate, build grid-flow). Map the raw exports onto the shared token palette/line weight for consistency and tune per-mark sizes for one optical weight. Build is now pure CSS (grid-flow replaces the RAF wave), so it drops 'use client' and renders as a server component. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(landing): add pricing, privacy, terms, and changelog pages - New public /pricing page: Free/Pro/Max/Enterprise cards with the full comparison breakdown transposed from shared upgrade data + JSON-LD; prices, CTAs, and features derive from shared billing constants so they can't drift from the in-app upgrade page. - Migrate /privacy, /terms, and /changelog into the (landing) route group via a shared prose-page system (single source of truth for legal/prose chrome). - Landing polish: solid-ink iso-mark illustrations + footer/cta/features/ mothership spacing and token cleanups; sitemap adds /pricing. - Audit pass: crawlable ChipLink CTAs, correct heading hierarchy, structured-data featureList derived from the visible comparison data, legal plan name Team->Max. * large edits across landing finalization * feat(auth): port OAuth-only signup + Microsoft provider from staging Align auth-page logic with origin/staging (PR #5073) while keeping the new chip-styled UI: - Add Microsoft as a better-auth social sign-in provider (auth.ts) and surface it through the OAuth provider checker, providers API + contract, login/signup forms, SocialLoginButtons, and the landing auth modal. - Gate email/password signup behind the emailSignupEnabled server flag (DISABLE_EMAIL_SIGNUP) so signup becomes OAuth-only when configured. - Add DISABLE_MICROSOFT_AUTH / DISABLE_EMAIL_SIGNUP env + feature flags. * fix(icons): render brand icons legibly when bare and on light tiles (#5292) Monochrome brand icons hardcoded a single white or black fill matched to their colored tile, so they vanished when rendered bare on the home Suggested actions list (white-on-white in light mode, black-on-black in dark mode). Convert those marks to currentColor so they adapt to context, and make tile foregrounds contrast-aware via getTileIconColorClass instead of a hardcoded text-white. Also centralize all color math in apps/sim/lib/colors (perceived brightness, hex/rgb/hsl conversion, contrast-text) and route every consumer through it: the bare-icon audit, block tiles, logs trace view, whitelabeling theming, workspace presence, and the PPTX renderer no longer carry duplicate copies. Adds a bare-icon CI audit (scripts/check-bare-icons.ts) and authoring guidance. --------- Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu> Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Waleed <walif6@gmail.com> |
||
|
|
d0aed14a0f |
feat(integrations): wave-4 tool-depth (Slack/Asana/Jira/Google Docs/Trello/Monday) + context.dev validation (#5289)
* fix(context_dev): validation pass — add search numResults/country, accuracy fixes Comprehensive /validate-integration of all 22 context.dev tools against the live API docs found the integration clean (no correctness bugs). Applied the actionable items: - search: expose numResults (10-100) + country inputs (API supported them; users were silently capped at 10 results) - accuracy: scrape_html type description (+doc/docx), map meta description (+sitemapsSkipped), brand links description (+contact) - robustness: trim string query values in appendParam * feat(integrations): wave-4 tool-depth — Slack, Asana, Jira, Google Docs, Trello, Monday Deepen six existing blocks with 38 new tools, no new OAuth scopes (all under already-granted scopes), additive/backwards-compatible: - Slack (7): schedule/list/delete scheduled messages; archive/rename/set-topic/set-purpose conversation - Asana (8): create/get project, list workspaces, create subtask, delete task, add followers, create/list sections (via internal routes + contracts) - Jira (5): list/get project, get transitions, list issue types, get fields - Google Docs (6): delete content range, named ranges, paragraph bullets, update paragraph style (documents.batchUpdate) - Trello (7): create board/list, get board/card, add checklist/label/member - Monday (5): change column value, create board/column, get groups, duplicate item Route baseline 873->881 for the 8 new Asana internal routes. * fix(integrations): wave-4 validation pass — fix alignment enum, GraphQL input-object, scope/UI gaps Comprehensive /validate-integration of all 6 modified integrations (existing + new tools) vs live API docs. Fixes: - google_docs: CRITICAL alignment enum LEFT/RIGHT/JUSTIFY -> API enum START/END/JUSTIFIED (mapped); namedStyleType 'unchanged' option; 'zero-based' index wording - monday: CRITICAL search_items columns now emits GraphQL input-object with unquoted keys (was always failing the non-cursor branch) - slack: schedule_message DMs via user-id-as-channel; add channels:manage/groups:write/reactions:read scope descriptions; nextCursor optional - jira: list_projects expand=lead so lead outputs populate (was always null) - trello: get_actions limit now applies to the card path too - asana: add missing 'completed' + 'projects' subBlocks (were unsettable in UI); request permalink_url via opt_fields on create routes * fix(integrations): clamp context.dev search bounds; precise Google Docs index wording - context_dev/search: clamp numResults to the documented 10-100 range; normalize country to trimmed uppercase - google_docs: replace ambiguous '1-based'/'zero-based' index wording with the concrete fact (the document body starts at index 1), matching buildInsertLocation (index<1 appends) and buildContentRange * fix(integrations): validate context.dev country (ISO-2); regenerate google_docs docs - context_dev/search: reject non-2-letter country values with a clear error instead of forwarding them - docs: regenerate google_docs.mdx so the public index-contract wording matches the updated tool descriptions (body starts at index 1) * fix(asana): omit completed unless explicitly set (don't send false on unchecked) The new completion checkbox mapped an unchecked/untouched state to completed:false, which made update_task silently un-complete tasks and search_tasks filter to incomplete. Now only sends completed when the box is checked (undefined otherwise). * fix(slack): expose Destination toggle for schedule_message so DM scheduling is reachable The mapper already routes schedule_message DMs (user-id-as-channel); add schedule_message to the destinationType condition so users can deliberately choose Channel vs DM instead of it only triggering via leftover state. |
||
|
|
48c1b453df |
feat(integrations): extend ElevenLabs, Google Drive, Firecrawl, Pinecone, Resend, and S3 tool depth (#5270)
* feat(firecrawl): add crawl status/cancel, batch scrape + status, extract status, credit usage tools * feat(resend): add audiences, broadcasts, and cancel-email tools * feat(pinecone): add delete/update vectors, index, and stats tools * feat(google-drive): add revisions, comments, and export tools * feat(elevenlabs): add voices, settings, models, user, sound-effects, speech-to-speech, audio-isolation tools * feat(s3): add bucket CRUD, head-object, presigned-url, and batch-delete tools * chore(api-validation): bump route baseline to 873 for wave-3 internal tool routes (s3, elevenlabs, google_drive export) * docs(integrations): regenerate docs + catalog for wave-3 tools * fix(integrations): audit fixes for wave-3 - pinecone: read camelCase vectorType/deletionProtection (with snake_case fallback) so list_indexes/describe_index populate them; make describe_index_stats casing defensive - google-drive: URL-encode fileId in the export route - remove extraneous inline/section-divider comments across new blocks/tools; convert type docs to TSDoc * fix(integrations): address review — elevenlabs settings bleed, batch-scrape job-id guard, s3 head-object existence - elevenlabs: select stability/similarityBoost by operation so a stale edit-settings value can't bleed into a TTS call - firecrawl: fail fast with a clear error when batch scrape returns no job id (avoids a misleading polling timeout) - s3: head_object on a missing key now returns exists:false instead of a generic failure * fix(s3): allow exists:false in head-object response contract (schema must match the missing-object output) * fix(pinecone): guard JSON.parse of ids/filter/values/sparseValues/setMetadata Malformed JSON-string input now throws a clear '<field> must be valid JSON' error via a shared parseJsonParam helper instead of crashing the request body builder. * fix(pinecone): enforce mutual exclusivity of ids/deleteAll/filter in delete_vectors Pinecone treats these delete selectors as mutually exclusive; the tool now requires exactly one and throws a clear error otherwise, instead of sending a conflicting body. * fix(integrations): I/O fidelity vs API docs (wave-3 audit) - pinecone: normalize describe_index_stats per-namespace vector_count -> vectorCount - firecrawl: remove phantom 'sources' from extract_status, add real creditsUsed/tokensUsed; expose batch_scrape maxConcurrency/ignoreInvalidURLs - google-drive: drop undocumented supportsAllDrives from the files.export URL - elevenlabs: add next_page_token input to list_voices (fixes pagination dead-end) - resend: surface segment_id on get_broadcast; declare replyTo + segment_id in block outputs |
||
|
|
c59631698f |
chore(deploy): remove deploy as a2a (#5255)
* chore(deploy): remove a2a * add block |
||
|
|
5a8134119a |
feat(workspaces): fork + push/pull (#5210)
* feat(workspaces): fork + push/pull * type fix * fix tests * progress on ux * remove modal section * improve UI of modal * update more ui * make rollback part of the footer * track skipped count correctly * address comments * make it workspace admin level * update skipped count * address more comments * deal with unbounded memory possibility * fix deleted kb article bug * no deployed workflow case * UI/UX cleanup * fix oauth dropdown case * fix oauth selector issue * infra work + activity log * consolidate migration * update modal state * more UI simplification * grammar * update audit report ui * perf improvements * fix tool input scenarios and add dependsOn UI handling * minor comments * fix webhook stability issues + drift detection removal * make dependsOn subblock mapping cleanly stored * fix: harden fork dependent-value mapping (clear stale rows, identity-guard first-sync fallback, perf + cleanup) * address comments * update comment * enforce admin perms for activity api * fix required + dependsOn combo |
||
|
|
3143a15dde |
feat(uptimerobot): add UptimeRobot v3 integration (#5229)
* feat(uptimerobot): add UptimeRobot v3 integration
- 24 tools across monitors, incidents, maintenance windows, alert contacts,
public status pages, and account (UptimeRobot v3 REST API, Bearer auth)
- Block with operation-scoped subBlocks, status-page logo/icon file uploads
via internal multipart routes, and BlockMeta templates + skills
- Registered tools/block, added icon, generated docs
- Updated add-integration/add-block/validate-integration docs links to /integrations
* fix(uptimerobot): address review — heartbeat URL, file/JSON edge cases
- Block: URL is not required for HEARTBEAT monitors (no URL)
- buildMonitorBody: throw on malformed assignedAlertContacts/customHttpHeaders
JSON instead of silently dropping the field
- PSP route: error (400) when a supplied logo/icon cannot be resolved to a
stored file instead of silently omitting the image
- PSP route: guard success-path JSON parsing; return a controlled 502 on a
non-JSON provider response instead of an uncaught 500
* fix(uptimerobot): spec-conformance audit fixes
- pause/start monitor: send Content-Type: application/json (v3 spec requires it
on these POSTs even with an empty body)
- update maintenance window: drop autoAddMonitors (not in UpdateMaintenanceWindowDto);
gate the block field to create only
* fix(uptimerobot): rename monitor timeout param to avoid reserved name
The tool runner treats a top-level `timeout` param as the outbound HTTP-client
timeout (ms), so a monitor check-timeout of e.g. 30s would abort the API call in
30ms. Rename the input to `checkTimeout` (block subBlock, tool params, inputs,
numeric coercion) and map it to the API body's `timeout` key in buildMonitorBody.
* fix(uptimerobot): reject empty/non-object PSP responses
A successful PSP create/update must return the PspDto object; an empty or
non-object body now returns a controlled 502 instead of mapping a phantom
status page (id: 0, empty name, null images) back to the workflow.
* fix(uptimerobot): validate core PSP fields before mapping
Reject successful PSP responses that lack a positive numeric id and non-empty
friendlyName (a {} or metadata envelope) with a controlled 502, instead of
mapping a phantom status page.
|
||
|
|
6260eda226 |
fix(ssr): harden credential query-key factory + fetchers against the 'use client' stub bug (#5206)
* fix(ssr): move credential query-key factory + fetchers to non-client modules
Preventively closes the same 'use client' SSR client-reference-stub class that
crashed the tables page. Server-evaluated modules (the credential block def, the
workflow-comparison helpers) imported workspaceCredentialKeys /
fetchWorkspaceCredentialList / fetchCredentialSetById from 'use client' hook
modules, where they resolve to client-reference stubs on the server (a future
server call path would throw 'X is not a function').
Extract them into non-client hooks/queries/utils/{credential-keys,
fetch-workspace-credentials,fetch-credential-set}.ts (mirroring folder-keys.ts /
fetch-workflow-envelope.ts) and import from there. No behavior change — these
values were only ever called from browser paths.
* docs+ci: codify the 'use client' server-import rule + add check:client-boundary
Document the Next.js rule that server code can only render a 'use client'
export as a component, never call it (server imports resolve to client-reference
stubs that throw — the tables-page crash). Add the rule to
.claude/rules/sim-queries.md + a cross-ref in sim-architecture.md.
Add scripts/check-client-boundary-imports.ts (wired into CI as check:client-boundary)
that flags any value import from a 'use client' module in a server-evaluated,
non-JSX surface (prefetch / route handler / trigger / block definition), so this
class can't silently recur. Escape hatch: // client-boundary-allow: <reason>.
|