mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
codex/atomic-start-capabilities
1748 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
98ca60c318 |
Replace fixed-sleep hook waits with event-driven waitForHook helper (#1879)
Hook-related e2e tests (hookWorkflow, hookCleanupTestWorkflow,
hookDisposeTestWorkflow, hookWithSleepWorkflow, distributedAbortController)
previously slept a fixed 5 seconds before calling getHookByToken to wait
for the hook to be registered. On slower runtimes — notably the snapshot
runtime on Vercel where each workflow round-trip is several seconds longer
than replay — that fixed budget is too tight and the test fails with
HookNotFoundError. On faster runtimes it's unnecessarily slow.
Adds a waitForHook(token, { timeoutMs, intervalMs, runId }) helper that
polls until the hook resolves or the timeout (default 30s) expires, with
an optional runId filter for token-reuse tests where eventually-consistent
backends may briefly still report a stale hook. Each hook-wait site now
uses this helper. Non-hook fixed sleeps (workflow-progress polling for
sleepingWorkflow cancel tests, payload-processing waits in
hookWithSleepWorkflow) are unchanged.
|
||
|
|
c1163eb146 | [world-local] Reject run_failed on non-existent run (#1894) | ||
|
|
2f52d14f38 | [vitest] [world-local] Fix local-world data recovery isolation (#1895) | ||
|
|
cd50618d1f |
ci: switch Vercel deployment-protection bypass to OIDC Trusted Sources (#1882)
* ci: switch Vercel deployment-protection bypass to OIDC Trusted Sources The e2e, benchmark, and docs-smoke CI jobs previously used the static `VERCEL_AUTOMATION_BYPASS_SECRET` deployment-protection bypass token to reach protected Vercel deployments. Switch them over to the new OIDC Trusted Sources flow: the GitHub Actions runner mints a short-lived OIDC token via `core.getIDToken()` and forwards it on requests in the `x-vercel-trusted-oidc-idp-token` header. Each workbench project (and `workflow-docs`) has been configured with a matching trusted-source rule: aud=https://github.com/vercel, repository=vercel/workflow The shared header helper now lives at `scripts/trusted-sources-headers.mjs` and is imported by both the e2e/bench tests and the docs smoke script, removing the previous duplication. * rename to VERCEL_OIDC_TOKEN and wire through world-vercel - Rename the env var from VERCEL_TRUSTED_OIDC_TOKEN to VERCEL_OIDC_TOKEN to match Vercel's convention (also read by @vercel/oidc's getVercelOidcToken()). - In @workflow/world-vercel, replace the legacy VERCEL_WORKFLOW_SERVER_PROTECTION_BYPASS / x-vercel-protection-bypass flow with VERCEL_OIDC_TOKEN / x-vercel-trusted-oidc-idp-token. The trusted-source header is attached on every outbound workflow-server request (both proxied through api.vercel.com and direct). - Drop the bypass header from the encryption-key and resolve-latest-deployment fetches: those go to api.vercel.com which is public. - Drop VERCEL_WORKFLOW_SERVER_PROTECTION_BYPASS plumbing from tests.yml. - Update the pending world-vercel changeset to describe the final trusted-sources flow. * . * . * ci: add statuses:read permission for wait-for-vercel-project action The action queries /commits/{sha}/status (Commit Statuses API) in addition to the Deployments API, in order to extract the Vercel `dpl_...` ID. With an explicit permissions block in place, GITHUB_TOKEN now needs `statuses: read` or the action 403s when resolving the deployment ID. Reported by Copilot review on #1882. * ci(docs): log status code and body when waitForServer times out Helps diagnose deployment-protection / OIDC-trusted-source bypass failures (e.g. SSO redirects) on the workflow-docs preview. * ci(docs): log OIDC token claims (aud, repository, etc.) for diagnostics Helps determine whether the bypass is failing because of missing trusted-source config, claim mismatch, or audience mismatch. * ci(docs): add curl debug step to verify OIDC header reaches Vercel * . * ci: remove debug logging now that trusted-sources config is correct The fetch-failure root cause was the trusted-sources rule format: the labs workbench projects had been PATCHed with just `to.slugs` (no `preset`), but Vercel's edge requires the dashboard-form-style `to.preset: 'all-custom'` field plus `development` in the slug list to match incoming requests. After re-PATCHing all projects with the correct format, the bypass works end-to-end. * ci(docs): debug — test trusted-sources bypass against docs and labs deployments Trying repository_owner claim added to one labs project to see if that fixes the bypass. * ci(docs): revert curl debug step The GitHub Actions OIDC trusted-sources bypass returns 401 on all tested projects regardless of claim configuration (including workflow-docs which was set up via the dashboard). This is not a per-project config issue. Need to investigate with Vercel team before continuing. * ci(docs): probe trusted-sources bypass and surface x-vercel-id Adds a debug step that does two HEAD requests against the docs preview deployment (with and without the OIDC trusted-sources header) and prints the response status line plus `x-vercel-id` for each. The proxy-side trusted-sources changes for GitHub Actions OIDC tokens are rolling out gradually (~12+ hours), so the edge-node identifier in `x-vercel-id` helps explain why a request might succeed or fail during the rollout window. Also includes `x-vercel-id` in the `waitForServer` timeout error so post-mortem analysis of failing runs has the same edge-node info. * ci(docs): drop trusted-sources curl probe — bypass works once proxy fix reaches the serving edge node The probe served its purpose: confirmed the bypass is functional once the request lands on a region that has the proxy-side trusted-sources fix rolled out. The waitForServer error message still surfaces x-vercel-id for any future rollout-window debugging. * . * world-vercel: log outbound OIDC token claims once per process Adds a one-shot diagnostic that prints the non-sensitive claims of the OIDC token (`iss`, `aud`, `owner_id`, `project_id`, `environment`, `sub`, `scope`, `exp`) on the first request that uses bearer auth. This is invaluable for debugging Vercel deployment-protection trusted-source rule mismatches: a 401 from the edge tells you nothing about why the rule didn't match, and the token's claims are the only thing that determines that. The signature is never logged. Gated to once per process — Vercel-issued tokens are process-stable for the lambda's lifetime so further log lines would just be redundant spam. * world-vercel: route trusted-sources header through getVercelOidcToken() The Authorization bearer correctly preferred config.token (a static Vercel auth token from CLI / Actions runner) and fell back to getVercelOidcToken() inside a Vercel function. But the trusted-sources bypass header (x-vercel-trusted-oidc-idp-token) was being read directly from process.env.VERCEL_OIDC_TOKEN inside getHeaders(). That env var is the bake-time token, frozen at deployment-creation time — on a project that has been redeployed after a settings change, it carries stale claims (e.g. an iss from when the project was briefly in 'global' mode) that no longer match the workflow-server's trusted-sources rule. Move trusted-sources header attachment from getHeaders() (sync) to getHttpConfig() (async) and source it from getVercelOidcToken(). That function reads getContext().headers['x-vercel-oidc-token'] first — a freshly minted per-request token that always reflects current project settings — and only falls back to the env var when that header is missing. Bearer auth source remains config.token-first. Also expand the diagnostic to log claims from BOTH the per-request OIDC token AND the bake-time env var so the divergence is visible in logs when debugging future trusted-source mismatches. Removes the now-misleading getProtectionBypassHeader() helper (its 'read env var directly' semantics were exactly the bug). * world-vercel: skip OIDC trusted-sources header on proxied path The two outbound flows have different auth requirements: 1. Proxied (usingProxy=true) — calls api.vercel.com/v1/workflow. Public endpoint, authenticated with a static Vercel auth token via config.token. The api-workflow proxy mints its own OIDC token before forwarding to workflow-server, so the trusted-sources bypass header on the SDK→proxy hop is meaningless. CLI, GitHub Actions, and other API-client callers take this path. 2. Direct (usingProxy=false) — runs inside a Vercel deployment talking straight to workflow-server. workflow-server validates a Vercel OIDC bearer; Vercel's edge validates the trusted-sources header. Both must come from getVercelOidcToken() (the per-request fresh token), not process.env.VERCEL_OIDC_TOKEN (the bake-time token that can be stale after a project config change). Previously getHttpConfig attached x-vercel-trusted-oidc-idp-token on both paths whenever getVercelOidcToken() resolved. That accidentally forwarded the GitHub Actions OIDC token (when wired into VERCEL_OIDC_TOKEN by the test runner) onto every SDK→proxy request, which is harmless but wrong-by-design — the proxy is public, doesn't look at that header on its inbound side, and the GHA token isn't its intended audience. Bearer auth source rules: - Proxied: only config.token. (No fallback to OIDC; that auth pathway doesn't go through the proxy's auth checks.) - Direct: config.token (for tests / local dev), falling back to getVercelOidcToken() (for Vercel-runtime calls). * world-vercel: throw if proxied path is hit without a Vercel auth token The api-workflow proxy authenticates the caller with a regular Vercel auth token (not OIDC), so reaching the proxied path with no config.token is always wrong: the proxy will reject the request and the SDK caller would see an opaque 401 with no actionable hint. Throw at config-resolution time with a clear message that points to the WORKFLOW_VERCEL_AUTH_TOKEN env var the SDK reads from. Adds tests covering both the no-token-throws case and the with-token-attaches- bearer-and-skips-trusted-sources case. * test(e2e): include x-vercel-id in startWorkflowViaHttp error message When the trusted-sources bypass returns 401, the error message now surfaces the response's x-vercel-id header so we can identify which edge node served the failure. Helps distinguish proxy-rollout incompleteness from actual config errors during incremental rollouts of edge-side changes. * ci: mint GHA OIDC tokens on demand to survive 5-minute expiry GitHub Actions OIDC tokens have a hard 5-minute lifetime that cannot be extended (no API to ask for a longer TTL — exp is always iat + ~300s). Pre-minting once at the start of the job and shipping the result down to the test runner via env var means tests that run late in the suite hit an expired token and 401 on /api/trigger-pages (and any other trusted-sources protected endpoint). Move minting into scripts/trusted-sources-headers.mjs: - getTrustedSourcesHeaders() is now async. - It calls the runner's ACTIONS_ID_TOKEN_REQUEST_URL endpoint directly (the env vars GHA exposes when permissions: id-token: write is on) and re-mints 60s before the cached token's exp. - Falls back to process.env.VERCEL_OIDC_TOKEN for non-GHA contexts (Vercel runtime, local dev). Workflow files drop the now-redundant 'Mint OIDC token' step and the VERCEL_OIDC_TOKEN env-var passthrough on the test step. The runner env vars propagate to subsequent steps automatically. Updates all 17 callers in e2e.test.ts / bench.bench.ts / utils.ts / docs/scripts/check-docs-smoke.mjs to await the now-async call. * address PR #1882 code review - Drop `statuses: read` from the three workflow permission blocks (the wait-for-vercel-project action works without it on a public repo). - Revert the `x-vercel-id` debug logging in `startWorkflowViaHttp`. - Delete `packages/world-vercel/src/jwt-claims.ts` (debug-only helper). - Drop the JWT claims diagnostic logging from `getHttpConfig`. - Tighten the auth-flow comment in `getHttpConfig` and remove the historical 'no longer attaches' note from `getHeaders`/its test. - Restore `.changeset/world-vercel-protection-bypass.md` (already shipped in a beta release per .changeset/pre.json). - Trim the `.changeset/world-vercel-trusted-sources.md` description to one short paragraph. * docs(AGENTS): document local VERCEL_OIDC_TOKEN via vercel env pull Configured trustedSources.projects on all 11 workbench app projects so each one accepts a Vercel-issued OIDC token from any of the others. A developer running e2e locally can now do `vercel env pull` from any workbench app's directory and use the resulting VERCEL_OIDC_TOKEN to bypass Deployment Protection on any of the workbench preview/prod deployments — no need to disable protection on the project just to run the suite locally. |
||
|
|
072e86bbef |
Add additional tests for event consumer fixes for hook/sleep/step race conditions (#1528)
Co-authored-by: Peter Wielander <mittgfu@gmail.com> |
||
|
|
74b13cd3ed |
Add first-class serialization for FatalError and RetryableError (#1513)
* Add WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE to FatalError and RetryableError
Add custom serialization methods to FatalError and RetryableError in
@workflow/errors, enabling the SWC plugin to discover and register them
through the standard class serialization pipeline. This preserves class
identity (instanceof), the fatal flag, and the retryAfter date when
these errors cross serialization boundaries.
- Add @workflow/serde dependency to @workflow/errors
- Add WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE static methods to both classes
- Add unit tests verifying Instance-based round-trip serialization
- Add e2e workflow tests verifying class identity preservation end-to-end
* Address review feedback on FatalError/RetryableError serde
- FatalError: preserve cause property when present (Copilot feedback)
- RetryableError: preserve cause property when present
- RetryableError: serialize retryAfter as numeric timestamp for realm
safety (the Date reducer uses instanceof global.Date which fails
across VM realms; timestamps sidestep that issue)
- Replace e2e tests with step return value serialization (step throw
path always reconstructs errors as FatalError, so those tests don't
exercise the new serde code path)
- Add unit tests for cause preservation on both classes
* Bump fatal-retryable-error-serialization changeset to minor
Adding WORKFLOW_SERIALIZE / WORKFLOW_DESERIALIZE hooks to FatalError
and RetryableError is a feature, not a bug fix.
* Switch FatalError/RetryableError to first-class serialization
Replace the WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE static methods on
FatalError and RetryableError with dedicated reducers/revivers in the
common serialization module. The Instance/Class pipeline relies on the
SWC plugin discovering classes and registering them by classId, which
means values constructed in environments that don't run the plugin
(vitest e2e runner, ad-hoc Node scripts) can't be deserialized.
Treating FatalError/RetryableError as first-class serialization targets
makes them round-trip from any environment with no setup, matching the
behavior of TypeError, RangeError, etc. added in the previous commit.
- Drop @workflow/serde dependency on @workflow/errors
- Remove WORKFLOW_SERIALIZE/DESERIALIZE statics from FatalError/RetryableError
- Add FatalError/RetryableError reducers to serialization/reducers/common.ts
with cached base-reducer factories for the subclasses that wrap the
shared shape (RetryableError, AggregateError)
- Migrate unit tests off registerSerializationClass setup
- Extend the errorSubclassRoundTripWorkflow e2e test to cover FatalError
and RetryableError, and drop the parallel errorFatalSerdeRoundTrip /
errorRetryableSerdeRoundTrip tests
* Address review feedback on FatalError/RetryableError serde
- Soundness: split makeErrorSubclassReducer into a shared base helper
(reduceErrorBase / reduceNamedErrorSubclassBase returning the
BaseErrorPayload shape) plus a thin wrapper constrained to subclass
keys whose serialized shape is exactly that base payload. The
AggregateError and RetryableError reducers — which extend the base
with extra fields — now consume reduceNamedErrorSubclassBase
directly instead of calling makeErrorSubclassReducer with an
unsound type cast. The compiler now rejects accidental misuse
(SimpleErrorSubclassKey type guard).
- Realm safety: RetryableError reviver constructs retryAfter via
new global.Date(...) to match the rest of the module and ensure
the resulting Date passes instanceof global.Date checks in the
target realm.
- Test strength: assert serialized payloads contain the literal
devalue marker ["FatalError",N] / ["RetryableError",N] rather
than the bare class name (which would also match a generic Error
payload whose name happens to be "FatalError"). Also assert the
generic ["Error",N] marker is absent.
* Match Error subclass reducers by `value.name`, not constructor name
Bundlers like Turbopack compile `export class FatalError extends Error
{...}` into a registration call like `e.s(["FatalError", 0, class
extends Error {...}])` — passing an anonymous class expression as a
function argument. The resulting constructor function has `name === ''`,
which broke the previous `value.constructor?.name === subclassName`
match: an instance of the bundled FatalError class no longer matched the
dedicated FatalError reducer and instead fell through to the generic
`Error` reducer, losing class identity across the workflow boundary.
This was caught by the local-prod CI matrix, where each Next.js route
gets its own bundled chunk: a real `new FatalError('fatal!')` returned
from a workflow was serialized as a plain Error and revived without
`instanceof FatalError` holding on the consumer side.
Switch the match in `reduceNamedErrorSubclassBase` to `value.name`,
which:
- works for built-in subclasses (TypeError/RangeError/… all set
`name` automatically and aren't bundled, so behavior is unchanged
in practice).
- works for FatalError/RetryableError, whose constructors set
`this.name` explicitly — robust across realms AND bundlers.
- is consistent with how `FatalError.is()` / `RetryableError.is()`
already identify their values.
Two existing cross-VM Error tests (added in #1164) used `name = 'FatalError'`
on a plain Error to stand in for any cross-realm error — which now hits
the dedicated FatalError reducer (returning a host-realm FatalError)
instead of the generic Error reducer (which constructs a VM-realm Error).
Renamed the stand-in to `'CustomError'` so they continue to exercise the
intended path.
|
||
|
|
d5517a1814 | docs: note WORKFLOW_PUBLIC_MANIFEST=1 requirement for local e2e dev server (#1890) | ||
|
|
e7ea0684f4 |
Add first-class serialization for built-in Error subclasses (#1511)
* Add first-class serialization for built-in Error subclasses Replace the generic Error reducer/reviver with specific handlers for TypeError, RangeError, SyntaxError, URIError, ReferenceError, EvalError, and AggregateError. Preserve the cause property on all Error types, only serializing it when present to maintain absence vs presence semantics. * Refactor Error subclass reducers/revivers with helper functions Extract repetitive reducer/reviver logic into makeErrorSubclassReducer and makeErrorSubclassReviver helpers. Reduces ~120 lines of duplicated code to ~50 while preserving identical behavior. AggregateError remains as a wrapping extension since it needs to preserve the errors array. * Bump error-subclass-serialization changeset to minor Adding new serialization support for built-in Error subclasses is a feature, not a bug fix. |
||
|
|
78301695ea |
fix(builders): avoid false-positive node-module errors for step-only usage in shared modules (#1821)
The workflow-node-module-error plugin records violations in onResolve, which runs before esbuild's tree-shaker. A shared module with both a workflow-safe export and a step-only export that imports node:* would be flagged even when the workflow never reached the step-only export. Mark node:/bun: imports as sideEffects: false so esbuild can tree-shake them when unused in surviving code, enable metafile, and filter recorded violations in onEnd against outputs[].imports so only builtins that actually survive in the emitted bundle are reported. Fixes #1817 |
||
|
|
9f3516ec28 |
Refactor: Extract serialization into modular architecture and wire into existing pipeline (#1299)
* Add serialization module foundation: types, codec interface, format prefix
Start of the serialization refactor (separate from snapshot-runtime).
New files:
- serialization/types.ts — SerializationFormat enum, SerializableSpecial
interface, Reducers/Revivers types
- serialization/codec.ts — Codec interface with formatPrefix, serialize,
deserialize, and optional deserializeLegacy
- serialization/format.ts — Format prefix encode/decode/peek, moved from
the monolithic serialization.ts
The Codec interface enables future alternative formats (CBOR, JSON) while
keeping the devalue implementation as the current default.
* Add reducers, devalue codec, encryption, and mode-specific modules
Serialization refactor Phase 1: create the new module structure alongside
the existing monolithic serialization.ts (which continues to work).
New files:
- serialization/reducers/common.ts — Date, Error, Map, Set, URL, BigInt,
typed arrays, Headers, Request, Response, RegExp, URLSearchParams
- serialization/reducers/class.ts — Class/Instance with WORKFLOW_SERIALIZE/
DESERIALIZE support
- serialization/reducers/step-function.ts — StepFunction with closure vars
- serialization/codec-devalue.ts — devalue Codec implementation
- serialization/encryption.ts — composable encrypt/decrypt layer
- serialization/workflow.ts — synchronous, no encryption, for VM use
- serialization/step.ts — async with encryption, for step handler
- serialization/client.ts — async with encryption, for start() API
- serialization/index.ts — re-exports all public API
- serialization/serialization.test.ts — 25 focused tests
All modes compose their reducer/reviver sets from the shared building blocks.
Cross-mode compatibility verified: data serialized in any mode can be
deserialized in any other mode (for common types).
Existing 108 serialization tests continue to pass unchanged.
* Add sub-path exports for workflow serialization module
- Add ./serialization/workflow export to @workflow/core package.json
- Add ./internal/serialization re-export to workflow meta-package
- The workflow bundle can now import serialize/deserialize via:
import { serialize, deserialize } from 'workflow/internal/serialization'
Full test suite passes: 493 tests across 22 files (including 25 new
serialization module tests).
* Address code review feedback
1. Fix reducer composition order: Class/Instance reducers now come BEFORE
common reducers in all three modes (workflow, step, client). This ensures
custom Error subclasses with WORKFLOW_SERIALIZE are handled by the
Instance reducer before the generic Error reducer (devalue uses
first-match-wins semantics).
2. Fix encryption decrypt() to fail fast when encrypted data is encountered
without a decryption key, instead of silently returning encrypted bytes
that would fail later with an unhelpful format error.
3. Remove Request/Response from common reducers — they don't have matching
common revivers, so including them caused asymmetric behavior (serialize
as Request, deserialize as plain object). Request/Response handling
belongs in mode-specific modules that can provide proper revivers.
4. Document Node.js dependency in the workflow serialization re-export.
The current implementation uses node:util and Buffer. For the QuickJS
VM (snapshot runtime), these will need polyfills — tracked separately.
* Move reducer/reviver composition into the devalue codec
The Codec interface now takes a SerializationMode ('workflow', 'step',
'client') instead of raw reducers/revivers. The reducer/reviver
composition is internal to the devalue codec implementation.
This is the right abstraction because reducers/revivers are devalue-
specific concepts. A future CBOR codec would handle Date, typed arrays,
Map, Set natively via the CBOR type system — it wouldn't use reducers
at all. A JSON codec would only support standard JSON types.
The mode-specific modules (workflow.ts, step.ts, client.ts) are now
simpler — they just pass the mode string to the codec.
* Replace SerializationFormatType enum with open-ended FormatPrefix type
The format prefix is now a branded string type validated by
isFormatPrefix() — any 4-character [a-z0-9] string is valid.
This removes the hard-coded enum of known formats, making the system
truly open for extension:
type FormatPrefix = string & { __brand: 'FormatPrefix' };
function isFormatPrefix(value: string): value is FormatPrefix;
The SerializationFormat object still provides well-known constants
('devl', 'encr') but they're now just typed constants, not an
exhaustive enum.
peekFormatPrefix() and decodeFormatPrefix() use isFormatPrefix() for
validation instead of checking against a known list. Unknown but valid
prefixes (e.g. 'cbor', 'json', 'v2b1') are accepted — the caller
decides whether they can handle the format.
6 new isFormatPrefix tests covering: valid strings, too short, too long,
uppercase, special characters. 1 new test for unknown-but-valid prefixes.
* Wire modular serialization modules into serialization.ts, add 138 unit tests
Replace duplicate format prefix, reducer/reviver, and encryption helper
code in the monolithic serialization.ts with imports from the modular
serialization/ directory. This completes the refactoring started in the
earlier additive-only commits.
Key changes:
- serialization.ts now imports types, format prefix, common/class/step-function
reducers and revivers, and encryption helpers from ./serialization/ modules
- Removed ~450 lines of duplicate code from serialization.ts
- Made encryption error messages consistent between old and new modules
- Added 138 comprehensive unit tests covering types, format prefix,
encryption, codec, all three reducer modules, all three mode modules,
cross-mode compatibility, and edge cases
- Updated one existing test assertion for new error message wording
* Address code review feedback
- encryption.ts: throw WorkflowRuntimeError instead of plain Error in
decrypt() to preserve the error contract from legacy maybeDecrypt()
- format.ts: document that open-ended prefix validation ([a-z0-9]{4})
is intentional for forward compatibility — callers check support
- errors.ts: extract duplicated formatSerializationError into shared
utility, remove 4 copies from workflow.ts, step.ts, client.ts
- codec-devalue.ts: document that globalThis default is a known
limitation; legacy dehydrate/hydrate path still supports custom global
* Fix codec-devalue.ts comment: clarify modular modules are not used in current runtime
The globalThis default is not a limitation for the current runtime —
all serialization goes through dehydrate*/hydrate* in serialization.ts
which passes the correct global. The modular modules are infrastructure
for the future snapshot runtime where serialization runs inside the VM.
* Wire dehydrate/hydrate functions through modular serialize/deserialize
The dehydrate*/hydrate* functions in serialization.ts now delegate to
the modular mode modules (workflowModule, stepModule, clientModule)
instead of directly calling devalue stringify/parse/unflatten.
Key changes:
- Extended Codec interface with CodecOptions (global, extraReducers,
extraRevivers) so the codec can receive VM globals and mode-specific
stream/Request/Response handlers
- devalueCodec threads global through to all reducer/reviver factories
so instanceof checks work across VM boundaries
- Mode modules (workflow.ts, step.ts, client.ts) accept CodecOptions
and pass them through to the codec
- dehydrate*/hydrate* functions now call module serialize/deserialize
with stream and Request/Response reducers/revivers passed as extras
- v1Compat path remains inline (pre-codec, uses stringify + revive)
- Error context strings preserved via try/catch re-wrapping
* Bump changeset from patch to minor for serialization refactor
Return types of public get*Reducers/get*Revivers functions narrowed
from Reducers/Revivers to Partial<Reducers>/Partial<Revivers>, which
is a TypeScript-level breaking change. Also adds new sub-path exports
(@workflow/core/serialization/workflow, workflow/internal/serialization)
which is additive. Minor bump is the appropriate semver for both.
* Remove unused workflow/internal/serialization re-export and @workflow/core/serialization/workflow sub-path
Both exports had zero consumers in the repo. The workflow/internal/serialization
export was previously removed on main in #1082 for the same reason. The
modular workflow.serialize/deserialize is still reachable via
@workflow/core/serialization when needed. These exports can be reintroduced
by the snapshot runtime branch if/when it actually needs them.
Also updates the changeset to drop the 'new sub-path exports' bullet.
* Downgrade changeset from minor to patch
After auditing actual consumers of the narrowed return types
(getExternalReducers/getWorkflowReducers/getExternalRevivers/getWorkflowRevivers
now return Partial<Reducers>/Partial<Revivers>), no in-repo or external
consumer indexes specific keys on the returned object in a way that would
break. The only internal caller that did (runtime/run.ts) was updated in
this same PR. The narrowing is type-safer but effectively invisible at
runtime and for idiomatic callers that spread or forward the object.
Since the refactor is internally restructuring only, patch is the
appropriate semver bump.
* Trim serialization-refactor changeset
* Dedup formatSerializationError: import from serialization/errors.ts
The legacy serialization.ts had its own inlined copy of
formatSerializationError. Now that the helper is exported from
serialization/errors.ts (already consumed by workflow.ts/step.ts/client.ts),
import it here too to keep the single source of truth.
|
||
|
|
e0ec429bb3 |
fix(builders): bundle aliased project-local helpers instead of externalizing (#1885)
Externalizing aliased helpers was unsafe: their source on disk could contain further alias imports that Node's ESM loader doesn't know about, causing 'Package subpath ... is not defined by "exports"' / ERR_MODULE_NOT_FOUND at runtime. Now bundle inline so all alias resolution happens at build time. |
||
|
|
eb8562ddda |
Version Packages (beta) (#1884)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>workflow@5.0.0-beta.4 |
||
|
|
640a0505b8 | Add missing changeset for #1872 (trace viewer polish) (#1883) | ||
|
|
aeb495bccc |
[trace viewer] Polish items (#1872)
* wip
* context
* cleanup
* wip
* wip
* Update trace-viewer.tsx
* adjust hovers
* time marker
* tweak
* Update event-list.tsx
* header
* Update utils.ts
* search
* Update timeline.tsx
* ship
* Update timeline.tsx
* Update event-list.tsx
* Create icons.tsx
* Update trace-viewer.tsx
* Update utils.ts
* new trace viewrr
* Update workflow-trace-view.tsx
* Fix: Runtime crash when `trace` is `undefined`: `NewTraceViewerComponent` receives `undefined` (cast as `Trace`) and immediately accesses `trace.spans`, causing "Cannot read properties of undefined".
This commit fixes the issue reported at packages/web-shared/src/components/trace-viewer-new.tsx:28
**Bug Analysis:**
In `trace-viewer-new.tsx`, the `buildTrace` function returns `undefined` when `!run?.runId`. The result is stored in `trace` which has type `TraceWithMeta | undefined`. However, on line 28, `trace` is passed directly to `NewTraceViewerComponent` with a type assertion `trace as Trace`, which silences the TypeScript error but does not prevent the runtime crash.
Inside `NewTraceViewerComponent` (in `new-trace-viewer/trace-viewer.tsx` line 98), the component immediately accesses `trace.spans`:
```tsx
<ActiveSpanProvider spans={trace.spans}>
```
When `trace` is `undefined`, this produces: `TypeError: Cannot read properties of undefined (reading 'spans')`.
This happens whenever the component renders before `run.runId` is available — a normal scenario during initial loading.
The old `WorkflowTraceViewer` component (in `workflow-trace-view.tsx` line 953) correctly handles this with a `if (!trace)` guard that renders a loading skeleton. The new component lacks this guard.
**Fix:**
Added a null guard in `trace-viewer-new.tsx` that checks `if (!trace)` before rendering `NewTraceViewerComponent`. When trace is undefined, a simple loading placeholder is rendered instead. This prevents the crash and follows the same pattern as the existing `WorkflowTraceViewer`. The `as Trace` cast on line 28 is now safe because `trace` is guaranteed to be defined after the guard.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: mitul-s <mitulxshah@gmail.com>
* fixed edge view
* Update timeline.tsx
* ship it
* cleanup
* Update trace-viewer.tsx
* wip
* wip
* Add detail pane to the new trace viewer + cleanup (#1714)
* detail pane
* cleanup
* cleanup
* cleanu
* fix
* Update workflow-trace-view.tsx
* cleanup
* Update entity-detail-panel.tsx
* move sidebar provider into export
* dead code
* cleaning up more
* remove decorative indenting & output loader
* fix bars
* Update inspector-theme.ts
* cleanups
* Update copyable-data-block.tsx
* cleanup
* chonky
* Update timeline.tsx
* bug fxi
* marker lines
* wip
* changes
* Update event-list.tsx
* rounded
* Update event-list.tsx
* middle truncate component
* cleanup
* height updatres
* colour fixes
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
|
||
|
|
8f9ca384ec | Version Packages (beta) (#1868) workflow@5.0.0-beta.3 | ||
|
|
03dfab35e7 | Updated Workflow Trace viewer (#1454) | ||
|
|
3ad8ee7e33 |
fix(world-local): prevent path traversal via request-supplied IDs (#1829)
* fix(world-local): prevent path traversal via request-supplied IDs Request-supplied identifiers (runId, eventId, stepId, hookId, correlationId, stream names, and tags) flowed directly into path.join() calls, allowing a client to send values like '../../../package' and cause the backend to read or write files outside the workflow data directory. Add a centralized validator (assertSafeEntityId) that rejects IDs which are empty, start with '.', or contain path separators or NUL bytes. Apply it at each storage-layer entry point that composes IDs into filesystem paths: fs.taggedPath / readJSONWithFallback / paginatedFileSystemQuery, the runs / steps / events / hooks storage methods, and the streamer. * address review feedback - UnsafeEntityIdError now extends WorkflowWorldError for consistency with other storage-layer errors and the platform error-to-HTTP mapping. - Add resolveWithinBase(basedir, ...segments) containment helper and apply it at every taggedPath / readJSONWithFallback / .locks path construction site in events-storage and legacy, so a forgotten assertSafeEntityId at a future call site can't silently regress. - Truncate attacker-controlled values in the error message. - Drop unused assertSafeEntityIds helper and the unreachable typeof check under the TS signature. - Fix docstrings on assertSafeEntityId / taggedPath JSDoc example / filePrefix validation comment to match what the code actually does. - handleLegacyEvent now re-asserts runId locally so the invariant is documented at the call site instead of implicitly inherited from events.create. --------- Co-authored-by: JJ Kasper <jj@jjsweb.site> |
||
|
|
fe382021b9 |
docs: document full Vercel E2E env vars in AGENTS.md (#1819)
* docs: update AGENTS.md with complete Vercel E2E env vars * docs: remove WORKFLOW_PUBLIC_MANIFEST from local E2E instructions |
||
|
|
59ec3983b9 |
docs: tighten changeset description guidance in AGENTS.md (#1833)
* docs: tighten changeset description guidance in AGENTS.md Specify that changeset descriptions should be one sentence (two at most), and remove the redundant BREAKING CHANGE marker guidance since breaking changes are already communicated via the major semver bump. * docs: address Copilot review feedback - Align command reference to `pnpm changeset add` (matching line 188) - Drop the "see existing changesets for examples" parenthetical since existing changesets predate the new sentence-limit guidance |
||
|
|
2d66c75ad0 |
ci: fail fast when Next.js dev server is wedged on Windows (#1871)
A recurring Turbopack-on-Windows bug causes the dev server to enter a 'MODULE_UNPARSABLE' state during HMR in dev.test.ts, after which every request returns 500. The remaining e2e suite then polls stuck workflows for 60s each, burning the full 30-minute job window before getting cancelled (~50% of recent main runs). Bail out of the Windows e2e job as soon as dev.test.ts fails, and health-check the dev server before kicking off test:e2e so any other silent breakage is surfaced quickly instead of via a 30-minute timeout. |
||
|
|
906f7c1213 |
refactor(next): remove step file copy mechanism from deferred builder (#1796)
* refactor(next): remove step file copy mechanism from deferred builder Step sources are now imported directly into the generated `step/route.js` using the same `getImportPath`-based logic already used for serde files. This is possible because the SWC plugin's client mode was merged into step mode (#1686), so the workflow loader always runs in step mode and transforms every file it sees, including those from packages. Removed: - `__workflow_step_files__/` per-file copies with hashed names, metadata comments, inline source maps, and bare-specifier rewriting via `enhanced-resolve` - `createResponseBuiltinsStepFile`; builtins are now imported via `require.resolve('workflow/internal/builtins')` at build time - `step-copy-utils.ts` and all copy-specific branches in `loader.ts` - `enhanced-resolve` dependency The deferred builder still removes the legacy `__workflow_step_files__/` directory on boot so upgrades leave no stale artifacts behind. Dev tests that inspected copied step file contents now inspect the manifest.json entries (which list every discovered step keyed by source path). * refactor(next): scrub historical phrasing from code comments * test: update next step stack expectations --------- Co-authored-by: JJ Kasper <jj@jjsweb.site> |
||
|
|
0ec805a999 |
fix(e2e): increase utf8StreamWorkflow test timeout to 120s (#1869)
Matches the convention used by readableStreamWorkflow and webhookWorkflow. The 60s budget is too tight when a single Vercel Queue delivery retry can take 30-60s, causing intermittent flakiness on the nitro workbench. Made-with: Cursor |
||
|
|
787bb15df0 |
ci: use GitHub API commit mode for changesets action (#1867)
The repo enforces "Commits must have verified signatures" via an org/enterprise-level ruleset, which blocks unsigned commits pushed via the Git CLI by GITHUB_TOKEN. Switching the changesets action to commitMode: github-api makes commits GPG-signed by GitHub. |
||
|
|
89738e5236 |
ci: stop using Release App token in release workflows (#1866)
The Release App has been temporarily removed. Switch the Release and Backport workflows to use the default GITHUB_TOKEN, and disable the cross-repo Front dispatch workflow until the App is restored. Also add a workflow_dispatch trigger to release.yml so the Version Packages PR can be created/updated manually (since pushes made by GITHUB_TOKEN do not trigger downstream workflow runs). |
||
|
|
9ea125427f |
Decode UTF-8 stream chunks (#1852)
* Decode typed array stream chunks * Render decoded stream bytes with raw view * Render decoded bytes in data inspector * Use generic byte inspector for streams * review feedback: narrow stream-display exports, fix tab a11y, add collapseRefs tests - Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay exports; keep only the formatArrayBufferViewForDisplay path actually used by DataInspector. - Replace broken role=tablist/role=tab on the Decoded/Bytes switcher with aria-pressed toggle-button semantics. - Export collapseRefs/isBytesDisplay and add regression tests covering typed-array detection (top-level, nested in object/array/Map/Set, DataView exclusion). * Replace eval with JSON.parse in serialization revive helper (#1848) * Replace eval with JSON.parse in serialization revive helper devalue.stringify() always produces valid JSON — special values (undefined, NaN, Infinity, -0) are encoded as negative integer sentinels. JSON.parse yields the same flattened array form that unflatten() expects, without the eval anti-pattern (VULN-918). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Drop redundant workflow package from changeset Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * Add e2e test for UTF-8 parseable stream chunks Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended, CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and asserts each chunk round-trips through TextDecoder({ fatal: true }). Exercises the same decode path the web inspector relies on for typed-array stream values. Made-with: Cursor --------- Co-authored-by: Pranay Prakash <pranay.gp@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cbecbaa5fe |
feat(nitro): forward externals.external to workflow builder (#1844)
Nitro users can declare packages as externals via
`externals: { external: ['fsevents'] }` in `nitro.config.ts`. Previously
the workflow SDK's step/workflow/webhook bundles were built in isolation
and ignored that config, forcing users to either patch the Nitro adapter
or ship a custom builder (as in the 250MB bundle-size workaround gist).
Forward string entries from `nitro.options.externals.external` to the
builder's esbuild `external` option. Only string entries are forwarded
— RegExp and function matchers aren't supported by esbuild's
`external` API, so those are skipped.
|
||
|
|
64c66d211e |
docs(cookbook): rewrite core recipes, dedupe, and reorder sidebar (#1850)
* fix(docs): highlight active top-bar nav item and drop duplicate keys Match the current pathname (accounting for the hidden default locale) and forward an `active` prop to the Radix NavigationMenuLink so the selected top-bar item stays highlighted. Also remove module-level `key` props from the intro Loading/Success/Error indicators — they fired React's duplicate-key warning whenever two lines shared a state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * update saga cookbook docs with adaptation guidance and streaming example Made-with: Cursor * docs(cookbook): rewrite core recipes, dedupe, reorder sidebar Pass over the cookbook focused on correctness, concrete examples, and tighter information architecture. Rewrites: - Saga / Batching / Scheduling: concrete use cases (order fulfillment, CSV import, cancellable drip campaign); added "how it works" + "adapting" sections. - Durable Agent / Human-in-the-Loop / Stop Workflow: fixed broken patterns (tool invocations don't stream before return; prepareStep flag checks never fire); canonical single example per page. - AI SDK integration: refocused from DurableAgent showcase to streamText(); added multi-turn session pattern with durable stream slicing, ai-sdk.dev deep links, and a pitfalls section. - Sandbox integration: rewrote around the real @vercel/sandbox SDK; flagship pattern is one workflow run = one persistent sandbox session that outlives the 5-hour VM cap via snapshot rotation. - Chat SDK integration: full rewrite — previous page documented AI SDK's useChat, not chat-sdk.dev. New page covers durable chat sessions, Mermaid flowchart, and cross-SDK serialization. - Publishing Libraries: swapped ASCII tree for Fumadocs <Files> (font-independent); corrected isomorphic guidance ("use workflow" / "use step" are safe no-ops when SWC plugin doesn't process them); added Mux AI and World ID adopter examples with canonical links. Deletions (redundant or flawed): - Fan-Out, Conditional Routing (covered by Batching / not a pattern). - Tool Streaming, Tool Orchestration (covered by foundations/streaming and ai/defining-tools). - Durable Objects (hook-in-loop bug; framing doesn't hold up). - Custom Serialization, Isomorphic Packages (merged into foundations/serialization and publishing-libraries respectively). Sidebar restructure: - Reordered: Overview -> Agent Patterns -> Common Patterns -> Integrations -> Advanced (Agent Patterns leads since it drives most new-visitor traffic). - Moved Child Workflows + Distributed Abort Controller from Common Patterns to Advanced (composition / coordination concerns, not common reliability recipes); permanent redirects added in next.config.ts. Made-with: Cursor * docs(cookbook): rename Agent Stop Signal → Agent Cancellation, migrate common patterns The agent cancellation page now covers two named patterns — Hard Cancellation via getRun(runId).cancel() and Stop Signal via a hook + Promise.race — so the prior "Agent Stop Signal" title biased toward only one of them. Rename to the broader noun phrase and update the intro, description, and section heading to frame both approaches equally. Also migrate all patterns from foundations/common-patterns into dedicated cookbook recipes (timeouts, sequential-and-parallel, workflow-composition) and remove the foundations page. Permanent redirects added for the prior URLs (stop-workflow, agent-stop-signal, foundations/common-patterns) so no inbound link breaks. Made-with: Cursor * docs(cookbook): fix CI typecheck failures on three snippets - publishing-libraries.mdx: the second processPayment snippet uses getWorkflowRunId() defined in the prior block. Each block is type-checked independently, so add @skip-typecheck (matches the existing pattern in this file). - ai-sdk.mdx: the "snapshot tailIndex first" pitfall is a fragment referencing probe / turnHook / run / text from the surrounding multi-turn pattern. Add @skip-typecheck. - chat-sdk.mdx: the [platform] webhook route used Next.js 15 typegen (RouteContext<"/api/webhooks/[platform]">), which isn't part of the docs-typecheck environment. Switch to the manually-typed { params }: { params: Promise<{ platform: string }> } form — still fully supported by Next.js 15+ and doesn't depend on .next/types. Made-with: Cursor --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7d07fab692 |
Replace eval with JSON.parse in serialization revive helper (#1848)
* Replace eval with JSON.parse in serialization revive helper devalue.stringify() always produces valid JSON — special values (undefined, NaN, Infinity, -0) are encoded as negative integer sentinels. JSON.parse yields the same flattened array form that unflatten() expects, without the eval anti-pattern (VULN-918). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Drop redundant workflow package from changeset Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
baba580794 |
Write Next diagnostics to dist dir (#1857)
* Write Next diagnostics to dist dir * Simplify Next dist dir config access |
||
|
|
a38f140ce3 |
Make TypeScript peer dependency optional (#1830)
* Make TypeScript peer dependency optional * Handle missing TypeScript more gracefully * Fix TypeScript plugin build after optional peer change |
||
|
|
3a08eaa0a1 |
ci: refactor wait-for-vercel-project to use GitHub Deployments API (#1861)
* ci: refactor wait-for-vercel-project to use GitHub Deployments API Replaces the Vercel SDK / Vercel API token-based implementation with one that resolves the deployment URL via the GitHub Deployments API: - Find the GitHub Deployment for (target SHA, environment) where environment matches the Vercel-app-created "Preview \u2013 <slug>" or "Production \u2013 <slug>" naming pattern. - Wait for the latest deployment status to be `success` (or `inactive` when Vercel skips a duplicate build, in which case its environment_url still points at the live deployment). - Probe the URL to confirm the edge can route to it (any non-5xx response counts as live, including 401/403 from Deployment Protection and 404/405 from the app). Manual redirect handling treats redirects to vercel.com as "still building". - Resolve the dpl_xxx deployment ID from the matching commit status (Vercel posts `Vercel \u2013 <slug>` statuses where target_url's last path segment is the inspector ID == deployment ID without the prefix). Inputs change: project-slug + bypass-secret + github-token (with GITHUB_TOKEN default) replace team-id + project-id + vercel-token. Removes the @vercel/sdk dependency, shrinking the bundled dist from 5.4MB to 829KB. The VERCEL_DOCS_TOKEN secret is no longer referenced anywhere in the repo and can be deleted from GH after this lands. * ci(wait-for-vercel-project): drop URL probe and bypass-secret input The GitHub Deployment status transitions to `success` only after the Vercel app finishes building and routing is live, so an extra HTTP liveness probe of the deployment URL was redundant. Removing it lets us also drop the bypass-secret input \u2014 protected deployments don't need a workaround anymore because we never make the request. Reduces the action surface area and eliminates a runtime fetch. * ci(wait-for-vercel-project): address PR review - Fail loudly when the dpl_xxx deployment ID can't be resolved instead of returning an empty string. Consumers wire this into VERCEL_DEPLOYMENT_ID, which world-target uses to pick between the vercel and local worlds (packages/utils/src/world-target.ts), so an empty value would silently flip execution mode. - Pass the GitHub App token to wait-for-vercel-project in the dispatch release workflow. The job sets `permissions: contents: read`, which blocks the default GITHUB_TOKEN from reading the Deployments API. The App token (already generated for workflow,front) has the necessary scopes. |
||
|
|
d6f7eb620b |
test: enable step source-map assertions for vite local dev (#1862)
* test: enable step source-map assertions for vite local dev Vite ^7.3.2 (bumped in #1827) preserves step bundle source maps in dev mode, so stack traces now contain original file paths. Update hasStepSourceMaps() so vite returns true in local dev and stays false only in local prod, fixing the consistently failing 'basic step error' and 'cross-file step error' e2e tests. * chore: drop body from empty changeset * address review: drop redundant vite local-prod guard The default `!DEV_TEST_CONFIG` fall-through already returns false for vite local prod, so the vite-specific guard is dead code. Just remove the vite block entirely now that vite local dev matches the default 'has source maps' behavior. |
||
|
|
28dc089eef |
ci: fix VERCEL_WORKFLOW_SERVER_* env var ternary on main (#1859)
* ci: fix VERCEL_WORKFLOW_SERVER_* ternary so main actually unsets them In GitHub Actions expressions, '' is falsy, so the original `cond && '' || secrets.X` pattern always fell through to the secret regardless of branch. The result was that pushes to main were sending preview workflow-server values to production, causing 'invalid_url' errors on `x-vercel-workflow-api-url` across all e2e jobs. Flip the condition so the secret sits in the truthy branch and || correctly selects '' on main. * ci: skip pnpm cache in matrix-generation jobs The Get Test Matrix and Get Community Worlds Matrix jobs only run a small Node script to emit a JSON matrix; they never run `pnpm install`. With `cache: 'pnpm'` set on actions/setup-node, the post-job cache save step fails with 'Path Validation Error' because the pnpm store path was never created, marking the whole job as failed. Add a cache-pnpm input to setup-workflow-dev (default true) and opt out in the two matrix-generation jobs. |
||
|
|
393ffd86fc |
Lock next@canary test version (#1860)
|
||
|
|
56ba32feb8 | Bump vite (#1827) | ||
|
|
e2ef3568a5 |
CI script improvements (#1826)
* CI script improvements Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * address codex feedback: harden remaining workflows - add allowlist regex in prepare-workbench-path to block path traversal - move matrix/input values to env vars across e2e-vercel-prod, benchmarks (local/postgres/vercel), and the reusable community-world workflows - validate app-name/world-id/world-package inputs in the reusable community-world workflows - pipe getCommunityWorldsMatrix script output through jq -c to prevent \$GITHUB_OUTPUT injection Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
354840e93b |
feat(world-vercel): support new env vars for Vercel Deployment Protection (#1824)
* feat(world-vercel): support WORKFLOW_VERCEL_PROTECTION_BYPASS env var Allows sending a Vercel Deployment Protection bypass secret via the `x-vercel-protection-bypass` header on all outbound requests made by the Vercel world, enabling use against protected deployments (e.g. previews, or workflow-server once protection is enabled). * feat(world-vercel): support VERCEL_WORKFLOW_SERVER_URL env var Replace hard-coded WORKFLOW_SERVER_URL_OVERRIDE constant with a function that reads from the VERCEL_WORKFLOW_SERVER_URL env var. Allows configuring the workflow-server URL per-deployment (e.g. workbench Preview envs pointing to a branch deployment) without editing source. * fix(world-vercel): preserve inline WORKFLOW_SERVER_URL_OVERRIDE const Keep the inline const as an empty-string literal so external CI rewrite tooling continues to work unmodified; the env var is a fallback when the inline value is empty. * refactor(world-vercel): rename to VERCEL_WORKFLOW_SERVER_PROTECTION_BYPASS Align env var naming with VERCEL_WORKFLOW_SERVER_URL. * ci: expose workflow-server protection bypass env vars to e2e-vercel-prod Set VERCEL_WORKFLOW_SERVER_URL and VERCEL_WORKFLOW_SERVER_PROTECTION_BYPASS on PR runs so e2e tests hit the protected workflow-server preview; leave unset on main so production runs use the public default URL. * refactor(world-vercel): address PR review comments - Consolidate bypass header logic in getHeaders() to reuse getProtectionBypassHeader() instead of duplicating env lookup. - Use consistent 'Authorization' casing in direct fetch() calls. - Add unit tests for getProtectionBypassHeader, getHttpUrl, and getHeaders covering env var toggling and proxy/override combinations. |
||
|
|
b163860fe4 |
Guard fibonacciWorkflow against non-finite n (#1814)
Throw a FatalError at the top of fibonacciWorkflow if n is not a finite number. Prevents a runaway recursion if the workflow is ever invoked without its numeric argument — NaN - 1 === NaN, NaN <= 1 === false, so the base case would never fire and every descendant would spawn two more children. |
||
|
|
b27921985c |
Add distributed abort controller guide and implementation (#1811)
* feat: add distributed abort controller guide and implementation Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com> * feat: implement abort signal step function and refactor workflows to use it Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com> * feat: enhance distributed abort controller with user-provided ID Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com> * feat: enhance distributed abort controller with TTL and reconnection logic Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com> * feat: add grace period to abort controller workflow Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com> * feat: add distributed abort controller workflow and tests Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com> * add the pattern to the sidebar * fix: correct start() call signature and run.id → run.runId start() takes args as a positional second argument, not an options object property. The Run object exposes runId, not id. Made-with: Cursor * fix: address PR review comments on distributed abort controller - Make abort() idempotent by catching hook-not-found/expired errors - Add error handling to signal getter's stream reader IIFE - Fix tests: use instance methods instead of non-existent static methods, pass required ttlMs/graceMs args, include expired field in assertions Made-with: Cursor * fix: add missing import to Custom TTL docs code sample The docs typecheck runs each code block in isolation. The Custom TTL example was missing the DistributedAbortController import. Made-with: Cursor * fix: only sleep grace period on TTL expiration, not manual abort Manual aborts now complete immediately instead of sleeping through the full TTL + grace period. This fixes vitest timeouts where all 3 distributed-abort-controller tests exceeded the 30s limit. Made-with: Cursor * fix: wait for hook registration before aborting in test The DistributedAbortController instance test was timing out because abort() was called before the workflow had registered the hook. The idempotent catch silently swallowed the "not found" error, leaving the workflow running forever. - Make runId readonly (was private) so tests can access it - Add waitForHook(getRun(controller.runId)) before controller.abort() Made-with: Cursor * fix: apply conditional grace period to E2E workflow copy The distributedAbortControllerWorkflow in 99_e2e.ts had the same unconditional grace sleep bug, causing the reconnect test to timeout at 60s while waiting ~66s for the grace period after manual abort. Made-with: Cursor --------- Co-authored-by: v0 <v0[bot]@users.noreply.github.com> Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com> Co-authored-by: Karthik Kalyanaraman <karthik.kalyanaraman@vercel.com> |
||
|
|
5889d84aef |
ci: auto-resolve skills/ conflicts in backport to stable (#1798)
* ci: auto-resolve skills/ conflicts in backport to stable The skills/ directory is not maintained on the stable branch (skill files are unrelated to npm packaging). Extend the backport workflow's auto-resolution — and the AI-assisted fallback prompt — to treat skills/ conflicts the same way docs app conflicts are handled: keep the stable side and drop the incoming change from main. Also updates AGENTS.md to document which directories are stable-only placeholders and how the backport action handles them. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nathan Rajlich <n@n8.io> --------- Signed-off-by: Nathan Rajlich <n@n8.io> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
e295bae417 |
feat: allow start() to be called directly inside workflow functions (#1491)
* feat: allow start() to be called directly inside workflow functions
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
* fix(next): don't copy package step files in deferred builder to avoid duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
* fix(next): only skip copying package files that are serde classes, not all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
* fix(next): generate thin wrappers for package serde step files instead of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
* fix(next): use forceStepModeFiles to transform package serde files in step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
* fix(next): use step mode for all files with step/serde patterns, not just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
---------
Co-authored-by: Nathan Rajlich <n@n8.io>
|
||
|
|
3f6d98f3f0 | [ai] Align toolsToModelTools with core AI SDK's prepareToolsAndToolChoice (#1544) | ||
|
|
a12c0e8511 | ci: bump opencode model to claude-opus-4.7 in backport workflow (#1795) | ||
|
|
71d594c5dd | [ci] Skip community world E2E tests on main (#1783) | ||
|
|
5e0a0c7ab0 |
Prefer stable version for docs app conflicts in backport workflow (#1791)
* Prefer stable version for docs app conflicts in backport workflow After #1786 restored a minimal Next.js placeholder docs app on stable, docs app conflicts should resolve to the stable branch version rather than being deleted. Only docs/content/ is actively maintained on stable, so conflicts there should still be resolved normally. Update both the auto-resolution logic in backport.yml and the AI prompt to reflect the new policy, and update AGENTS.md to match. * Use git show :2:$file to detect ours-side presence in conflicts git ls-files --error-unmatch succeeds for unmerged paths even when the file only exists on the incoming (theirs) side, which would then fail on git checkout --ours. Use git show :2:$file to specifically check for a stage-2 entry, which indicates the file exists on the ours (stable) side. |
||
|
|
69af0c1374 |
ci: upgrade pnpm/action-setup to v5 and read version from package.json (#1785)
* ci: upgrade pnpm/action-setup to v6 and read version from package.json Removes hardcoded pnpm version (10.14.0) from all workflows and instead reads the version from the packageManager field in package.json, so CI stays in sync with the version used locally. * ci: update setup-workflow-dev composite action to use pnpm/action-setup@v6 Also removes the pnpm-version input since the action now reads the version from package.json#packageManager. * ci: downgrade pnpm/action-setup to v5 v6 installs pnpm 11 RC/beta, which has a regression (pnpm/pnpm#11264, pnpm/action-setup#225/#227/#228) that causes 'ERR_PNPM_BROKEN_LOCKFILE: expected a single document in the stream' when the project's packageManager pins a 10.x pnpm version. v5 is the latest stable release before v6 and supports reading the version from package.json#packageManager. |
||
|
|
417c4930be |
refactor(swc-plugin): remove client transform mode, merge into step mode (#1686)
* refactor(swc-plugin): remove client transform mode, merge into step mode Remove the `client` transform mode from the SWC compiler plugin. The `client` and `step` modes were nearly identical — both preserved step function bodies, replaced workflow bodies with throw stubs, and emitted the same JSON manifest. Step mode now absorbs all client-mode behaviors: - Dead code elimination (previously only workflow + client) - Hoisted variable references for object property steps - All integrations use mode: 'step' instead of 'client' BREAKING CHANGE: The `client` value for the SWC plugin `mode` option is no longer accepted. Use `step` instead. * fix(nitro): force-inline workflow packages in dev mode for serde classId registration In dev mode, Nitro's Rollup externalizes npm packages like @workflow/core, so the SWC transform plugin never processes files like run.js. This means serde classes (e.g. Run) never get the classId registration IIFE, causing serialization failures when step functions return Run instances. Uses a Rollup resolveId hook to force workflow SDK packages to be bundled (non-external) while leaving all other dependencies external. This is more targeted than noExternals=true which bundles everything and causes TDZ errors from circular imports in packages like vue-bundle-renderer/h3. The Nitro module now also ignores .nitro/workflow/** in watchOptions so writing generated workflow bundles does not retrigger Nitro's own dev bundle rebuild loop. Also wraps dev:reload workflow rebuilds and makes LocalBuilder.build() atomic (writes to temp files, renames on success) to avoid partial output state during HMR. For Nuxt, also configures Vite's ssr.noExternal to bundle workflow packages in the SSR context. * fix(nitro,nuxt): address review feedback on dev-mode classId fix - nitro builders: use crypto.randomUUID() for temp file suffix instead of Date.now() to avoid collisions under rapid/concurrent build() calls, and serialize concurrent build() calls through an internal queue so two overlapping dev rebuilds cannot clobber each other's temp outputs. - nitro index: use fileURLToPath() to convert file:// URLs to filesystem paths, which correctly handles Windows paths (file:///C:/... -> C:\...) and percent-decoding, instead of relying on new URL(...).pathname. - nuxt module: normalize vite.ssr.noExternal to an array (preserving any existing string/RegExp/array entry) before appending workflow package matchers, so the force-bundle behavior is not a no-op when noExternal is already set to a non-array value. |
||
|
|
cda0b12eed |
docs(migration-guides): audit and fix accuracy across four source frameworks (#1779)
Co-authored-by: VaguelySerious <mittgfu@gmail.com> Co-authored-by: Peter Wielander <mittgfu@gmail.com> |
||
|
|
5714c2335a | Version Packages (beta) (#1711) workflow@5.0.0-beta.2 | ||
|
|
b7d6595c25 | fix false-positive unconsumed event in hook loop replay (#1778) |