Commit Graph
96 Commits
Author SHA1 Message Date
WaleedandClaude Opus 4.8 16954e46fe feat(integrations): add ClickHouse block and expand Dagster + Tinybird tools (#4883)
* feat(integrations): add ClickHouse block and expand Dagster + Tinybird tools

* fix(tinybird): fail loudly on invalid query_pipe parameters JSON

parsePipeParameters previously returned {} on any JSON parse error, so a
mistyped 'parameters' input produced a successful pipe call with the dynamic
filters silently dropped. Throw a clear error for non-empty, non-object input
instead; an omitted/empty value still means 'no parameters'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dagster): guard NaN numeric coercions and bound list_assets pagination

Address PR review:
- Route all block numeric coercions (list_runs limit/createdAfter/createdBefore,
  get_run_logs logsLimit, list_assets assetsLimit) through a toFiniteNumber()
  guard so invalid/wand-generated text becomes undefined instead of NaN.
- list_assets now applies a default page size (100) when no limit is given, so
  paging stays bounded and hasMore is meaningful even when limit is omitted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dagster): make list_assets hasMore exact via fetch N+1

Address PR review (hasMore true on exact page): request one extra row
(pageSize + 1), use its presence as the authoritative hasMore, slice it off,
and derive the returned cursor from the last RETURNED asset's key path
(JSON-serialized; Dagster normalizes JS/Python whitespace on the way in).
This removes the false-positive hasMore when the final page is exactly full.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(clickhouse): enforce read-only query operation and harden WHERE-clause guard

* fix(dagster): make list_runs hasMore exact via fetch N+1

Address PR review (list runs false hasMore): request one extra row
(pageSize + 1), use its presence as the authoritative hasMore, and slice it
off before mapping. Removes the false-positive hasMore (and misleading cursor)
when the final page is exactly `limit` runs long. Mirrors the list_assets fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(clickhouse): restrict DROP PARTITION to literal values to prevent SQL injection

* fix(clickhouse): reject chained statements in read-only query operation

* fix(clickhouse): force JSON output on query path and ignore comments when detecting chained statements

* fix(tinybird): encode datasource/pipe names in URL paths to prevent traversal

A user-or-llm datasource/pipe name interpolated raw into the URL path (e.g.
'real_ds/../../other') is normalized by the WHATWG URL parser and can target a
different endpoint. Wrap the path segment with encodeURIComponent in the
truncate, delete, and query_pipe URLs. Events/append pass the name via
URLSearchParams, which already encodes, so they were unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(clickhouse): block WITH-led writes/DDL in read-only query operation

* fix(clickhouse): validate column types structurally and normalize FORMAT around SETTINGS

