Commit Graph

608 Commits

Author SHA1 Message Date
Vercel Release Bot 295f6cd0b6 Version Packages (beta) (#1013)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
workflow@4.1.0-beta.56
2026-02-12 20:09:35 -08:00
Pranay Prakash ff9fcc25ad Fix benchmark history continuity and caching (#1034)
* Fix benchmark history continuity across metric name rename

The concurrent step benchmarks were renamed in beta.53 (dropping the
"stress test: " prefix), which broke the history API's exact-name lookup
against older gh-pages data. Add legacy alias fallbacks so charts show
continuous history across the rename boundary.

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

* Paginate GitHub API calls and use generic legacy name fallback

Three fixes:
- Paginate gh-pages commits (was fetching 100 of 216, missing most history)
- Paginate tags (workflow@ tags were cut off beyond page 1)
- Paginate main branch commits for commits mode
- Replace hardcoded LEGACY_METRIC_ALIASES with generic "stress test: "
  prefix fallback

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

* Cache benchmark history with unstable_cache and aggressive CDN TTLs

The snapshot map (200+ GitHub API fetches) is now cached via
unstable_cache for 1 hour, shared across all parameter combinations.
Tags are cached separately. Individual fetch calls use force-cache
with long revalidation periods since historical data is immutable.

CDN cache bumped from 5min to 1h with 24h stale-while-revalidate,
matching how infrequently benchmark data changes.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 19:57:18 -08:00
Pranay Prakash 8d117cd219 Retry 5xx errors from workflow-server in step handler (#1011)
* Retry 5xx errors from workflow-server in step handler

Add `withServerErrorRetry` helper that retries world calls on 5xx errors
with exponential backoff (500ms, 1s, 2s ≈ 3.5s total). Applied to all
`world.events.create` calls in the step handler so transient
workflow-server errors don't consume step attempts.

If retries are exhausted, the error is thrown to the queue for
higher-level retry without burning a step attempt.

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

* Address PR review comments on 5xx retry handling

- Fix misleading `maxAttempts` log field to `maxRetries` in withServerErrorRetry
- Update step-handler comment to accurately note that queue retries may
  still consume step attempts since step_started has already incremented
- Add unit tests for withServerErrorRetry (7 tests covering success,
  retry/backoff, exhaustion, and non-5xx passthrough)

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

* Add tests for 429/5xx retry handling

Unit tests for withThrottleRetry and withServerErrorRetry helpers, plus
an e2e test that exercises the 5xx retry codepath during step execution
via run-scoped fault injection.

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

* Wrap VQS errors in WorkflowAPIError and add retry to queueMessage

VQS throws its own error types (InternalServerError, ConsumerDiscoveryError,
ConsumerRegistryNotConfiguredError) that don't match WorkflowAPIError.is().
Wrapping them at the world-vercel boundary enables withServerErrorRetry in
queueMessage() to automatically retry transient queue failures.

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

* Update changeset to include @workflow/world-vercel

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

* Remove queue retry logic per review feedback

Queue retrying will be handled natively by the @vercel/queue client
instead. Reverts VQS error wrapping and withServerErrorRetry in
queueMessage().

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 18:42:36 -08:00
JJ Kasper 94760b4640 Track loader transform deps instead of clearing Next cache (#1029)
* Track loader transform dependencies and remove SWC cache invalidation

* changeset
2026-02-12 17:41:47 -08:00
Nathan Rajlich 8a53c3fa3d Use react-inspector for data rendering in o11y sidebar (#1018)
## Summary

Replace the custom `JSON.stringify`-based data renderer with [`react-inspector`](https://www.npmjs.com/package/react-inspector)'s `ObjectInspector` component for proper display of all JavaScript types in the o11y sidebar panel.

## Before

Data was rendered via `JSON.stringify(value, null, 2)` in a `<pre>` block. Non-plain-object types like `Map`, `Set`, `URLSearchParams`, `Date`, `Error`, `RegExp`, and typed arrays all rendered as `{}` or lost their type information.

## After

Uses `react-inspector` (the same rendering engine as Chrome DevTools) to display data as expandable tree views with proper type labels:
- `URLSearchParams {size: 12}` with entries visible
- `Map(3)` / `Set(5)` with contents
- `Date "2026-02-11T..."` 
- `Error {message: "...", stack: "..."}` 
- `Uint8Array(32)` with byte values

## Implementation

- Replace `JsonBlock` function with `DataInspector` component using `ObjectInspector`
- Custom theme with transparent background and design system CSS variables (`--ds-gray-*`, `--font-mono`)
- Dark/light mode support via `useDarkMode` hook
- `StreamRef` and `ClassInstanceRef` objects still render as custom components (clickable stream links and styled class cards)
- Expand level defaults to 2 for a good overview without overwhelming

## Dependencies
- Added `react-inspector@9` to `@workflow/web-shared`
2026-02-12 17:37:50 -08:00
Nathan Rajlich 4938b24674 Decouple detail panel from trace viewer web worker pipeline (#1017)
## Summary

Render the `EntityDetailPanel` (step/hook/event detail sidebar) outside the trace viewer's `TraceViewerContextProvider`, so hydrated data no longer passes through the web worker's `postMessage` structured clone boundary.

## Problem

The trace viewer uses a web worker for span layout calculations, sending data via `postMessage` which requires structured cloning. After moving hydration client-side, the hydrated data (containing `URLSearchParams`, `Headers`, and other non-structurally-cloneable types) was embedded in trace span attributes and sent to the worker, causing `DOMException: URLSearchParams object could not be cloned`.

## Solution

Render the detail panel as a **sibling** to the trace timeline instead of nested inside the context:

```
Before:                          After:
TraceViewerContext               [flex row]
  └─ Timeline                     ├─ TraceViewerContext
       └─ Panel (inside worker)   │    └─ Timeline (worker)
                                  └─ Panel (direct props)
```

- `WorkflowTraceViewer`: flex layout with timeline + panel as siblings
- `SelectionBridge`: lightweight component inside context that reads the selected span and passes it up via callback
- `EntityDetailPanel`: no longer imports `useTraceViewer()`, receives selected span info via `selectedSpan` prop
- Panel has independent width/resize state (no longer coupled to the trace viewer context)
- Strip `input`/`output`/`error` from trace span attributes to keep worker data lightweight

## Also fixes
- Stale `customPanelComponent` bug — the panel was stored in context state during initialization and never updated, causing data to be lost on re-renders
2026-02-12 17:17:07 -08:00
Nathan Rajlich c56dc3848e Extract browser-safe serialization format and move hydration client-side (#1015)
## 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
2026-02-12 16:58:19 -08:00
Nathan Rajlich 7653e6bfdb Migrate @workflow/web from Next.js to React Router v7 (#1005)
## 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)
2026-02-12 16:30:51 -08:00
Nathan Rajlich d7d005b54b Allow @ in workflow names to support scoped packages (#990) 2026-02-12 11:40:17 -08:00
Hayden Bleasel 2cc8c3dc9c Update docs (#1010)
* Run update script

* Update deps

* Restore code block modifications

* Reapply navbar patch

* Restore custom proxy config

* Add md-metadata

* Add new frontmatter
2026-02-12 19:27:52 +00:00
Nathan Rajlich 63caf93138 Add format prefix to stream chunks and use .bin extension for chunk files (in world-local) (#1020)
Stream chunks were serialized as raw devalue text without the 'devl'
format prefix, unlike step inputs/outputs. This meant stream data
couldn't go through the same hydration/deserialization pipeline.

Core changes (serialization.ts):
- getSerializeStream: each chunk is now individually wrapped with
  encodeWithFormatPrefix('devl', payload) — same as step data
- getDeserializeStream: handles both format-prefixed chunks (current)
  and legacy newline-delimited text (backwards compatible)

World-local changes (streamer.ts):
- Stream chunk files now use .bin extension instead of .json, since
  the content is binary (1-byte eof flag + serialized chunk data)
- Add listFilesByExtension() utility to fs.ts for generic file listing

* chore: add changeset

* world-local: add backwards compat for legacy .json chunk files

When reading stream chunks, list both .bin (current) and .json
(legacy) files so streams written before the extension change
continue to work.

* core: add format prefix and length-prefix framing to stream chunks

Stream chunks were serialized as raw devalue text without the 'devl'
format prefix or any framing, unlike step inputs/outputs.

Core changes (serialization.ts):
- getSerializeStream: each chunk is now wrapped with
  encodeWithFormatPrefix('devl', payload) inside a length-prefixed
  frame: [4-byte big-endian length][format-prefixed payload]
- getDeserializeStream: handles both length-prefixed framed chunks
  (current) and legacy newline-delimited text (backwards compatible)
- Added 6 unit tests covering framing, round-trip, concatenation,
  splitting, and legacy fallback

World-local changes (streamer.ts):
- Stream chunk files now use .bin extension instead of .json
- readFromStream lists both .bin and .json files for backwards compat
- Updated streamer.test.ts to expect .bin extension

Split changesets: core (format + framing) and world-local (.bin extension)
2026-02-12 11:06:41 -08:00
Peter Wielander b2ba6ff70e [ci] Add flag that can disable non-vercel e2e tests (#1006) 2026-02-11 16:53:55 -08:00
JJ Kasper dc2dc6ac79 next: stop esbuild bundling for deferred step route (#992)
* next: remove deferred step esbuild bundle and use copied step files

* chore: bump workspace Next.js deps to canary

* web: avoid type re-exports from use server actions module

* next: resolve copied step import extensions for symlinked TS sources

* next: fix deferred copied step dependency discovery and imports

* next: add deferred step copy source-map plumbing

* add changeset

* next/builders: scope deferred logging and warning suppression

* next: derive deferred transitive entries from tracked deps

* fix(next): include transitive step files in deferred step copy

* fix(next): include transitive serde files in deferred builds

* fix(next): clean deferred artifacts on startup

* reset to stable next

* bump
2026-02-11 16:26:41 -08:00
Vercel Release Bot 9f442d2dd7 Version Packages (beta) (#987)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
workflow@4.1.0-beta.55
2026-02-11 15:46:37 -08:00
Nathan Rajlich 054e40c91b Fix anonymous class expression names for serialization classes (#991)
When upstream bundlers like tsup/esbuild transform class declarations into anonymous class expressions (e.g. `var Foo = class { ... }`), the SWC plugin now re-inserts the binding name into the class expression (e.g. `var Foo = class Foo { ... }`). This preserves the class `.name` property through subsequent bundling passes, preventing runtime failures in downstream bundlers like Nitro's Rollup bundler that rely on the class expression name for serialization class registration.
2026-02-11 23:34:01 +00:00
Pranay Prakash 87478d7d54 ignore jj (jujitsu vcs files) 2026-02-11 15:00:52 -08:00
Nathan Rajlich a4a9b526a8 docs: use markdown image syntax in observability page (#1008) 2026-02-11 14:44:11 -08:00
JJ Kasper bb45816f0a Fix hydration mismatch in Next.js workbench workflow defaults (#1009) 2026-02-11 22:44:04 +00:00
JJ Kasper 8cb7be5f48 Ensure unit tests are included in required check (#1001) 2026-02-10 23:25:48 -08:00
JJ Kasper 3d770d5385 Expose workflows manifest under diagnostics folder (#998)
* Expose workflows manifest under Next.js diagnostics folder

* feat(builders): emit diagnostics manifest for vercel world

* dedupe function

* update changeset

* fix(builders): write vercel diagnostics manifest under workflows

* fix(builders): rename vercel diagnostics manifest path
2026-02-10 17:25:19 -08:00
JJ Kasper d8a9ee9c29 ci: add final E2E required-check job (#996)
* ci: add final e2e required check job

* bump
2026-02-10 15:30:42 -08:00
Nathan Rajlich 613d008c6b Remove @workflow-worlds/starter from worlds manifest (#995)
The starter world uses in-memory storage that can't be shared across
processes, making E2E tests fundamentally incompatible with the CI
setup (test runner and dev server run in separate processes). This
caused the "E2E Community World (Starter)" job to hang until the
30m timeout.
2026-02-10 14:48:43 -08:00
JJ Kasper a5935abec7 Add lazy workflow/step discovery via deferredEntries in next (#976)
* Add lazy workflow/step discovery via deferredEntries in next

* discover serde in loader

* fix implicit steps discovery

* update serde

* fix symlink case

* pre-warm non-next

* add resolution cache

* Fix deferred build error propagation and restore start-arg workflow

* revert

* Fix step function alias lookup and dev prewarm stability

* Fix step function alias tests for updated reviver context

* next: address deferred discovery review feedback

* update lockfile

* builders: extract shared workflow alias resolver

* chore: pin next deps to latest canary

* unlock from canary version from testing
2026-02-10 12:03:59 -08:00
Karthik Kalyan fc4cad6808 [core] Fix workflow runtime error normalization to avoid [object Promise] and preserve provider failure context (#989)
* [workflow/core] normalize errors cleanly

* test fix
2026-02-10 09:44:13 -08:00
JJ Kasper da64519db8 ci: use PR head SHA for tests workflow checkout (#988)
Signed-off-by: JJ Kasper <jj@jjsweb.site>
2026-02-09 16:51:16 -08:00
Nathan Rajlich 1adcc6a618 Fix discovery of serde classes to detect [WORKFLOW_SERIALIZE] and [WORKFLOW_DESERIALIZE] computed property usage in bundled code (#908) 2026-02-09 15:05:56 -08:00
Peter Wielander 56f22219b3 [core] Handle 429 and 500 errors from worlds in runtime (#966) 2026-02-09 14:43:12 -08:00
Vercel Release Bot d25630a27c Version Packages (beta) (#971) workflow@4.1.0-beta.54 2026-02-09 13:47:40 -08:00
Peter Wielander aa448c29b4 [world-vercel] Allow specifying backend URL via env variable (#985) 2026-02-09 13:38:38 -08:00
Dustin Townsend ea145a65d3 Fix manifest 404 errors in community world e2e tests (#977)
Add missing WORKFLOW_PUBLIC_MANIFEST env var to e2e-community-world.yml.
The e2e tests fetch the workflow manifest from
/.well-known/workflow/v1/manifest.json, but this endpoint is only
exposed when WORKFLOW_PUBLIC_MANIFEST=1 is set. Every other CI workflow
(tests.yml, benchmarks.yml, benchmark-community-world.yml) already sets
this variable, but it was missing from the community world e2e workflow.

https://claude.ai/code/session_01GezavpthQcEpyYVisScncM

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-08 12:16:17 -08:00
Nathan Rajlich d9e98590fa Generate runId client-side in start() and simplify streaming types (#954)
## Summary

- Generates `runId` client-side in `start()` using ULID (`wrun_${ulid()}`), instead of deferring to the server
- Passes client-generated `runId` to `events.create()` and verifies the server accepted it
- Simplifies `runId` types from `string | Promise<string>` to `string` throughout the serialization layer
- Removes the `withResolvers<string>()` / deferred runId pattern that was needed when `runId` was server-generated
- Updates `Streamer` interface and `WorkflowServerWritableStream` to use plain `string` runId
- Updates `Storage.events.create()` first overload to accept `string | null` instead of just `null`

This is a prerequisite for E2E encryption, where we need the `runId` before serialization so it can be used as part of the encryption context.

## Test plan

All existing tests pass. Updated `start.test.ts` to expect client-generated runId, removed `writable-stream.test.ts` promise runId test (no longer applicable).
2026-02-08 00:27:40 -08:00
Nathan Rajlich fcfaf8bbaa Support step function serialization in client mode (#924) 2026-02-07 15:34:29 -08:00
JJ Kasper 2d1d69f4ca Fix getImportPath package handling (#972)
* Fix getImportPath package handling

* Add root entrypoint comments and coverage for package imports
2026-02-07 14:02:07 -08:00
Nathan Rajlich ef23b0be77 Fix step functions nested multiple levels deep in an object (#923)
Fixed a bug where step functions nested multiple levels deep in an object weren't being properly transformed.

### What changed?

- Enhanced the SWC plugin to recursively process nested objects to find step functions
- Added support for step functions defined inside deeply nested object properties, including function call arguments
- Implemented proper path handling for nested step IDs, using compound paths (e.g., `vade/tools/VercelRequest/execute`)
- Fixed variable name generation by replacing slashes with `$` to create valid JavaScript identifiers
- Updated documentation with examples of nested object property step functions
- Added test cases for deeply nested step functions and agent tool steps

### How to test?

Test with code that has step functions nested multiple levels deep in objects:

```javascript
export const config = createConfig({
  level1: {
    level2: {
      level3: {
        myStep: async (input) => {
          "use step";
          return input * 2;
        },
      },
    },
  },
});
```

Or with agent tools:

```javascript
export const vade = agent({
  tools: {
    VercelRequest: {
      execute: async (input, ctx) => {
        "use step";
        return 1 + 1;
      },
    },
  },
});
```

### Why make this change?

This fix ensures that step functions can be properly identified and transformed regardless of how deeply they are nested in object structures. This is particularly important for complex configurations like agent tools where step functions might be several levels deep in the object hierarchy.
2026-02-07 11:14:20 -08:00
Nathan Rajlich 5b5b36a03b [e2e] Remove /api/hook and /api/test-health-check endpoints, call APIs directly
- Remove debug logging from bench.bench.ts
- Remove awaitReturnValue() wrapper, use run.returnValue directly in benchmarks
- Add changeset for Nitro builder manifest fix
- Refactor hookWorkflow tests to call getHookByToken()/resumeHook() directly
  (pass hook object to resumeHook to avoid duplicate lookups)
- Refactor queue-based health check test to call healthCheck() directly
- Assert specific error message for invalid hook token test
- Remove /api/hook and /api/test-health-check endpoints from all workbench apps
  (nextjs-turbopack, nextjs-webpack, vite, hono, express, fastify, sveltekit,
  astro, nuxt, nitro-v2, nitro-v3, nest, example)
2026-02-07 09:00:19 -08:00
JJ Kasper 82c209c669 Add missing env for tests on deploy (#973) 2026-02-06 16:57:11 -08:00
Nathan Rajlich 86f62f2779 Refactor e2e tests to no longer use "trigger" endpoint (#958)
## Summary

Refactors the E2E tests to call `start()` from `workflow/api` directly instead of going through the `/api/trigger` HTTP endpoint in each workbench app. This removes a layer of indirection — the tests now use the same API that users would use to start workflows programmatically.

### Before

```ts
const run = await triggerWorkflow('addTenWorkflow', [123]);
const returnValue = await getWorkflowReturnValue(run.runId);
```

- `triggerWorkflow()` sent an HTTP POST to `/api/trigger` on the workbench app
- The workbench app looked up the workflow function, called `start()`, and returned the run ID
- `getWorkflowReturnValue()` polled `GET /api/trigger?runId=...` until the workflow completed

### After

```ts
const run = await start(await e2e('addTenWorkflow'), [123]);
const returnValue = await run.returnValue;
```

- `e2e()` / `getWorkflowMetadata()` fetches the manifest from `/.well-known/workflow/v1/manifest.json` to look up the correct `workflowId`
- `start()` is called directly from the test process via the configured World
- `run.returnValue` polls for completion via the World (no HTTP polling endpoint needed)

### Changes

**`packages/core/e2e/e2e.test.ts`**
- Removed `triggerWorkflow()` and `getWorkflowReturnValue()` helpers
- Added `fetchManifest()` to fetch and cache the workflow manifest from the deployment
- Added `getWorkflowMetadata(file, fn)` to look up `{ workflowId }` from the manifest
- Added `e2e(fn)` shorthand for the common case of `workflows/99_e2e.ts`
- All tests call `start()` and `run.returnValue` directly
- Error tests use `.catch()` to inspect `WorkflowRunFailedError`
- Output stream tests use `run.getReadable()` directly (skipped on local world where cross-process streaming isn't supported)
- `beforeAll` configures the local World with the correct data directory and base URL
- Pages Router tests use `startWorkflowViaHttp()` to specifically validate the HTTP trigger path

**Workbench apps (hono, express, fastify, nest)**
- Removed `/api/trigger` route handlers
- Kept `/api/hook`, `/api/test-direct-step-call`, `/api/test-health-check` endpoints
- Re-added `_workflows.js` side-effect import for hono/express/fastify to maintain Nitro's HMR dependency graph

**Deleted trigger-only route files** from: nextjs-turbopack, nextjs-webpack, vite, sveltekit, astro, nuxt, nitro-v2, nitro-v3, example

**`.github/workflows/tests.yml`**
- Added `WORKFLOW_PUBLIC_MANIFEST: '1'` to all E2E test jobs

### Dependencies

Stacked on #963 which adds `WORKFLOW_PUBLIC_MANIFEST` support to all framework builders.
2026-02-06 16:25:47 -08:00
Peter Wielander f7fd88ea96 [core] Tidy health check latency calculation (#951) 2026-02-06 15:54:53 -08:00
Vercel Release Bot 835e2fbd3c Version Packages (beta) (#921)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
workflow@4.1.0-beta.53
2026-02-06 14:20:08 -08:00
Nathan Rajlich a733449e48 Fix release PR bad lockfile (#968) 2026-02-06 14:08:43 -08:00
Nathan Rajlich 54432085e1 Ignore "swc-playground" app in changesets (#967) 2026-02-06 13:51:30 -08:00
Karthik Kalyan f090de1eb4 Refactor web-shared to separate UI components from server concerns (#927)
* Refactor web-shared to separate UI components from server concerns

- Move UI components into src/components/ subdirectory
- Move server actions and API client to @workflow/web
- Add world-actions/ module with shared helpers (recreateRun, cancelRun, etc.)
- Add runtime subpath exports to @workflow/core
- Components now accept callbacks as props for data fetching

* add support for streams

* update lock file

* Migrate helpers from web-shared to core

* Migrate helpers from web-shared to core

* Migrate helpers from web-shared to core

* Migrate helpers from web-shared to core

* Fix claude's comments

* Fix server actions bug for web

* fix copilot bugs
2026-02-06 13:16:48 -08:00
Nathan Rajlich 661724c01e Expose workflow manifest via HTTP when WORKFLOW_PUBLIC_MANIFEST=1 (#963)
## Summary

When `WORKFLOW_PUBLIC_MANIFEST=1` is set, each framework builder exposes the workflow manifest at `/.well-known/workflow/v1/manifest.json` via the most appropriate mechanism for the framework:

- **Next.js**: Copies manifest to `public/.well-known/workflow/v1/manifest.json` (served as a static file)
- **SvelteKit**: Copies manifest to `static/.well-known/workflow/v1/manifest.json` (served as a static file)
- **Vercel Build Output API** (example workbench): Copies manifest to `.vercel/output/static/.well-known/workflow/v1/manifest.json` (served as a static file)
- **Nitro** (vite, hono, express, fastify, nuxt, nitro-v2, nitro-v3): Registers a virtual handler that reads and serves the manifest JSON
- **Astro**: Generates a `manifest.json.js` page route that returns the manifest JSON
- **NestJS**: Adds a `@Get('manifest.json')` endpoint on the `WorkflowController` (gated by the env var at runtime)

### Other changes

- `BaseBuilder.createManifest()` now returns the manifest JSON string so framework builders can use it
- Added `shouldExposePublicManifest` getter to `BaseBuilder`
- Fixed Astro `LocalBuilder` which was missing the `createManifest()` call that all other framework builders have
- Removed unused `public/` directory and static file copy from the example workbench build script
- Added `WORKFLOW_PUBLIC_MANIFEST` to `turbo.json` build task env so Vercel builds can access it
- Added generated manifest paths to `.gitignore`
2026-02-06 12:22:37 -08:00
Pranay Prakash 01732dac93 [docs] Add single-region limitation to Vercel World (#953)
* [docs] Add single-region limitation to Vercel World page

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

* [docs] Use bullet point format for limitations section

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 11:52:26 -08:00
Pranay Prakash 088de0ae42 Various Performance Improvements (#932)
* Parallelize async operations in step handler for performance

- Parallelize getPort(), getSpanKind(), and world.steps.get() calls
- Start step_started event creation while hydrating arguments (CPU work)
- Parallelize step_completed event with trace serialization

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

* Fix race condition: await step_started before hydration

Reverts Optimization 1 to fix a race condition where hydrateStepArguments()
could throw before stepStartedPromise was awaited, causing stale step.attempt
in the catch handler and potentially allowing extra retries.

Optimizations 0 and 2 are preserved as they don't have this issue.

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

* Skip initial world.steps.get() in step handler for performance

Eliminate the world.steps.get() HTTP call by calling step_started first
and relying on server-side validation. This saves 50-80ms per step
execution by removing one HTTP round-trip.

The server (workflow-server) now validates:
- Step not in terminal state (returns 409)
- retryAfter timestamp reached (returns 425 with Retry-After header)
- Workflow still active (returns 410 if completed)

Changes:
- Remove world.steps.get() from initial Promise.all
- Call step_started first to get step entity and validate state
- Handle 409 (terminal state) by re-queueing workflow
- Handle 425 (retryAfter not reached) by returning timeout
- Handle 410 (workflow gone) as no-op

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

* Add retryAfter validation to local and postgres worlds

Add server-side retryAfter validation to match workflow-server behavior:
- Check retryAfter timestamp before allowing step_started
- Return HTTP 425 with retryAfter timestamp in response meta
- Clear retryAfter field when step starts successfully

This ensures consistent behavior across all world implementations
and allows the step-handler optimization to work correctly.

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

* Fix step terminal state HTTP status code to 409 (Conflict)

Aligns local and postgres worlds with workflow-server, which returns
409 via InvalidOperationStateError for step in terminal state.

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

* Fix world-vercel telemetry to use parent application's tracer

The world-vercel package was creating spans under a separate
'workflow-world-vercel' service name, causing HTTP spans for
workflow-server API calls (step_started, step_completed) to be
filtered out when viewing traces for the main application service.

Now uses the same 'workflow' tracer name as @workflow/core to
ensure all spans are reported under the parent application's service.

* Update /demo command to include OTEL tracing with Jaeger

- Start Jaeger container for local trace visualization
- Configure OTEL exporter environment variables for dev server
- Open Jaeger UI automatically
- Add documentation about available trace attributes

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

* Add OTEL instrumentation to world-local and improve trace consistency

- Add telemetry.ts and instrumentObject.ts to world-local for tracing parity
  with world-vercel (world.runs, world.steps, world.events, world.hooks spans)
- Change workflow span name from uppercase "WORKFLOW" to lowercase "workflow"
  for consistency with step spans and OTEL naming conventions
- Add step.execute child span to trace actual user step function execution
  separately from step handler infrastructure

These changes enable local development to have the same observability as
production deployments, making performance analysis and debugging easier.

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

* Use uppercase WORKFLOW and STEP span names for consistency with HTTP spans

HTTP spans use uppercase methods like "GET /path" and "POST /path".
Following the same convention, workflow and step spans now use:
- WORKFLOW <workflow-name>
- STEP <step-name>

Child spans (workflow.run, workflow.loadEvents, step.execute, world.events.create)
remain lowercase as they represent internal operations, not top-level entries.

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

* Add W3C trace context headers to step queue messages

Include traceparent and tracestate headers when queueing step
execution messages. This enables automatic trace propagation
by Vercel's infrastructure, potentially linking step invocation
spans to the parent workflow trace.

The trace carrier is now serialized once and included in both:
- Payload: for manual context restoration in step handler
- Headers: for automatic HTTP-based trace propagation

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

* Improve OTEL tracing with service attribution and enhanced instrumentation

- Add peer.service attributes for workflow-server and VQS for Datadog service maps
- Rename queueMessage span to queue.publish for consistency
- Add step.hydrate, step.dehydrate, and workflow.replay spans
- Include event type in world.events.create span names (e.g., "world.events.create step_started")
- Add span.recordException() for errors with category classification (fatal/retryable/transient)
- Add span events for milestones: retry.scheduled, step.skipped, step.delayed
- Add HTTP semantic conventions with peer.service for world-vercel HTTP calls
- Add baggage propagation for workflow context (run_id, workflow_name)

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

* Update changesets to reflect all PR changes

- step-handler-parallelization.md: Add race condition fix and 409 status code fix
- world-vercel-telemetry-tracer.md: Add peer.service and event type in span names
- otel-tracing-improvements.md: New changeset for comprehensive OTEL improvements

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

* Use span name for rpc.method to show event type in Datadog resource

Datadog derives the resource name from rpc.method attribute. Updated to
use the full span name (which includes event type) instead of just the
method name, so Datadog shows "world.events.create step_started" instead
of "world.events.create workflow-server".

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

* Use lazy ref resolution for events where client discards response data

Skip expensive S3 ref resolution (~200-460ms) for event types where
the client doesn't use the response entity data (step_created,
step_completed, step_failed, run_completed, etc). Only resolve refs
for run_created, run_started, and step_started where the client
reads the response.

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

* Update changeset to include lazy ref resolution optimization

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

* Address PR review comments: module scope const, comment cleanup, status code audit

- Move eventsNeedingResolve Set to module scope (PR review #2)
- Rewrite changelog-style OPTIMIZATION comments as current-state docs (PR review #4)
- Fix run terminal state errors: 410 → 409 in world-local and world-postgres to
  match workflow-server's InvalidOperationStateError (409) (PR review #3)
- Fix remaining step terminal state 410 → 409 in world-postgres fallback paths

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:52:03 -08:00
Karthik Kalyan fc077108ef Remove dirs option (#961) 2026-02-05 22:56:03 -08:00
Nathan Rajlich 90ca1eff27 Remove "beads" config (#952)
* Remove "beads" config

* Remove .beads
2026-02-05 16:19:16 -08:00
Nathan Rajlich c28d6ca660 Upload Next.js server logs as artifact for Windows E2E (#946) 2026-02-05 11:10:22 -08:00
Nathan Rajlich efb33b2b5e Pass class as this context to custom serializer/deserializer methods (#947)
* fix: pass class as this context to custom serializer/deserializer methods

When calling WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE static methods,
use .call(ctor, value) and .call(cls, data) respectively to ensure the
class is passed as 'this' context. This is necessary for serializers that
access static properties or methods via 'this'.

Adds test case to verify this behavior.

* fix: address PR review feedback

- Fix test to actually use `this` instead of `Counter` directly, which
  properly validates the fix
- Standardize variable naming: rename `ctor` to `cls` in the Instance
  reducer for consistency with the reviver

* Add changeset

* fix: use this in test serde functions with biome-ignore

The test now properly uses `this.serializedCount` and `this.deserializedCount`
to validate the fix. Added biome-ignore comments to prevent the linter from
auto-fixing `this` back to the class name.
2026-02-05 11:09:53 -08:00
Peter Wielander 4712931c26 [docs] Fix image link in NestJS getting started guide (#941) 2026-02-05 09:44:27 -08:00