Commit Graph
157 Commits
Author SHA1 Message Date
Waleed 04535ee33b feat(buffer): add Buffer integration with posts, channels, and ideas (#5637)
* feat(buffer): add Buffer integration with posts, channels, and ideas

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

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

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

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

* fix(buffer): guard missing post on PostActionSuccess responses
2026-07-13 12:07:37 -07:00
Vikhyath Mondreti ef7c8e24b2 feat(platform): settings permissions, admin, billing attribution (#5545)
* fix(invites): preserve active organization for external access

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

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

* address comments

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

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

* regen migrations

* address comments

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

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

* upgrade global work

* fix lint

* address comments

* legacy callbacks correctness

* address comments

* update

* guardrail attribution
2026-07-13 00:53:49 -07:00
Theodore Li 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
2026-07-10 20:16:53 -04:00
Theodore LiandClaude Opus 4.8 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>
2026-07-10 19:43:52 -04:00
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>
2026-07-10 15:08:24 -07:00
Theodore Li 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
2026-07-09 22:38:01 -04:00
Waleed 6e87d74a93 feat(jupyter): add Jupyter integration (contents, kernels, sessions) (#5527)
* feat(jupyter): add Jupyter integration (contents, kernels, sessions)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

A segment like foo%2f..%2fsecret has no literal slash, so splitting on
literal '/' first and decoding each piece in isolation treats it as
one opaque segment and never notices the '..' hiding behind the
encoded slash. Decode the full path once, then split and check every
segment the target server's own single URL-decode pass would see.
2026-07-08 19:35:57 -07:00
Vikhyath MondretiandSiddharth Ganesan 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>
2026-07-08 12:15:28 -07:00
Waleed 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.
2026-07-08 11:26:23 -07:00
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>
2026-07-07 12:19:34 -07:00
Waleed 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
2026-07-06 19:42:36 -07:00
Vikhyath Mondreti 24ebba9acc chore(credential-sets): cleanup feature (#5460) 2026-07-06 18:49:06 -07:00
Waleed fb3f95d5dc feat(aws): expand SES/STS/Secrets Manager tool coverage, fix API alignment gaps (#5450)
* feat(aws): expand SES/STS/Secrets Manager tool coverage, fix API alignment gaps

- SES: add suppression list management, email identity CRUD, template
  update, configuration set creation, custom verification email (10
  new tools); fix silent httpsPolicy drop in create_configuration_set
  and unvalidated suppression reason enum in list_suppressed_destinations
- STS: add AssumeRoleWithWebIdentity and AssumeRoleWithSAML (unsigned,
  no static credentials required); extend assume_role with
  policyArns/tags/transitiveTagKeys session params
- Secrets Manager: add describe_secret, tag_resource, untag_resource,
  restore_secret, rotate_secret; fix list_secrets dropping
  rotation/version metadata fields; normalize tool versions to 1.0.0
  and alphabetize registry entries

All 31 tools verified param-by-param against live AWS API docs across
two independent audit passes.

* fix(aws): address review findings on SES config/identity and Secrets Manager rotation

- ses_create_configuration_set: validate suppressedReasons against
  BOUNCE/COMPLAINT enum before calling AWS (was silently reaching AWS
  as a generic 500 for bad values); tags now a proper Zod array schema
  instead of a string with route-side JSON.parse
- ses_create_email_identity: dkimSigningAttributes and tags now proper
  Zod object/array schemas instead of strings with route-side
  JSON.parse, matching the pattern used elsewhere (e.g. sts_assume_role
  tags, secrets_manager_tag_resource)
- secrets_manager_rotate_secret: reject automaticallyAfterDays and
  scheduleExpression when both are supplied — AWS RotationRules
  accepts only one
- sts createUnauthenticatedSTSClient: corrected a misleading comment
  claiming these calls are fully unsigned; the SDK still falls through
  its default credential provider chain

* fix(ses): correct json input types for tags/dkimSigningAttributes

The SES block declared the tags and dkimSigningAttributes block inputs
as 'string' instead of 'json', so the generic block executor never
parsed the JSON code-editor value before forwarding it — workflow runs
sent a raw JSON string where the contract now expects a structured
object/array, failing validation. Also corrected the corresponding
tool param TypeScript types, which were still typed as string | null.

* fix(ses): stop coercing switch string 'false' to true in create_configuration_set

Boolean('false') evaluates to true, so turning off the
reputationMetricsEnabled or sendingEnabled switch sent the opposite of
the user's choice to SES. Match the established === 'true' string
comparison pattern used elsewhere in the codebase.

* fix(sts): stop double-parsing assume_role session tags input

tags was declared as a 'json' block input, so the generic executor
JSON.parse'd it before the switch-case handler ran — but that handler
already converts the raw table-rows array (or a passthrough string)
into the JSON string the sts_assume_role contract expects. Declaring
it 'json' broke that conversion for non-string inputs. Reverted to
'string' so the handler's existing string/array disambiguation runs
on the untouched raw value.

* fix(sts): supply placeholder credentials to the unauthenticated client

createUnauthenticatedSTSClient omitted credentials entirely, so the
SDK's signing middleware fell through the default credential provider
chain and threw CredentialsProviderError before the request was sent
in any environment with no ambient AWS identity — even though
AssumeRoleWithWebIdentity/AssumeRoleWithSAML never check the
signature. Static placeholder credentials skip that resolution
without granting or requiring any real IAM identity.
2026-07-06 17:26:44 -07:00
Theodore Li 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
2026-07-04 15:57:24 -04:00
Waleed 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).
2026-07-03 19:37:17 -07:00
Vikhyath Mondreti b4b666bfbe improvement(forking): fork time ux (#5348)
* improvement(forking): fork time ux

* add storage quota

* address comments

* merge latest staging

* address comments
2026-07-02 12:12:30 -07:00
Waleed 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.
2026-07-02 11:03:59 -07:00
Waleed 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".
2026-07-02 10:23:48 -07:00
Waleed 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).
2026-07-01 16:58:45 -07:00
Waleed 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.
2026-07-01 01:17:11 -07:00
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>
2026-06-30 21:23:11 -07:00
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>
2026-06-30 13:50:04 -07:00
Waleed 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.
2026-06-30 11:39:11 -07:00
Waleed 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
2026-06-29 15:59:14 -07:00
Vikhyath Mondreti c59631698f chore(deploy): remove deploy as a2a (#5255)
* chore(deploy): remove a2a

* add block
2026-06-28 20:01:24 -07:00
Vikhyath Mondreti 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
2026-06-28 13:13:57 -07:00
Waleed 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.
2026-06-26 18:38:50 -07:00
Waleed 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>.
2026-06-24 18:46:18 -07:00
Theodore Li cff7a49310 feat(file): workspace-scoped inline images + public-share cascade (#5203)
* feat(file): workspace-scoped inline images + public-share cascade

Embedded markdown images now resolve only within the document's workspace,
and public file shares cascade to the images the shared document embeds.

- New /api/workspaces/[id]/files/inline (in-app, workspace-scoped) and
  /api/files/public/[token]/inline (public cascade) routes; the public one
  serves an embed only when it is referenced-by-doc, same-workspace, and
  passes a magic-byte image sniff
- Embed srcs (serve-key and view-id forms) rewrite through one scoped inline
  route; one shared isomorphic parser owns the embed grammar for both the
  frontend renderer and the server doc scan
- Accept wf_ file ids on the view/export routes (were 400ing on .uuid())

* feat(file): add Image command to the markdown editor slash menu

- New /Image slash command uploads an image via a file picker and inserts
  it at the caret (same upload+insert path as paste/drop)
- Inserted src is the workspace serve URL, so it renders in-app and
  cascades to public shares like any other embed
- Per-editor handler wired through slash-command storage (the extension
  set is a shared singleton); only active when the editor is editable

* fix(file): export rewrites all embed forms; cap embedded refs combined

Addresses PR review:
- Markdown export now rewrites the in-app `/workspace/<ws>/files/<id>`
  embed form too (not just `/api/files/view/<id>`), so a bundled asset
  never leaves a broken link in an offline export (Bugbot)
- extractEmbeddedFileRefs bounds total references (keys + ids) to 50
  combined rather than 50 each, matching MAX_EMBEDDED_IMAGES intent
2026-06-24 21:07:39 -04:00
Theodore Li 8f312d299b feat(guardrails): PII redaction via Presidio sidecar (native VIN, per-rule language) (#5174)
* fix(logs): run PII redaction over HTTP and fix Presidio provisioning

- resolve the guardrails venv via candidate paths and fail fast instead of
  silently falling back to system python3 (the misleading "Presidio not
  installed" that broke redaction and the guardrails block in deployed runtimes)
- install the en_core_web_lg spaCy model in setup.sh and app.Dockerfile
- route log redaction through an internal /api/guardrails/mask-batch endpoint
  so Presidio always runs in the app container, including async executions that
  persist inside the trigger.dev runtime

* fix(guardrails): chunk + time-bound internal PII mask requests

- chunk maskPIIBatchViaHttp by count (2000) and bytes (256KB) so large
  executions split across requests and never hit the contract's 100k cap
- add AbortSignal.timeout(45s) per request so a slow/unreachable app container
  aborts and the caller scrubs, instead of hanging the trigger.dev job
- catch maskPIIBatch failures in the route: log and return a structured 500
  (broken venv fails loudly server-side; caller still scrubs, no leak)
- add mask-client tests (order across chunks, count split, non-2xx, empty)

* fix(guardrails): mint internal token per mask request

A single token (5min TTL) could expire mid-batch when a large execution
fans out into many sequential chunk requests; mint one per request instead.

* feat(guardrails): run PII via Presidio sidecars + TS recognizer registry

- replace the per-call python3 subprocess (cold spaCy load every call) with
  two long-lived Presidio sidecars (analyzer + anonymizer) reached over HTTP;
  the app image no longer carries Python/Presidio/venv
- add PRESIDIO_ANALYZER_URL / PRESIDIO_ANONYMIZER_URL
- move VIN out of Python into a TS recognizer (check-digit validated) behind a
  CUSTOM_RECOGNIZERS registry so new custom detectors are one entry; masking is
  handled uniformly by the anonymizer
- drive the guardrails block's PII type picker from the shared pii-entities
  catalog (adds VIN, fixes drift) so block + Data Retention never diverge
- delete validate_pii.py, requirements.txt, setup.sh and the Dockerfile venv step

* fix(guardrails): bound-parallelize mask batch; refresh stale comments

- maskPIIBatch runs per-string sidecar calls with bounded concurrency (8) via
  mapWithConcurrency, so a chunk of many small leaves finishes within the 45s
  request timeout instead of aborting and scrubbing; order + fail-on-error kept
- drop stale comments referencing the deleted Python venv / 30s subprocess timeout

* refactor(guardrails): single Presidio image, native VIN, per-rule redaction language

- collapse the analyzer/anonymizer URLs into one PRESIDIO_URL (combined image
  serves /analyze + /anonymize)
- remove the TS VIN recognizer (vin.ts, recognizers.ts) — VIN is now native +
  multi-language in the image; validate_pii is a thin analyze→anonymize client
- trim KR_RRN/TH_TNIN from the catalog (no Korean/Thai model in the image)
- add per-rule redaction language: PII_LANGUAGES catalog drives the contract enum,
  the Data Retention rule modal, and the guardrails block dropdown; resolver +
  logger thread it through to maskPIIBatch (default en), so non-English entity
  rules (e.g. ES_NIF) actually fire instead of silently no-op'ing under en

* fix(guardrails): correct sidecar port (5001) + README for combined image

The combined Presidio image (docker/pii.Dockerfile) serves /analyze + /anonymize
on a single port 5001 with native VIN + multi-language recognizers. Fix the
PRESIDIO_URL default (was 5002) and rewrite the README, which still described two
stock containers and a TS VIN recognizer.

* fix(guardrails): coerce stored redaction language in the resolver

The persist-path resolver accepted any stored language string, so a stale/invalid
code (e.g. a dropped locale) would reach Presidio and scrub the log even though the
admin UI shows English. Coerce against the supported set via a shared
coercePiiLanguage helper (now reused by the data-retention route too), falling back
to en for unknown values.

* fix(guardrails): rename PRESIDIO_URL env var to PII_URL

Match the infra taskdef, which sets PII_URL on the app container for the
combined Presidio sidecar.
2026-06-23 05:29:01 -04:00
Waleed d8da1e2577 fix(state): align server/client state with best practices (query-key bugs, persist hygiene, useState) (#5166)
* fix(queries): close React Query key/fetch-arg drift cache collisions

Several query hooks fetched with an identifier that was absent from their
queryKey, so distinct fetch args shared one cache entry. Thread the missing
args into the key factories and update all callsites/invalidations.

- organization: useOrganization always fetched the ACTIVE org via
  getFullOrganization() while caching under detail(orgId). Pass orgId through
  to the better-auth call (query.organizationId); active-org behavior unchanged.
- logs: logKeys.detail now keys on (workspaceId, logId) to prevent cross-
  workspace collision; updated useLogDetail, useLogByExecutionId, prefetchLogDetail,
  useCancelExecution optimistic path, and external callsites.
- inbox: inboxKeys.taskList now includes cursor/limit (pagination args were sent
  but omitted from the key); keepPreviousData pagination UX preserved.
- a2a: narrow create/update byWorkflows() invalidation to byWorkflow(ws, wf)
  since their responses reliably carry both ids; delete/publish stay broad.

Not bugs (verified, left unchanged):
- kb/connectors update/delete invalidate knowledgeKeys.detail(kbId), which is a
  prefix of connectorKeys.all(kbId) — connector list/detail are invalidated
  transitively by React Query prefix matching.

Harness: add a key-fetch-arg-drift check to check-react-query-patterns.ts that
flags a camelCase identifier the queryFn forwards into the fetch but is absent
from the queryKey (excludes the requestJson contract arg, PascalCase/SCREAMING
constants, and signal/pageParam machinery). Document the rule in sim-queries.md.
tables.useTable annotated rq-lint-allow (tableId globally unique; workspaceId is
only an authz scope).

* fix(stores): whitelist durable fields in persist partialize

chat/terminal/panel persist configs leaked actions and transient state
into localStorage. Replace the chat full-state spread with an explicit
durable whitelist, and add partialize to terminal and panel (which had
none) so isResizing and _hasHydrated are no longer persisted. Panel keeps
activeTab + panelWidth because the layout.tsx blocking script reads them
from panel-state to set data-panel-active-tab before hydration (SSR
tab-flash prevention).

Harden sim-stores doctrine: persist MUST use an explicit partialize
whitelist; never persist transient flags or _hasHydrated.

* fix(state): model component useState as single source of truth

- edit-knowledge-base-modal: reset fields on closed→open via prevOpenRef
  render idiom instead of mirroring props into state through useEffect
  (a prop change while open no longer clobbers in-progress edits)
- use-verification: collapse contradictory isLoading/isVerified/isInvalidOtp
  booleans into a single status enum + errorMessage; consumer derives flags
- contact-form / demo-request-modal: derive busy/success from the mutation
  object; delete duplicated submitSuccess local state
- sim-hooks.md: add state-shape rule (no props-into-state, status enum,
  derive mutation state)

* fix(verify): clear lingering message on complete OTP (restore parity)

* docs(state): convert inline reset comment to TSDoc

* docs(state): tighten harness rules for accuracy (queryFn forwards, partialize whitelist, mutation-flag caveat)

* fix(verify): block auto-verify while a resend is in flight (restore parity)

* fix(logs): key cancel optimistic detail by route workspaceId (not the log row)
2026-06-21 22:44:14 -07:00
Theodore Li 7349bf403f feat(files): password, email-OTP, and SSO auth for public file shares (#5140)
* feat(files): password, email-OTP, and SSO auth for public file shares

* fix(files): suppress filename in share previews for email/sso, not just password

* fix(files): normalize allow-list emails to lowercase; genericize shared SSO denial message

* fix(security): make isEmailAllowed case-insensitive; normalize email at client gates

* test(security): cover isEmailAllowed case-insensitive matching

* fix(security): bind auth cookie to auth type; password endpoint rejects non-password shares

* chore(db): format generated migration meta

* fix(files): share upsert validation returns 400 not 500; disabling always succeeds

* feat(access-control): org admins can restrict allowed file-share auth types
2026-06-19 18:53:12 -04:00
Siddharth Ganesan 5925651cbc feat(vfs): add lazy vfs + remove dynamic fields for prompt caching hits (#5138)
* feat(vfs): add lazy vfs + remove dynamic fields for prompt caching hits

* feat(vfs): send typed workspace snapshot for append-only deltas

Build the workspace inventory from the primary db (fixes replica-lag staleness)
and emit it as a typed VfsSnapshotV1 `vfs` payload alongside the markdown, so the
mothership can diff it into append-only baseline/delta messages. Generate the TS
contract mirror from the Go-owned JSON schema (sync-vfs-snapshot-contract) and
sort connector types so diffs stay byte-stable.

* fix(lint): fix lint

* fix(vfs): forward the typed snapshot through the branch payload builder

The branch buildPayload implementations hand-list the params they pass to
buildCopilotRequestPayload and forwarded workspaceContext but dropped vfs, so the
typed snapshot never reached the Go request (req.Vfs was always nil and the
append-only delta path never engaged). Forward vfs in both the workflow and
workspace branches, and add a regression guard asserting the branch threads it
through (the bug slipped past tests because post.test mocked the payload builder
and payload.test called it directly, bypassing the branch).

* improvement(contracts): update vfs contracts
2026-06-19 15:12:42 -07:00
Theodore Li 208d135dac feat(enrichment): add enrichment details sidebar with cost + provider cascade (#5139)
* feat(enrichment): add enrichment details sidebar with cost + provider cascade

* fix(enrichment): address review — persist detail on cancel/skip, exclude not_run from ran count, refetch on panel open

* fix(enrichment): keep cascade detail sticky on upsert; mark unattempted providers not_run on abort

* fix(enrichment): show Cancelled in details panel for aborted runs
2026-06-19 17:19:13 -04:00
Theodore Li f0b3550729 feat(files): public share links for workspace files (#5130)
* feat(files): public share links for workspace files

* improvement(files): drop reserved public_share columns until used; sync audit mock

* fix(files): share modal tracks authoritative saved state until toggled

* feat(files): per-IP rate limit on public share endpoints

* fix(files): address PR review — public CSV OOM, content cache, share FK, soft-delete filter, download anchor

* fix(files): disable CSV import action in read-only preview (public share)

* refactor(files): drive CSV preview import affordance off readOnly, not disableImport

* fix(files): version public viewer caches by file updatedAt so edits aren't stale

* fix(files): 409 (not corrupt source) when a shared generated doc has no compiled artifact

* feat(files): gate public sharing behind an access-control permission
2026-06-18 21:26:49 -04:00
Theodore Li 63a3e6d2cb feat(files): stream large CSV previews and add import-as-table (#5125)
* feat(files): stream large CSV previews and add import-as-table

* fix(files): validate fileId in csv-preview route, guard double-import, fix sniff perf and toggle flash

* fix(files): scope mothership preview-toggle loading guard to CSV files only
2026-06-17 21:54:18 -04:00
Waleed 8b93e43037 improvement(integrations): validate BigQuery/Forms/PageSpeed + regenerate integration docs (#5109)
* improvement(integrations): validate BigQuery/Forms/PageSpeed + regenerate integration docs

- BigQuery: mark null-defaulted outputs optional (get_table type/numRows/numBytes/creationTime/lastModifiedTime/location, list_datasets location, list_tables type, query totalBytesProcessed)
- Google Forms: add response pagination (pageToken + filter params, nextPageToken output), fix pageSize visibility, advanced-mode pagination subBlocks + filter wandConfig
- PageSpeed: add a 7th BlockMeta template (competitor benchmark)
- Regenerate integration docs; add manual intro sections to new datagma/dropcontact/enrow/icypeas/leadmagic pages

* fix(docs-gen): preserve apostrophes in tool descriptions when generating docs

The doc generator extracted tool descriptions with a character class that
excluded both quote types (['"]([^'"]...)['"]), so a double-quoted description
containing an apostrophe (e.g. "Find someone's email") was truncated at the
apostrophe — the generated docs/catalog showed stubs like "Find someone".

Anchor extraction on the actual opening quote (single/double/backtick), matching
the existing extractDescription helper, in both buildToolDescriptionMap and
extractToolInfo. Regenerated docs restore full descriptions across all affected
integrations (Apollo, Ahrefs, LeadMagic, Findymail, OpenAI, Slack, etc.).

* fix(docs-gen): resolve tools defined in a sibling file + scope params per tool

The doc generator located a tool's definition only by filename convention
(decompress.ts / index.ts), so file_decompress — which lives in compress.ts
alongside file_compress — fell back to index.ts and rendered an empty Input
table. It also read the params block from the first tool in a multi-tool file,
so every tool in such a file inherited the first tool's inputs/outputs.

- getToolInfo: when no candidate file declares the exact tool ID, scan the whole
  tool-prefix directory for the file that does.
- extractToolInfo: read the params block scoped to the specific tool, falling
  back to the full file for tools that inherit params via spread.

Regenerated docs eliminate ~50 empty/incorrect input tables across integrations
(clickhouse, rb2b, reddit, file, etc.); param-less OAuth-only tools correctly
keep an empty input table.
2026-06-16 23:05:30 -07:00
Vikhyath Mondreti 7b4626e547 improvement(perm-groups): allow workspace filter for permission groups (#5070)
* improvement(perm-groups): allow workspace filter for permission groups

* show errors correctly

* address comments

* address concurrent edit concern

* address locks

* address comments"

* index migration safety

* address at route level
2026-06-15 20:52:01 -07:00
Waleed 05e8c7cd71 refactor(connectors): split client metadata from server runtime (#5076)
* refactor(connectors): split client metadata from server runtime + cover node:net in client bundle

The browser build broke with `Cannot find module 'node:net'`. Server-only
SSRF code in `input-validation.server.ts` (`dns/promises`, and since PR #5060
`undici` → `node:net`/`node:tls`) is statically reachable from the client
bundle via the tool/connector registries, which the workflow editor imports
for metadata. Node networking builtins have no browser shim, so Turbopack
cannot compile them for the client.

Two changes:

1. Split each connector's client-safe declarative metadata into a sibling
   `meta.ts` (`<name>ConnectorMeta`), mirroring the `XBlockMeta` /
   `BLOCK_META_REGISTRY` pattern. `connectors/registry.ts` is now the
   client-safe `CONNECTOR_META_REGISTRY` (+ `getConnectorMeta` /
   `getAllConnectorMeta`); the full registry with runtime fns moves to
   `connectors/registry.server.ts`. Client components consume the meta
   registry; the sync engine and knowledge API routes consume the server
   registry. This removes connectors from the client's server-only graph.
   Connector metadata is byte-for-byte identical before/after; runtime fns
   are untouched.

2. Extend the existing #4899 `turbopack.resolveAlias` browser stub — which
   already mapped `dns`/`dns/promises` to an empty module for the browser —
   to also cover `net`/`tls` (+ `node:` variants), since `undici` now pulls
   those in. The remaining tool/provider definitions still reach
   `input-validation.server` server-side; the browser-only stub keeps those
   Node builtins out of the client bundle while the real modules stay on the
   server, so SSRF validation and IP pinning are unaffected.

Connector authoring/validation skills updated to teach the meta.ts split.

* fix(icons): use Square logo glyph only, drop wordmark

* fix(connectors): share Discord max-messages default across meta and runtime

Discord defined DEFAULT_MAX_MESSAGES separately in meta.ts (config placeholder)
and discord.ts (sync behavior), which could drift. Export it from meta.ts and
import it in the runtime, matching the single-source pattern used by the other
connectors (e.g. gmail, intercom).

* refactor(tools): route grafana/agiloft egress server-side, drop SSRF browser shim

Move the server-only SSRF-pinned fetch out of the grafana (update_dashboard,
update_alert_rule) and agiloft (11 record/search tools) definitions and into
internal API routes, the same pattern the rest of the server-side tools (and
agiloft's own attach/retrieve) already use. The tool definitions are now purely
declarative (request → internal route), so they no longer import
`input-validation.server` and the tools registry is fully client-safe.

With connectors (meta split) and these tools no longer reaching server-only code
from the client bundle, the browser no longer pulls in `dns`/`net`/`tls`:

- Add `import 'server-only'` to `input-validation.server.ts` so any future client
  import fails loudly at build time instead of silently bloating the bundle.
- Remove the `turbopack.resolveAlias` browser stub and delete
  `empty-node-fallback.browser.ts` — the root cause is fixed, the shim is gone.

Behavior is unchanged: each route runs the exact merge/validation/fetch logic the
tool ran before (every header, param branch, JSON-parse guard, error string, and
SSRF pinning preserved); only the location of execution moved from the client-
bundled definition to a server route.

* fix(connectors): move onedrive tagDefinitions into meta; drop server-only guard

- onedrive's tagDefinitions lived in the runtime file, so the client meta
  registry returned undefined for it and the add-connector tag opt-out section
  stopped rendering for onedrive. Move it into meta.ts like the other connectors
  so client and server see identical metadata (verified across all 50).
- Remove the 'server-only' import from input-validation.server.ts: the meta/route
  split already keeps it out of the client bundle, and blocks/tools registries
  don't use the guard either.

* fix(grafana): surface upstream error when the prefetch GET fails

Check response.ok on the existing-resource GET in both update routes and return
the upstream status/body, matching how the tool framework surfaced GET errors
before the move to internal routes (the framework checks response.ok before
transformResponse). Without this, a failed prefetch produced a generic
'Failed to fetch existing ...' message and dropped Grafana's error detail.

* fix(grafana): reject invalid panels JSON instead of silently ignoring it

Grafana's dashboard API treats panels as a required array and returns 400 on
invalid JSON; this route already errors on every other JSON param. Return
'Invalid JSON for panels parameter' instead of swallowing the parse error and
proceeding with a misleading success.

* fix(grafana): trim dashboard/alert-rule UID in route URLs (carry over #5082)

PR #5082 added .trim() on dashboardUid/alertRuleUid in the original tool URL
builders. Those tools now build their URLs in the internal routes, so apply the
same trim there to preserve that behavior.

* fix(grafana): route update_folder egress server-side (carry over #5082)

#5082 added a grafana update_folder tool that does SSRF-pinned fetch in its
postProcess, re-introducing the client-bundle leak. Convert it to the internal
API route pattern like the other update tools so the def is declarative and
input-validation.server stays out of the client bundle.

* fix(grafana): surface route failures in transformResponse instead of masking them

The grafana update tools' transformResponse hardcoded success: true and dropped
the route's error, so an upstream/validation failure (HTTP 200 with
{ success: false, error }) was reported to the workflow as a success. Forward
data.success and data.error (matching the agiloft tools) so failures propagate
as before the move to internal routes.
2026-06-15 19:53:37 -07:00
Theodore Li 3fe061e3b3 feat(feature-flags): AppConfig-backed gated feature flags (#5059)
* feat(feature-flags): AppConfig-backed gated feature flags

* fix(ci): repoint 'Validate feature flags' step to env-flags.ts after rename

* improvement(feature-flags): drop in-code defaults; fallback resolves a per-flag secret, gating is AppConfig-only

* improvement(feature-flags): make flag names a closed set so every flag requires a fallback secret

* improvement(feature-flags): single FEATURE_FLAGS registry — each entry defines name, description, and fallback in one place

* improvement(feature-flags): fallback is the env secret key (keyof typeof env), resolved to a boolean
2026-06-15 21:56:12 -04:00
Theodore LiandClaude Opus 4.8 f277f5fba5 feat(db): zero-downtime migration safety lint + db-migrate skill (#5041)
* feat(db): zero-downtime migration safety lint + db-migrate skill

Add scripts/check-migrations-safety.ts (check:migrations), a CI gate that
classifies statements in newly-added migrations into hard errors (rewrite),
annotate-to-acknowledge contract ops (`-- migration-safe: <reason>`), and
backfill warnings. Wire it into test-build.yml. Add the /db-migrate skill as
the judgment half (expand/contract phasing, app-code cross-ref, annotation
authoring).

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

* feat(skills): run cleanup and db-migrate safety checks in /ship

* fix(db): address review — DROP INDEX lock symmetry, RENAME CONSTRAINT false-positive, alter-type literal match

- Non-concurrent DROP INDEX is now a hard error (ACCESS EXCLUSIVE lock), symmetric with CREATE INDEX; DROP INDEX CONCURRENTLY after a COMMIT passes clean. Removes the false-confidence annotate path.
- RENAME rule narrowed to RENAME COLUMN / table RENAME TO; RENAME CONSTRAINT and ALTER INDEX ... RENAME (metadata-only) no longer flagged.
- alter-type regex now requires TYPE to follow the column identifier, so it no longer matches TYPE inside a string default.

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

* fix(db): enforce IF EXISTS on DROP INDEX CONCURRENTLY for replay idempotency

Symmetric with the CREATE INDEX CONCURRENTLY rule: a post-COMMIT DROP INDEX
CONCURRENTLY replays from the top on failure, so without IF EXISTS it aborts
re-dropping an already-gone index.

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

* improvement(skills): gate /ship cleanup on UI changes; default migration base to staging

- /ship runs /cleanup only when the diff touches UI code (.tsx or apps/sim/components|hooks|stores); the six passes are React-only.
- /ship runs check:migrations against origin/staging (the PR base).
- check:migrations default baseRef is now origin/staging instead of origin/main.

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

* docs(db-migrate): add contract-pending TODO convention for deferred drops

Establishes a durable, greppable marker (`contract-pending(<precondition>): ...`)
left on the legacy column in schema.ts when an expand defers a drop, so the
contract phase doesn't rot. The outstanding-work list is `grep -rn contract-pending`;
the contract PR's `-- migration-safe:` annotation references the expand and deletes
the marker.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:00:50 -04:00
Waleed dd32abef1e feat(jsm): add Atlassian Assets (Insight/CMDB) tools for asset management (#5072)
* feat(jsm): add Atlassian Assets (Insight/CMDB) tools for asset management

Add nine JSM Assets tools so workflows can read and write Atlassian Assets
(Insight/CMDB) objects — the foundation for keeping JSM asset tables in sync
for software/hardware asset management.

Tools (wired into the Jira Service Management block):
- jsm_list_object_schemas, jsm_get_object_schema
- jsm_list_object_types, jsm_get_object_type_attributes
- jsm_search_objects_aql (AQL search with pagination)
- jsm_get_object, jsm_create_object, jsm_update_object, jsm_delete_object

Each tool proxies through an internal route that resolves the Jira cloudId and
the Assets workspaceId, then calls the Assets API via the OAuth 2.0 (3LO)
gateway form (/ex/jira/{cloudId}/jsm/assets/workspace/{workspaceId}/v1).

Adds the CMDB OAuth scopes to the jira provider (read/write/delete cmdb-object,
read cmdb-schema/type/attribute) with descriptions, contract schemas for each
route, and block operations/subBlocks/outputs. Bumps the API-validation route
baseline for the nine new routes.

* refactor(jsm): harden Assets param coercion and response typing

- Add toOptionalInt helper so non-numeric pagination inputs never emit NaN
  into the Assets query string (startAt/maxResults/page/resultsPerPage)
- Replace Record<string, any> in mapAssetObject with typed Raw* interfaces

* fix(jsm): validate Assets workspaceId and honor `last` pagination flag

Address review findings on the Assets tools:
- Add validateAssetsWorkspaceId and guard the workspaceId in every Assets
  route before it is interpolated into the API path (mirrors the existing
  cloudId guard) — prevents a crafted workspaceId from escaping the
  workspace-scoped path
- Object schema list now falls back to the `last` flag when `isLast` is
  absent, so pagination doesn't stop early

* feat(jsm): allow overriding the auto-resolved Assets workspace

Atlassian provisions one Assets workspace per site, so workspace discovery
uses values[0] by design. For the rare multi-workspace site, expose an
advanced "Assets Workspace ID" override on the block that flows through to
every Assets operation, and document the single-workspace assumption.

* refactor(jsm): include Assets responses in the JsmResponse union

Append the nine Assets tool response types to JsmResponse for completeness
and consistency with the rest of the JSM tool surface.
2026-06-15 16:40:24 -07:00
Siddharth Ganesan d538b76eda feat(copilot): server-side mothership tool/vfs/file metrics (#5071) 2026-06-15 16:32:35 -07:00
Theodore Li 18edc94b2c fix(billing): deploy modal gates on workspace entitlement, not viewer plan (#5055)
* fix(billing): deploy modal gates on workspace entitlement, not viewer plan

The deploy modal showed the upgrade wall to a free user in a PAID workspace,
because it gated on the viewer's individual plan (useSubscriptionData) while the
server gates on the workspace billed account (rolled-up plan). Add a workspace
api-execution-entitlement endpoint that mirrors isWorkspaceApiExecutionEntitled,
and gate the API/MCP/A2A tabs on it so the UI matches the server exactly.

* fix(billing): key deploy gate on URL workspaceId + refetch entitlement on open

Address review findings:
- key useWorkspaceApiExecutionEntitlement on the URL workspaceId (available on
  mount) instead of workflowWorkspaceId (null until the workflow map resolves),
  so the gate fires immediately instead of leaving the tabs ungated until then
- staleTime 0 so reopening the deploy modal refetches entitlement; a plan upgrade
  happens outside this query's invalidation graph, so the gate self-heals on open

* refactor(billing): workspace owner access state instead of bespoke entitlement endpoint

Replace the single-purpose api-execution-entitlement endpoint with a reusable
workspace-owner billing/access concept — the workspace-scoped counterpart to the
viewer-scoped useSubscriptionData:

- getWorkspaceOwnerSubscriptionAccess(workspaceId): the billed account's rolled-up
  subscription access fields (mirrors getSimplifiedBillingSummary's flag derivation)
- GET /api/workspaces/[id]/owner-billing + useWorkspaceOwnerBilling hook
- deploy modal derives its gate via the existing getSubscriptionAccessState
  (hasUsablePaidAccess) on the owner data, exactly like every other paid feature

Audited the rest of the app: no other UI gates on the viewer's plan where the
server gates on the workspace owner — programmatic execution is the only
workspace-owner-scoped feature; inbox/KB-live-sync/credential-sets all gate
consistently on both sides.

* fix(billing): deploy gate on owner isPaid, not hasUsablePaidAccess

hasUsablePaidAccess rejects past_due and billing-blocked, but the server gate
(isWorkspaceApiExecutionEntitled) allows any paid plan in an entitled status
(active or past_due). Gate on the owner's isPaid so a past_due paid workspace
isn't shown the upgrade wall while the API still works.
2026-06-15 15:36:56 -04:00
Siddharth Ganesan cefb2dc239 improvement(mship): add enrichment tool, clean up dead tools (#5058)
* feat(mothership): add enrichment_run server tool for one-off lookups

Implement the Sim-side handler for the copilot enrichment_run tool: runs the
enrichment provider cascade for a single entity and returns the result inline,
surfacing the hosted-key cost as _serviceCost for per-round billing (matching
the media tools). Registered in the server-tool router.

Regenerate the copilot tool catalog/schemas to include enrichment_run.

* fix(mothership): remove leftover touch_plan tool references

touch_plan was removed from the copilot tool catalog earlier, but the Sim side
still referenced it. Regenerating the generated tool catalog (for enrichment_run)
synced it to the current contract and dropped the stale TouchPlan export, which
broke the build where router.ts still imported it.

Remove the dead touch_plan server tool and its test, drop its router
registration and WRITE_ACTIONS entry, simplify getServerToolRegistry (no more
beta-gated server tools), and clean up stale "use touch_plan" guidance strings.

* chore(mothership): trigger dev redeploy

* improvement(mothership): log billed cost on enrichment_run lookups

* fix(contracts): regenerate mship contracts

* fix(contracts): fix mship contracts
2026-06-15 10:57:29 -07:00
Waleed 940506ad09 feat(square): add Square integration with 34 commerce operations (#5053)
* feat(square): add Square integration with 34 commerce operations

Add a Square integration (API-key auth via personal access token) covering
payments, refunds, customers, locations, orders, invoices, catalog, and
inventory. Catalog image upload routes through an internal API endpoint using
the shared UserFile handling pattern. Adds a dedicated square-errors extractor.

* fix(square): correct catalog image part name and address review feedback

- Fix catalog image upload: Square's multipart part for the binary is `file`,
  not `image_file` (per the live API cURL examples); this would have caused
  upload failures
- Catalog image route: check response.ok before parsing, drop the unreachable
  legacy base64 path, derive MIME from the uploaded file
- Block: split the search query field per operation so placeholders match each
  endpoint's schema; parse each JSON field individually so errors name the field
- Round out coverage: complete_payment version_token; customer nickname/birthday;
  batch inventory states/updated_after/limit

* fix(square): correct canonical file param usage and revert query split

- Read the catalog image file from the canonical `params.file` (the basic/advanced
  inputs are collapsed before the params function runs) instead of the raw
  uploadFile/fileRef ids, which no longer exist at that point — fixes the
  Canonical Param Validation test and a latent upload bug
- Revert the per-operation query split: canonicalParamId is only valid for
  basic/advanced pairs under one condition. Use a single query field with a
  schema-neutral placeholder and a wand prompt that covers each search operation

* chore(square): trigger fresh review

* fix(square): single-location invoice search and guard numeric coercion

- SearchInvoices: Square's invoice filter accepts only one location, so take a
  single locationId (string) instead of an array and wrap it as
  query.filter.location_ids: [locationId]
- Block: fail locally with a clear "<field> must be a valid number" error when
  amount/limit/version/orderVersion are non-numeric instead of forwarding NaN

* fix(square): accept real booleans for autocomplete/includeRelatedObjects

Coerce these from both the dropdown's string values and actual booleans
(which can arrive via connected blocks or templated inputs), so true is not
silently flipped to false.

* fix(square): validate parsed JSON field shapes (array vs object)

parseJsonField now enforces the expected shape so a valid-but-wrong-type value
(e.g. a JSON string where an array is expected for locationIds/objectTypes/
paymentIds/catalogObjectIds/states, or a non-object for order/invoice/etc.)
fails locally with a clear message instead of a confusing Square API error.
2026-06-15 09:55:14 -07:00
Waleed eb1009da1c improvement(react-query): codebase-wide audit — server-state hooks, webhook coherence, resume migration (#5024)
* improvement(react-query): codebase-wide audit — server-state hooks, webhook coherence, resume migration

* chore(react-query): add static pattern linter + address review feedback

- add scripts/check-react-query-patterns.ts (staleTime/signal/key-factory/inline-key
  enforcement) wired into CI as check:react-query; strict zone hooks/queries/**, ratchet elsewhere
- fix(resume): use same-origin relative path for resume POST (getBaseUrl could cross origin
  on whitelabel/preview hosts and drop session cookies) — Cursor Bugbot
- remove explanatory inline comments in favor of TSDoc per repo convention
2026-06-13 10:14:07 -07:00
Waleed 58cff68b5e feat(deployments): add v1 deployment endpoints and Deployments block (#5009)
* feat(deployments): add v1 deployment endpoints and Deployments block

* fix(deployments): require deployed workflow for rollback, normalize warnings, guard orphaned workspaceId

* fix(deployments): workspace-bound tool routes, optional-body parsing, version bounds, and 404 masking

- Tool routes now require the executing workspace ID and reject cross-workspace targets
- v1 deploy/rollback read optional bodies via parseOptionalJsonBody (size-capped, 400 on malformed JSON)
- Version numbers bounded to the Postgres integer range
- v1 mutation routes mask access failures as 404, matching the v1 detail route
- listWorkflowVersions returns description and normalizes admin-api deployedByName (parity with mothership get_deployment_log)
- Workflow selector no longer auto-selects the first workflow (new autoSelectFirstOption opt-out)
- Shared deployment version metadata field schemas across UI/v1/tool contracts

* chore(api-validation): bump route baseline for rebased staging (826)

* fix(docs): use real ID formats in OpenAPI examples

Workflow, workspace, folder, knowledge-base, document, and execution IDs are
plain UUIDv4; workspace file IDs are wf_<shortId>; table and row IDs are
tbl_/row_ + de-dashed UUID. Replaces all fake prefixed example IDs (wf_abc123,
ws_xyz789, exec_..., kb_..., etc.) accordingly and marks the deploy body
description as nullable to match the shared schema.

* feat(deployments): resolve workflow names in block UI, add workflow_undeployed Sim trigger event

- Deployments block now uses the workflow-selector subblock (same as the
  Workflow block), so the canvas tile shows the workflow name instead of the
  raw ID; reverts the now-unneeded dropdown autoSelectFirstOption prop
- Adds workflow_undeployed to the Sim workspace-event trigger, emitted by
  performFullUndeploy through a shared lifecycle-event dispatch loop
2026-06-12 16:38:08 -07:00
Waleed 636bd74f06 fix(integrations): resolve OAuth connect UI by service id instead of display name (#5001)
* fix(integrations): resolve OAuth connect UI by service id instead of display name

* test(integrations): pin OAuth service resolution for all catalog integrations; fix credential branding reverse lookup

* fix(docs-gen): blank string literals and comments before brace scanning in extractOAuthServiceId
2026-06-12 11:38:01 -07:00
Waleed 3db7161b1c feat(integrations): add Vanta integration with compliance, evidence file, people, vendor, vulnerability, and risk tools (#4993)
* feat(integrations): add Vanta integration with compliance, evidence file, people, vendor, vulnerability, and risk tools

* fix(integrations): use write-only scope for vanta document submit and stream-cap document downloads

* fix(integrations): vanta review feedback - unique svg ids, mime type for base64 uploads, auth check consistency

* fix(integrations): bound vanta base64 fileContent size at the contract level

* improvement(integrations): cache vanta tokens and retry once on revocation to avoid concurrent token races

* improvement(integrations): deduplicate in-flight vanta token exchanges

* fix(integrations): hash vanta client secret in token cache keys

* improvement(integrations): expose vanta upload mime type in block and align fileContent bound with the 100MB cap

* chore: resolve api-validation baseline after rebase onto staging (816 + 3 vanta routes)
2026-06-12 10:56:27 -07:00