* fix(clickhouse): balance-check ORDER BY/PARTITION BY and skip leading comments in read-only guard

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:49:51 -07:00
Theodore LiandClaude Opus 4.8 3518b999af feat(tables): background import for large CSVs with live progress (#4861)
* feat(tables): background import for large CSVs with live progress

* fix(tables): address review — import heartbeat, overlap guard, column/empty validation

* fix(tables): guard sync import overlap, scope fileKey to workspace, delete-on-replace after download

* fix(tables): stream large CSV imports from storage instead of buffering the whole file

* test(tables): fix async-import route tests for workspace-scoped fileKey + name uniquification

* fix(tables): append imports start after existing rows; reconcile missed import failures in the tray

* fix(tables): delete the uploaded CSV from storage after the import finishes

* fix(tables): validate replace before deleting rows; ignore stale replayed import events by importId

* fix(tables): bind import worker to its importId (no stale-worker clobber/overlap) and destroy storage stream on failure

* feat(tables): byte-based import progress, cancel support, and a start toast that opens the import view

* fix(tables): don't emit ready after cancel; honor cancel during the upload phase

* improvement(tables): use a stop (square) icon for canceling an active import

* fix(tables): make markTableImporting an atomic claim to close the concurrent-import TOCTOU race

* improvement(tables): preview CSV import from a slice, drop client row-count warning

The import dialog parsed the entire file in the browser to show an exact row
count and a row-limit warning. That holds the whole file in memory, blocks the
main thread, and hits V8's ~512MB string ceiling — so the dialog capped the
effective import size well below what the streaming importer handles.

Parse only the first 512KB (headers + sample for the mapping); drop the exact
count and the "would exceed the row limit by N" gate. The DB row-count trigger
already enforces max_rows server-side, so an over-limit import fails fast during
the run with a clear message instead of being blocked by an expensive parse.

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

* fix(tables): gate import ownership every batch and stop canceled imports reappearing

- Worker checked run ownership only at the progress cadence (~every 5k rows), so
  a canceled/superseded import could insert several more batches (incl. the final
  partial batch) before stopping. Move the updateImportProgress ownership gate to
  the top of every flush — a run that lost the table stops within one batch.
- A list/dialog import canceled mid-upload left the server row `importing` until
  the in-flight server cancel landed; hydration re-seeded it from useTablesList,
  so the dismissed import flickered back. Flag the real table id canceled on the
  mid-upload cancel path, skip re-seeding flagged tables in hydration, and clear
  the flag once the server import is terminal.

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

* refactor(tables): drive import tray by polling derived from server, not SSE

Import progress no longer holds an SSE connection per importing table. The tray
now derives its importing rows live from the table list (React Query), polled
only while an import is in flight; the table detail page keeps its own
cell-state SSE for grid refresh.

- store holds only client-only state now: optimistic uploads, which terminal
  completions to surface this session, canceled ids, menu open — no copied
  importStatus/rowsProcessed.
- useWorkspaceImports is the single source: polls via a data-predicate
  refetchInterval, derives rows, and fires completion toasts on the
  importing -> terminal transition.
- kickoff handlers use startUpload/setUploadPercent/endUpload; the invalidated
  list refetch surfaces the server row and polling takes over.
- removes use-hydrate-import-tray + use-import-progress-tracker (folded in).
- trims over-verbose comments across the import paths.

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

* fix(tables): ignore superseded-run import events in the detail SSE cache

applyImport applied every replayed import payload to the detail cache. The SSE
buffer can replay a prior import's terminal event for the same table, stomping a
newer in-flight import's UI. Lock to the active run's importId (and ignore a
replayed terminal before the id is known), matching the guard the header tracker
used to have.

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

* fix(tables): close sync-import TOCTOU by claiming the atomic import gate

The sync import route checked importStatus from a checkAccess snapshot, then
parsed/validated/wrote seconds later without taking the atomic claim. A
concurrent async kickoff (markTableImporting) could slip into that window and
both writers would run together — for replace mode, two delete+insert passes
leave the table indeterminate.

Claim the same atomic gate (markTableImporting) right before the write and
release it in the finally (before the response returns, so a client refetch
never sees the transient status). A row-level FOR UPDATE was avoided on purpose:
it would invert lock order against the position advisory lock / row-count
trigger and risk a deadlock — markTableImporting is the established gate.

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

* fix(multipart): keep abort wired after resolve so a mid-upload disconnect tears down the stream

readMultipart resolves on the file-part header and hands the caller an un-drained
stream, but settle() ran cleanup() and detached the abort listener on that path
too. A client disconnect mid-upload then destroyed nothing — busboy never saw EOF,
the file stream stalled, and the route's `for await` held a request slot until
maxDuration (300s). Re-arm an abort handler scoped to the file stream on resolve,
detached when the stream closes.

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-03 21:26:06 -04:00
Vikhyath Mondreti 5d9752d563 fix(mothership): connect integrations from chat without state_mismatch (#4848)
* fix(oauth):  skipStateCookieCheck flag change

* browser initated solution

* fix draft timing issue
2026-06-02 12:22:38 -07:00
Waleed 3f3efc98c3 chore(auth): remove deprecated OAuth MCP provider plugin and backing tables (#4847) 2026-06-02 10:46:20 -07:00
Waleed e5a46d7959 feat(linq): add Linq iMessage/SMS/RCS integration (34 tools, block, attachment upload) (#4831) 2026-06-01 13:44:33 -07:00
WaleedandClaude Opus 4.8 403a02c3af feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)
* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers

* fix(providers): guard Ollama streaming fast-path with hasActiveTools

Match Together/Baseten/Fireworks: when tools are supplied but all are
filtered out (usageControl 'none'), take the single streaming call instead
of an extra non-streaming round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers): filter non-chat model types from Together model list

* refactor(providers): dedupe Ollama Cloud upstream schema

ollamaCloudUpstreamResponseSchema was byte-for-byte identical to
ollamaUpstreamResponseSchema (both /api/tags endpoints return the same
{ models: [{ name }] } shape). Drop the duplicate and reuse the shared schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 12:17:29 -07:00
Waleed e1e773f487 feat(slack): add install + privacy section to integration landing page (#4799)
* feat(slack): add install + privacy section to integration landing page

Adds a hand-authored, slug-keyed landing-content module (separate from the generated integrations.json so it survives regeneration) and renders an install walkthrough + privacy-policy link on integration pages when present. Also refreshes generated docs (data-enrichment entry, icon mappings, tool mdx).

* fix(landing): render privacy section independently, align CTA analytics label

* docs(landing): clarify the Slack install button is behind sign-in

* refactor(landing): bake integration landing content into generated json via docs-gen

Moves landing content (install walkthrough + privacy) out of a render-time augment and into the generation pipeline: generate-docs reads the pure-data content map and writes landingContent into integrations.json, so the page reads a single source (integration.landingContent). Canonical types live in integrations/data/types.ts.
2026-05-29 16:33:30 -07:00
Vikhyath Mondreti 4b0dab4682 chore(copilot): deprecate mcp server (#4797)
* chore(copilot): deprecate mcp

* update error codes

* deprecate copilot api v1 route
2026-05-29 14:34:15 -07:00
Theodore LiandClaude Opus 4.7 7f24ae12fd feat(block): Add data enrichment block (#4774)
* feat(enrichment): workflow Enrichment block + /api/tools/enrichment/run

Add a generic Enrichment workflow block that runs a code-defined enrichment
(Work Email, Phone Number, Company Domain, Company Info, …) and returns its
outputs — usable in workflows, not just tables.

- New internal endpoint POST /api/tools/enrichment/run (checkInternalAuth +
  contract) runs the same runEnrichment provider cascade; injects the
  workspace's hosted/BYOK key via executeTool.
- New tool enrichment_run posts to it and surfaces hosted-key cost on the
  output so the workflow logging session bills it.
- New block blocks/blocks/enrichment.ts generated from the enrichment registry:
  operation dropdown = enrichments, per-enrichment conditional inputs, union of
  conditional outputs. New registry entries appear automatically.
- EnrichmentRunContext.tableId/rowId made optional (workflow path has no row).
- Register tool + block; bump api-validation route baseline; add EnrichmentIcon
  and generated docs page.

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

* chore: re-trigger CI

* refactor(enrichment): share mapFieldType helper between block and tool

* fix(enrichment): reserved output keys (matched/provider) win over enrichment outputs

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:19:08 -04:00
WaleedandClaude Opus 4.8 c898e2e623 feat(integrations): add ZoomInfo, align Wiza, audit Apollo, refresh docs (#4776)
* feat(integrations): add ZoomInfo, align Wiza, audit Apollo, refresh docs

- Add ZoomInfo integration: search/enrich contacts & companies, intent, news (6 tools), proxy route, block, and icon
- Validate and align Wiza tools/block/outputs against live API docs
- Audit Apollo tools: tighten params, outputs, and types
- Update tool docs (.mdx), icons, icon mappings, and integrations.json

* fix(zoominfo): use useId for ZoomInfoIcon clipPath to avoid duplicate DOM ids

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(apollo): address PR review on sequence add and bulk enrich

- sequence_add_contacts: send large contact_ids/label_names arrays in the
  POST body (Rails merges query + body params) to avoid reverse-proxy URL
  length limits; keep scalar settings in the query string
- organization_bulk_enrich: add back-compat shim mapping the legacy
  `organizations` ({name, domain}[]) subBlock value to the new `domains`
  string array so saved workflows keep running

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(integrations): unique ZoomInfo icon clip id, numeric employee range filters

- ZoomInfoIcon: derive clipPath id from useId() so multiple instances don't collide
- ZoomInfo company search: send employeeRangeMin/Max as numbers, matching revenueMin/Max

* fix(zoominfo): send employeeRange filters as strings per API schema

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(zoominfo): send contactAccuracyScoreMin as string per Contacts Search schema

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(apollo): harden people_search pagination, correct bulk-update output docs

- people_search: read pagination from both the nested `pagination` object
  (legacy /mixed_people/search) and top-level fields, avoiding silent
  fallback to defaults
- account_bulk_update: correct output descriptions — accounts support up to
  1000 per request and async is opt-in (not auto-triggered at 100 like contacts)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(wiza): correct company enrichment credits shape in output docs

Company enrichment returns api_credits { total, company_credits }, not the email/phone/scrape breakdown used by individual reveals. Description-only fix verified against docs.wiza.co.

* fix(apollo): send sequence add contact_ids/label_names as query params per docs

Apollo documents every field for emailer_campaigns/:id/add_contact_ids as a query parameter with no request body. Append contact_ids[]/label_names[] to the query string instead of the JSON body to match the documented contract.

* feat(apollo): expose account_stage_id uniform field for bulk update accounts

Apollo documents account_stage_id as a Body Param for /accounts/bulk_update ('when using account_ids, apply this account stage to all accounts'). Adds it to the tool params, body builder, type, block subBlock, and params mapper alongside name/owner_id.

* docs(apollo): correct contact_update typed_custom_fields description

Apollo's update-a-contact endpoint documents typed_custom_fields, so drop
the inaccurate "not officially documented" caveat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(zoominfo): default required outputFields on enrich; parse nested API error object

- ZoomInfo enrich endpoints require outputFields; send a curated default set when omitted so requests don't fail
- extractZoomInfoError now reads the GTM REST nested error object ({error:{code,message}}) instead of dropping it to a generic HTTP message

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(wiza): add wandConfig to complex prospect-search filter fields

Adds AI-assist wandConfig (json-object) with format examples to the structured filter inputs (job_title, job_company, past_company, company_industry, location, company_location) and the full filters object, completing the wandConfig checklist item for the Wiza block.

* fix(findymail): surface API .error messages and alphabetize registry

- transformResponse error branches now fall back to the response body's
  `error` field before the generic status string, so Findymail's actual
  messages ("Not enough credits" on 402, "Subscription is paused" on 423,
  "One identifier is required..." on 422) reach the user instead of a
  bare "Findymail API error: <status>". Applied to all 11 tools.
- alphabetize the findymail entries in tools/registry.ts to match the
  already-alphabetical import block and the integration guideline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-28 15:30:25 -07:00
WaleedandRheagalFire 81bf93b184 feat(litellm): add LiteLLM as AI gateway provider (#4739)
* feat: add LiteLLM as AI gateway provider

* fix: add litellm to attachments, provider store, utils, and block guards

* fix: add frontend model discovery pipeline for litellm provider

Add API route, contract, query hook case, and ProviderModelsLoader
entry so litellm models are fetched and synced to the store on
workspace load, matching the vllm/ollama/openrouter/fireworks pattern.

Also fixes defaultModel to empty string and adds litellm/ prefix
early-return in blocks/utils.ts (reviewer feedback).

* fix: remove azureEndpoint fallback from LiteLLM provider

Copy-paste artifact from vLLM provider. LiteLLM should only use
LITELLM_BASE_URL, not fall back to azureEndpoint which could cause
requests to be routed to the wrong server.

* fix(litellm): close audit gaps from PR #4644

- byok.ts: add litellm branch to getApiKeyWithBYOK so workflow
  block execution can resolve the proxy key instead of throwing
  "API key is required for litellm ..."
- check-api-validation-contracts.ts: bump route baseline 755 -> 756
  to account for the new /api/providers/litellm/models route
- .env.example: document LITELLM_BASE_URL / LITELLM_API_KEY
- copilot edit-workflow validation: include LiteLLM in the list of
  user-configured prefixed providers shown to the model
- providers/utils.ts: drop stray optional-chain on providers.litellm
  to match the vllm pattern
- lint: apply biome formatting fixes (multi-line if, SVG path,
  multi-line DYNAMIC_MODEL_PROVIDERS)

* fix(litellm): final parity gaps from second audit

- blocks/utils.ts getModelOptions(): include litellm models in the
  combined model dropdown — was previously dropping any
  proxy-discovered models from the agent block model picker.
- get-blocks-metadata-tool.ts mockProvidersState: add litellm bucket
  so the server-side copilot block-metadata fallback can render
  model options when the providers store is not initialized.
- blocks/utils.test.ts: add litellm to mock providers state (initial
  + beforeEach reset) and add a parallel store-bucket guard test
  mirroring the vLLM case.
- providers/utils.test.ts: add parallel getApiKey test for litellm.

* feat(litellm): use official LiteLLM brand icon and color

- icons.tsx: replace the placeholder letterform with the official
  LiteLLM brand mark embedded as a PNG data URI in an SVG image.
- models.ts: set color: #040229 on the litellm provider definition
  to match the brand background.

* chore(litellm): validate /v1/models response with shared schema in initialize()

Match the API route handler — both code paths now run the same
vllmUpstreamResponseSchema.parse() over the upstream /v1/models
JSON instead of a raw type-cast, so malformed upstream payloads
surface a descriptive ZodError instead of a downstream TypeError.

Addresses Greptile review feedback on PR #4739.

---------

Co-authored-by: RheagalFire <arishalam121@gmail.com>
2026-05-26 10:38:24 -07:00
Waleed 21c956cf97 improvement(hubspot): OAuth-native polling trigger replacing webhook flow (#4705)
* improvement(hubspot): OAuth-native polling trigger replacing webhook flow

* feat(hubspot): property autocomplete, multi-filter, property-changed, list-membership, pipeline/owner dropdowns

* fix(hubspot): freeze cursor on failure + request full OAuth scopes

* chore(api): bump API route baseline from 749 to 753 for HubSpot selector routes

* fix(hubspot): make eventType required conditional on visibility

* improvement(hubspot): align trigger name and longDescription with poll-trigger conventions

* fix(hubspot): encodeURIComponent on search path segment for defense-in-depth

* fix(hubspot): cursor-based seed for list_membership polling

* fix(hubspot): Map-backed property snapshot + drop redundant filter parse
2026-05-21 17:25:19 -07:00
Vikhyath Mondreti 47bd7fa345 fix(logs-cleanup): listing active workspaces into mem + download time streaming lims (#4692)
* fix(logs-cleanup): listing active workspaces into mem + download time streaming lims

* fix

* fix ci

* address comments

* add skill

* fix client-server sep

* fix parse bytes enforcement

* address comments, antipatterns

* address slides ssrf comment

* more fixes

* fix tests

* fix
2026-05-21 16:16:03 -07:00
WaleedandClaude Opus 4.7 46db40620f feat(mcp): OAuth 2.1 + PKCE for outbound MCP servers (#4441)
* feat(mcp): OAuth 2.1 support for outbound MCP servers

* fix(mcp): tighten OAuth refresh race and session-error detection

Re-load the OAuth row inside withMcpOauthRefreshLock so concurrent
callers observe predecessor-written tokens instead of a stale snapshot
loaded before lock acquisition. Without this, the second caller's
provider held a rotated-out refresh token and the SDK tripped
invalid_grant, forcing reauthorization.

Switch isSessionError to match the SDK's typed StreamableHTTPError
(code 404/400) instead of substring-checking arbitrary error messages,
removing false positives on URLs that happen to contain those digits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(mcp): tighten OAuth callback contract and registration metadata

- Validate callback query params via mcpOauthCallbackContract instead of
  raw searchParams.get, matching the rest of the MCP route surface.
- Drop non-RFC-7591 application_type field from dynamic client registration
  to avoid rejection by strict authorization servers.
- Collapse the pre-lock OAuth row load in createClient — the row is now
  loaded exclusively inside withMcpOauthRefreshLock, removing a redundant
  query and a stale-snapshot path.

* fix(mcp): narrow workspaceId before async closure in OAuth createClient

* fix(mcp): return authType from create-server endpoint

The POST /api/mcp/servers handler omitted authType from the success
response, so useCreateMcpServer always saw data.data.authType as
undefined and never triggered the OAuth popup after creating an
OAuth-protected server. Thread authType through performCreateMcpServer
into the response so the client can decide whether to auto-start OAuth.

* fix(mcp): mirror server null normalization in optimistic oauthClientId update

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): revert optimistic oauthClientId to undefined to match McpServer type

The response contract preprocesses null → undefined, so McpServer.oauthClientId
is string | undefined. Using null broke type checking.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): tighten OAuth probe signal and clear stale popup interval

- probe: only classify as OAuth on resource_metadata or scope params.
  Bare `Bearer error="invalid_token"` is generic and used by API-key servers,
  so it must not auto-flip the auth type to OAuth.
- popup hook: clear any existing close-watcher interval before overwriting
  when startOauthForServer is invoked twice for the same serverId.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): normalize empty-string oauthClientId at route boundary

Orchestration already converts falsy → null via `|| null` (server-lifecycle.ts),
so the DB was never receiving an empty string. Tightening the route layer to
match the same convention keeps the boundary contract consistent and avoids
relying on downstream normalization.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(canvas): expand MCP tool params into per-row labels on block tile

The MCP Tool block on the workflow canvas previously crammed every selected-
tool parameter into a stringified blob under the `Tool` row. Now, when a tool
is selected, the tile reads the cached `_toolSchema` and emits one labeled
SubBlockRow per parameter (matching the Exa block's per-param layout). Labels
reuse `formatParameterLabel` for parity with the editor panel; values pass
through the existing `getDisplayValue` so booleans/numbers/arrays render
identically to other blocks. Deterministic tile height counts expanded rows
so the tile sizes correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(logs): show MCP icon and strip prefix in trace tool spans

Tool spans for MCP calls were rendering the raw id (e.g.
`mcp-f908f259-planetscale_list_organizations`) with the default blank-
square icon. Now they read just the tool name and render the MCP block's
icon and bgColor, matching how workflow-execute tools render.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): lift near-black trace icon backgrounds for dark-mode contrast

Block bgColors below a small luminance threshold (e.g. the MCP block's
#181C1E) rendered nearly invisible against the dark-mode surface
(--bg: #1b1b1b). Adds a tiny adjustBgForContrast helper that floors each
RGB channel at 0x33 only when luminance is below 30,000, leaving every
branded color above that band untouched. Applied to both the trace tree
row and the detail pane.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): fall back to neutral gray for near-black trace icon bgs

#333333 was still too close to the dark-mode surface to read. For bgs
below the luminance threshold (e.g. the MCP block's #181C1E) we now fall
back to DEFAULT_BLOCK_COLOR (#6b7280) — the same neutral the renderer
uses for blocks with no distinct identity. Clearly visible in both
themes; brighter brand colors still pass through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(db): drop 0209_mcp_oauth migration ahead of staging merge

Staging shipped 0209_smiling_fixer; the MCP OAuth migration will be
regenerated on top of staging as 0210.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(db): regenerate MCP OAuth migration as 0210

Re-runs drizzle-kit generate on top of staging's 0209_smiling_fixer.
Same schema (mcp_server_oauth table + mcp_servers.auth_type / oauth_*
columns) as the dropped 0209_mcp_oauth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(audit): bump route baseline 748 → 749 after staging merge

The post-merge route count is 749 (this branch's OAuth start/callback
plus staging's new route). I had set the baseline to 748 in the merge
conflict resolution — bumping to match reality so the strict audit
passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: remove source-command skill files committed by accident

These were untracked-then-accidentally-staged in 05c4bc19e via a wide
`git add -A`. They aren't part of this PR's scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 12:10:04 -07:00
Theodore LiandClaude Opus 4.7 f0311a6f5e feat(table): chunked dispatcher + workflow cascade (#4672)
* feat(table): chunked dispatcher for workflow-column runs

Replaces the all-rows-at-once runWorkflowColumn with a row-window dispatcher
backed by a new table_run_dispatches row. Each user click inserts a dispatch
row and triggers a trigger.dev task that crawls the table 20 rows at a time,
re-enqueueing itself between windows. The HTTP/Mothership entrypoints return
{ dispatchId } immediately instead of holding the request open for minutes
on multi-thousand-row dispatches.

- Per-row cancel stamps cancelledAt; the dispatcher skips cells whose
  cancelledAt > dispatch.requestedAt so a mid-cascade cancel sticks even
  under isManualRun.
- Table-wide cancel marks active dispatches cancelled atomically so the
  dispatcher bails on its next iteration.
- New 'dispatch' SSE event variant plumbed; client ignores for v1.

* fix(table): eager bulk clear on column run so cells flip immediately

Run-column with run-mode 'all' wasn't visually flipping rows that already
had data — the cell renderer's "value wins" branch kept showing the prior
output behind the queued/running state. The dispatcher only cleared one
window of rows at a time, so most of the column stayed stale until the
cursor walked to it.

Now:
- Dispatcher's `pending → dispatching` transition runs a single SQL UPDATE
  that wipes targeted `data` output columns and `executions[gid]` across
  every targeted row (mode-aware: 'incomplete' skips fully-filled rows).
- Per-window clear in `dispatcherStep` is gone — rows are pre-cleared,
  the loop only filters cancel tombstones / unmet deps and enqueues.
- Optimistic patch in `useRunColumn` mirrors the bulk clear by nulling
  output values in the cached row, so the UI flips queued/running
  instantly without waiting for the SSE catch-up.

* fix(table): bulk clear honors in-flight execs under mode: 'incomplete'

The eager bulk clear for mode: 'incomplete' only skipped rows that were
already fully filled, so two overlapping dispatches could race — dispatch B
would nuke executions[gid] on a row dispatch A had just stamped 'queued',
flickering the cell and potentially confusing the worker.

Skip any row whose targeted group is currently queued/running/pending — an
'incomplete' run shouldn't touch what another dispatch is actively working
on. The per-walk 'in-flight' eligibility skip already handles rows that
flip in-flight between the clear and the cursor reaching them.

* refactor(table): dispatcher uses batchTriggerAndWait + tag-based cancel

Switch the per-window cell fan-out from fire-and-forget tasks.trigger to
tasks.batchTriggerAndWait. The dispatcher is now a single long-lived
trigger.dev task that loops dispatcherStep until the table is exhausted;
trigger.dev CRIU-checkpoints the parent during each wait so we don't pay
compute while cells execute. Queue depth is bounded at WINDOW_SIZE per
dispatch — no more flooding trigger.dev with a million queued runs.

- dispatcher.ts builds payloads via the new shared buildPendingRuns helper
  and calls tasks.batchTriggerAndWait directly. Pre-stamps each cell to
  `queued` (jobId=null) so the UI flips instantly.
- table-run-dispatcher.ts is now a plain while-true loop. No
  RUN_BUDGET_MS, no self-re-enqueue, no cold-start tax per window.

Cancel:
- New cancelCellRunsByTags(tags) paginates runs.list + runs.cancel(id).
- cancelWorkflowGroupRuns fires the tag-sweep alongside the per-jobId
  queue.cancelJob path (preserved for auto-fire cells that have real
  jobIds from single tasks.trigger calls).
- Trigger.dev acks the cancel → batchTriggerAndWait resumes → dispatcher
  observes the dispatch-row cancel flag → exits.

Side fixes:
- getAsyncBackendType returns 'trigger-dev' whenever taskContext.isInsideTask
  is true, regardless of TRIGGER_DEV_ENABLED env. The preview/dev-sim
  worker silently routing cell jobs to DatabaseJobQueue (no poller) is
  fixed without any env config change.
- runWorkflowColumn skips the dispatcher entirely when trigger.dev is
  disabled, running cells inline via DatabaseJobQueue.runInline. HTTP
  response returns dispatchId: null in that mode.
- runColumnContract response schema updated to dispatchId.nullable().

* fix(table): show Stop button on optimistic-pending row cells

isExecInFlight required a jobId for `pending` status, gating it as "real
backend pending" vs "optimistic flag only." The row-gutter Stop button
keyed on this — so a freshly clicked Play sat as `pending` (no jobId) and
the user couldn't cancel it until the server-side `queued` stamp arrived
via SSE. With the dispatcher pre-batch stamping cells as `queued` (not
`pending`) and no per-cell jobIds under batchTriggerAndWait, the gap was
worse.

Drop the jobId requirement. `pending` now counts as in-flight everywhere.
Cancel writes `cancelled` to the cell exec authoritatively whether or not
a real trigger.dev run exists yet — cancelling an optimistic cell means
"don't run this," which is correct.

Also collapse isOptimisticInFlight into isExecInFlight since the two
helpers are now identical.

* refactor(table): loop-in-cell cascade + dispatcher-everywhere routing

Two coupled changes:

1. Cell-task runs the row's full cascade in-process. executeWorkflowGroupCellJob
   acquires a Redis lock per (tableId, rowId) with heartbeat (10s/30s TTL),
   then loops through eligible workflow groups for the row. One cell-task =
   one row's full cascade, not N. Resume worker holds the same lock and
   continues the cascade after a HITL resume. Shared withCascadeLock helper
   in lib/table/cascade-lock.ts.

2. Every cell-enqueue goes through the dispatcher. The implicit
   scheduleRunsForRows reactor in service.ts is removed — 8 callsites
   (insertRow, batchInsertRows, upsertRow, updateRowsByFilter,
   batchUpdateRows, addWorkflowGroup, updateWorkflowGroup) now fire
   runWorkflowColumn with mode: 'incomplete', isManualRun: false. HTTP
   routes that call updateRow directly also fire runWorkflowColumn
   afterwards. scheduleRunsForTable / scheduleRunsForRowIds deleted;
   scheduleRunsForRows demoted to private (only the TRIGGER_DEV_ENABLED=false
   fallback uses it). skipScheduler flag dropped from UpdateRowData /
   BatchUpdateByIdData — no longer meaningful since there's nothing implicit
   to suppress.

Plumbed isManualRun through the dispatch row (new is_manual_run column,
default true) so auto-fire callers honor autoRun: false and don't re-run
completed cells.

Stamp 'pending' (not 'queued', executionId: null) before
batchTriggerAndWait — cell-task writes its own 'queued' on lock acquire.

Small UI polish: row gutter Play button spacing, "Delete workflow" →
"Delete column" label, optimistic-pending cells now show Stop button
(isExecInFlight no longer requires jobId).

* fix(table): SQL cancellation guard allows worker to claim a null-execId cell

The dispatcher's pre-batch `pending` stamp leaves executionId unset so any
cell-task that wins the cascade lock can claim the cell. The cancellation-
guard SQL clause was rejecting these claims because it tested
`executions->gid IS NULL` (whole exec missing) but the pre-stamp leaves
the exec present with executionId=null.

Add a third carve-out: `executions->gid->>'executionId' IS NULL`. Now the
guard reads "write allowed if no exec exists, OR no executionId is set
yet, OR the executionId matches ours."

Symptom: every cell-task's first markWorkflowGroupPickedUp call would log
"SQL guard saw cancelled" and skip, leaving cells stuck at the dispatcher's
pending stamp.

* fix(table): dispatcher cursor starts at -1 so position 0 is included

The dispatcher's row-window SELECT is `position > cursor` for exclusive
lower-bound semantics. With cursor initialized to 0, position-0 rows were
never picked up — every dispatch silently skipped the table's first row.

Start cursor at -1 instead. First window's filter `position > -1` matches
position 0; subsequent iterations advance to `lastPosition` which then
correctly excludes already-processed rows.

* refactor(table): align optimistic UI with new dispatcher; sticky cancel via 'new' mode

Fix 0: new `DispatchMode = 'new'` for auto-fire callsites. Eligibility skips
rows with any prior `executions[gid]` entry — cancelled / errored / completed
cells stay sticky until a manual run. Dispatcher's windowed SELECT pushes
`NOT jsonb_exists_any(...)` to SQL so CSV imports into mostly-attempted
tables don't pay a per-window load+JS-filter. `batchInsertRows` drops its
`rowIds` payload (keeps dispatch scope tiny on big imports).

Fix A/B/D: client optimistic patches now mirror the backend's actual
invariants. `useCreateTableRow.onSuccess` stamps eligible groups via
`optimisticallyScheduleNewlyEligibleGroups` so newly-inserted rows show
`Queued` instantly. `useCancelTableRuns.onMutate` distinguishes optimistic-
only pending (`executionId == null` — strip silently) from real worker
claims (stamp cancelled; SSE will reconcile). Drop `onSettled` invalidation
on `useUpdateTableRow` / `useBatchUpdateTableRows` to kill the
delete-cell flicker.

Fix C: active-dispatches overlay. New `listActiveDispatches` helper,
contract, and `GET /api/table/[tableId]/dispatches` route. `kind:'dispatch'`
SSE events carry scope+cursor+mode on every transition. New
`useActiveDispatches` hook + `resolveCellExec` synthesize a virtual
`pending` exec for cells in an active dispatch's scope ahead of cursor —
queued indicators now survive page refresh during long Run-all dispatches.
`cancelWorkflowGroupRuns` emits `kind:'dispatch',status:'cancelled'`
events so the overlay clears without a refetch.

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

* refactor(table): unify trigger.dev and inline dispatcher paths

`runWorkflowColumn` now always inserts a `table_run_dispatches` row and
drives the dispatcher state machine. The trigger.dev / in-process branch
narrows to a single line: trigger.dev fires `tableRunDispatcherTask` (which
calls the new `runDispatcherToCompletion`), the inline path calls the same
helper fire-and-forget. Deletes `scheduleRunsForRows` and
`stampQueuedOrCancel` — the inline-fallback no longer duplicates window
walking, SSE emission, or cancel.

The dispatcher's window-execute call goes through `JobQueueBackend`:
- New `batchEnqueueAndWait` interface method.
- Trigger.dev impl wraps `tasks.batchTriggerAndWait` behind a
  `taskContext.isInsideTask` guard (clear error if called from outside a
  task).
- Database impl skips `async_jobs` entirely — `Promise.all` over
  `options.runner(payload, signal)` per item, with per-cell AbortControllers
  tracked by `cancelKey` for cancel.

`cancelInlineRun` moves to the interface as `cancelByKey` so
`cancelWorkflowGroupRuns` no longer reaches into the database backend.

Fix `mode: 'new'` SQL filter:
- `${array}::text[]` interpolated as a tuple-cast which Postgres rejected
  ("cannot cast type record to text[]") and every inline dispatch silently
  failed. Switched to `ARRAY[${sql.join(...)}]::text[]`.
- Predicate was `jsonb_exists_any` ("any one targeted group present"),
  which excluded rows that needed at least one group re-run after a
  downstream output was deleted. Switched to `jsonb_exists_all` — per-group
  JS eligibility handles the rest.

Cascade-loop workflowId bug: `runRowCascadeLoop` was not threading the new
group's `workflowId` when advancing across groups. The cell-task ran the
previous group's workflow against the next group's cell, terminating
`completed` with empty `accumulatedData`. Fixed by tracking
`currentWorkflowId` alongside `currentGroupId` / `currentExecutionId`.

Client optimistic-patch tightening:
- `useRunColumn.onMutate` mirrors server eligibility — skip cells with
  unmet deps so unmet rows don't flash Queued and get stuck (no SSE will
  arrive for cells the server skipped).
- `resolveCellExec` overlay synthesizes a virtual `pending` only when
  `areGroupDepsSatisfied` is true. Rows with unmet deps render Waiting,
  matching the dispatcher's actual behavior.

Cleanup from /simplify pass:
- Use `generateShortId(20)` instead of
  `generateId().replace(/-/g, '').slice(0, 20)`.
- Inline `batchEnqueueAndWait` no longer allocates synthetic ids
  (returned `string[]` is unused).
- Flattened the per-cell `tracked` array — only push entries that
  registered controllers, drop the null placeholders.
- Extracted `runDispatcherToCompletion` to share the loop between the
  trigger.dev wrapper and the in-process path.

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

* feat(table): backend running counter, dep-aware retrigger, sidebar polish

Counter (Fix 1): top-right "X running" + per-row badge are now
backend-bootstrapped via a count on `user_table_rows.executions ->> 'status'
= 'running'` returned alongside active dispatches. SSE `kind: 'cell'` events
compute a delta from `prev → next` status to keep the cache live; cell
events for rows outside the loaded page slice trigger a run-state refetch.
On `pruned` we invalidate the cache. Counts only worker-claimed `running`
cells — optimistic queued/pending no longer inflate the badge, and rows
outside the loaded page slice are counted too.

Sidebar (Fix 2 + 3a): `Run after` no longer ticks every column by default
for new groups (empty list). Save is disabled with an inline error when
auto-run is on with zero deps. `edit-group` mode anchors the left-of-current
filter to the group's leftmost column, so a workflow can only depend on
columns to its left.

Reorder scrub (Fix 3b): `updateTableMetadata` walks the schema's workflow
groups when `columnOrder` is in the patch and drops any dep whose new
position lands at or after the group's leftmost column (uses the existing
`stripGroupDeps` helper). Metadata + schema updates land atomically.

Server returns ordered columns (Fix 3b cont'd): `getTableById` /
`listTables` now sort `schema.columns` by `metadata.columnOrder` before
returning, via a new `applyColumnOrderToSchema` helper. Every consumer
(grid, sidebar, copilot, mothership) gets one ordered list — the sidebar's
leftmost-group-column anchor now points at the right index.

Dep-aware retrigger (Fix 4): editing a value that a downstream workflow
depends on now re-runs that workflow.
- `deriveExecClearsForDataPatch` returns
  `{ executionsPatch, inFlightDownstreamGroups }`. Walks
  `schema.workflowGroups[].dependencies.columns` for every column in the
  patch, clears terminal-state downstream entries, and reports in-flight
  entries.
- `updateRow` calls `cancelWorkflowGroupRuns` + `runWorkflowColumn`
  (`mode: 'incomplete' + isManualRun: true`) for in-flight downstream
  groups, then always fires `runWorkflowColumn({ mode: 'new' })` for the
  cleared groups. Skips both when `executionsPatch` is provided by the
  caller — those are cell-task / cancel writes that would otherwise spawn
  a recursive flood of dispatches per partial-write.
- `cancelWorkflowGroupRuns(tableId, rowId, { groupIds? })` accepts a
  per-group filter so the cancel only touches the affected groups, not
  every in-flight cell on the row.
- `pickNextEligibleGroupForRow` now treats a dispatcher pre-stamp
  (`pending` + `executionId: null`) as claimable — the cascade-loop is the
  real owner. Without this, the dispatcher's pre-stamp of downstream
  groups made the cascade-loop see them as "in-flight" and skip them,
  stranding `pending` cells forever.
- `optimisticallyScheduleNewlyEligibleGroups` extends the cache patch to
  flip dep-touched groups to `pending` regardless of their current status,
  matching the server's cancel-then-rerun behavior.

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

* fix(table): paused workflow cells route through executeResumeJob; render Pending + viewable

Three connected issues with workflows that pause mid-cell (e.g. wait blocks):

1. `/api/resume/poll` (the time-pause auto-resumer) called
   `PauseResumeManager.startResumeExecution` directly, bypassing
   `executeResumeJob` from `background/resume-execution.ts`. The wrapper is
   where the cell-context restoration + cascade-loop continuation lives —
   without it, the resumed workflow ran to completion but never wrote the
   terminal state back to the table cell. Cell stays `pending` forever
   even though the underlying execution finished.

   Fix: dynamically import `executeResumeJob` and use it for the
   `'starting'` branch. Same primitive the trigger.dev `resumeExecutionTask`
   wraps — calling it directly handles both trigger.dev-disabled local dev
   and trigger.dev-enabled prod identically.

2. The cell renderer mapped `status: 'pending'` to `kind: 'queued'` (gray
   "Queued" badge) regardless of whether the run had started. A HITL-paused
   run has `status: 'pending'` + `jobId` prefixed `paused-` + a real
   `executionId` — semantically very different from "queued, hasn't run."
   Now renders as `pending-upstream` (the existing Pending pill) for
   paused-jobId rows.

3. Right-click "View execution" was disabled for `pending` cells (gated to
   `completed | error | running`), so users couldn't open the trace for a
   paused execution. Paused runs have a viewable trace (the executionId is
   real and the log row exists). Both the per-row context menu and the
   action-bar derivation now recognize `pending` + `paused-` jobId as a
   started run.

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

* feat(table): typewriter reveal for SSE-driven workflow cell values

Workflow-output cells now reveal their text character-by-character when an
SSE update lands, while page reloads and virtualization remounts still paint
the value instantly. A first-render guard inside the new useTypewriter hook
distinguishes hydration from live updates with no plumbing through the cell
tree.

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

* fix(table): address bugbot/greptile review feedback

Two P1 issues + one cleanup from the bot reviewers:

1. **Double-dispatch + completed-output wipe.** Both PATCH row routes
   (`app/api/table/[tableId]/rows/[rowId]` and
   `app/api/v1/tables/[tableId]/rows/[rowId]`) were firing a second
   `runWorkflowColumn({ mode: 'incomplete' })` after `updateRow` returns.
   `updateRow` already fires `mode: 'new'` internally for user edits, so
   the second call created a concurrent dispatch. Worse, the
   `mode: 'incomplete'` path's `bulkClearWorkflowGroupCells` wipes ALL
   targeted output columns on any row where any one column is empty —
   meaning sibling-group completed outputs could be erased. Removed both
   route-level calls; auto-dispatch lives entirely in `updateRow`.

2. **`runWorkflowColumn` log-spamming on plain tables.**
   `if (targetGroups.length === 0) throw new Error(...)` fired on every
   row insert/update for tables without any workflow groups (the
   majority). Every caller wraps with `.catch(logger.error)`, so each
   PATCH produced an error-level log. Return `{ dispatchId: null }`
   silently — manual `runWorkflowColumn` callers pass `groupIds`
   explicitly so they can't reach this branch.

3. **`isManualRun` plumbed through dispatch SSE events.** Late-arriving
   `kind: 'dispatch'` events for dispatches not in the initial fetch
   were hardcoding `isManualRun: false`. Added the field to the event
   shape, emit it from `dispatcherStep` (pending → complete, dispatching
   transitions) and `markActiveDispatchesCancelled`, and consume it in
   the SSE handler with a sensible fallback for legacy emits.

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

* refactor(table): row executions sidecar + left-to-right dep retrigger + cancel counter refresh

Split per-row workflow-group execution state out of the user_table_rows.executions
JSONB column into a new table_row_executions sidecar keyed by (row_id, group_id).
Dispatcher filters, "X running" counter, bulk clears, and the cancellation guard
all hit indexed columns instead of walking JSONB. Wire shape unchanged — server
merges sidecar rows back into row.executions on the way out.

Also:
- deriveExecClearsForDataPatch now walks workflowGroups left-to-right with a
  propagating dirtied-column set so transitive dep chains (edit col A → group 1
  re-runs → group 2 depends on group 1's output → group 2 re-runs) collapse to
  a single forward pass.
- useCancelTableRuns.onSettled invalidates the activeDispatches query so the
  top-right counter and row gutter Stop button refetch from the server after
  any Stop (per-cell, row, or table-wide). countRunningCells is the source of
  truth; client no longer needs duplicate state.

Three migrations on this branch (0209 + 0210 + new sidecar) collapsed into one
since the feature is unreleased.

* fix(table): address remaining cursor/greptile review feedback

- Mothership update_row no longer double-dispatches. updateRow already fires
  the auto-cascade internally; the second `mode: 'incomplete'` call here
  raced with it and could bulk-clear sibling-group outputs.
- SSE dispatch events no longer dropped when the activeDispatches cache is
  cold. Seed an empty TableRunState if the initial fetch hasn't landed yet
  so the queued overlay doesn't lose the first dispatch event.
- batchUpdateRows now runs cancel+rerun for per-row in-flight downstream
  groups, mirroring updateRow. Without this, dep edits in a batch left
  running workflows reading stale upstream values.

* fix(table): cancel prior runs, scope batch insert dispatch, recover orphan pre-stamps

Addresses cursor + greptile review feedback on table dispatcher edge cases:

- Manual table-wide Run-all / Run-column now cancels prior active dispatches
  AND in-flight cell workers before bulk-clearing. Without this, mode:'all'
  deleted running sidecar rows out from under their workers (which kept
  writing into the wiped state) and a second Run-all could enqueue overlapping
  cells racing on the same rows. Row-scoped manual calls (dep-edit cascade)
  are excluded — those already cancel their own scope.
- batchInsertRowsWithTx now scopes its auto-dispatch to the newly-inserted
  row ids. Without this, after the sidecar migration the NOT EXISTS filter
  matches every existing row (zero sidecar entries), so a CSV import would
  walk the entire table dispatching workflow runs on every pre-existing row.
- classifyEligibility carve-out: pending + executionId=null is an orphan
  pre-stamp (cascade-lock contention, batchEnqueueAndWait failure, etc.),
  treated as claimable so future dispatchers can re-stamp instead of skipping
  it as 'in-flight' forever. Matches pickNextEligibleGroupForRow's logic.
- On batchEnqueueAndWait failure, dispatcherStep now sweeps the orphan
  pre-stamps it wrote for the failed batch so the cells don't render Queued
  forever; the next user action picks them up cleanly.

* fix(table): row-scoped Refresh cancels in-flight; counter includes queued/pending

- runWorkflowColumn now cancels prior in-flight cells for row-scoped manual
  runs too (context-menu Refresh on a row subset, action-bar Refresh on
  selected rows). Previously only the table-wide path cancelled, so a
  row-scoped Refresh would bulk-clear running sidecar rows without aborting
  workers. Per-row cancel skips markActiveDispatchesCancelled so unrelated
  dispatches keep running.
- countRunningCells now counts all in-flight statuses (queued / running /
  pending) instead of just running. The row gutter Run/Stop button reads
  this map — with the old behavior, clicking Play during the queued window
  would re-enqueue an already-queued cell. SSE applyCell handler updated
  to use isExecInFlight so client deltas track the same semantics.

* fix(table): per-row Stop tombstones ahead-of-cursor rows during Run-all

Per-row Stop only cancelled sidecar rows already in flight. A row the
dispatcher hadn't reached yet had no exec record, so Stop was a no-op there
— the dispatcher would later walk to it, classify the group eligible, and
re-fire workflows the user thought they stopped.

cancelWorkflowGroupRuns now, for a per-row cancel, checks active dispatches
whose scope covers the row and writes `cancelled` tombstones (cancelledAt =
now) for the at-risk groups that don't already have a sidecar entry. The
dispatcher's existing `cancelledAt > dispatch.requestedAt` filter then skips
them when the cursor arrives. onConflictDoNothing guards against clobbering
a concurrently-written entry; the active-dispatch check avoids stamping
spurious cancels on idle rows.

* fix(table): seed dispatch overlay on Run; surface batch-enqueue failures as error

- useRunColumn.onSuccess invalidates the activeDispatches query so the
  resolveCellExec queued overlay populates immediately for ahead-of-cursor
  rows (scrolled-in / refetched), instead of waiting for the first dispatch
  SSE. Targeted at activeDispatches only — the rows cache stays owned by
  useTableEventStream.
- On batchEnqueueAndWait failure, dispatcherStep now flips the orphan
  pre-stamps to a terminal `error` state and emits a cell SSE event, rather
  than deleting them. The cursor still advances past the window, but the
  dropped cells are now visible (Error pill) instead of silently empty, stay
  out of the in-flight set, and re-run on the next manual run.

* fix(table): seed dispatch overlay on Run; surface batch-enqueue failures as error

- useRunColumn.onSuccess invalidates activeDispatches so the resolveCellExec
  queued overlay populates immediately for ahead-of-cursor rows instead of
  waiting for the first dispatch SSE. Rows cache stays owned by SSE.
- On batchEnqueueAndWait failure, dispatcherStep flips orphan pre-stamps to a
  terminal error state (+ cell SSE) instead of deleting them, so the dropped
  window is visible (Error pill) rather than silently empty and re-runs on the
  next manual run.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 05:44:09 -04:00
Vikhyath Mondreti 974a18dd61 improvement(media-blocks): new versions of image and video gen with latest models + fixes (#4667)
* improvement(media-blocks): new versions of image and video gen with latest models + fixes

* respect versioning for icons

* fix integration routes

* address comments

* address api mismatches

* more ltx 2.3 durations

* typing tightness
2026-05-19 16:42:04 -07:00
WaleedandClaude Opus 4.7 08eeecbebe fix(security): KB fileUrl LFI, MCP/Agiloft SSRF pinning, form OTP, KB authz (#4639)
* fix(security): KB fileUrl LFI, MCP/Agiloft SSRF pinning, form OTP, KB authz

* fix(otp): don't leak caught error.message; fail-closed on DB retry exhaust

- Chat/form OTP routes: replace `error.message || fallback` with generic
  `Failed to process request` in 500 responses (logger still captures detail).
- otp.ts incrementOTPAttempts DB path: on MAX_RETRIES exhaustion, delete the
  verification row and return `'locked'` instead of trusting a possibly-
  undercounted final read.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): use undici fetch directly in pinned-fetch for typed dispatcher

Replace `globalThis.fetch` + double-cast with `undici.fetch` so the
`dispatcher` option is part of the real type contract. This guarantees
pinning won't silently break if a future runtime swaps the underlying
fetch implementation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(build): keep agiloft/grafana tool configs client-safe

Tool config files are statically reachable from the client bundle (via
tools/registry.ts → tools/{service}/index.ts). Importing
`@/lib/core/security/input-validation.server` from these files pulled
`node:dns/promises` into the Turbopack client bundle and broke the build.

Split agiloft utils into client-safe (`utils.ts`, plain fetch + sync
`validateExternalUrl`) and server-only (`utils.server.ts`, DNS-pinned
variants). Routes that need TOCTOU protection import the pinned helpers;
the executor-side tool path falls back to sync URL validation (matches
the supabase precedent and pre-PR baseline).

Grafana update tools likewise switch from `secureFetchWithValidation`
(server-only) to inline sync `validateExternalUrl` + plain fetch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(knowledge): case-insensitive scheme checks for fileUrl

Boundary schema accepted uppercase schemes (e.g. HTTPS://, DATA:) via the
case-insensitive http regex, but the processor's case-sensitive
startsWith('data:') / startsWith('http') / startsWith('https://') checks
rejected them with a confusing "Unsupported fileUrl scheme" error.
Aligns processor checks to the schema using case-insensitive regex per
RFC 3986 §3.1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mcp): annotate undici/DOM type-bridge double-casts in pinned-fetch

Strict audit was failing on two new `as unknown as` casts in pinned-fetch.ts.
They bridge DOM `RequestInit`/`Response` ↔ undici equivalents (structurally
compatible at runtime since Node's global fetch is undici) and are required
to satisfy the FetchLike contract. Annotate so they count as documented
exemptions instead of new violations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 23:05:52 -07:00
Theodore Li fffb87901d feat(wait): Async toggle, chained-wait resume fix, execution status API (#4514)
* fix(wait): poll partially_resumed rows so chained waits resume

The chained-pause flow leaves a row in 'partially_resumed' status (wait1 done, wait2 still waiting). The poll's WHERE filter only matched 'paused', so wait2 was never picked up. Include 'partially_resumed' in the filter.

* feat(wait): make in-process threshold env-overridable for local testing

Adds WAIT_INPROCESS_MAX_MS env var (default 300000ms = 5 min). Lower it locally (e.g. 5000) to exercise the suspend/cron-resume path with short waits.

* feat(wait): add Suspend Workflow toggle, restore 5-min in-process default

* fix(wait): address bot review — setNextResumeAt + suspend unit default

- setNextResumeAt now matches paused OR partially_resumed; otherwise the
  cron poller can't null nextResumeAt after dispatching a chained-wait
  row, so it keeps reappearing in every poll batch until execution ends
  (flagged by both greptile and bugbot)
- Suspend mode now defaults missing timeUnitLong to 'minutes' instead of
  falling back to 'seconds' and immediately erroring (flagged by bugbot)

* improvement(wait): hint the wait-amount cap on the input

Restores the pre-#4331 description on the Wait Amount field so the limit is visible before submit instead of only at runtime. Mentions both the 5 min default and the 30 day cap with Suspend Workflow.

* refactor(wait): rename Suspend Workflow toggle to Async

* fix(wait): reword cap errors to say 'async mode'

* feat(workflows): add GET /workflows/[id]/executions/[executionId] status endpoint

Normalized status (pending|running|paused|completed|failed|cancelled)
across workflowExecutionLogs and pausedExecutions in a single response.
Surfaces paused-state details (resumeAt, pauseKind, blockedOnBlockId)
when a row exists in pausedExecutions, and the error string for failed
runs. finalOutput is opt-in via ?includeOutput=true.

* feat(workflows): support ?selectedOutputs= on execution status endpoint

Returns per-block outputs filtered by selectedOutputs paths (same
shape as the execute endpoint). Reads from executionData.traceSpans,
walks children recursively, and resolves dot-paths into each block's
output. Bare blockId returns the full output.

* fix(wait): drop dead tooltip prop on Async switch

Switch sub-blocks return null from renderLabel (sub-block.tsx:238), so
the tooltip never reached the user. The trade-off explanation already
lives in longDescription and bestPractices. Flagged by bugbot.

* docs(api): document GET /workflows/[id]/executions/[executionId]

Adds the WorkflowExecutionStatus schema and the getWorkflowExecution
operation to the OpenAPI spec, including completed/paused/failed
response examples and the includeOutput + selectedOutputs query params.
Registers the page in the Workflows section of the API reference.

* docs(api): tighten getWorkflowExecution descriptions
2026-05-15 22:09:06 -04:00
Waleed 8d7bbbc670 chore(utils): migrate to shared random/ID utilities and add enforcement linting (#4623)
* chore(utils): migrate to shared random/ID utilities and add enforcement linting

- Replace all Math.random(), crypto.randomUUID(), crypto.randomBytes(), nanoid, and uuid usages with shared @sim/utils/random and @sim/utils/id helpers across 72 files
- Add new @sim/utils exports: deepClone, omit, filterUndefined (object), truncate (string), backoffWithJitter, parseRetryAfter (retry), getErrorMessage (errors)
- Sweep all getErrorMessage, sleep, deepClone callsites across 500+ files to use shared utilities
- Add Biome noRestrictedImports rule to catch nanoid, uuid, and crypto named imports at lint time
- Add scripts/check-utils-enforcement.ts to catch Math.random and crypto.* global property access
- Add check:utils script to package.json

* chore(utils): replace deepClone wrapper with structuredClone built-in

deepClone() was a one-line wrapper around structuredClone(), which is
universally available in Node 17+ and all modern browsers. Removing the
abstraction reduces indirection and means contributors don't need to
learn a project-specific name for a well-known built-in.

- Remove deepClone from packages/utils/src/object.ts and index.ts
- Replace all 17 call sites with structuredClone() directly
- Update check:utils script suggestion text
- Update CLAUDE.md and global.md docs

* fix(utils): add missing biome noRestrictedImports rule and correct truncate docs

- Add noRestrictedImports to biome.json under style — bans nanoid and uuid
  package imports at lint time (crypto.randomUUID/randomBytes are caught by
  the check:utils grep script which handles global property access)
- Correct truncate() TSDoc and parameter name: sliceLength makes it clear
  that total output length is sliceLength + suffix.length, matching the
  behavior all callers were already written to expect

* fix(utils): add missing getErrorMessage imports at 4 call sites

The sweep agents added getErrorMessage calls without the corresponding
import in 4 files, causing test failures. Added the missing imports.

* fix(utils): fix build errors from getErrorMessage sweep and retry.ts Turbopack issue

- Fix retry.ts cross-file import: Turbopack cannot resolve './random.js' for
  internal package imports; inline the jitter crypto call directly
- Add missing getErrorMessage imports to 32 files where the sweep added calls
  without the corresponding import (caught by type-check and test runs)
- Remove accidental getErrorMessage import from crowdstrike/query/route.ts
  which has its own domain-specific getErrorMessage for parsing CrowdStrike's
  JSON error format
- Fix use-sub-block-value.ts type error from structuredClone narrowing:
  add 'as T' cast at emitValue callsite (safe — valueCopy is always a
  structural copy of newValue)

* fix(tools): use toError in crowdstrike catch block instead of local getErrorMessage

The catch block was calling the local getErrorMessage function which
parses CrowdStrike API JSON responses, not JavaScript Error objects.
Use toError(error).message to correctly extract the message from a
caught value in this context.
2026-05-15 17:31:27 -07:00
c9118e775b feat(files): folders, multiselect, vfs update (#4572)
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* feat(files): folders + vfs update

* address comments

* address comments

* cleanup unnused code

* address comments

* perf improvements

* address next set

* cycle detect

* error handling

* path improvements

* cleanup, best practices

* react query best practices: targeted invalidation, optimistic updates, key factory hierarchy

- Add workspaceLists(workspaceId) intermediate key level to both workspaceFilesKeys
  and workspaceFileFolderKeys so invalidation targets only the affected workspace
  instead of all workspaces
- Replace all lists() invalidation calls with workspaceLists(workspaceId) across
  every mutation (upload, rename, delete, restore, update content, folder mutations)
- Add optimistic updates to useRenameWorkspaceFile and useUpdateWorkspaceFileFolder
  with onMutate snapshot, onError rollback, onSettled reconciliation
- Move storage key into the content() factory as optional param so query keys are
  always built through the factory (useWorkspaceFileContent, useWorkspaceFileBinary)
- Fix AnimatePresence wrapping in FilesActionBar so exit animation fires on deselect
- Fix ResourceColGroup to use percentage weights instead of pixel widths to prevent
  horizontal scroll on narrow viewports

* add shift-click range selection and selection-aware context menu for files

- Extend SelectableConfig.onSelectRow with optional shiftKey param; DataRow captures shiftKey before onCheckedChange fires via a ref so the Radix Checkbox interaction chain stays intact
- Implement shift-click range selection in files.tsx using lastSelectedIndexRef; tracks last-selected index in visibleRowIds to compute the range
- Reset lastSelectedIndexRef on deselect and select-all
- Add selectedCount prop to FileRowContextMenu; hide Open and Rename when multiple items are selected, show "Delete N items" / "Download N items" labels in multi-select mode

* add Move submenu to file context menu and fix shift-click anchor update

- Add nested Move submenu to FileRowContextMenu using DropdownMenuSub/SubTrigger/SubContent; shows available folders filtered by selection, converts '__root__' -> null for moving to the root level
- Add handleContextMenuMove in files.tsx that calls moveItems.mutateAsync directly (no modal) and clears selection on success
- Fix shift-click range selection: update lastSelectedIndexRef after range select so chained shift-clicks extend from the new anchor point correctly

* fix move submenu: use folder names with tree-ordered indentation instead of stale paths

- Compute folder depth from parentId chain client-side (avoids stale server-computed path field)
- Tree-order folders so parents appear before their children, sorted by sortOrder then name
- Show folder.name instead of folder.path so optimistic renames are reflected immediately
- Indent each folder by depth * 12px in the submenu so po/shit renders as 'shit' indented under 'po'
- MoveOption gains optional depth field; contextMenuMoveOptions is a separate memo from moveFolderOptions (modal keeps its existing path-label behavior)

* fix shift-click anchor drift and remove dead stopPropagation constant

- Remove dead stopPropagation const in resource.tsx (replaced by handleSelectRowClick)
- Reset lastSelectedIndexRef when visibleRowIds changes so search/filter/folder navigation doesn't leave a stale anchor that produces wrong ranges on the next shift-click
- Update lastSelectedIndexRef in handleRowContextMenu when right-clicking resets selection to a single item, so the anchor matches the newly-selected row
- Add visibleRowIds to handleRowContextMenu deps (now reads it to compute anchor index)
- Remove moveItems.mutateAsync from handleContextMenuMove deps per project convention (.mutateAsync is stable in TanStack v5)

* complete workspace files feature: audit logs, posthog events, folder restore, empty state, keyboard shortcuts, storage indicator, breadcrumb rename

- Audit + PostHog: wire file_renamed, file_deleted, file_moved, file_bulk_deleted, folder_created, folder_renamed, folder_deleted, folder_moved events to all file/folder API routes
- Add AuditAction.FOLDER_UPDATED, FILE_MOVED, FOLDER_MOVED to audit types
- Folder restore: server function, contract, API route (POST /files/folders/[folderId]/restore), hook (useRestoreWorkspaceFileFolder), Recently Deleted integration with new File Folders tab
- Empty state: contextual emptyMessage passed to <Resource> based on search/filters/folder context
- Keyboard shortcuts: Delete/Backspace deletes selection, Escape deselects, Cmd+A selects all (list view only, input-aware guard)
- Storage indicator: useStorageInfo drives compact "used / limit" display in file list header via leadingActions
- Breadcrumb rename: current folder breadcrumb gains Rename dropdown + inline editing via breadcrumbRename (useInlineRename)
- Resource: thread leadingActions prop from ResourceProps to ResourceHeader

* cleanup: accessibility, emcn design tokens, react best practices across workspace UI

- Add sr-only ModalDescription to dialogs/modals for accessibility
- Replace hardcoded colors and z-indices with design token CSS variables
- Apply emcn design review fixes across tables, knowledge, logs, settings, workflows

* fix audit and posthog: FOLDER_RESTORED action on restore, fire folder_moved event separately from file_moved

* sidebar: add Files section with nested folder tree; polish move UX and cleanup

- Files section in sidebar shows folder/file tree with expand/collapse,
  matching Workflows section structure; collapsed sidebar shows flyout menu
- Move action bar now uses nested DropdownMenuSub tree instead of flat modal
- Context menu and action bar share renderMoveOption from move-options.tsx
- FolderInput added to emcn icons barrel; all FolderInput imports migrated
- Drag ghost uses CSS vars (--border, --shadow-medium, --z-toast)
- Selection pruning converted from useEffect to render-time comparison
- Keyboard listener stabilized with handleBulkDeleteRef pattern
- toError() used consistently in restore and move route handlers

* remove Files section from sidebar

* restore Files nav item in sidebar workspace section

* fix infinite re-render on files page - revert selection pruning to useEffect

* add filefolder resource type for ingesting workspace file folders

* export filefolder tree types; add toast feedback for file/folder mutations

* regenerate migration as 0208 after rebase onto staging

* add workspaceFileFolder to schema mock

* add FILE_MOVED, FOLDER_MOVED, FOLDER_UPDATED to audit mock

* add filefolder ChatContext kind and wire through schema and resolver

* add filefolder to AgentContextType

* add filefolder to chat context kind registry; fix resolver to use workspaceFiles table

* add .deepsec to gitignore

* cleanup: effect, emcn tokens, mutation error handling

- Replace selection-pruning useEffect with inline state adjustment during render
- Fix drag overlay using invalid --accent HSL token → --brand-secondary; z-50 → z-[var(--z-dropdown)]
- Move static inline styles on context menu trigger div to className
- Add missing onError toast to useUpdateWorkspaceFileFolder, useRestoreWorkspaceFileFolder, useRestoreWorkspaceFile

* lint

* fix: remove duplicate handleCopilotStopGeneration from rebase

* feat(copilot): folder-aware file context in WORKSPACE.md

* feat(copilot): add move operation to file manage API

* fix(files): make targetFolder optional in move file contract

* perf(files): parallelize buffer fetches, fix N+1 folder queries, stabilize drag useMemo

- download route: fan out all fetchWorkspaceFileBuffer calls with Promise.all
  before zip assembly so 100 files resolve in one round-trip instead of sequentially
- getWorkspaceFileFolder: replace per-ancestor SELECTs with a single workspace-wide
  folder load + buildWorkspaceFileFolderPathMap, making depth irrelevant to query count
- ensureWorkspaceFileFolderPath: pre-load all workspace folders in one SELECT before
  the segment loop; resolve existing segments from an in-memory map; only hit the DB
  to CREATE missing segments; conflict retry path preserved and also updates the map
- files.tsx rowDragDropConfig: move activeDropTargetId into a ref so the useMemo
  does not recompute on every drag-over event

* fix(files): remove files/ path stripping, fix stale path in optimistic update

- splitWorkspaceFilePath: remove the unconditional .replace(/^files\//, '')
  that clobbered paths for files inside a folder literally named "files"
- useUpdateWorkspaceFileFolder: when a name update is in flight, recompute
  the path field for the renamed folder (replace last segment) and propagate
  the new prefix to all descendant folders so breadcrumbs stay correct
  during the optimistic window

* fix(files): revert broken ref opt, clean 409 on restore, null parentId on orphaned restore

- files.tsx: revert the activeDropTargetId ref optimization — the ref doesn't
  trigger re-renders so the drop-target highlight never updated during drag;
  activeDropTargetId is back in state and in the rowDragDropConfig deps
- restore/route.ts: catch Postgres 23505 unique-constraint violation and
  return a clean 409 instead of leaking the raw error as 400
- restoreWorkspaceFileFolder: check if the parent folder is still archived
  before restoring; if it is, restore to root (parentId: null) so the folder
  is never orphaned under an archived parent

* feat(search): show folder path for files in cmd-k modal, strip extraneous comments

- FileItem interface with folderPath?: string[] added to search modal utils
- MemoizedFileItem component renders folder breadcrumb identically to
  MemoizedWorkflowItem — truncated path segments on the right with / separators
- FilesGroup rewritten as a dedicated memo component (was createIconGroup factory)
  so it accepts FileItem[] and includes folderPath segments in the search value
- searchModalFiles in sidebar splits f.folderPath string into string[] segments
- search-modal.tsx typed to FileItem and includes folderPath in filterAndSort
- Remove self-explanatory "Phase 1" section label from download route
- Remove redundant TSDoc on the unique index in db schema

* fix(workspace-files): audit fixes — transaction, status codes, contract refinements, guards

* fix(vfs): pass folderPath separately so buildWorkspaceMd groups files correctly

* fix(types): narrow unknown fileInput with Record cast after object guard

* fix(routes): replace instanceof Error with toError() across new workspace file routes

* improvement(files): cleanup pass — remove unnecessary useCallbacks, consolidate emcn icon imports

- Remove useCallback from 5 drag-event handlers in DataRow (passed to native <tr> elements, no observer)
- Remove stable useCallback fns from 3 useMemo deps arrays in files.tsx (editingId/editValue remain)
- Merge all @/components/emcn/icons subpath imports into barrel (files.tsx, action-bar, file-row-context-menu)

* fix(files): apply activeSort to folders, reject drop onto current parent folder

- visibleFolders now respects activeSort column (name/updated/created) and direction
  so folder ordering stays consistent with file ordering
- isInvalidDropTarget now returns true when all dragged items are already direct children
  of the target folder, preventing a no-op move mutation

* fix breadcrumb

* add new tools to rename, create, delete folders

* move more ui actions into orchestration dir

* address comments

* fix params

* fix tests

* address comments

* improve error codes

* address comments

* address more nits

* fix mcp server error code

---------

Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: waleed <walif6@gmail.com>
2026-05-15 12:05:03 -07:00
Theodore Li 4a9e248eac feat(cloudwatch): add mute and unmute alarm operations (#4602) 2026-05-14 14:54:35 -04:00
Siddharth GanesanandTheodore Li 0b2cfaf7f7 feat(mothership): add superuser env selection (#4558)
* feat(table): live cell updates via SSE + per-table event buffer

Replaces the polling-based row refetch with a push-based SSE stream that
patches the React Query cache directly as cell-state events arrive.

Architecture:
- New per-table event buffer in apps/sim/lib/table/events.ts. Redis sorted-set
  with monotonic eventId, 1h TTL, 5000-event cap, in-memory fallback. Modeled
  after apps/sim/lib/execution/event-buffer.ts but stripped of complexity
  tables don't need (no per-execution lifecycle, no id-batching, no write
  queue serialization). ~150 lines instead of 700.
- writeWorkflowGroupState appends a fat event after each successful 'wrote'.
  Status transitions carry executionId + jobId; terminal/partial transitions
  also include the new output values inline so the client can patch row data
  without a follow-up refetch.
- New SSE route at /api/table/[tableId]/events/stream?from=<lastEventId>.
  Replays from buffer on connect, polls at 500ms (mirrors workflow execution
  stream), heartbeat every 15s, signals 'pruned' if the caller fell off the
  back of the buffer.
- Client hook useTableEventStream subscribes via EventSource. Reconnect-resume
  with last-seen eventId. On 'pruned', invalidates the rows query and resumes
  from the new earliest. Cache patches walk every cached query under
  rowsRoot(tableId) so filter/sort variants all stay live.
- Removes refetchInterval from useTableRows and the per-page polling effect
  from useInfiniteTableRows. React Query's refetchOnWindowFocus +
  refetchOnReconnect cover the durability gap if any push is dropped.

Out of scope:
- Bulk-cancel events (cancellation path is being redesigned separately).
- Generalizing the workflow event-buffer module to a shared primitive (defer
  until a third use case appears; for now the table buffer is the simpler
  cousin of the workflow one).

* fix(table): drop run-mutation refetch so SSE patches aren't overwritten

useRunColumn.onSettled was canceling in-flight queries and invalidating the
rows query — leftover behavior from the polling era. With the SSE stream
now keeping the cache live via incremental patches, this refetch races the
stream and snaps the cache back to whatever DB shows at the refetch moment,
which can lag the just-arrived queued/running events. Cells appeared stuck
on the optimistic 'pending' even though the SSE was delivering the real
transitions.

* chore(table): simplify SSE plumbing — reuse helpers, drop dead polling code

- Reuse snapshotAndMutateRows for SSE cache patches instead of reimplementing
  the page-walk + cache-shape detection. Adds a {cancelInFlight: false} opt
  for the SSE caller (mutations still cancel as before).
- Drop client-side type duplication in use-table-event-stream — import
  TableEvent and TableEventEntry from lib/table/events directly.
- Drop the now-dead mergePagePreservingIdentity + rowEqual from tables.ts;
  their only caller was the polling effect that was removed earlier.
- Drop the defensive try/catch around appendTableEvent in cell-write — the
  function is documented as never-throwing (returns null on failure).
- Combine INCR + ZADD into one Lua eval in events.ts. Halves Redis RTT per
  cell-write. Lua returns the new eventId; the script splices it into the
  pre-built entry JSON.
- Trim refs to plain let bindings inside the effect; trim stale
  comments referencing the old polling implementation.

* fix(table): address PR review on SSE buffer

- TTL-expiry silent miss: when all keys expire, hgetall(meta) returns empty
  so earliestEventId is undefined and the prune branch was skipped. Reconnect
  with non-zero afterEventId now checks the seq counter — its absence (TTL
  expired) signals pruned so the client refetches. Memory fallback mirrors.
- Unbounded ZRANGEBYSCORE: cap reads at TABLE_EVENT_READ_CHUNK = 500 events
  per call. The route's 500ms poll loop drains chunks across ticks instead of
  flushing 5000 entries (multi-MB) in one tick after a long disconnect.
- Pruned handler closes EventSource client-side: server-side close was firing
  onerror and routing through the 500ms backoff path. Now we close
  proactively, reset the reconnect attempt counter, and reconnect immediately
  from the new earliest.

* Cross env copilot

* Force deploy

* Run migration

* Updates

* Fix migration

* Redeploy

* Make dev db push

* restore old migs

* Cross env copilot

* Add custom tools, skills, mcps to mothership

* Update migration

* Fix migs

* UPdate

* Fix types

* Fix

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-05-11 16:58:48 -07:00
Vikhyath Mondreti a170255633 fix(script): biome format wrap (#4541) 2026-05-09 16:16:08 -07:00
Vikhyath Mondreti 13666b162d fix(zustand): v5 selector stability issues (#4539)
* fix(zustand): v5 selector stability issues

* address comments
2026-05-09 16:07:12 -07:00
Theodore LiandClaude Opus 4.7 eb871fc0de feat(table): live cell updates via SSE + per-table event buffer (#4508)
* feat(table): live cell updates via SSE + per-table event buffer

Replaces the polling-based row refetch with a push-based SSE stream that
patches the React Query cache directly as cell-state events arrive.

Architecture:
- New per-table event buffer in apps/sim/lib/table/events.ts. Redis sorted-set
  with monotonic eventId, 1h TTL, 5000-event cap, in-memory fallback. Modeled
  after apps/sim/lib/execution/event-buffer.ts but stripped of complexity
  tables don't need (no per-execution lifecycle, no id-batching, no write
  queue serialization). ~150 lines instead of 700.
- writeWorkflowGroupState appends a fat event after each successful 'wrote'.
  Status transitions carry executionId + jobId; terminal/partial transitions
  also include the new output values inline so the client can patch row data
  without a follow-up refetch.
- New SSE route at /api/table/[tableId]/events/stream?from=<lastEventId>.
  Replays from buffer on connect, polls at 500ms (mirrors workflow execution
  stream), heartbeat every 15s, signals 'pruned' if the caller fell off the
  back of the buffer.
- Client hook useTableEventStream subscribes via EventSource. Reconnect-resume
  with last-seen eventId. On 'pruned', invalidates the rows query and resumes
  from the new earliest. Cache patches walk every cached query under
  rowsRoot(tableId) so filter/sort variants all stay live.
- Removes refetchInterval from useTableRows and the per-page polling effect
  from useInfiniteTableRows. React Query's refetchOnWindowFocus +
  refetchOnReconnect cover the durability gap if any push is dropped.

Out of scope:
- Bulk-cancel events (cancellation path is being redesigned separately).
- Generalizing the workflow event-buffer module to a shared primitive (defer
  until a third use case appears; for now the table buffer is the simpler
  cousin of the workflow one).

* fix(table): drop run-mutation refetch so SSE patches aren't overwritten

useRunColumn.onSettled was canceling in-flight queries and invalidating the
rows query — leftover behavior from the polling era. With the SSE stream
now keeping the cache live via incremental patches, this refetch races the
stream and snaps the cache back to whatever DB shows at the refetch moment,
which can lag the just-arrived queued/running events. Cells appeared stuck
on the optimistic 'pending' even though the SSE was delivering the real
transitions.

* chore(table): simplify SSE plumbing — reuse helpers, drop dead polling code

- Reuse snapshotAndMutateRows for SSE cache patches instead of reimplementing
  the page-walk + cache-shape detection. Adds a {cancelInFlight: false} opt
  for the SSE caller (mutations still cancel as before).
- Drop client-side type duplication in use-table-event-stream — import
  TableEvent and TableEventEntry from lib/table/events directly.
- Drop the now-dead mergePagePreservingIdentity + rowEqual from tables.ts;
  their only caller was the polling effect that was removed earlier.
- Drop the defensive try/catch around appendTableEvent in cell-write — the
  function is documented as never-throwing (returns null on failure).
- Combine INCR + ZADD into one Lua eval in events.ts. Halves Redis RTT per
  cell-write. Lua returns the new eventId; the script splices it into the
  pre-built entry JSON.
- Trim refs to plain let bindings inside the effect; trim stale
  comments referencing the old polling implementation.

* fix(table): address PR review on SSE buffer

- TTL-expiry silent miss: when all keys expire, hgetall(meta) returns empty
  so earliestEventId is undefined and the prune branch was skipped. Reconnect
  with non-zero afterEventId now checks the seq counter — its absence (TTL
  expired) signals pruned so the client refetches. Memory fallback mirrors.
- Unbounded ZRANGEBYSCORE: cap reads at TABLE_EVENT_READ_CHUNK = 500 events
  per call. The route's 500ms poll loop drains chunks across ticks instead of
  flushing 5000 entries (multi-MB) in one tick after a long disconnect.
- Pruned handler closes EventSource client-side: server-side close was firing
  onerror and routing through the 500ms backoff path. Now we close
  proactively, reset the reconnect attempt counter, and reconnect immediately
  from the new earliest.

* improvement(table): persist SSE lastEventId in sessionStorage

Tab refresh / navigate-away-and-back now resume the stream from where the
previous mount left off instead of replaying from from=0. Mirrors the
useExecutionStream pattern (saveExecutionPointer / loadExecutionPointer).
First-ever mounts and new tabs still start at 0 — sessionStorage is
per-tab, so the safe default applies. On a 'pruned' fallback the new
earliestEventId is also persisted so the next reconnect starts there.

* fix(table): include runningBlockIds + blockErrors in SSE event payload

The cell renderer's 'queued' vs 'running' vs 'pending-upstream' decision
reads exec.runningBlockIds + exec.blockErrors. Without those fields the
inFlight branch falls through to 'pending-upstream' (amber Pending pill)
even when the worker has already written status=running. The worker writes
both fields to DB; the SSE event was stripping them. Thread them through
events.ts → cell-write.ts → use-table-event-stream.ts.

* fix(table): show value once column output has landed mid-run

The cell renderer treated any `status: 'running'` event as in-flight,
even when the column's own output had already been written. During a
multi-block group run, partial-write events for a later block carry
the earlier block's outputs but tag only the later block as running
— that flipped the finished column back to the amber Pending pill
until the terminal `completed` event arrived.

Re-order the priority chain so the column's value wins over
`pending-upstream`. Active re-run of the column itself
(`blockRunning`) still wins over the stale value, so a re-run on a
previously-completed cell still surfaces the running pill before the
new value overwrites.

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

* chore(table): address PR review nits on tables.ts

- Merge duplicate JSDoc on snapshotAndMutateRows into a single block
- Remove unused useQueryClient() calls from useTableRows and
  useInfiniteTableRows (leftover from polling-era code)

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

* chore(table): apply biome formatting fixes

CI lint job flagged import order in two files and over-wrapped union
in lib/table/events.ts. No behavior change.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 04:18:08 -04:00
Theodore LiandClaude Opus 4.7 98f8e854eb improvement(tables): extract TablesDetail wrapper, ship trigger followups (#4476)
* ui improvements

* Update status pils, make checkbox column sticky

* add Run workflow to context menu

* Refactor dispatching logic

* fix checkbox width to be smaller if csv is small

* Add drag behavior for workflows, stop workflow on multi select

* fix z index of checkbox to left, add view workflow button

* Switch to emcn buttons for Add inputs

* Split up workflow sidebar from column sidebar, refactor cells

* Lint and add auto run toggle

* fix column reordering, add action bar

* Create and use emcn square

* Reconcile post-merge: drop positionMap, use rowId-based selection

Staging refactored Tables UI to decouple from DB position (gutter from
array index, checkedRows keyed by rowId, no PositionGapRows). Bring
HEAD's action-bar / context-menu helpers in line: contextMenuRowIds,
selectedRowIds, actionBarRowIds now key off row.id and walk `rows`
directly. Drop the maxPosition / positionMap derived state. Collapse
COLUMN_SIDEBAR_WIDTH_CSS to a numeric COLUMN_SIDEBAR_WIDTH used by both
the sidebar shell and the table's reserved padding-right.

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

* feat(table): backfill remapped workflow outputs from execution logs

When a workflow column is re-pointed to a different (blockId, path),
populate its existing rows with the new output's value pulled from saved
execution logs instead of leaving them empty until the next run. Rows
where the new mapping has no logged value clear (matching the previous
behavior for those rows), but rows where the workflow already has the
new output's value surface immediately.

Refactor backfillAddedGroupOutputs into a generalized
backfillGroupOutputsFromLogs helper with an `overwrite` flag — used in
both the added-outputs path (preserves hand-edited values) and the new
remapped path (overwrites since the new mapping is the source of truth).

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

* fix(table): update column type when remapping workflow output

A remap that changes the output's leaf type (string → number, json →
boolean, etc.) was leaving the column's declared type stale. The clear-
then-backfill flow then failed schema validation on every row, so the
backfill silently aborted and the column stayed empty.

Resolve the new leaf type via flattenWorkflowOutputs +
columnTypeForLeaf for each mappingUpdate, and patch
schema.columns[i].type before the schema write. The clear-tx then
backfill ordering now works end-to-end across type changes. If the
workflow or its target output can't be resolved (workflow deleted,
block removed), fall back to leaving the column type alone — the
backfill will skip rows whose picked value doesn't match, same as
before.

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

* fix(table): stringify objects instead of "[object Object]" in cells

If a column's declared type lags its row data (e.g. a workflow column
mid-remap, where the schema cache hasn't refetched yet but the row data
already has the new mapping's value), formatValueForInput and the
cell-render text variant fell through to String(value) and rendered
"[object Object]". JSON-stringify objects in both spots so the transient
skew shows the actual data.

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

* fix(table): drop extra left border on workflow group meta header

The meta cell had border-r/b/l while regular headers have only border-r/b.
With border-separate tables, that extra 1px left border shifted the
meta cell's content one pixel right of the columns below it.

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

* fix(table): align workflow meta header without dropping its left border

Restore border-l and pull the cell back -1px with -ml-px so the visible
left border overlaps the previous cell's right border instead of adding
1px to the meta cell's box. Content lines up with the columns below.

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

* fix(table): draw meta header left border via ::before pseudo

Adding border-l to the meta cell shifted its content right by 1px
because table-fixed + border-separate honors the border inside the
colspan'd cell's width budget. -ml-px doesn't work on <th>. Render the
visible left edge via a ::before at left: -1px instead — paints over
the prior cell's right border without consuming any of the meta cell's
content area. Content lines up with the columns below.

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

* refactor(tables): add missing barrels, drop doubled-path imports

Match the convention used by logs/components: every component folder
exposes its public API via index.ts so consumers import from the folder
name, not from its internal filenames.

- New barrels: column-config-sidebar/, workflow-sidebar/,
  table-action-bar/, table/cells/, table/headers/.
- Rename table-filter/index.tsx → index.ts (barrel is not a component).
- Top-level components/index.ts re-exports every sibling folder so
  external consumers have one import path.
- Replace `from '../foo/foo'` doubled paths in table.tsx with the
  shorter barrel-anchored form.

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

* refactor(tables): introduce TablesDetail wrapper as thin passthrough

Phase 1 step 0 of the wrapper extraction (see plan
okay-lets-make-a-shimmying-trinket.md). page.tsx now renders
TablesDetail, which today is a passthrough to <Table>. Subsequent
commits lift surface state out of <Table> into this wrapper one piece
at a time.

The mothership chat path (<Table embedded>) is untouched — <Table>
stays exportable as a lower-level component for embedded contexts.

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

* refactor(tables): lift slideout panel state into TablesDetail wrapper

The three right-edge slideout panels (column config, workflow config,
execution details) move out of <Table> into the wrapper. The wrapper
owns a single useReducer that encodes the at-most-one-open invariant
as a discriminated union — opening any one panel automatically closes
the others. <Table> emits open requests via three new callback props.

Also extract <ExecutionDetailsSidebar> from inline-in-table.tsx to its
own folder so the wrapper can compose it cleanly. Update the embedded
mothership callsite (resource-content.tsx) to render <TablesDetail
embedded> instead of <Table embedded>.

Phase 1 step 1 of the wrapper extraction. <Table> shrinks from 3849 →
3787 lines; <TablesDetail> grows from 19 → 145 lines.

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

* refactor(tables): lift delete-table modal + mutation into wrapper

The delete-table confirmation modal and `useDeleteTable` mutation move
out of <Table> into TablesDetail. <Table> exposes a new
`onRequestDeleteTable` callback fired by the page-header Delete action.

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

* refactor(tables): lift CSV import dialog into wrapper

ImportCsvDialog moves out of <Table>. Grid exposes
`onRequestImportCsv` fired by the page-header menu item; wrapper owns
the open state and renders the dialog.

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

* refactor(tables): lift RowModal (edit + delete) into wrapper

Both RowModal instances move out of <Table> into the wrapper. Grid
emits `onOpenRowModal(row)` (Space key) and
`onRequestDeleteRows(snapshots)` (context menu).

Post-delete cleanup (push undo, clear selection) needs grid-internal
state, so the grid populates an `afterDeleteRowsSinkRef` callback that
the wrapper's modal `onSuccess` invokes.

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

* refactor(tables): lift delete-columns modal into wrapper

The destructive delete-columns confirmation modal moves into the
wrapper. Grid emits `onRequestDeleteColumns(names)`; the cascade itself
(per-column mutation, undo push, columnOrder + columnWidths cleanup)
stays in the grid as a sink the wrapper invokes on confirm — too
grid-internal to lift cleanly.

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

* refactor(tables): lift run/stop mutations + TableActionBar to wrapper

useRunGroup and useCancelTableRuns move out of <Table> into the
wrapper, along with the <TableActionBar> render. Grid receives
onRunGroup, onRunRows, onStopRow, onStopRows, onStopAll, and
cancelRunsPending as props — used by the per-row gutter Play/Stop, the
workflow-group meta-cell run menu, and the right-click context menu's
Run/Stop on selection items.

Action-bar selection state (actionBarRowIds, runningInActionBar,
hasWorkflowColumns) is derived from grid-internal state, so the grid
emits a `SelectionSnapshot` via `onSelectionChange` from a useEffect.
Wrapper uses the snapshot to drive the floating <TableActionBar>.

Phase 2 step 1 of the wrapper extraction.

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

* refactor(tables): lift queryOptions to wrapper

queryOptions (filter + sort) moves out of <Table> into the wrapper,
making it a single source of truth that drives one useTable call. The
wrapper passes the bundle down to the grid; sort/filter handlers in
the grid call onQueryOptionsChange.

Eliminates the previous double-useTable pattern (one for the grid's
filtered/sorted view, one in the wrapper's hardcoded null/null query
for sidebar metadata).

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

* refactor(tables): lift page header (breadcrumbs/options/filter) to wrapper

Phase 3 of the wrapper extraction. The full page-header surface moves
out of <Table>:

- ResourceHeader (breadcrumbs, table-rename UI, headerActions, createTrigger)
- ResourceOptionsBar (sort + filter toggle)
- TableFilter (filter panel — wrapper owns filterOpen state)
- RunStatusControl (in the leading actions when runs are active)

useRenameTable + useInlineRename for the breadcrumb name move to the
wrapper. The grid populates pushTableRenameUndoSinkRef so the rename is
still part of the grid's undo stack.

Extract NewColumnDropdown and RunStatusControl from inline-in-table.tsx
to their own folders so the wrapper composes them cleanly without
reaching into the grid's internals.

Hoist generateColumnName from grid-internal useCallback to a shared util
so both the page-header and inline-header NewColumnDropdowns use the
same logic.

After this lift <Table> is the data grid only — no page surface, no
modals, no slideouts, no breadcrumbs. The selection snapshot now
includes totalRunning so the wrapper can render the page-header
RunStatusControl from outside the grid.

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

* chore(tables): cleanup pass on TablesDetail wrapper extraction

Six-pass cleanup against the wrapper extraction diff:

- Effects: add content-compare bailout to onSelectionChange emit so
  unchanged snapshots don't churn wrapper re-renders.
- Memos: drop unnecessary activeSortState memo, fold into sortConfig.
- Callbacks: remove ~10 useCallbacks with no observed reference (sidebars
  not memoized, modals not memoized, inline arrows on non-memoized
  children); keep the ones that feed into <DataRow>/<RunStatusControl>/
  <ResourceHeader> (memoized) or grid-side useCallback deps.
- Dead props: drop onQueryOptionsChange/onRequestDeleteTable/
  onRequestImportCsv from <Table> — the page-header lift made them
  unused but the props weren't removed.
- React Query: drop redundant tableWorkflowGroupsRef (created when
  onRunRows was useCallback-wrapped; after callback cleanup it can read
  the query data directly).
- emcn: normalize Loader sizing to h-[14px] w-[14px] to match the
  codebase convention.

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

* fix(table): re-seed columnOrder when columns change server-side

The metadata-seed effect short-circuited after the first seed, so any
later schema change (e.g. adding a workflow output column) couldn't
push the new column into local columnOrder. The new column would then
fall into the "remaining" bucket of `displayColumns` and render at the
end of the table — until the user refreshed and the grid re-mounted
with the now-current metadata.

Drop the `metadataSeededRef.current` short-circuit from the early
return so the effect can also reach the after-first-load re-seed
branch, which already does the right thing (only re-seeds when the set
of columns changes, leaves pure-reorder cases alone).

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

* refactor(tables): rename wrapper to <Table>, grid to <TableGrid>

Match the naming convention used elsewhere in the workspace
(workflow.tsx → <Workflow>, base.tsx → <Base>, logs.tsx → <Logs>).

- tables-detail.tsx → table.tsx (exports <Table>)
- components/table/ → components/table-grid/ (exports <TableGrid>)
- components/table-grid/table.tsx → table-grid.tsx
- Drop <ExecutionDetailsSidebar> — was a 3-line passthrough
  (executionId → useLogByExecutionId → <LogDetails>); inline directly
  into table.tsx where it's used.
- Flatten components/run-status-control/ folder to a single
  components/run-status-control.tsx file. 25-line single-use component
  with no internal subdirs — folder was overhead. Matches knowledge's
  max-badge.tsx precedent.

Net: 1 wrapper rename + grid rename + 2 folder collapses, all imports
updated. The mothership chat callsite updates from <TablesDetail
embedded> to <Table embedded>.

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

* fix(table): don't show "Waiting" for autoRun=false workflow groups

A workflow group with autoRun=false never fires from the scheduler —
the cell stays empty until the user clicks Run manually. Treating
empty cells as "Waiting" misleads the user into thinking the group
will auto-fire once deps are filled, which it won't.

Skip autoRun=false groups when computing the per-row waiting labels
so their cells render the empty-dash instead of the Waiting pill.

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

* chore(copilot): regenerate tool catalog from copilot dev (#247)

Pulls in the workflow_group operations on user_table:
add_workflow_group / update_workflow_group / delete_workflow_group /
add_workflow_group_output / delete_workflow_group_output /
run_workflow_group, plus the autoRun / blockId / dependencies / groupId
parameters and a tightened mapping description for import_file.

Also picks up biome import-order fixes from `bun run lint`.

* improvement(table): action bar in mothership + per-execution mode

Three related improvements to the table action bar:

1. Reposition from `position: fixed` to `position: absolute` inside
   the table's container. Fixed-positioning anchored to the viewport,
   which centered the bar across the whole window instead of the table
   panel — wrong in mothership embedded view, where the table sits in
   the right half. Absolute scopes the bar to the table's bounds.

2. Show the bar for single-execution highlights — when the user
   selects one workflow-output cell, or 1 row × N cols all within the
   same workflow group. The bar enters per-execution mode with Run /
   Stop / View execution buttons targeting that one cell or group.

3. Skip View execution for cancelled cells. A cancelled cell may have
   been cancelled before the worker ever picked the job up, so its
   executionId can't be relied on. Tighten the gate everywhere
   (context menu + action bar) to only `completed` / `error` / `running`.

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

* fix(table): backfill on add_workflow_group_output, don't re-run

addWorkflowGroupOutput (the one-shot single-output add path used by
the copilot user_table tool) was calling triggerWorkflowGroupRun({
mode: 'all' }) after appending the output — that re-fired the workflow
on every row. Trace a307ed8fd5fe2d931aa84dedab5a60f0 shows ~75
workflow-group-cell jobs enqueued in the seconds after a single
add_workflow_group_output call.

Replace with backfillGroupOutputsFromLogs (overwrite: false), the same
flow updateWorkflowGroup uses when receiving newOutputColumns. Reads
each row's saved trace spans and writes the new output's value back —
no compute beyond a JSONB write per row, no double-billing the user
for runs they didn't ask for.

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

* fix(table): drop sql.raw quote-escaping in column-name interpolation

Six call sites in lib/table/service.ts built JSON-key string literals
at runtime via `sql.raw(\`'\${name.replace(/'/g, "''")}'\`)` for use
with PostgreSQL's `data->'key'` / `data->>'key'` operators. Practically
safe (NAME_PATTERN gates column names to alphanumeric+underscore at
insert time) but a smelly pattern that breaks the moment validation
loosens.

Both `data->` and `data->>` accept a parameterized text value as the
key, so the `sql.raw` is unnecessary. Replace each with a normal
`${name}::text` binding. No behavior change; eliminates the manual
quote-escaping surface.

Affected sites: renameColumn (the data-rewrite UPDATE), upsertRow's
match filter, updateColumnType's IS-NOT-NULL gate, updateColumnConstraints'
required-check + unique-duplicate-check.

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

* feat(copilot-tool): forward autoRun + mappingUpdates on update_workflow_group

The sim-side service and contracts already accept both fields, but the
copilot tool's update_workflow_group handler was dropping them on the
floor. Now `args.autoRun` (toggle the persisted auto-fire flag) and
`args.mappingUpdates` (per-output (blockId, path) swap) get forwarded
through to updateWorkflowGroup.

Pairs with the upcoming copilot-side change that exposes these in the
tool catalog JSON / Go handler / prompting (see copilot branch
redo-workflow-tools).

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

* fix(table): keep gutter border visible when hovering Run-row button

The per-row Run button sat flush against the row-gutter cell's right
border. Its hover background (rounded-rect surface-2) painted over the
border line for the 20px height of the button, making the gutter
divider appear to disappear at the hovered row.

Add mr-px to the button so the hover bg stops 1px short of the cell's
right edge, leaving the divider intact.

* fix(table): unify auto-fire and manual run paths in scheduler

scheduleWorkflowGroupRuns now owns eligibility, autoRun semantics,
dep evaluation, and enqueue for both paths. Auto-fire callers omit
opts; manual callers (triggerWorkflowGroupRun) pass { groupId,
isManualRun: true } to bypass the autoRun=false skip and (for
autoRun=false groups) the dep check.

Per-row /run-workflow-group route delegates to triggerWorkflowGroupRun
with rowIds=[rowId]. Single server-side path for both manual entry
points.

Also: optimisticallyScheduleNewlyEligibleGroups skips autoRun=false
groups so editing a row's data doesn't phantom-mark autoRun=false
output cells as Queued.

* fix(table): render empty cells as blank, not em-dash

Empty cells (any column type) showed an em-dash placeholder. Drop it
so empty cells render blank — matches what the user expects when
nothing's there.

* fix(table): per-row Run fires autoRun=false groups regardless of deps

handleRunRow filtered out every group whose deps weren't satisfied,
which silently dropped autoRun=false groups (since their deps usually
aren't satisfied — that's the whole point of autoRun=false). Click
Run row, the autoRun=false group's cells stayed empty.

Mirror the scheduler's semantics: autoRun=false bypasses the dep check,
autoRun=true still requires deps.

* fix ui shape

* improvement(table): collapse run ops into run_column, derive action-bar buttons from selection

The action bar now reflects what's actually selected:
- Selection-driven scope (cells the user highlighted, not their full rows)
- Play visible when there's anything empty/failed; Refresh when there's anything completed; both for mixed
- run_cell / run_row deleted; everything funnels through run_column
- Per-row gutter Play, right-click "Run workflows on N rows", and column-header menu all share the canonical run path
- Shared RunMode type from the contract; cleanup pass via /simplify (readExecution / isExecInFlight reuse, runScope helper, flat onViewExecution prop)

* chore(copilot): regen tool catalog after dropping run_cell / run_row + dependencies.workflowGroups

Mirror the copilot-side catalog change so the generated TS catalog matches the deployed copilot tool surface.

* fix(table): atomic per-key writes for executions, plus run-op race fixes

The executions blob on user_table_rows was read-modify-written wholesale on every
update. Concurrent writers (a column edit and a manual-retry stamp, two pickup
calls, a cancel and a cascade) each computed a merge from their own snapshot,
and the last writer clobbered keys it never touched — producing stuck "queued"
cells, vanished stamps, and stale completed exec records reappearing after
retries.

Fixes:
- updateRow / batchUpdateRows now apply executionsPatch via a SQL jsonb merge
  expression. Each writer only mutates the keys it explicitly patches; other
  keys are preserved. Eliminates the cross-key clobber.
- writeWorkflowGroupState bypasses the stale-worker guard for `queued` (new
  scheduler stamp) and `cancelled` (authoritative cancel) writes — those ARE
  the new authority for the cell. Previously the new run's stamp was being
  rejected by the same guard meant to block the OLD worker's writes.
- skipScheduler flag on UpdateRowData / BatchUpdateByIdData lets the cancel
  path and runWorkflowGroupsInternal opt out of the implicit auto-fire pass
  (cancel was waking up siblings; manual-run was racing its own scheduler).
- CELL_CONTENT pinned to h-[22px] so status badges don't grow rows.

* chore(table): remove table-row sockets, both sides

Tables don't use realtime sockets in prod — strip the dead path so we stop
paying the per-row HTTP forward + socket emit on every cell write. Polling on
running execs already covers reconciliation.

Sim side:
- service.ts: drop notifyTableRowUpdated/Deleted, notifyTableDeleted, the
  postRealtimeBridge helper, and all callsites.
- hooks/queries/tables.ts: drop the socket subscription block in useTableRows;
  poll-on-running stays. Remove useEffect / useSocket imports.
- app/.../tables/[tableId]/hooks/use-table.ts: drop the merge-on-event
  useEffect and unused imports.
- app/workspace/providers/socket-provider.tsx: drop joinTable/leaveTable,
  onTableRowUpdated/Deleted/onTableDeleted, currentTableId state, related
  events + types.

Realtime side:
- handlers/tables.ts deleted; index.ts no longer wires it.
- routes/http.ts: drop /api/table-row-updated, /api/table-row-deleted,
  /api/table-deleted endpoints.
- rooms/{memory,redis}-manager.ts: drop emitToTable, handleTableRowUpdated/
  Deleted, handleTableDeleted, related imports.
- rooms/types.ts: drop method declarations, TableRowUpdatedPayload type,
  tableRoomName helper.
- middleware/permissions.ts: drop unused verifyTableAccess.

Bonus from parallel work:
- cell-content typewriter trigger refinement.

* fix(table): clearing a workflow output cell also clears its exec record

When the user wipes a workflow output column value, the auto-fire reactor
needs to be re-armed for that group. Previously, a stale cancelled / error
exec record blocked the eligibility predicate (gate at line 79 hard-rejects
those statuses on auto-fire) and the cell stayed stuck in its old terminal
state — visible as "Cancelled" cells that wouldn't re-run no matter what.

Both updateRow and batchUpdateRows now derive an `executionsPatch[gid] = null`
for any output column the patch sets to empty. The data clear and the exec
clear ride the same SQL transaction, so the row never lands in a stale-
status-with-empty-data state.

Symmetric to how `completed` already worked via `areOutputsFilled` in the
predicate — clearing the cell wins over the prior exec status, regardless of
what that status was.

(Also revert typewriter-trigger experiment from a parallel session that was
in-progress on this branch.)

* fix(table): waiting state, optimistic UX, schema-mutation polling, exec cleanup

A bundle of small UX + correctness fixes around workflow-cell run state.

cell-render.tsx
- In-flight (queued/running/pending) now wins over the existing value, so
  re-runs surface immediately instead of looking like nothing happened until
  the worker writes the new value.
- "Waiting on X" wins over a stale `cancelled` / `error` exec when deps are
  unmet — clearing a dep now reads as actionable instead of stuck.

useRunColumn (hooks/queries/tables.ts)
- onSettled now cancels in-flight polls before invalidating. Stops a poll
  that landed mid-mutation from clobbering the optimistic state with stale
  data, which produced the queued → cancelled → queued flicker.

addWorkflowGroup / updateWorkflowGroup (autoRun toggle on)
- Awaits scheduleRunsForTable instead of fire-and-forget. The route returned
  before the queued exec stamps committed, so the post-mutation refetch saw
  no in-flight cells and polling never started — cells looked stuck even
  though the server eventually stamped them.

deleteColumn / deleteColumns
- Strip orphaned executions[gid] keys when deleting a column orphans its
  parent group. Without this, stale running/queued exec records lingered on
  every row forever and inflated the page-header "N running" counter even
  on tables with no actually-running cells.

UI
- Action-bar leading label: "Selected N workflow cell(s)".
- Context menu: Run / Refresh items mirror the action bar's Play / Refresh
  split, gated on the same selection-status flags so both surfaces show the
  actions that match the current state.

* refactor(table): consolidate exec-status helpers + fix N-running counter

Cleanup pass on the recent table changes — pulls duplicated predicates and
SQL snippets into shared helpers and fixes one drift bug along the way.

- isExecInFlight: now single export from lib/table/deps.ts. Removed the
  duplicate in components/table-grid/utils.ts. Used by isGroupEligible
  (server eligibility) and runningByRowId (client counter).
- isOptimisticInFlight: kept local to hooks/queries/tables.ts — renamed from
  isInFlight to disambiguate from the stricter isExecInFlight. The two
  predicates differ on `pending` without a jobId: optimistic patches and
  poll-trigger want the broader version, eligibility wants the strict one.
- areOutputsFilled: single export from lib/table/deps.ts, dropped duplicate
  from workflow-columns.ts.
- classifyExecStatusMix: shared row × group walker in table-grid/utils.ts.
  Replaces two copies of the same loop in table-grid.tsx (selectionStats +
  contextMenuStats). Both surfaces now have the same short-circuit
  semantics, including the seen-all-selected-rows early break that
  contextMenuStats was missing.
- stripGroupExecutions: SQL helper in service.ts. Replaces three copies of
  the `UPDATE user_table_rows SET executions = executions - $gid::text`
  pattern across deleteColumn / deleteColumns / deleteWorkflowGroup.

Drift bug:
- runningByRowId / totalRunning counted only `running` and `queued`. Every
  other in-flight check in the codebase treats post-stamp `pending` as
  in-flight too, so the page-header "N running" badge briefly dropped to 0
  between scheduler stamp and worker pickup. Now uses isExecInFlight.

* fix(table): address pr review (drop dead workflowNameById prop, reset didDragRef on dragend, align sidebar width)

* fix(table): scope post-clear schedule to targeted groups, forward mode

Multi-group manual runs (Run row, gutter Play, action-bar Play across mixed
completed + cancelled cells) re-fired completed-and-filled siblings.
runWorkflowGroupsInternal cleared only the groups it filtered, then called
scheduleRunsForRows with isManualRun: true and no group / mode filter — so
the post-clear pass walked every group on the table with default mode 'all',
and any autoRun=true completed sibling whose deps were satisfied got queued
again. Scope the post-clear call to targetGroups and forward mode.

* fix(table): meta-cell drag-leave flicker guard + plumb unique on create

* fix(table): strip sibling deps when removing workflow output via updateWorkflowGroup

deleteWorkflowGroup already stripped removed-column deps from sibling
groups, but updateWorkflowGroup (the path the UI takes when deleting one
output of a multi-output group) didn't — schema validation then rejected
the update with 'Group X depends on missing column Y'.

* improvement(table): debug logs at every cascade decision branch

* improvement(table): parallelize queued-stamp writes within concurrency-cap chunks

* Simplify stripping column names

* fix lint, ci

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:43:05 -04:00
WaleedandClaude Opus 4.7 28e60bfd4e fix(docker): drop scripts/ from workspaces array (#4484)
`turbo prune sim --docker` strips `scripts/` from the pruned output (sim
doesn't depend on it), but the pruned root package.json still listed it
as a workspace, causing `bun install` to fail with "Workspace not found
'scripts'" in the Docker build.

scripts/ is dev-only tooling that runs from the repo root via `bun run
scripts/*.ts`. Its imports (glob, yaml) resolve against the root
node_modules — they're already in root devDependencies.

- Remove "scripts" from root workspaces array
- Delete scripts/package.json (no longer a workspace, manifest unused)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:25:07 -07:00
WaleedandClaude Opus 4.7 a251e45400 feat(sap): add SAP Concur integration block and SAP S/4HANA validation fixes (#4483)
* feat(sap): add SAP Concur integration block and SAP S/4HANA validation fixes

* added

* fix(sap_s4hana): preserve raw Set-Cookie array for CSRF cookie join

SecureFetchHeaders previously collapsed multi-value Set-Cookie headers
with ", ", forcing consumers to re-split via a fragile regex. Cookie
values containing "=" or "," (e.g., Base64 session tokens) could be
misparsed and produce malformed Cookie strings on CSRF-protected
mutations.

Add SecureFetchHeaders.getSetCookie() that returns the raw array, and
update the S/4HANA OData proxy's joinSetCookies to consume it directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sap-concur): rename misleading exchange-rate tool, drop unusable refresh_token grant, validate geolocation host

- Rename sap_concur_get_exchange_rate to sap_concur_upload_exchange_rates (POST bulk upload, not GET)
- Remove refresh_token from SapConcurGrantType / Zod enum / block dropdown / docs (no implementation)
- Validate Concur geolocation hostname against SAP_CONCUR_ALLOWED_DATACENTERS

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* finished

* docs

* fix(docs): escape braces in tool/trigger description prose for MDX

Tool and trigger descriptions can contain URL path placeholders like
{reportId} or JSON-shape hints like { Items, NextPage }. When rendered
as MDX prose (not table cells), these were emitted unescaped and MDX
parsed them as JSX expressions, failing prerender with
"ReferenceError: reportId is not defined".

Escape { and } in the operation-level description and trigger
description renderers, matching the existing escaping in table-cell
descriptions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sap-concur): align with live API on travel-profile, itineraries, and context types

- list_travel_profiles_summary: rename Status query to Active with 1/0 values, tighten LastModifiedDate format hint
- list_itineraries / get_itinerary: use documented userid_type / userid_value / ItemsPerPage / Page query keys
- create_report_comment: contextType allows MANAGER (move to EXPENSE_READ_CONTEXT_TYPE_OPS)
- get_list_item: drop unused listId from block (tool only needs itemId)
- Tighten description copy on list_expenses/get_itemizations/associate_attendees/remove_all_attendees

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sap-concur): correct Cash Advance v4.1 paths, add SCIM filter param

- Update Cash Advance create/get/issue tools from /cashadvance/v4/ to /cashadvance/v4.1/ to match the live API
- Add filter query param to list_users (SCIM v4.1 supports filtering by userName, employeeNumber, externalId)
- Regenerate docs MDX

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sap-concur): drop SCIM list_users filter param (not supported on v4.1 GET)

SCIM Identity v4.1 GET /Users does not accept a filter query parameter — filtering
is only supported via POST /Users/.search (already exposed by sap_concur_search_users).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sap-concur): final live-API alignment

Verified against live SAP Concur docs (concur/developer.concur.com preview branch):

- Revert Cash Advance paths to /cashadvance/v4/ (v4.1 endpoints do not exist; live spec is v4)
- Travel Profile v2 summary has no Active/Status query param — drop the filter from tool, types, and block
- Report Comments v4 contextType is TRAVELER or PROXY only (NOT MANAGER) — move create_report_comment + list_report_comments into the TRAVELER/PROXY context group
- Trip v1.1 query keys: userid_type / userid_value / ItemsPerPage / Page (snake/Pascal per docs) — already correct, kept

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs

* fix(sap-concur): restore Cash Advance v4.1 paths

Re-verified against live developer.concur.com docs at /api-reference/cash-advance/v4-1.cash-advance.html — only v4.1 endpoints are documented:
- POST /cashadvance/v4.1/cashadvances
- GET /cashadvance/v4.1/cashadvances/{cashAdvanceId}
- POST /cashadvance/v4.1/cashadvances/{cashAdvanceId}/issue

The /cashadvance/v4/ docs page returns 404. Reverts the prior local rollback in 9ef3a11d7.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:32:27 -07:00
WaleedandClaude Opus 4.7 7953c56aca fix(security): xlsx CVE bump and bundled security hardening (#4481)
* fix(security): xlsx CVE bump and bundled security hardening

* fix(stripe): use configured secret key for SDK init

Avoids leaving a recognisable placeholder string in heap dumps and
error serialisations. Webhook verification remains a purely local
HMAC operation; the SDK's constructor key is unused by it.

Addresses Greptile feedback on #4481.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(stripe): use static Stripe.webhooks for verification

Avoids instantiating a Stripe client just to access constructEvent.
The webhook signing secret is per-trigger (user-provided whsec_…) and
unrelated to our billing STRIPE_SECRET_KEY, so coupling them was wrong.
Stripe.webhooks is exposed as a static — no client, no API key needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ci): revert client-bundled tools to avoid .server import in client

* fix(security): collapse 403 to 404 on v1 detail-by-ID routes

* chore(security): remove unused validateAgiloftInstanceUrl helper

* fix(security): bump minimatch + clean up scripts/ workspace

Resolves CVE-2026-27903 (GHSA-7r86-cg39-jmmj) by adding a root-level
minimatch ^10.2.5 override. Also resolves CVE-2026-0969 in next-mdx-remote
(bumped to ^6.0.0).

Cleanup:
- Make scripts/ a proper bun workspace (root workspaces array)
- Remove duplicate scripts/package-lock.json (this repo uses bun)
- Remove redundant scripts/bun.lock (now hoisted to root)
- Remove vestigial scripts/setup-doc-generator.sh
- Slim scripts/package.json to its real deps (glob, yaml)
- Gitignore stray package-lock.json files
- Update scripts/README.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 16:36:51 -07:00
WaleedandClaude Opus 4.7 d721dc3358 feat(enterprise): add data drains for continuous export to S3 / webhook (#4440)
* feat(enterprise): add data drains for continuous export to S3 / webhook

* chore(data-drains): regenerate migration on top of staging + bump route baseline

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(data-drains): clarify retention pairing is user-coupled, not enforced

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(data-drains): preserve explicit forcePathStyle=false + reserve x-sim-signature

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(data-drains): drift guard ensures every webhook header is reserved

Asserts that any header buildHeaders writes is rejected when reused as a
custom signatureHeader. Adding a new metadata header without mirroring it
into RESERVED_SIGNATURE_HEADER_NAMES now fails CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 18:04:02 -07:00
09f4c94b3c feat(block): Allow wait block to wait up to 30 days (#4331)
* v0.6.29: login improvements, posthog telemetry (#4026)

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

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

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

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* feat(block): Allow wait block to wait up to 30 days

* restore ff

* Filter out waits from hitl endpoints

* Use correct count, filtering out wait blocks

* improvement(wait): tighten poll route and pause-manager helpers

- Parallelize per-row dispatch with Promise.all
- Add status='paused' guard on nextResumeAt rewrite to prevent clobbering concurrent resumes
- Extract computeEarliestResumeAt + PauseResumeManager.setNextResumeAt helpers
- Use canonical PausePoint type in poll route (drop StoredPausePoint)
- Narrow UNIT_TO_MS via as const + WaitUnit guard
- Bump LOCK_TTL_SECONDS above route maxDuration
- Clearer error when allowedPauseKinds rejects a resume

---------

Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
2026-05-05 19:28:01 -04:00
WaleedandClaude Opus 4.7 029ac9fb05 fix(logs): split summary/detail contracts to make trace tab gate type-safe (#4431)
* fix(logs): split summary/detail contracts to make trace tab gate type-safe

The Trace tab was silently missing from the Log Details sidepanel because
list and detail rows shared one WorkflowLog type with executionData:
z.unknown(). The UI couldn't distinguish a summary row (no spans) from a
detail row (with spans), so the tab gate read undefined and hid itself.

Splits into WorkflowLogSummary (list) and WorkflowLogDetail (typed
executionData with optional traceSpans). Detail and by-execution routes
both write through to the same logKeys.detail(id) cache, eliminating the
two-key fragmentation that caused the merge memo workaround. List route
moves to cursor pagination on (sortValue, id) with proper NULLS LAST
handling and SQL-side sort across workflow + job execution tables.
Detail route now requires and asserts workspaceId. Deep-link path uses
useLogByExecutionId instead of auto-paginating the entire workspace.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): audit follow-ups — render side-effect, stats invalidation, enhanced spread order

- Move onActiveTabChange call from render into useEffect to avoid
  side-effects during render (StrictMode safety).
- Re-add logKeys.stats() invalidation to cancel/retry mutations so
  the dashboard reflects status flips immediately.
- Reorder enhanced: true after ...execData spread in detail and
  by-execution routes so the literal discriminator is never
  overwritten by stale execData.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): mirror SQL NULLS LAST in JS merge for cursor consistency

The in-memory merge of workflow + job pages negated the comparator
for DESC, which placed null sort values at the start. SQL orders
both ASC and DESC with NULLS LAST, so DESC pages emitted a cursor
{v: <last non-null>, id: ...} while null rows still satisfied the
cursor predicate (OR sort_expr IS NULL) on the next page —
producing duplicate null rows across pages on cost/duration sorts.

Handle nulls explicitly in the JS comparator so they always sort
last regardless of direction, matching the SQL ordering.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): final-audit follow-ups — stable tab callback, byExecution invalidation, optimistic detail patch, trace loading state

- Wrap LogDetails -> LogDetailsContent onActiveTabChange in useCallback
  so the child useEffect doesn't refire on every parent render.
- Add logKeys.byExecutionAll() to cancel + retry invalidation so the
  table-embedded sidebar picks up status changes immediately.
- Optimistic write-through to logKeys.detail in useCancelExecution so
  the open sidebar reflects 'cancelling' instantly; rolls back on error.
- Distinguish trace loading from trace-empty: when log.executionData is
  not yet fetched, render "Loading trace…" instead of the empty state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(logs): migrate stores/components to contract types

Replace the legacy `WorkflowLog` / `LogsResponse` / `WorkflowData` /
`CostMetadata` / `ToolCallMetadata` shapes in
`stores/logs/filters/types.ts` with direct use of the contract types
`WorkflowLogSummary`, `WorkflowLogDetail`, and a new `WorkflowLogRow`
alias for surfaces that render either form. Removes the
`summaryToWorkflowLog` / `detailToWorkflowLog` bridge in the React
Query layer along with their double-cast annotations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(logs): address PR review feedback

- Whitelist sort columns against logSortBy enum to prevent client crash
  when non-sortable headers (workflow, trigger) reach the contract parser.
- Extract fetchLogDetail helper shared by /api/logs/[id] and
  /api/logs/by-execution/[executionId] — collapses ~360 duplicated lines
  to a single source of truth keyed on lookup column.

* fix(logs): exclude job logs when level filter is workflow-only

When level=running or level=pending (workflow-only states involving
endedAt/pausedExecutions semantics), jobLevelConditions stayed empty
so no level constraint reached jobConditions — every job log in the
workspace leaked into the result. Skip the job side entirely when the
level filter has no job-applicable values (error/info).

* chore(logs): drop dead utils — mapToExecutionLog and friends

Remove ExecutionLog/RawLogResponse/ExecutionCost/LogWithExecutionData/
TraceSpan/BlockExecution interfaces and the mapToExecutionLog,
mapToExecutionLogAlt, extractOutput functions — all unreferenced after
the contract split. -212 lines.

* chore(logs): drop unused LOG_COLUMN_ORDER and LogColumnKey

* fix(logs): hydrate filters from URL synchronously on mount

The previous useEffect-based initializeFromURL caused useLogsList and
useDashboardStats to fire once with default store filters, then refetch
after the effect updated filters from the URL. Move the initial hydrate
into a useState lazy initializer so the first render already reads
URL-derived filters; the popstate handler keeps the existing effect for
back/forward navigation.

* chore(logs): trim verbose comments added during PR

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): guard navigation arrows when selected log is off-page

Deep-linked logs resolved via useLogByExecutionId may not be in the
current page list, leaving selectedLogIndex at -1. The hasNext prop
was evaluating -1 < logs.length - 1 (true for any non-empty list),
which enabled the next arrow and jumped to the first item on click.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(logs): sync active-tab callback before paint to keep keyboard guards aligned

Run the resolvedTab → onActiveTabChange propagation in useLayoutEffect
so the parent's activeTabRef updates synchronously before the next
paint. This closes the brief window where window keydown handlers
in the logs page would still see activeTabRef.current === 'trace'
and short-circuit arrow-key navigation immediately after switching
to a log without a Trace tab.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 16:18:44 -07:00
Waleed a479e88718 feat(files): export markdown as zip with embedded images (#4413)
* feat(files): export markdown as zip with embedded images in assets/ folder

* fix(files): sanitize zip filenames, fix storage context cast, cap embedded image count

* fix(files): fix race condition in asset filename deduplication

* chore(files): remove extraneous comments from export route

* fix(files): sanitize markdown zip entry name, full uuid fallback for filename dedup

* fix(files): extract userId const to satisfy TypeScript narrowing in async callback

* fix(files): use replacer function to prevent $ special-char corruption in markdown URL rewrite

* fix(files): wrap zip buffer in Uint8Array for NextResponse BodyInit compatibility

* lock behavior

* fix(workflow): track resolved isAdmin in prevIsAdminRef to prevent stale lock notification

When workspacePermissions loads asynchronously, prevCanAdminRef (which only
tracked effectivePermissions.canAdmin) would not detect the change, causing
the early-return guard to skip rebuilding the notification with the correct
unlock-button visibility. Track the same resolved value (workspacePermissions
?.viewer?.isAdmin ?? effectivePermissions.canAdmin) that is actually used to
build the notification.

* refactor(uploads): rename server fn to fetchWorkspaceFileBuffer, move client download to uploads/client/download.ts as triggerFileDownload

* more lock updates

* fix(tests): update mocks for fetchWorkspaceFileBuffer rename
2026-05-02 20:07:48 -07:00
Waleed af55bad491 fix(uploads): direct-to-upload workspace files + shared transport (#4407)
* fix(uploads): direct-to-S3 workspace files + shared transport

* chore(testing): centralize posthog and storage-service mocks

* fix(uploads): address PR review — abort propagation, orphan cleanup, error handling

- Throw immediately on AbortError in KB retry loop (no useless 14s backoff)
- Cleanup S3/Blob object on quota or size-cap rejection in registerUploadedWorkspaceFile
- Enforce MAX_WORKSPACE_FILE_SIZE at registration (defense vs presigned PUT lying about size)
- Handle non-OK / non-JSON responses in workspace-files upload paths

* fix(uploads): add Zod contracts for workspace presigned/register routes

* fix(uploads): correct BlobServiceClient type name in headBlobObject

* fix(uploads): address PR review — typo, complete-failure cleanup, double-increment

* fix(uploads): preserve fallback size and reuse existing display name on re-register

* fix(uploads): surface server error message and bypass quota for local-storage fallback

* fix(uploads): align register response schema with UserFile; skip presigned for KB large files

- registerWorkspaceFileResponseSchema now matches the UserFile shape the route actually returns; previous schema required workspace DB-row fields that were never populated, causing requestJson validation to reject successful uploads.
- KB batch presigned fetch now skips files >= LARGE_FILE_THRESHOLD since multipart bypasses the per-file presigned URL anyway.

* fix(uploads): idempotent register skips duplicate audit/posthog; add edge-case tests

- registerUploadedWorkspaceFile now returns { file, created } so the route can skip captureServerEvent and recordAudit on idempotent re-register (existing metadata reused). Previously a re-register fired duplicate analytics + audit log entries.
- Add tests covering: idempotent re-register skips audit/analytics, isNetworkError matches econnreset/timeout/etc keywords, multipart complete failure fires action=abort cleanup.

* fix(uploads): include 50MiB boundary in batch presigned fetch

* fix(uploads): trust HEAD size to prevent quota inflation

The head.size > 0 fallback let a client PUT 0 bytes and register
with an inflated size, debiting quota without storing data. HEAD
on an existing object always returns the true byte count, so trust
it directly — a genuine 0-byte file correctly contributes 0.

* fix(uploads): audit verified file size, not client-supplied

* fix(uploads): handle register retries and name-collision races

Two bugs in registerUploadedWorkspaceFile:

1. Register retry could orphan storage. When a successful response
   was lost on the wire and the client retried, the quota check saw
   the bytes already counted, failed, and cleanupOrphan deleted the
   already-registered storage object — leaving the DB row pointing
   to nothing. Fix: check getFileMetadataByKey before quota guard
   and short-circuit on existing record.

2. Concurrent same-named uploads could lose data. allocateUniqueWorkspaceFileName
   is best-effort; two racing uploads can pass it and both attempt
   the same display name. The loser's insert hits 23505, the catch
   block called cleanupOrphan, and successfully-uploaded bytes
   were deleted. Fix: retry on 23505 with a fresh allocateUniqueWorkspaceFileName,
   matching the pattern in uploadWorkspaceFile. Throw FileConflictError
   after exhaustion.

* fix(uploads): retry transient DirectUploadErrors at outer KB level

The KB outer retry only triggered on isNetworkError, missing
transient 5xx from S3/Azure (DirectUploadError code
DIRECT_UPLOAD_ERROR or MULTIPART_ERROR). Adds isTransientUploadError
and retries on it, restoring resilience for small-file presigned
PUTs against flaky cloud storage.

* fix(uploads): only retry transient 5xx, not deterministic 4xx

DirectUploadError now carries the HTTP status. isTransientUploadError
gates on 5xx so callers don't loop on 400/403/404 (e.g., malformed
request, expired signature). Multipart per-part retry also short-circuits
on 4xx — same reasoning.

* refactor(uploads): collapse getFileContentType into resolveFileType

The two helpers differed only in whether application/octet-stream
falls back to the extension map. Add an option flag to resolveFileType
and keep getFileContentType as a thin wrapper for direct-PUT callers
that need to preserve the exact browser-reported content-type.

* chore(uploads): trim verbose comments

Drop inline comments that restate code ("Use the full storageKey as fileName"),
collapse a multi-line block comment into a tighter TSDoc on the existence
check, and prune verbose vitest file headers — describe blocks already
document what's tested.

* fix(uploads): regenerate fileId per insert retry; require cloud storage for register

* fix(uploads): cap formdata fallback at 100MB; drop unused size param

* fix(uploads): abort multipart on get-part-urls failure; retry register on transient errors

* fix(uploads): drop vestigial size field from register contract

* fix(uploads): abort multipart on complete-fetch throw

* fix(uploads): set kb presignedEndpoint fallback; race-safe blob HEAD

* fix(uploads): include ?type=knowledge-base on kb presigned fallback

* fix(uploads): remove abort listener on xhr timeout

* fix(uploads): add timeout/abort to kb api fallback upload
2026-05-02 17:59:40 -07:00
Theodore Li 31cfb74dc2 feat(table): add workflow execution column type (#4338)
* Add table triggers for columns and row added

* Add async batching job for running column

* Add ui improvements, stop mechanism

* Use trigger dev for workflow runs

* Use unified column sidebar for table

* Add socket waits for tables, multi column workflow support

* change back to cell based trigger jobs

* Reuse code, add view log inline in table view

* reorganize db to treat each column separately

* adjust column naming strategy

* Column ui improvements

* fix live update on table

* Change new column behavior

* fixed errored workfows not showing as stopped

* Column sidebar improvements

* fix table column swapping behavior

* fix bugs

* add prompting, fix lint

* fix ui stuff

* flip feature flag

* Add zod contracts fix initial auto-run of columns

* ui improvements

* Use db filter to query unran rows

* Use live workflow run

* Change wording for deleting workflow column

* Update tools

* Add tool to run selected rows

* Add mothership tools

* adjust col width

* Restore ff

* fix drizzle migration

* fix test
2026-05-02 15:48:12 -04:00
Waleed 66bab935db fix(chat): close SSO auth bypass via checkSSOAccess body flag (#4408)
* fix(chat): close SSO auth bypass via checkSSOAccess body flag

- Remove checkSSOAccess short-circuit; SSO branch always validates via getSession()
- Skip chat_auth cookie issuance/validation for SSO deployments to prevent replay
- Split eligibility pre-flight into dedicated POST /api/chat/[identifier]/sso route
- Drop .passthrough() and checkSSOAccess from deployed chat contracts
- Add SSO branch test coverage in chat utils

* fix(chat): cast allowedEmails to string[] for SSO eligibility check

* fix(chat): close SSO GET cookie replay and add eligibility rate limit

- Skip chat_auth cookie validation for SSO in GET handler (replay vector for pre-fix cookies)
- Route SSO GET through getSession() instead of always returning auth_required_sso so post-IdP config fetch works
- Add per-IP rate limiting to /api/chat/[identifier]/sso to prevent allowlist enumeration
2026-05-02 11:16:25 -07:00
Vikhyath Mondreti b10b4479fe improvement(repo): update ship skills, flatten internal tools contracts dir (#4379)
* improvement(repo): update ship skills, flatten internal tools contracts dir

* update baselines

* address comments
2026-05-01 12:08:00 -07:00
Waleed 8b6307aea4 feat(gmail): add edit draft and update label tools (#4374)
* feat(gmail): add edit draft and update label tools

* fix(gmail): correct legacy block access list and docs heading for edit_draft_v2

* fix(gmail): use shared contract for edit-draft route

* regen docs

* fix(knowledge): inline reranker model list in description for doc generator

* resolve
2026-04-30 19:51:02 -07:00
Vikhyath Mondreti be9c959f1a improvement(types): enforce patterns outside just hooks directory and fix CI check + fix tracing billing issue (#4367)
* improvement(types): enforce outside just hooks dir and update CI checks

* fix billing account details for kb embeddings

* more fixes

* fix byok issue

* address comments

* fix

* more comments

* address bugbot
2026-04-30 19:37:51 -07:00
Vikhyath Mondreti b8959eb20d improvement(repo): zod based client-server boundary (#4355)
* improvement(repo): centralized zod contracts (#4336)

* improvement(repo): zod schema contracts

* type checks

* fix(notion): correctly register tool (#4337)

* fix func blokc

* more improvements

* fix tests

* type check

* remove v3 refs

* minor type improvements

* address comments

* update jira contract

* remove validateJsonBody

* improvement(repo): consolidation of boundary helpers + better unknown usage (#4352)

* improvement(repo): consolidation of boundary helpers + better unknown usage

* address comments

* improve file transfer error messaging

* fix docs listing schema drift

* fix inocrrect type casting

* address council comments

* remove prefix
2026-04-30 12:16:28 -07:00
5f0f0edd63 improvement(repo): separate realtime into separate app (#4262)
* improvement(repo): restructuring to make realtime image narrower scoped

* improvements

* chore(repo): rebase fixes and quality improvements for realtime split

Addresses merge-time issues and gaps from the realtime app split:
- Retarget stale vi.mock paths to @sim/workflow-persistence/subblocks
- Restore README branding, fix AGENTS.md script reference
- Restore TSDoc on workflow-persistence subblocks helpers
- Use toError() from @sim/utils/errors in save.ts
- Add vitest config + local mocks so @sim/audit tests run standalone
- Move socket.io-client to devDependencies in apps/realtime
- Add missing package COPY steps to docker/app.Dockerfile
- Add check:boundaries/check:realtime-prune scripts and wire into CI

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(security): consolidate crypto primitives into @sim/security

Move general-purpose crypto primitives out of apps/sim into the
@sim/security package so both apps/sim and apps/realtime can share them.

@sim/security exports (all pure, dependency-free):
  ./compare    safeCompare (constant-time HMAC-wrapped equality)
  ./encryption encrypt/decrypt (AES-256-GCM, iv:cipher:tag format)
  ./hash       sha256Hex
  ./tokens     generateSecureToken (base64url)

Migrate apps/sim call sites to use these + @sim/utils helpers:
  crypto.randomUUID()            -> generateId() from @sim/utils/id
  createHash('sha256').digest    -> sha256Hex
  timingSafeEqual on hashed hex  -> safeCompare
  new Promise(setTimeout)        -> sleep from @sim/utils/helpers

No behavior change: encryption format, digest output, and token
length are preserved exactly.

* refactor(copilot): use toError in remaining otel/finalize sites

Replace the last two `error instanceof Error ? error : new Error(String(error))`
patterns with toError from @sim/utils/errors. Completes the sweep of clean
candidates — no behavior change.

* refactor(security): consolidate HMAC-SHA256 primitives into @sim/security

Adds hmacSha256Hex and hmacSha256Base64 to @sim/security/hmac and migrates
15 webhook providers plus 5 other hot paths (deployment token signing,
outbound webhook requests, workspace notification delivery, notification
test route, Shopify OAuth callback) off bare `createHmac` calls. Secret
parameter accepts `string | Buffer` to cover base64-decoded Svix-style
secrets (Resend) and MS Teams' HMAC scheme. AWS SigV4 signing in S3 and
Textract tools intentionally retains direct `createHmac` usage — its
multi-step key derivation chain doesn't fit a generic helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(packages): post-audit test + packaging polish

- Add safeCompare unit tests (identity, length mismatch, hex-nibble diff).
- Add Buffer-secret cases to hmac tests to lock in Svix/MS-Teams contract.
- Declare `reactflow` as a peerDependency on @sim/workflow-types — only used for type imports.
- Add a barrel export to @sim/workflow-persistence for consumers that prefer package-level imports; subpath exports retained.
- Document the data-field invariant in load.ts for loop/parallel subflow patching.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(realtime): address PR review feedback

- Remove redundant SOCKET_PORT=3002 env from Dockerfile runner stage
  (env.PORT already defaults to 3002 via zod schema).
- Reorder PORT fallback so an explicitly-set SOCKET_PORT wins over
  the schema default for PORT; keeps SOCKET_PORT functional as an
  override instead of dead code.
- Add dedicated type-check CI step for @sim/realtime so TS errors
  surface pre-deploy (the Dockerfile runs source TS via Bun and has
  no implicit build-time type check).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(realtime): remove unused SOCKET_PORT env var

SOCKET_PORT has lived in the socket server since the June 2025 refactor
but was never actually set in any deploy config — docker-compose.prod,
helm values/templates, .env.example, and docs all use PORT or the 3002
default exclusively. No self-hoster was ever pointed at SOCKET_PORT, so
removing it is safe.

Simplifies realtime port resolution to `env.PORT` (zod-validated with a
3002 default) and drops the orphaned sim-side schema entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 23:06:16 -07:00
Siddharth Ganesan 0aeab026a8 feat(observability): add mothership tracing (#4253) 2026-04-22 09:06:01 -07:00
WaleedandTheodore Li 147ac89672 feat(docs): fill documentation gaps across platform features (#4110)
* feat(docs): fill documentation gaps across platform features

* fix(docs): address PR review comments on chat OTP cookies and MCP env var placeholders

* fix(docs): replace smart quotes with straight quotes in JSX attributes

* update(docs): update mcp, custom tools, and variables docs

* Fix grammar

* mothership docs, tags, connectors, api, chat deploy, etc

* more info

* more

* feat(docs): auto-generate per-provider trigger documentation

Extends scripts/generate-docs.ts to produce one MDX page per trigger
provider (39 pages) in apps/docs/content/docs/en/triggers/. The 5
hand-written pages (index, start, schedule, webhook, rss) are never
touched.

Key additions to the generation script:
- resolveConstVariable() resolves module-level const spreads so
  providers like Vercel that build outputs from const variables (not
  just functions) are fully documented
- resolveTriggerBuilderFunction() extended to expand variable spreads
  (...varName) in addition to function-call spreads (...fn())
- groupTriggersByProvider() deduplicates v1/v2 trigger variants by
  name, keeping the highest-versioned one per provider
- writeIconMapping() adds bare-name aliases for versioned block types
  (github_v2 → github, fireflies_v2 → fireflies, etc.) so
  BlockInfoCard resolves icons for all 39 trigger providers
- extractTriggerConfigFields() filters readOnly display blocks (webhook
  URL displays, sample payloads, curl examples) from config tables

Each generated page includes: BlockInfoCard with correct icon/color,
trigger count, polling note where applicable, Configuration table, and
Output table for every trigger. No "Type:" lines.

* refactor(docs): align trigger docs structure with tools docs

- Use ### `trigger_id` headings (matching ### `tool_id` in tools docs)
- Wrap all trigger sections under a ## Triggers header
- Rename Configuration/Output to #### level (matching #### Input/Output)
- Use Parameter column header to match tools docs table style
- Map UI widget types to semantic types: short-input/long-input/dropdown
  → string, switch → boolean, slider → number, oauth-input → string

* refactor(docs): use human-readable names for trigger section headings

Trigger IDs are internal identifiers; users scan by name. Switch from
### `trigger_id` to ### Trigger Name for cleaner sidebar navigation
and better readability.

* fix(docs): resolve subBlock builder functions for all trigger Config sections

Extends generate-docs.ts to parse subBlock builder functions so all 15
providers previously missing Configuration sections now generate them.

Handles three patterns:
- `buildTriggerSubBlocks({extraFields: buildX(...)})` — extracts extra
  fields from the call site and resolves them from the provider's utils.ts
- `return [...]` — direct array return (Attio, Confluence, etc.)
- `blocks.push(...)` — imperative push pattern (Linear, Ashby)

Also resolves const-reference field IDs (SCREAMING_CASE) by searching
the webhook provider constants cache, fixing Gong's `gongJwtPublicKeyPem`
field which was previously unresolvable. Adds title-as-description fallback
for OAuth credential fields that have no explicit description.

* fix(docs): correctly destructure nested implicit-object trigger outputs

Fixes a parser bug where output fields with no top-level `type` key but
child fields each having their own `type`/`description` were incorrectly
parsed. The `type:` and `description:` regex matches were not
depth-aware, so values from nested children bled into the parent field.

Changes:
- Add `isAtDepthZero()` helper for brace-depth-aware regex matching
- Fix `parseFieldContent` to only match `type:` at brace depth 0
- Fix `extractDescription` to only match `description:` at brace depth 0
- Add implicit-object fallback: when no top-level `type` exists but child
  fields have their own types, treat as `object` with `properties`
- Regenerate all affected trigger docs (Cal.com payload, Linear data,
  Jira issue.fields, Ashby application, Greenhouse candidate, etc.)

* chore(docs): update static trigger and start page images

* feat(providers): add claude-opus-4-7 model with adaptive thinking support

* Add workflow version screenshots

* Add function block screenshots

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-16 11:51:49 -07:00
0abcc6e813 improvement(mothership): restructured stream, tool structures, code typing, file write/patch/append tools, timing issues (#4090)
* fix build error

* improvement(mothership): new agent loop (#3920)

* feat(transport): replace shared chat transport with mothership-stream module

* improvement(contracts): regenerate contracts from go

* feat(tools): add tool catalog codegen from go tool contracts

* feat(tools): add tool-executor dispatch framework for sim side tool routing

* feat(orchestrator): rewrite tool dispatch with catalog-driven executor and simplified resume loop

* feat(orchestrator): checkpoint resume flow

* refactor(copilot): consolidate orchestrator into request/ layer

* refactor(mothership): reorganize lib/copilot into structured subdirectories

* refactor(mothership): canonical transcript layer, dead code cleanup, type consolidation

* refactor(mothership): rebase onto latest staging

* refactor(mothership): rename request continue to lifecycle

* feat(trace): add initial version of request traces

* improvement(stream): batch stream from redis

* fix(resume): fix the resume checkpoint

* fix(resume): fix resume client tool

* fix(subagents): subagent resume should join on existing subagent text block

* improvement(reconnect): harden reconnect logic

* fix(superagent): fix superagent integration tools

* improvement(stream): improve stream perf

* Rebase with origin dev

* fix(tests): fix failing test

* fix(build): fix type errors

* fix(build): fix build errors

* fix(build): fix type errors

* feat(mothership): add cli execution

* fix(mothership): fix function execute tests

* Force redeploy

* feat(motheship): add docx support

* feat(mothership): append

* Add deps

* improvement(mothership): docs

* File types

* Add client retry logic

* Fix stream reconnect

* Eager tool streaming

* Fix client side tools

* Security

* Fix shell var injection

* Remove auto injected tasks

* Fix 10mb tool response limit

* Fix trailing leak

* Remove dead tools

* file/folder tools

* Folder tools

* Hide function code inline

* Dont show internal tool result reads

* Fix spacing

* Auth vfs

* Empty folders should show in vfs

* Fix run workflow

* change to node runtime

* revert back to bun runtime

* Fix

* Appends

* Remove debug logs

* Patch

* Fix patch tool

* Temp

* Checkpoint

* File writes

* Fix

* Remove tool truncation limits

* Bad hook

* replace react markdown with streamdown

* Checkpoitn

* fix code block

* fix stream persistence

* temp

* Fix file tools

* tool joining

* cleanup subagent + streaming issues

* streamed text change

* Tool display intetns

* Fix dev

* Fix tests

* Fix dev

* Speed up dev ci

* Add req id

* Fix persistence

* Tool call names

* fix payload accesses

* Fix name

* fix snapshot crash bug

* fix

* Fix

* remove worker code

* Clickable resources

* Options ordering

* Folder vfs

* Restore and mass delete tools

* Fix

* lint

* Update request tracing and skills and handlers

* Fix editable

* fix type error

* Html code

* fix(chat): make inline code inherit parent font size in markdown headers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improved autolayout

* durable stream for files

* one more fix

* POSSIBLE BREAKAGE: SCROLLING

* Fixes

* Fixes

* Lint fix

* fix(resource): fix resource view disappearing on ats (#4103)

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

* Fixes

* feat(mothership): add execution logs as a resource type

Adds `log` as a first-class mothership resource type so copilot can open
and display workflow execution logs as tabs alongside workflows, tables,
files, and knowledge bases.

- Add `log` to MothershipResourceType, all Zod enums, and VALID_RESOURCE_TYPES
- Register log in RESOURCE_REGISTRY (Library icon) and RESOURCE_INVALIDATORS
- Add EmbeddedLog and EmbeddedLogActions components in resource-content
- Export WorkflowOutputSection from log-details for reuse in EmbeddedLog
- Add log resolution branch in open_resource handler via new getLogById service
- Include log id in get_workflow_logs response and extract resources from output
- Exclude log from manual add-resource dropdown (enters via copilot tools only)
- Regenerate copilot contracts after adding log to open_resource Go enum

* Fix perf and message queueing

* Fix abort

* fix(ui): dont delete resource on clearing from context, set resource closed on new task (#4113)

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

* improvement(mothership): structure sim side typing

* address comments

* reactive text editor tweaks

* Fix file read and tool call name persistence bug

* Fix code stream + create file opening resource

* fix use chat race + headless trace issues

* Fix type issue

* Fix mothership block req lifecycle

* Fix build

* Move copy reqid

* Fix

* fix(ui): fix resource tag transition from home to task (#4132)

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

* Fix persistence

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Waleed Latif <walif6@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
2026-04-13 16:46:35 -07:00
Emir Karabegandwaleed c1d788ce94 improvement(integrations, models): ui/ux (#4105)
* improvement(integrations, models): ui/ux

* fix(models, integrations): dedup ChevronArrow/provider colors, fix UTC date rendering

- Extract PROVIDER_COLORS and getProviderColor to model-colors.ts to eliminate
  identical definitions in model-comparison-charts and model-timeline-chart
- Remove duplicate private ChevronArrow from integration-card; import the
  exported one from model-primitives instead
- Add timeZone: 'UTC' to formatShortDate so ISO date-only strings (parsed as
  UTC midnight) render the correct calendar day in all timezones

* refactor(models): rename model-colors.ts to consts.ts

* improvement(models): derive provider colors/resellers from definitions, reorient FAQs to agent builder

Dynamic data:
- Add `color` and `isReseller` fields to ProviderDefinition interface
- Move brand colors for all 10 providers into their definitions
- Mark 6 reseller providers (Azure, Bedrock, Vertex, OpenRouter, Fireworks)
- consts.ts now derives color map from MODEL_CATALOG_PROVIDERS
- model-comparison-charts derives RESELLER_PROVIDERS from catalog
- Fix deepseek name: Deepseek → DeepSeek; remove now-redundant
  PROVIDER_NAME_OVERRIDES and getProviderDisplayName from utils
- Add color/isReseller fields to CatalogProvider; clean up duplicate
  providerDisplayName in searchText array

FAQs:
- Replace all 4 main-page FAQs with 5 agent-builder-oriented ones
  covering model selection, context windows, pricing, tool use, and
  how to use models in a Sim agent workflow
- buildProviderFaqs: add conditional tool use FAQ per provider
- buildModelFaqs: add bestFor FAQ (conditional on field presence);
  improve context window answer to explain agent implications;
  tighten capabilities answer wording

* chore(models): remove model-colors.ts (superseded by consts.ts)

* update footer

---------

Co-authored-by: waleed <walif6@gmail.com>
2026-04-10 20:46:44 -07:00
Vikhyath Mondreti efb582e96a feat(voice): voice input migration to eleven labs (#4041)
* feat(speech): unified voice interface

* add metering for voice input usage

* ip key

* use shared getclientip helper, fix deployed chat

* cleanup code

* prep merge

* merge staging in

* add billing check

* add voice input section

* remove skip billing

* address comments
2026-04-08 01:01:51 -07:00
Waleed 762fbbd3e2 fix(docs): resolve missing tool outputs for spread-inherited V2 tools (#4020)
* fix(docs): resolve missing tool outputs for spread-inherited V2 tools

* fix(docs): add word boundary to baseToolRegex to prevent false matches

* fix(docs): remove unnecessary case-insensitive flag from baseToolRegex
2026-04-07 12:41:23 -07:00
WaleedandClaude Opus 4.6 68df7320bd refactor(triggers): consolidate v2 Linear triggers into same files as v1 (#4010)
* refactor(triggers): consolidate v2 Linear triggers into same files as v1

Move v2 trigger exports from separate _v2.ts files into their
corresponding v1 files, matching the block v2 convention where
LinearV2Block lives alongside LinearBlock in the same file.

* updated

* fix: restore staging registry entries accidentally removed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs

* fix: restore integrations.json to staging version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(generate-docs): extract all trigger configs from multi-export files

The buildTriggerRegistry function used a single regex exec per file,
which only captured the first TriggerConfig export. Files that export
both v1 and v2 triggers (consolidated same-file convention) had their
v2 triggers silently dropped from integrations.json.

Split each file into segments per export and parse each independently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore staging linear handler and utils with teamId support

Restores the staging version of linear provider handler and trigger
utils that were accidentally regressed. Key restorations:
- teamId sub-block and allPublicTeams fallback in createSubscription
- Timestamp skew validation in verifyAuth
- actorType renaming in formatInput (avoids TriggerOutput collision)
- url field in formatInput and all output builders
- edited field in comment outputs
- externalId validation after webhook creation
- isLinearEventMatch returns false (not true) for unknown triggers

Adds extractIdempotencyId to the linear provider handler for webhook
deduplication support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore non-Linear files accidentally modified

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove redundant extractIdempotencyId from linear handler

The idempotency service already uses the Linear-Delivery header
(which Linear always sends) as the primary dedup key. The body-based
fallback was unnecessary defensive code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* idempotency

* tets

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 22:13:48 -07:00
WaleedandClaude Opus 4.6 e5aef6184a feat(profound): add Profound AI visibility and analytics integration (#3849)
* feat(profound): add Profound AI visibility and analytics integration

* fix(profound): fix import ordering and JSON formatting for CI lint

* fix(profound): gate metrics mapping on current operation to prevent stale overrides

* fix(profound): guard JSON.parse on filters, fix offset=0 falsy check, remove duplicate prompt_answers in FILTER_OPS

* lint

* fix(docs): fix import ordering and trailing newline for docs lint

* fix(scripts): sort generated imports to match Biome's organizeImports order

* fix(profound): use != null checks for limit param across all tools

* fix(profound): flatten block output type to 'json' to pass block validation test

* fix(profound): remove invalid 'required' field from block inputs (not part of ParamConfig)

* fix(profound): rename tool files from kebab-case to snake_case for docs generator compatibility

* lint

* fix(docs): let biome auto-fix import order, revert custom sort in generator

* fix(landing): fix import order in sim icon-mapping via biome

* fix(scripts): match Biome's exact import sort order in docs generator

* fix(generate-docs): produce Biome-compatible JSON output

The generator wrote multi-line arrays for short string arrays (like tags)
and omitted trailing newlines, causing Biome format check failures in CI.
Post-process integrations.json to collapse short arrays onto single lines
and add trailing newlines to both integrations.json and meta.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 16:30:06 -07:00
be6b00d95f feat(ui): add request a demo modal (#3766)
* fix(ui): add request a demo modal

* Remove dead code

* Remove footer modal

* Address greptile comments

* Sanatize CRLF characters from emails

* extract shared email header safety regex

Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>

* Use pricing CTA action for demo modal

Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>

* fix demo request import ordering

Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>

* merge staging and fix hubspot list formatting

Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>

* fix(generate-docs): fix tool description extraction and simplify script

- Fix endsWith over-matching: basename === 'index.ts'/'types.ts' instead
  of endsWith(), which was silently skipping valid tool files like
  list_leave_types.ts, delete_index.ts, etc.
- Add extractSwitchCaseToolMapping() to resolve op ID → tool ID mismatches
  where block switch statements map differently (e.g. HubSpot get_carts →
  hubspot_list_carts)
- Fix double fs.readFileSync in writeIntegrationsJson — reuse existing
  fileContent variable instead of re-reading the file
- Remove 5 dead functions superseded by *FromContent variants
- Simplify extractToolsAccessFromContent to use matchAll
- fix(upstash): replace template literal tool ID with explicit switch cases

* fix(generate-docs): restore extractIconName by aliasing to extractIconNameFromContent

* restore

* fix(demo-modal): reset form on open to prevent stale success state on reopen

* undo hardcoded ff

* fix(upstash): throw on unknown operation instead of silently falling back to get

---------

Co-authored-by: Theodore Li <teddy@zenobiapay.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Theodore Li <TheodoreSpeaks@users.noreply.github.com>
Co-authored-by: waleed <walif6@gmail.com>
2026-03-25 15:30:36 -07:00