The health command's `endpoint` flag and the shared `env` flag both
declared `char: 'e'`, causing ambiguity. Remove the short flag from
`endpoint` so `-e` unambiguously maps to `--env`.
The e2e tests spawn a CLI subprocess for every inspect/cancel/health call.
Each subprocess performs an npm registry version check on startup, which
can hang under load and exceed the 20s spawn timeout, causing SIGTERM.
Add WORKFLOW_NO_UPDATE_CHECK=1 env var support to skip the check, and
set it in the e2e test harness.
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
* 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>
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).
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.
* 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
* 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
* fix(workflow): improve error message for runtime API usage in workflow context and fix SKILL.md streaming docs
The workflow stub error now tells users to move calls to a step function or outside the workflow context. Also corrects the SKILL.md streaming section which incorrectly stated getWritable() only works in steps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add changeset
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(skill): address PR feedback on SKILL.md
- Remove `npx workflow start` from debugging section (args can't easily be passed via CLI)
- Mention --json and --help flags for CLI inspection
- Add reminder to check AI docs for DurableAgent details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(skill): fix SKILL.md debugging section CLI commands
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* other improvements
* fix(skill): address Copilot PR feedback on streaming examples
- Add missing imports and define all referenced variables
- Wrap writer.releaseLock() in finally blocks for safety
- Remove undefined `data`/`agent`/`messages` references
- Simplify examples to be self-contained and copy-pasteable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(skill): bump SKILL.md version to 1.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add skill version bump rule to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: John Lindquist <johnlindquist@gmail.com>
## Summary
- Adds `World.getEncryptionKeyForRun(run)` returning `Uint8Array | undefined` as the interface for retrieving per-run encryption keys
- Updates all 8 dehydrate/hydrate serialization functions to accept `key: Uint8Array | undefined`
- Updates runtime callers, CLI, and tests to thread the key parameter through
## Summary
- Makes all 8 dehydrate/hydrate serialization functions in `@workflow/core` async (returning `Promise<...>`)
- Updates all call sites across runtime, CLI, and test code to `await` the results
- Pure mechanical refactor — no functional changes, prerequisite for encryption support
Part 1 of the end-to-end encryption PR stack.
The CLI's `cancelRun` in `packages/cli/src/lib/inspect/run.ts` sent `{ eventType: 'run_cancelled' }` without `specVersion`, causing the Vercel API's `/v2/runs/{id}/events` endpoint to reject the request with a 400 error. The core runtime's `cancelRun` already handled this correctly, so the fix removes the redundant CLI wrapper and calls the core implementation directly.
The bug was introduced in 4966b72 ("implement event-sourced architecture (#621)") which changed the CLI cancel from `world.runs.cancel(runId)` to `world.events.create(runId, ...)` but omitted `specVersion` from the payload. The same commit correctly included it in `Run#cancel()` in core — it was simply missed in the CLI path. A later commit (86a7930) added v1Compat branching but still never added specVersion. The final merged state in a2b688d (PR #894) still omits it.
Also adds a CLI cancel e2e test that starts a long-running workflow, cancels it via the CLI command, and verifies the run status.
* Use `@vercel/cli-auth` for auth token reading and OAuth refresh
Replace the manual auth.json reading logic in the CLI with the `@vercel/cli-auth` package, which handles credential storage via CredentialsStore and OAuth token refresh via the OAuth client.
Previously, the CLI would read the token from disk but had no refresh logic — if the token was expired, API calls would fail.
Now, getAuthToken() checks token expiry and automatically refreshes it using the stored refresh token before returning it.
* Remove dead logging
## Summary
Split the serialization/deserialization logic into environment-specific layers so data hydration can happen client-side in the browser. This is a prerequisite for e2e encryption where decryption keys are only available in the browser.
## Architecture
### Layer 1: `@workflow/core/serialization-format` (new, browser-safe)
- Format prefix encoding/decoding (`devl`, future `encr`, etc.)
- Generic `hydrateData()` dispatch — handles Uint8Array (v2 binary), legacy arrays (v1), and plain values
- `hydrateResourceIO(resource, revivers)` resource-type dispatcher (step/hook/event/workflow field mapping)
- `ClassInstanceRef` (plain data class, no `node:util` dependency)
- `StreamRef`, type guards (`isStreamRef`, `isStreamId`, `isClassInstanceRef`), utility functions (`extractStreamIds`, `truncateId`)
- Shared `observabilityRevivers` for stream/class/step display overrides
- 36 unit tests covering all of the above
### Layer 2: Environment-specific revivers
- **`@workflow/web-shared`** (`lib/hydration.ts`) — browser-safe revivers using `atob()` for base64, real `URLSearchParams`/`Headers`/`URL` instances, `ClassInstanceRef` for UI rendering
- **`@workflow/cli`** (`lib/inspect/hydration.ts`) — Node.js revivers using `Buffer.from()` for base64, `CLIClassInstanceRef` with `util.inspect.custom` for CLI output
Each module exports a pre-bound `hydrateResourceIO(resource)` that uses its environment's revivers.
### Removed: `@workflow/core/observability`
- Deleted `observability.ts` and `observability.test.ts` entirely (no remaining consumers)
- Removed `"./observability"` export from `@workflow/core/package.json`
- Removed the `workflow` package's `internal/observability.ts` re-export
- All functionality has been split between `serialization-format.ts` (shared types/utilities) and the environment-specific hydration modules
## Web package changes
- Server passes raw world data through without hydration (CBOR preserves `Uint8Array`)
- Client calls `hydrateResourceIO` from `@workflow/web-shared` after receiving CBOR-decoded data
- No Vite `node:*` stubs needed since `@workflow/core/serialization-format` is browser-safe
- Optimized: event hydration finds the matching event before hydrating (instead of hydrating all)
- Reduced server log noise from handled API errors (4xx errors no longer logged)
## Packages affected
- `@workflow/core` — new `serialization-format` export (with tests), removed `observability` export
- `@workflow/web-shared` — new `lib/hydration.ts` with browser-safe revivers
- `@workflow/cli` — new `lib/inspect/hydration.ts` with Node.js revivers, updated `output.ts` import
- `@workflow/web` — client-side hydration, removed server-side hydration
- `workflow` — removed `internal/observability.ts` re-export
## Summary
- Replace Next.js App Router with React Router v7.13.0 framework mode (Vite-based), eliminating the large `next` dependency from the web, CLI, and workflow metapackages
- Serve the web UI in-process from the CLI via Express instead of spawning `next start` as a child process
- Switch RPC transport from JSON to CBOR to preserve binary data types across the wire
- Replace `nuqs` URL state management with React Router's `useSearchParams`
- Replace Next.js server actions with an RPC resource route (`/api/rpc`) and a thin CBOR-based client
## Motivation
The `next` package is ~300MB installed and was the single largest dependency in the monorepo. It also required spawning a separate child process from the CLI to run the o11y web server, adding complexity around process lifecycle management, port readiness polling, and environment variable forwarding.
With React Router framework mode, the web package builds to a standard Express-compatible server bundle that the CLI can import and serve directly in its own process.
## What changed
**Framework swap (`@workflow/web`):**
- `next.config.ts` / `postcss.config.mjs` → `react-router.config.ts` / `vite.config.ts`
- `src/` directory → `app/` directory (React Router convention)
- `src/app/layout.tsx` + `layout-client.tsx` → `app/root.tsx`
- `src/app/page.tsx` → `app/routes/home.tsx`
- `src/app/run/[runId]/page.tsx` → `app/routes/run-detail.tsx`
- Path alias `@/` → `~/`
- Removed all `'use client'` / `'use server'` directives
**Data transport:**
- Server actions → RPC resource route at `/api/rpc` with CBOR encoding
- CBOR preserves `Uint8Array` and other binary types natively (no base64 overhead)
- Stream reading → dedicated `/api/stream/:streamId` resource route
**URL state:**
- `nuqs` (`useQueryState`) → `useSearchParams` from `react-router`
**Fonts:**
- `next/font/google` → Geist `.woff2` files referenced directly from `node_modules/geist` via `@font-face` in CSS
**CLI integration (`@workflow/cli`):**
- `import('@workflow/web/server').then(m => m.startServer(port))`
- No child process, no readiness polling, no cleanup handlers
**Radix UI compatibility:**
- `onSubmit` preventDefault on `AlertDialogContent` and `SheetContent` to prevent Radix's internal `<form method="dialog">` from triggering React Router route actions
- Catch-all action on root route for any stray POSTs
## Dependencies removed
- `next`, `swr`, `nuqs`, `@tailwindcss/postcss`
## Dependencies added
- `react-router` / `@react-router/dev` / `@react-router/node` / `@react-router/express` (all `7.13.0`)
- `express`, `vite`, `@tailwindcss/vite`, `cbor-x`, `isbot`, `cross-env`
- `geist` (devDep)
* Add SDK version to workflow run executionContext for observability
Wire `@workflow/core` package version through the `executionContext` field
when creating workflow runs. Display the version in the observability UI
attribute panel. Uses genversion for build-time version injection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix workflowCoreVersion extraction and add turbo.json for caching
- Extract workflowCoreVersion from executionContext in hydrateResourceIO
before stripping the context (addresses Copilot review comment)
- Add turbo.json to include src/version.ts in build outputs for caching
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update workflowCoreVersion display name to @workflow/core version
Use a display name mapping to render the attribute as "@workflow/core version"
in the observability UI for better readability.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Added `WORKFLOW_SERVER_URL_OVERRIDE` configuration to the Vercel world adapter and removed the deprecated `WORKFLOW_VERCEL_SKIP_PROXY` and `WORKFLOW_VERCEL_BACKEND_URL` environment variables.
### What changed?
- Added a changeset for a patch release across multiple packages
- Removed `WORKFLOW_VERCEL_SKIP_PROXY` environment variable from GitHub workflow tests
- Removed `WORKFLOW_VERCEL_BACKEND_URL` from environment variables in CLI and core packages
- Simplified the URL resolution logic in the Vercel world adapter
- Added support for a `WORKFLOW_SERVER_URL_OVERRIDE` constant for testing against different workflow-server versions
- Added the `x-vercel-workflow-api-url` header when the URL override is set
### How to test?
1. Verify that Vercel deployments continue to work without the removed environment variables
2. Test with a custom workflow server URL by setting the `WORKFLOW_SERVER_URL_OVERRIDE` constant in the world-vercel package
### Why make this change?
This change simplifies the configuration for the Vercel world adapter by removing deprecated environment variables and standardizing on a cleaner approach for specifying the workflow API URL. The new implementation automatically determines whether to use the proxy based on project configuration, making it more intuitive and reducing the need for explicit configuration.