Commit Graph

1497 Commits

Author SHA1 Message Date
Peter Wielander d5bc418816 [web] [world-vercel] Ensure user-passed run IDs are URL encoded and call out self-hosted security (#1322) 2026-03-11 09:04:01 -07:00
Vercel Release Bot 5f4395de7b Version Packages (beta) (#1306) 2026-03-10 19:25:18 -07:00
Nathan Rajlich 54758f30ab Remove hard-coded VERCEL_DEPLOYMENT_KEY from nextjs-turbopack workbench (#1319)
Vercel now automatically sets VERCEL_DEPLOYMENT_KEY to a unique value
for every deployment, so the hard-coded value is no longer necessary.
2026-03-11 01:42:51 +00:00
Nathan Rajlich aa2f581b48 fix(web): move react-router deps to devDependencies (#1265)
* fix(web): move react-router deps to devDependencies

Add a custom entry.server.tsx (based on the default react-router template)
so that the @react-router/dev build plugin no longer requires
@react-router/node and isbot to be in the dependencies field of
package.json. This allows all react-router related packages to be
devDependencies since they are fully bundled at build time, leaving
express as the sole production dependency.

* Update packages/web/app/entry.server.tsx

Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Nathan Rajlich <n@n8.io>

---------

Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
2026-03-11 01:42:40 +00:00
Nathan Rajlich b28a5fd0be fix(ai): use workspace:* for workflow peer dependency (#1320)
* fix(ai): use workspace:* for workflow peer dependency

The hardcoded version range in peerDependencies would fall out of sync
with the actual workflow package version, causing changeset warnings.
Using workspace:* lets pnpm resolve the correct version at publish time.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Nathan Rajlich <n@n8.io>

* add changeset for @workflow/ai peer dependency fix

---------

Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 01:39:40 +00:00
Nathan Rajlich 854a25f910 fix(core): pass resolved deploymentId to getEncryptionKeyForRun in start() (#1318)
The start() function resolved deploymentId from world.getDeploymentId() but
then passed the original opts (without the resolved value) to
getEncryptionKeyForRun. In external contexts (e2e tests, CLI) where
deploymentId is inferred from the environment rather than explicitly provided,
this caused getEncryptionKeyForRun to receive undefined deploymentId and
silently skip encryption for the initial workflow input.
2026-03-10 23:10:55 +00:00
Nathan Rajlich d842ce1c43 fix: surface 429 rate-limit errors in e2e tests and CLI (#1309)
Multiple layered issues caused encryption key 429 errors to produce
confusing, unrelated-looking test failures:

- awaitCommand() in e2e utils now rejects on non-zero exit codes
  instead of silently resolving (the most critical fix)
- cliInspectJson/cliHealthJson throw on empty stdout instead of
  falling back to '{}'
- maybeDecryptFields re-throws HTTP errors instead of silently
  falling back to encrypted placeholders
- fetchRunKey includes response body and status text in errors
- Added 429 to CLI status text map
2026-03-10 00:23:05 -07:00
Nathan Rajlich 4a6ddd82c0 fix(world-local): return HTTP 200 instead of 503 for queue timeout re-enqueue signals (#1307)
* fix(world-local): return HTTP 200 instead of 503 for queue timeout re-enqueue signals

The local queue used HTTP 503 (Service Unavailable) as an internal signal
for re-enqueueing after a timeout delay. This is misleading since 503 is
an error status, causing confusion when inspecting logs or network traffic.

Changed to return 200 with timeoutSeconds in the body, and updated the
consumer to detect the re-enqueue signal from the response body instead
of the status code.

* fix(world-local): handle timeoutSeconds: 0 and add queue re-enqueue tests

Address review feedback: timeoutSeconds === 0 should trigger an immediate
re-enqueue without delay, not be treated as a normal success. Also adds
test coverage for the handler response codes and re-enqueue behavior.
2026-03-10 06:29:18 +00:00
Peter Wielander 887cc2bd55 [web-shared] [cli] Refactor observability data fetching (#1261) 2026-03-10 01:36:03 +00:00
Karthik Kalyan 33101a2292 Move decrypt button to the sidepanel (#1308) 2026-03-09 15:55:43 -07:00
Nathan Rajlich 9781afb490 test: enable encryption on nextjs-turbopack workbench (#1262)
* test: enable encryption on nextjs-turbopack workbench

Set VERCEL_DEPLOYMENT_KEY to enable e2e encryption for the
nextjs-turbopack workbench project. This allows the e2e test
suite to exercise encrypted workflow runs alongside existing
unencrypted runs for backwards compatibility testing.

* fix: fetch encryption key from API in external contexts (e2e, CLI)

Use VERCEL=1 env var to distinguish serverless runtime (can use local
HKDF derivation) from external contexts like e2e test runners, CLI,
and web dashboard (must fetch derived key from Vercel API).

Previously, when VERCEL_DEPLOYMENT_ID matched but VERCEL_DEPLOYMENT_KEY
was absent (e.g., e2e runner has the deployment ID from CI but not the
key), getEncryptionKeyForRun returned undefined — silently skipping
decryption. Now external contexts always use the API fetch path.

* fix: add 429 retry with exponential backoff to fetchRunKey

The e2e runner calls fetchRunKey for every run, which can exceed the
API rate limit (30/min per team) when many tests run in parallel.
Add retry logic with exponential backoff + jitter (up to 5 retries)
and respect the Retry-After header when present.

* Fix: Missing NaN guard on `retry-after` header parsing causes `setTimeout(r, NaN)` to resolve immediately (0ms), triggering rapid-fire retries against a rate-limited API endpoint.

This commit fixes the issue reported at packages/world-vercel/src/encryption.ts:138

**Bug Analysis:**

In `packages/world-vercel/src/encryption.ts`, the `fetchRunKey` function handles HTTP 429 (rate limited) responses by reading the `retry-after` header and computing a delay. The original code was:

```js
const retryAfter = response.headers.get('retry-after');
const delay = retryAfter
  ? Number.parseInt(retryAfter, 10) * 1000
  : BASE_DELAY_MS * 2 ** attempt + Math.random() * 500;
```

Per RFC 9110, the `Retry-After` header can contain either a number of seconds OR an HTTP date string (e.g., `"Wed, 21 Oct 2015 07:28:00 GMT"`). When the header contains a date string or any non-numeric value, `Number.parseInt(retryAfter, 10)` returns `NaN`. Since the header string is truthy (non-empty), the ternary takes the parseInt path rather than the exponential backoff fallback. `NaN * 1000` evaluates to `NaN`, and `setTimeout(r, NaN)` resolves immediately (~0ms, confirmed by testing). This causes rapid-fire retries (up to 5 iterations with zero delay) against a rate-limited API endpoint, wasting resources and potentially worsening the rate-limiting situation.

**Fix:**

Parse the integer first, then check if the result is NaN before using it. If NaN, fall back to exponential backoff with jitter. This matches the established pattern already used in `packages/world-vercel/src/utils.ts` (lines 309-315) where the same header is properly guarded with `!Number.isNaN(parsed)`.

```js
const retryAfterHeader = response.headers.get('retry-after');
const parsedRetryAfter = retryAfterHeader
  ? Number.parseInt(retryAfterHeader, 10)
  : NaN;
const delay = !Number.isNaN(parsedRetryAfter)
  ? parsedRetryAfter * 1000
  : BASE_DELAY_MS * 2 ** attempt + Math.random() * 500;
```

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: TooTallNate <n@n8.io>

* fix: add --decrypt to e2e CLI inspect calls

With encryption enabled on the nextjs-turbopack workbench, CLI inspect
output shows encrypted data as placeholders. The e2e tests check for
actual output values, so they need --decrypt to see the real data.

* fix: remove manual 429 retry, rely on undici RetryAgent; add changeset

The shared RetryAgent from getDispatcher() already handles 429/5xx
retries with exponential backoff and Retry-After header support.
The manual retry loop in fetchRunKey was redundant.

Also add changeset for the VERCEL=1 external context fix.

* refactor: use @ts-expect-error on dispatcher instead of as any cast

---------

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
2026-03-09 14:00:25 -07:00
Nathan Rajlich 83dbd46456 Stop reading WORKFLOW_VERCEL_* env vars at runtime to prevent unintended proxy routing (#1304)
* Stop reading WORKFLOW_VERCEL_* env vars at runtime to prevent unintended proxy routing

createWorld() in core no longer reads WORKFLOW_VERCEL_PROJECT, WORKFLOW_VERCEL_TEAM,
WORKFLOW_VERCEL_AUTH_TOKEN, WORKFLOW_VERCEL_ENV, or WORKFLOW_VERCEL_PROJECT_NAME from
process.env. These env vars are intended for CLI/observability tooling only, and
reading them at runtime caused all workflow traffic to route through the
api.vercel.com proxy when users mistakenly set them as Vercel project env vars.

The Vercel runtime already provides everything needed: OIDC tokens for auth,
VERCEL_PROJECT_ID for encryption context, and VERCEL_DEPLOYMENT_ID for world
selection. createWorld() now calls createVercelWorld() with no config.

A warning is emitted if the env vars are detected at runtime, telling users to
remove them.

The CLI and e2e tests are updated to call createVercelWorld() directly with an
explicit config object and inject via setWorld(), keeping the WORKFLOW_VERCEL_*
env vars scoped to tooling contexts only.

* Refactor inferVercelEnvVars to return config instead of relying on process.env

inferVercelEnvVars() now returns a VercelEnvVars object with the resolved
config values. setup.ts uses the returned object directly for
createVercelWorld() instead of re-reading from process.env via getEnvVars().

The writeEnvVars() calls inside inferVercelEnvVars are consolidated into a
single call at the end, retained only for the embedded web UI which reads
process.env as a fallback in its server actions. The scattered writeEnvVars
calls after each inference step are removed.

* Address review: consistent e2e gate and expanded warning

- Use WORKFLOW_VERCEL_ENV as the gate in both e2e.test.ts and
  bench.bench.ts for consistency (was WORKFLOW_VERCEL_AUTH_TOKEN in
  e2e.test.ts, WORKFLOW_VERCEL_ENV in bench.bench.ts)
- Expand the misconfiguration warning to also detect
  WORKFLOW_VERCEL_AUTH_TOKEN and WORKFLOW_VERCEL_ENV, listing the
  specific env vars that are set

* Add changeset

Signed-off-by: Nathan Rajlich <n@n8.io>

---------

Signed-off-by: Nathan Rajlich <n@n8.io>
2026-03-09 20:46:19 +00:00
Karthik Kalyan c59dd8c571 [web-shared] enrich sleep detail panel and fix attribute capitalization (#1305)
* Fix side panel

* add changeset

* Capitalization fix
2026-03-09 13:04:13 -07:00
Vercel Release Bot 0063180d65 Version Packages (beta) (#1291)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-07 20:33:25 +00:00
Pranay Prakash c71befe8ec fix(core): premature suspension when hooks have buffered payloads with concurrent pending entities (#1294)
* test: reproduce hook+sleep promiseQueue regression

Add failing tests that reproduce a regression from #1246 where the
sleep's WorkflowSuspension fires before all hook payloads are delivered
when a hook and sleep run concurrently.

Root cause: when the null event fires, the sleep queues a suspension
through promiseQueue. After the first hook payload resolves, subsequent
hook payload resolutions are queued AFTER the sleep suspension, causing
the workflow to terminate prematurely via Promise.race.

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

* test: expand tests to isolate bug to hook + pending entity pattern

Add control tests proving sequential steps are NOT affected by the
promiseQueue regression, isolating the bug to hooks specifically:

Failing (hook-based):
- hook + sleep: all 3 payloads → step invocation
- hook + sleep: 2 payloads → return
- hook + incomplete step: 2 payloads → return

Passing (step-based controls):
- sleep + sequential steps: both step events exist
- sleep + sequential steps: only 1st step completed
- incomplete step + sequential steps: all step events exist
- hook only (no concurrent entity): payloads + step

The bug is: any entity that queues a suspension through promiseQueue
at null-event time preempts hook payload delivery, because hooks
buffer payloads in payloadsQueue and only resolve them one-at-a-time
as the workflow code iterates. Steps are unaffected because each step
has its own events consumed before null fires.

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

* fix(core): move WorkflowSuspension calls back to setTimeout(0) from promiseQueue

The promiseQueue refactor (#1246) moved ALL promise resolutions through
the queue, including WorkflowSuspension calls. This caused premature
termination when hooks had buffered payloads and a concurrent entity
(sleep or incomplete step) was pending:

1. Null event fires → all subscribers run
2. Sleep/step queues WorkflowSuspension via promiseQueue.then()
3. Hook's next payload resolution is also queued via promiseQueue.then()
4. Suspension fires first → Promise.race terminates workflow
5. Hook payload never delivered → infinite retry loop

Fix: move WorkflowSuspension calls back to setTimeout(0) (macrotask).
Suspensions fire AFTER all microtask-based deliveries (promiseQueue
resolve/reject for step results and hook payloads) have completed.
Non-suspension resolve/reject calls remain on promiseQueue for
deterministic ordering of data delivery.

Affected paths:
- step.ts: null event handler
- sleep.ts: null event handler
- hook.ts: null event handler, eventLogEmpty suspension, dispose suspension

* fix(core): use pendingDeliveries counter + scheduleWhenIdle for suspensions

Replace all nested setTimeout/promiseQueue suspension patterns with a
clean idle-polling mechanism:

1. pendingDeliveries counter: incremented before async hydration (step
   results, hook payloads), decremented in finally block after delivery
2. scheduleWhenIdle(ctx, fn): polls via setTimeout(0) → check counter →
   if > 0, wait for promiseQueue.then() → repeat. Only fires fn when
   pendingDeliveries reaches 0.

This correctly handles:
- Sync deserialization (no encryption): counter is 0, fires immediately
  after first setTimeout(0)
- Async deserialization (with encryption): waits for decryption to
  complete before firing
- Multi-round hook payload delivery: each createHookPromise() call
  increments the counter, preventing premature suspension between
  delivery rounds

Also adds payloadsQueue.length check to hook null handler — don't
trigger suspension if buffered payloads can satisfy pending awaits.

Tests now run in both sync and async modes (14 total = 7 scenarios x 2).
All 164 tests pass.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
2026-03-07 11:02:08 -08:00
Peter Wielander 5e4ef657cf [web] Refactor event list view data fetching (#1288) 2026-03-06 15:50:47 -08:00
Peter Wielander d8daa2a9a9 [world] Add method to get individual events (#1287) 2026-03-06 14:35:12 -08:00
Nathan Rajlich 36a901d2d2 feat: expose workflow and step names in metadata functions (#1285)
* feat: expose workflow and step names in metadata functions

Add `workflowName` to `WorkflowMetadata` and `stepName` to `StepMetadata`,
making them available via `getWorkflowMetadata()` and `getStepMetadata()`.
This is useful for adding context to structured logs, collating logs during
investigations, and bucketing errors by workflow/step name.

Closes #1103

* add changeset

* Apply suggestion from @TooTallNate

Signed-off-by: Nathan Rajlich <n@n8.io>

---------

Signed-off-by: Nathan Rajlich <n@n8.io>
2026-03-06 21:51:16 +00:00
Vercel Release Bot 58afeb25c2 Version Packages (beta) (#1284)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
workflow@4.2.0-beta.66
2026-03-06 13:46:57 -08:00
Pranay Prakash 8b5a388a94 Support timeoutSeconds: 0 for immediate re-enqueue (#1290)
Previously, immediate re-enqueues used `timeoutSeconds: 1` as an
arbitrary minimum delay. This changes the convention so that
`timeoutSeconds: 0` signals an immediate re-enqueue:

- world-local: fix truthiness check so 0 triggers a 503 response
- world-vercel: omit `delaySeconds` from queue options when 0
- core: use `timeoutSeconds: 0` for hook conflict re-enqueues

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 13:18:15 -08:00
Pranay Prakash 15e6bc008c Fix docs typechecker to resolve workflow module types (#1271)
* Fix docs typechecker to actually resolve workflow module types

The docs typechecker was silently failing to resolve `workflow` imports
because:
1. repoRoot was computed 4 levels up instead of 3
2. No path mappings for workspace packages
3. Missing TS lib files for disposable and DOM iterables

This meant all imported symbols became `any` and type errors in docs
code samples went undetected.

Fixes:
- Fix repoRoot: '../../../..' -> '../../..'
- Add path mappings for workflow, @workflow/core, @workflow/ai, etc.
- Add lib.dom.iterable.d.ts and lib.esnext.disposable.d.ts
- Fix regex that ate first line of some code blocks
- Auto-skip error demonstration code blocks
- Add respondWith to global Request for webhook doc patterns

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

* Fix docs typecheck failures against updated base branch

- Skip incomplete code samples (with `...`/`/* ... */`) from type checking
- Fix sleep("1 month") → sleep("30d") (valid ms duration)
- Fix run.result() → run.returnValue (correct Run API)
- Add skip-typecheck for docs with intentionally simplified types

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

* Fix doc code samples to typecheck instead of skipping them

- hook-conflict.mdx: use error.message.includes() instead of non-existent error.slug
- get-hook-by-token.mdx: cast hook.metadata to expected shape
- ai/index.mdx: fix tool inputSchema to match execute function params
- get-writable.mdx: use ModelMessage[], spread result.messages, use model placeholder
- Add myModel global placeholder for docs examples

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

* Narrow TS2307 ignore to only non-mapped modules

Instead of blanket-ignoring all "Cannot find module" errors (TS2307),
only ignore them for modules not in our explicit paths mappings.
This ensures workspace package imports (workflow, @workflow/core, etc.)
are validated while still allowing relative imports and framework deps.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 11:45:40 -08:00
Pranay Prakash aadce97859 Add queue, event, push, subscribe keywords to workflow skill (#1286)
* feat: update SKILL.md keywords for better context matching

Add "queue, event, push, subscribe" to description triggers

Slack-Thread: https://vercel.slack.com/archives/C0ACY4D798Q/p1772785994953489?thread_ts=1772785994.953489&cid=C0ACY4D798Q
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

* feat: update skill version to 1.3 with new trigger keywords

Add trigger keywords: queue, event, push, subscribe.

Slack-Thread: https://vercel.slack.com/archives/C0ACY4D798Q/p1772785994953489?thread_ts=1772785994.953489&cid=C0ACY4D798Q
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

---------

Co-authored-by: v0 <v0[bot]@users.noreply.github.com>
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
2026-03-06 11:40:10 -08:00
Peter Wielander dff00c9400 [core] Skip port inference in vercel environment (#1283) 2026-03-05 17:08:42 -08:00
Vercel Release Bot 596d105323 Version Packages (beta) (#1279) workflow@4.2.0-beta.65 2026-03-06 00:20:52 +00:00
Peter Wielander 456c1aa455 Add vitest plugin for testing full workflows without setting up a server (#1237) 2026-03-05 16:08:48 -08:00
Nathan Rajlich 97932d3086 fix: thread runId through stream inspection for encryption key resolution (#1277)
CLI showStream:
- Requires --run with --decrypt for encrypted stream decryption
- Warns when --decrypt is used without --run

Web stream reading:
- readStreamServerAction accepts runId parameter for key resolution
- Stream API route reads runId from query param
- readStream client function passes runId to the API route
- useStreamReader hook accepts and passes runId
- run-detail-view passes runId to useStreamReader

Removes getRunIdFromStreamId helper (stream IDs don't always share
the run's ULID, e.g. streams serialized across step/workflow boundaries).
2026-03-05 23:36:36 +00:00
Peter Wielander 1eaff36f19 [web] Don't resolve data on initial run entity fetch (#1278) 2026-03-05 22:02:59 +00:00
Peter Wielander 11dcb646d3 [world-local] [world-postgres] [cli] Validate local run ID and quiet dotenv logs (#1273) 2026-03-05 21:35:23 +00:00
Vercel Release Bot 597afa5174 Version Packages (beta) (#1250)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
workflow@4.2.0-beta.64
2026-03-05 13:01:55 -08:00
Nathan Rajlich 02f706fb99 fix(world-local, world-postgres): default hooks.list() sort order to ascending (#1275)
hooks.list() in world-local and world-postgres defaulted to descending
order, while world-vercel defaults to ascending (creation order). This
caused the webhookWorkflow e2e test to deadlock: hooks were returned
newest-first, so the test sent the first webhook request to the manual-
response hook instead of the default-response hook.

- world-local: default sortOrder to 'asc', fix getCreatedAt returning
  new Date(0) which broke ascending cursor pagination
- world-postgres: respect the sortOrder param (was hardcoded to desc)
  and default to 'asc'
2026-03-05 20:14:33 +00:00
Pranay Prakash adfe8b6b11 Add isWebhook flag to prevent hooks from being resumed via public webhook endpoint (#1270)
* Add isWebhook flag to prevent hooks from being resumed via public webhook endpoint

Hooks created with createHook() are now non-resumable via the public webhook
endpoint by default (isWebhook=false). Only hooks created with createWebhook()
set isWebhook=true, allowing them to be resumed via the public URL.

Also adds HookNotFoundError thrown by all world backends when a webhook
token doesn't match any hook, and an e2e test for the new behavior.

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

* Fix world-local: default isWebhook to false and fix test assertions

- Default isWebhook to false at write time in events-storage
- Default isWebhook to false at read time in hooks-storage (for old data)
- Update test assertions to match HookNotFoundError message

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

* Fix: default isWebhook to true for backwards compat, add postgres migration

- Revert read-side default to `isWebhook ?? true` in world-local for
  backwards compatibility with existing hooks that predate the field
- Add postgres migration 0009 to add `is_webhook` column with default true
- Update drizzle schema to match

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 11:04:53 -08:00
Pranay Prakash a7ae7e9a61 Strengthen deterministic VM seed with additional run metadata (#1269)
* Strengthen deterministic VM seed with additional run metadata

Derive the PRNG seed from runId, workflowName, and startedAt instead of
runId alone. This makes the seed harder to predict from any single
piece of publicly exposed run information.

* fix(core): update hardcoded correlation IDs to match strengthened VM seed

---------

Co-authored-by: Nathan Rajlich <n@n8.io>
2026-03-05 11:03:28 -08:00
Pranay Prakash 1926296c67 Manually bump core packages to 4.2.x (#1272) 2026-03-05 10:48:58 -08:00
Pranay Prakash 30e24d441e Merge commit from fork
* Prevent deterministic tokens in createWebhook

Remove the `token` option from `WebhookOptions` to prevent unauthorized
access to the public webhook endpoint. Webhook tokens are now always
randomly generated. Deterministic tokens remain available for
`createHook()` with server-side `resumeHook()`.

**BREAKING CHANGE**: `createWebhook()` no longer accepts a `token` option.

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

* Address PR feedback: throw on token, revert unnecessary doc changes, replace examples

- createWebhook() now throws an error if token is passed at runtime
  instead of silently stripping it
- Revert unnecessary changes to create-hook.mdx (only keep
  slack_webhook -> slack_messages prefix change)
- Replace Slack webhook examples in create-webhook.mdx and hooks.mdx
  with generic event collector examples that make sense without
  deterministic tokens

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

* Address andresriancho's feedback: stricter token check, add unit test

- Use `token !== undefined` instead of `if (token)` for correctness
  (empty string should also be rejected)
- Add unit test verifying createWebhook({ token: "anything" }) throws
  the expected error message

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

* change changeset to minor

* Fix docs typechecker to actually resolve workflow module types

The docs typechecker was silently failing to resolve `workflow` imports
because:
1. repoRoot was computed 4 levels up instead of 3
2. No path mappings for workspace packages
3. Missing TS lib files for disposable and DOM iterables

This meant all imported symbols became `any` and type errors in docs
code samples went undetected.

Fixes:
- Fix repoRoot: '../../../..' -> '../../..'
- Add path mappings for workflow, @workflow/core, @workflow/ai, etc.
- Add lib.dom.iterable.d.ts and lib.esnext.disposable.d.ts
- Fix regex that ate first line of some code blocks
- Auto-skip error demonstration code blocks
- Add respondWith to global Request for webhook doc patterns

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

* Revert "Fix docs typechecker to actually resolve workflow module types"

This reverts commit 02d42ac866e6db1b6d016339739344cdd9538e90.

* chore: trigger CI

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 10:33:53 -08:00
Nathan Rajlich b68ed630ec fix(cli): read orgId from project entry in repo.json, not root (#1263)
The Vercel CLI's repo.json format puts orgId on each project entry,
not at the root level. The CLI was reading repoConfig.orgId (undefined)
instead of project.orgId, which meant teamId was never set. Without
teamId, the world-vercel URL selector used the direct vercel-workflow.com
URL instead of the api.vercel.com proxy, causing 401 errors because
vercel-workflow.com only accepts OIDC tokens (not CLI auth tokens).

Also fix the RepoProjectConfig/RepoProjectsConfig type definitions
to match the actual repo.json structure.
2026-03-04 23:22:46 +00:00
Nathan Rajlich bbe40ff00a Opt-in decryption for o11y tooling (CLI + web) (#1256)
* Add browser-compatible AES-GCM to core and HKDF key derivation to world-vercel

* update changeset

* Move HKDF key derivation server-side: API returns per-run derived key

* Refactor encrypt/decrypt to accept CryptoKey, export importKey for callers to import once per run

* Overload getEncryptionKeyForRun: accept context for start(), fetch WorkflowRun in resume-hook

* Split changeset into per-package descriptions for world, world-vercel, and core

* Remove unnecessary Uint8Array.from() wrapper around Buffer.from()

* Use zod to parse Vercel API response

* fix: restore world-vercel files to main versions

The rebase incorrectly picked up older versions of these files from
early encryption branch commits. The main versions are correct and
up-to-date.

* fix: add type cast for hydrateStepReturnValue return in hook.ts

* Make decryption an explicit opt-in for o11y tooling

* Restore encrypted data handling in o11y hydration layer

* Use EncryptedDataRef with util.inspect.custom for CLI encrypted data display

* Fix Decrypt button crash: use correct 'refresh' callback from useWorkflowResourceData

* Implement client-side decryption for web o11y with getEncryptionKeyForRun RPC

* Fix CLI decrypt: fetch WorkflowRun for key resolution, cache per runId

* Use named constructor pattern for encrypted data display in web o11y

* Decrypt event data when encryption key is available after Decrypt button click

* Lift encryption key to run-level state, auto-decrypt on fetch, fix field pollution

* Re-load expanded event data when encryption key becomes available

* Consolidate Decrypt to title bar Button, remove sidebar decrypt card

* Add hover tooltip to Decrypt button explaining scope and state

* Show flat Encrypted label for encrypted fields, use Lucide Lock icon in DataInspector

* Render eventData subfields individually to avoid encrypted markers in collapsed preview

* Revert: render eventData subfields individually

* Fix Lock icon vertical alignment in DataInspector encrypted label

* update changeset

* Update CLI, web, and stream callers for CryptoKey: importKey at resolution sites

* Pass teamId to the get-key endpoint

* fix: remove unused DataInspector import in events-list.tsx

* fix: restore world-vercel files to base branch versions

Cherry-pick conflict resolution incorrectly took the older opt-in-decrypt
versions of these files, reverting improvements from main (dispatcher,
createGetEncryptionKeyForRun extraction, nullable key response).

* fix: address PR review feedback

- Remove duplicate AttributePanel/EventsList rendering in entity-detail-panel.tsx.
  Thread encryptionKey into the existing EventsList render instead.
- Restore missing re-exports (isClassInstanceRef, isStreamId, isStreamRef)
  in web-shared/src/index.ts to maintain backwards compatibility.
- Add 'error' to replaceEncryptedWithMarkers field list in web-shared
  hydration.ts to match the decrypt path.
- Extend CLI hydration eventData decrypt/placeholder to cover all known
  serialized fields (output, metadata, payload) not just result/input.
- Add 'error' to CLI replaceEncryptedWithRef field list.
- Remove invalid encryptionKey option from useWorkflowResourceData call
  (hook doesn't support it yet), add TODO.
- Add 4 unit tests for hydrateDataWithKey in serialization-format.test.ts:
  encrypted+key decrypts, encrypted+noKey returns raw, non-encrypted
  hydrates normally, non-Uint8Array legacy data passes through.

* feat: thread encryptionKey through useWorkflowResourceData hook

Instead of leaving a TODO, implement the encryptionKey support directly:
- Add optional encryptionKey to useWorkflowResourceData options
- When key is available, use hydrateResourceIOWithKey (async decrypt)
  instead of hydrateResourceIO for all resource types
- Remove redundant hydrateResourceIO from fetchResourceWithCorrelationId

* fix: address comprehensive review feedback on PR #1256

High priority:
- Gate showStream key fetch on --decrypt flag, warn when --decrypt
  used without --run
- Fix workflow-server-actions.server.ts missing cryptoKey params
  (undefined for both getExternalRevivers and getDeserializeStream)
- Add hydration + decryption to listEvents (was completely missing)
- Fix error/eventData display: check isEncryptedMarker before
  hasDisplayContent so encrypted markers don't silently disappear

Medium priority:
- handleDecrypt: use toast.error() instead of console.error for
  user-visible feedback on key fetch failures
- CLI maybeDecryptFields: add try/catch with graceful fallback to
  encrypted placeholders + warning, also decrypt error field
- use-resource-data: wrap hook/sleep hydrate() in try/catch to
  prevent stuck loading state on decryption errors
- Decrypt button: also check run.error and step input/output for
  encrypted markers, not just run.input/output

Low priority:
- event-list-view: add .catch() to re-load useEffect promise
- Export ENCRYPTED_DISPLAY_NAME from hydration.ts and import in
  data-inspector.tsx instead of raw 'Encrypted' string
2026-03-04 13:17:09 -08:00
Nathan Rajlich 7618ac36c2 Wire AES-GCM encryption into serialization layer (#1251)
* fix(core): chain unconsumed event check onto promiseQueue to prevent false positives

The EventsConsumer's unconsumed event check (setTimeout(0)) was racing
against the promiseQueue's async deserialization. When parallel steps
completed and their hydrateStepReturnValue did real async work (e.g.,
decryption), the setTimeout(0) fired before the promise chain resolved
the step results and triggered the next subscribe() call. This caused
step_created events for sequential steps to be falsely flagged as
unconsumed/orphaned.

Fix: chain the unconsumed check onto the promiseQueue via getPromiseQueue()
so it only fires after all pending async work completes. Use
process.nextTick (not setTimeout) after the queue drains to give
synchronous subscribe() calls from resolved user code a chance to cancel.
Version-based cancellation replaces clearTimeout since the check is now
promise-based.

Adds getPromiseQueue option to EventsConsumerOptions. The workflow.ts
context uses a getter/setter to keep the promiseQueue holder in sync.

Reproduction test: parallel steps A+B with 10ms mock deserialization
delay, followed by sequential step C. Previously failed with
'Unconsumed event: step_created(C)'. Now passes.

* fix: chain hydrateWorkflowArguments onto promiseQueue to prevent false unconsumed events

The unconsumed event check was firing during the async gap between
run_started consumption and the workflow function subscribing its
first step callbacks. This happened because hydrateWorkflowArguments
is async, and during its await, the EventsConsumer advanced to
step_created events that had no subscriber yet.

Fix: chain hydrateWorkflowArguments onto the promiseQueue so the
unconsumed check (which waits for the queue to drain) doesn't fire
until after the workflow arguments are hydrated and the workflow
function has been invoked.

* fix: use setTimeout(0) macrotask for unconsumed check to ensure VM promise propagation completes

The process.nextTick-based unconsumed check was still racing against
VM promise propagation. After promiseQueue resolves and the user code's
resolve() fires, there are multiple microtask hops through the VM
boundary before the workflow code actually calls subscribe() for the
next steps. process.nextTick fires before those VM microtasks complete.

setTimeout(0) is a macrotask that is guaranteed to fire only after ALL
microtasks (including VM promise chain propagation) have drained. The
pendingUnconsumedTimeout handle is stored and cleared in subscribe()
to prevent keeping the event loop alive unnecessarily.

* fix: increase unconsumed event check delay to 100ms for cross-VM promise propagation

setTimeout(0) is insufficient because Node.js does not guarantee that
macrotasks fire after all cross-context (VM boundary) microtasks settle.
After promiseQueue resolves and resolve() fires in the host context,
there are multiple microtask hops through the VM boundary before the
workflow code actually calls subscribe(). A 100ms delay provides
sufficient time for this propagation while still detecting truly
orphaned events promptly.

Also update sleep.test.ts to wait 200ms for the unconsumed check.

* Add browser-compatible AES-GCM to core and HKDF key derivation to world-vercel

* update changeset

* Move HKDF key derivation server-side: API returns per-run derived key

* Refactor encrypt/decrypt to accept CryptoKey, export importKey for callers to import once per run

* Overload getEncryptionKeyForRun: accept context for start(), fetch WorkflowRun in resume-hook

* Split changeset into per-package descriptions for world, world-vercel, and core

* Remove unnecessary Uint8Array.from() wrapper around Buffer.from()

* Use zod to parse Vercel API response

* Wire encryption into serialization layer

* Wire AES-GCM encryption into serialization layer

* update changeset

* Add encryption unit tests: primitives, maybeEncrypt/maybeDecrypt, isEncrypted, complex type round-trips

* Accept CryptoKey in encrypt/decrypt, export importKey for callers to import once per run

* Fix review comments: cache stream encryption key, remove redundant casts, fix stale comments

* Trying to clean up some type non-sense

* fix: restore world-vercel files to main versions

The rebase incorrectly picked up older versions of these files from
early encryption branch commits. The main versions are correct and
up-to-date.

* fix: add type cast for hydrateStepReturnValue return in hook.ts

* fix: address review feedback on encryption PR

- Remove Vercel-specific error message from maybeDecrypt (core should
  not reference VERCEL_DEPLOYMENT_KEY)
- Move stream encryption/decryption from transport layer
  (WorkflowServerReadableStream/WritableStream) to framing layer
  (getSerializeStream/getDeserializeStream). Frame length headers stay
  in the clear so frame boundaries are always parseable regardless of
  transport chunking; encryption wraps the frame payload.
- Remove explicit Promise<unknown> return types from all 4 hydrate
  functions. On main these had inferred types (any from devalue),
  so callers didn't need casts. The encryption branch added explicit
  annotations that broke this.
- Revert unnecessary type casts in run.ts, step-handler.ts, hook.ts
  that were only needed due to the explicit Promise<unknown> annotations
- Revert closureVars type from unknown back to Record<string, any>
  in context-storage.ts to match the contract with getClosureVars
- Fix hydrateWorkflowArguments JSDoc for unused _runId parameter

* Revert more unnecessary changes

* cleanup: remove unused runId param, deduplicate processFrames, add legacy comments

- Remove unused _runId parameter from WorkflowServerReadableStream
  constructor and all 4 call sites
- Deduplicate processFrames decryption: decrypt first and reassign
  format/payload, then fall through to single deserialization path
- Add comments on all legacy non-Uint8Array branches explaining when
  this happens (specVersion 1 runs stored data as plain JSON arrays)
- Fix duplicate code block in hydrateStepReturnValue

* feat: wire cryptoKey through stream serialize/deserialize pipeline

Thread the encryption key through the entire stream serialization chain
so that ReadableStream and WritableStream values are encrypted/decrypted
at the framing level.

- Add optional cryptoKey param to getExternalReducers, getStepReducers,
  getExternalRevivers, getStepRevivers
- Pass cryptoKey to getSerializeStream/getDeserializeStream at all 8
  internal call sites within reducers/revivers
- Thread key from dehydrate/hydrate functions into their reducers/revivers
- Cache encryption key in Run class (resolved once via getEncryptionKey(),
  reused for returnValue, getReadable(), etc.)
- Make Run#getReadable() async to resolve the cached key before creating
  the deserialize stream
- Add encryptionKey to step context storage so getWritable() can access
  it during step execution

* fix: make cryptoKey required-but-nullable to prevent silent omission, add stream encryption tests

Change cryptoKey parameter from optional (cryptoKey?) to required-but-
nullable (cryptoKey: CryptoKey | undefined) on all 6 functions:
- getSerializeStream, getDeserializeStream
- getExternalReducers, getStepReducers
- getExternalRevivers, getStepRevivers

This ensures every call site must explicitly pass the key or undefined,
making it impossible to accidentally omit it and silently skip encryption.

Add 7 stream encryption round-trip tests:
- Encrypted frames have 'encr' prefix inside length header
- Full round-trip: encrypt serialize -> decrypt deserialize
- Concatenated encrypted frames (transport coalescing)
- Split encrypted frames (transport splitting)
- Error when encrypted data encountered without key
- No encryption when key is undefined
- Large payload round-trip

Full audit confirms all encryption key threading is complete:
- All 8 dehydrate/hydrate functions pass key to reducers/revivers
- All stream serialize/deserialize call sites pass key
- Run class caches key for reuse across returnValue and getReadable()
- Step context storage carries key for getWritable()

* fix: keep Run#getReadable() sync, resolve encryption key lazily in streams

- Revert Run#getReadable() to synchronous (non-breaking API).
  The encryption key is passed as a Promise through the chain and
  resolved lazily inside the first async transform() call.
- Add EncryptionKeyParam type alias that accepts CryptoKey, undefined,
  or Promise<CryptoKey | undefined>. Used by getSerializeStream,
  getDeserializeStream, and all reducer/reviver functions.
- Key promises are resolved once on first use via a keyState cache
  object inside each stream's transform closure.
- Fix CLI showStream to resolve encryption key from world when runId
  is provided via --run flag, instead of passing undefined.
- Remove incorrect CLI warning that --run is not supported for streams
  (it is now needed for encrypted stream decryption).

* .

* fix: address review feedback from PR #1251

- Fix 4 broken dehydrateWorkflowArguments calls in workflow.test.ts
  that were passing ops as runId (missing runId and key params)
- Use WorkflowRuntimeError instead of plain Error in decodeFormatPrefix
  for unknown serialization formats, for consistency and programmatic
  error handling
- Document maybeDecrypt throw behavior: callers should be aware this
  surfaces as a rejected promise during key rotation/misconfiguration
- Document key-fetch rejection timing in streams: promise rejection
  won't surface until the first chunk is processed
2026-03-04 12:19:49 -08:00
Nathan Rajlich 60bc9d5cb1 fix(core): chain unconsumed event check onto promiseQueue to prevent false positives (#1254)
* fix(core): chain unconsumed event check onto promiseQueue to prevent false positives

The EventsConsumer's unconsumed event check (setTimeout(0)) was racing
against the promiseQueue's async deserialization. When parallel steps
completed and their hydrateStepReturnValue did real async work (e.g.,
decryption), the setTimeout(0) fired before the promise chain resolved
the step results and triggered the next subscribe() call. This caused
step_created events for sequential steps to be falsely flagged as
unconsumed/orphaned.

Fix: chain the unconsumed check onto the promiseQueue via getPromiseQueue()
so it only fires after all pending async work completes. Use
process.nextTick (not setTimeout) after the queue drains to give
synchronous subscribe() calls from resolved user code a chance to cancel.
Version-based cancellation replaces clearTimeout since the check is now
promise-based.

Adds getPromiseQueue option to EventsConsumerOptions. The workflow.ts
context uses a getter/setter to keep the promiseQueue holder in sync.

Reproduction test: parallel steps A+B with 10ms mock deserialization
delay, followed by sequential step C. Previously failed with
'Unconsumed event: step_created(C)'. Now passes.

* fix: chain hydrateWorkflowArguments onto promiseQueue to prevent false unconsumed events

The unconsumed event check was firing during the async gap between
run_started consumption and the workflow function subscribing its
first step callbacks. This happened because hydrateWorkflowArguments
is async, and during its await, the EventsConsumer advanced to
step_created events that had no subscriber yet.

Fix: chain hydrateWorkflowArguments onto the promiseQueue so the
unconsumed check (which waits for the queue to drain) doesn't fire
until after the workflow arguments are hydrated and the workflow
function has been invoked.

* fix: use setTimeout(0) macrotask for unconsumed check to ensure VM promise propagation completes

The process.nextTick-based unconsumed check was still racing against
VM promise propagation. After promiseQueue resolves and the user code's
resolve() fires, there are multiple microtask hops through the VM
boundary before the workflow code actually calls subscribe() for the
next steps. process.nextTick fires before those VM microtasks complete.

setTimeout(0) is a macrotask that is guaranteed to fire only after ALL
microtasks (including VM promise chain propagation) have drained. The
pendingUnconsumedTimeout handle is stored and cleared in subscribe()
to prevent keeping the event loop alive unnecessarily.

* fix: increase unconsumed event check delay to 100ms for cross-VM promise propagation

setTimeout(0) is insufficient because Node.js does not guarantee that
macrotasks fire after all cross-context (VM boundary) microtasks settle.
After promiseQueue resolves and resolve() fires in the host context,
there are multiple microtask hops through the VM boundary before the
workflow code actually calls subscribe(). A 100ms delay provides
sufficient time for this propagation while still detecting truly
orphaned events promptly.

Also update sleep.test.ts to wait 200ms for the unconsumed check.

* add changeset
2026-03-04 18:52:38 +00:00
Nathan Rajlich c35b597552 fix(ci): fix npm 'latest' dist-tag not being set during release (#1255)
* fix(ci): fix npm 'latest' dist-tag not being set during release

The OIDC trusted publishing change (a78af46f) removed NPM_TOKEN from the
changesets/action step and added registry-url to actions/setup-node. This
caused the 'Add latest dist-tag' step to break for the unscoped 'workflow'
package:

- actions/setup-node writes an .npmrc at a temp path that references
  ${NODE_AUTH_TOKEN}, and sets NPM_CONFIG_USERCONFIG to point at it
- The dist-tag step was writing its own ~/.npmrc using NPM_TOKEN, but npm
  ignored it because NPM_CONFIG_USERCONFIG overrides the default path
- The OIDC token left over in NODE_AUTH_TOKEN worked for @workflow/* scoped
  packages (which already had 'latest' set) but returned E401 for the
  unscoped 'workflow' package

Fix: set NODE_AUTH_TOKEN to NPM_TOKEN_ELEVATED secret so npm picks up the
correct token from the .npmrc that actions/setup-node already configured.

Also fix silent failures: replace the piped while-read loop (which runs in
a subshell and swallows exit codes) with a for loop that tracks failures
and exits non-zero if any dist-tag operation fails.

* use process substitution instead of word-splitting for loop
2026-03-04 09:51:45 -08:00
Nathan Rajlich 860531d182 fix(core): ensure async deserialization resolves promises in event log order (#1246)
* fix(core): ensure async deserialization resolves promises in event log order

Add a sequential deserializationChain to WorkflowOrchestratorContext that
ensures step/hook hydration resolves in event log order even when individual
deserialization takes variable time (e.g. due to encryption/decryption).

Without this, concurrent async hydrations could resolve out of order,
breaking workflow replay determinism.

* test: add hook ordering test and afterEach mock cleanup

Address review feedback:
- Add hook-specific deserialization ordering test
- Add afterEach(vi.restoreAllMocks) to prevent spy leaks
- Remove manual mockRestore calls

* refactor(core): route all promise resolutions through sequential promiseQueue

- Rename deserializationChain -> promiseQueue (it now handles all
  event-driven promise resolutions, not just deserialization)
- Route step_failed, step_created corruption, unexpected event type,
  and null event suspensions through promiseQueue in step.ts
- Route hook_conflict, unexpected event type, null event suspensions,
  dispose suspensions, and eventLogEmpty suspensions through
  promiseQueue in hook.ts
- Route wait_completed and unexpected event type through promiseQueue
  in sleep.ts
- Remove all setTimeout(0) calls from step.ts, hook.ts, and sleep.ts
- Add stress tests: mixed step_completed/step_failed, 10 concurrent
  steps, sleep+step interleaving, interleaved concurrent steps

* fix: route createHookPromise conflict rejection through promiseQueue

- The hasConflict path in createHookPromise() was rejecting immediately,
  bypassing the promiseQueue and potentially settling out of event-log
  order relative to prior queued resolutions.
- Add missing runId and encryptionKey to sleep.test.ts context stub.
2026-03-03 14:42:08 -08:00
Vercel Release Bot 09343c1b13 Version Packages (beta) (#1238) workflow@4.1.0-beta.63 2026-03-03 12:47:33 -08:00
Nathan Rajlich 2b1c2bd8e6 [world-vercel] Fix run_failed event schema validation with separate resolve/lazy wire schemas (#1222)
* Fix run_failed event schema validation failure in lazy ref mode

The EventResultWireSchema used the strict WorkflowRunSchema (discriminated
union) which requires error: StructuredErrorSchema for status 'failed'. When
run_failed events use remoteRefBehavior 'lazy', the server returns the error
field as a RemoteRef or undefined, causing Zod validation to fail.

Switch to WorkflowRunWireBaseSchema which accepts error as string, structured
object, or undefined, and apply deserializeError() to normalize the run entity
before returning it to consumers.

Closes #1180

* Use strict WorkflowRunSchema for resolve mode, loose wire schema for lazy mode

Split EventResultWireSchema into two variants based on remoteRefBehavior:
- EventResultResolveWireSchema uses WorkflowRunSchema (discriminated union)
  for events where the client reads result.run (run_created, run_started,
  step_started), preserving type safety.
- EventResultLazyWireSchema uses WorkflowRunWireBaseSchema for events where
  the client discards the response, allowing error to be a string or
  undefined in lazy ref mode.

* Update changeset to reflect resolve/lazy schema split

* Simplify changeset description
2026-03-03 12:20:18 -08:00
JJ Kasper a9fea9132e Update workbench tests to build and run outside of monorepo (#1230)
* Setup fixes

* ci: run local e2e against staged tarball workbenches

* ci: update staged workbench tarball setup script

* chore: set nextjs workbenches back to next 16.1.6

* update lock

* test(e2e): resolve workbench path from WORKBENCH_APP_PATH

* fix: address deferred builder issues outside monorepo

* ci: stage tarball workbenches only for nextjs local e2e

* fix(next): discover deferred steps imported via workflows

* test(core): gate deferred step-discovery dev test to canary

* test(e2e): cover cross-file imported step in build/start lanes

* fix(e2e): use local manifest in local runs and relax dev rebuild timeout

* fix(workbench): add imported-step workflow symlink for sveltekit/astro

* test(e2e): scope imported-step workflow test to nextjs lanes

* fix(next): rebuild deferred entries on discovered file updates

* fix(next): watch transitive deferred step deps for dev rebuilds

* fix(next): restore socket-driven deferred step rebuilds

* add changeset

* chore: address review feedback on deferred e2e updates

* fix(cli): guard stream flush against closed write streams
2026-03-03 11:17:39 -08:00
John Lindquist 4ac75f4806 Add Fluid compute callout to Vercel World docs (#1242)
* Add Fluid compute warning callout to Vercel World docs

Follow-up from #1216 review feedback to include the
FluidComputeCallout on the Vercel World page.

* Register FluidComputeCallout in worlds/[id] page

The /worlds/vercel route uses a separate page component from
/docs/... and was missing the FluidComputeCallout in its MDX
component map, causing a 500 error.
2026-03-02 19:43:23 -08:00
Karthik Kalyan e2420bed29 fix sync front dispatch job (#1244) 2026-03-02 18:24:54 -08:00
Peter Wielander ece45a0d7c Lint after biome update (whitespace changes only) (#1243) 2026-03-02 17:21:23 -08:00
Pranay Prakash 4ab4412ae6 Add Run.wakeUp() method to interrupt pending sleeps (#1232)
* feat: add testing section and navigation link

Create new 'testing/' section and update Foundations index page.

Slack-Thread: https://vercel.slack.com/archives/C09125LC4AX/p1772375177894629?thread_ts=1772375177.894629&cid=C09125LC4AX
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

* docs: fix testing page API references and examples

Slack-Thread: https://vercel.slack.com/archives/C09125LC4AX/p1772375177894629?thread_ts=1772375177.894629&cid=C09125LC4AX
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

* fix: update testing.mdx and meta.json for docs-writer guidelines

Slack-Thread: https://vercel.slack.com/archives/C09125LC4AX/p1772375177894629?thread_ts=1772375177.894629&cid=C09125LC4AX
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

* feat: clarify unit testing focus on steps, remove workflow example

Update documentation to emphasize unit testing for steps only.

Slack-Thread: https://vercel.slack.com/archives/C09125LC4AX/p1772375177894629?thread_ts=1772375177.894629&cid=C09125LC4AX
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

* feat: reorganize testing page and fix unit test section

Move testing to top-level, fix content and update meta files.

Slack-Thread: https://vercel.slack.com/archives/C09125LC4AX/p1772375177894629?thread_ts=1772375177.894629&cid=C09125LC4AX
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

* feat: add Nitro server setup and observability section to tests

Add 'globalSetup' script and vitest config, and update docs for test debugging.

Slack-Thread: https://vercel.slack.com/archives/C09125LC4AX/p1772375177894629?thread_ts=1772375177.894629&cid=C09125LC4AX
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>

* [ci] Disable claude code CI (#1231)

* feat: add `Run.wakeUp()` method to programmatically interrupt pending sleeps

Exposes the existing `wakeUpRun` logic as an instance method on the `Run`
class, allowing users to skip sleep() calls in tests or custom UIs.

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

* fix: address PR review comments

- Fix JSDoc @returns to describe StopSleepResult object instead of number
- Clean up global world state in afterEach to prevent test leakage
- Add delay before wakeUp() in docs example to allow workflow replay

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

* chore: restore claude.yml to match base branch

The file was removed on main but still exists on the base branch.
Restoring it so this PR's diff only contains relevant changes.

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

* fix: add imports to standalone code snippet in get-run docs

The docs test type-checks each code block independently, so the
standalone wakeUp snippet needs its own imports and run variable.

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

* fix: use @setup preamble for standalone wakeUp snippet

Use the // @setup comment pattern to provide type context without
rendering the setup line, keeping the snippet lean.

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

* docs: document // @setup preamble pattern in docs-writer agent

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

---------

Co-authored-by: v0 <v0[bot]@users.noreply.github.com>
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-02 16:43:01 -08:00
Nathan Rajlich 809339ba1c fix(builders): enable directive discovery in dot-prefixed files and directories (#1228)
* fix(builders): enable directive discovery in dot-prefixed files and directories

The glob-based file scanning in getInputFiles() was using tinyglobby's
default behavior which skips dot-prefixed files/directories. This prevented
discovering files with 'use step' / 'use workflow' directives inside paths
like .config/step.ts or .hidden-workflow.ts.

Switch to relative glob patterns with per-directory cwd and dot: true to
ensure dot-files are scanned while still respecting the explicit ignore
list (.git, .next, .vercel, etc.).

* test: add E2E tests for dot-directory directive discovery

Add workbench fixtures and E2E tests verifying that 'use step' / 'use
workflow' directives inside dot-prefixed directories (.well-known/agent/)
are correctly discovered, included in manifests, and executed at runtime.

* fix: normalize paths in getInputFiles tests for Windows compatibility

tinyglobby returns forward-slash paths even on Windows, while Node's
path.join() uses backslashes. Normalize both sides to forward slashes
before comparison.

* refactor: use dirname() instead of join(.., '..') in test helper

Cleaner and more idiomatic way to get the parent directory.

* fix: add .nuxt and other build tool dot-directories to ignore list

With dot: true enabled, framework build output directories like .nuxt/
are now traversed and their generated files picked up as input files.
This caused a circular dependency in the nuxt builder where generated
.nuxt/workflow/steps.mjs was being re-discovered as an input file.

Add .nuxt, .turbo, .cache, .yarn, and .pnpm-store to the ignore list,
matching the directories already ignored by the eager builder's watcher.
2026-03-02 14:36:12 -08:00
John Lindquist 42361d651b Add Fluid compute warning callout across deployment docs (#1216)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
2026-03-02 14:31:02 -08:00
Karthik Kalyan 8e0d2a8a9c [web-shared] Fix date parsing logic for display on the detail panel (#1235)
* Fix date parsing error in the detail panel

* Update .changeset/brown-chicken-act.md

Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Karthik Kalyan <105607645+karthikscale3@users.noreply.github.com>

* [workflow o11y] add error boundary tests

---------

Signed-off-by: Karthik Kalyan <105607645+karthikscale3@users.noreply.github.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
2026-03-02 20:28:34 +00:00