2025-10-23 12:07:52 +03:00
|
|
|
{
|
|
|
|
|
"name": "@workflow/core",
|
2026-09-09 12:12:11 -07:00
|
|
|
"version": "5.0.0-beta.49",
|
2026-03-29 16:05:39 -07:00
|
|
|
"description": "Core runtime and engine for Workflow SDK",
|
2025-10-23 12:07:52 +03:00
|
|
|
"type": "module",
|
|
|
|
|
"main": "dist/index.js",
|
|
|
|
|
"files": [
|
Workflow Skills for npx skills (#842)
* Add workflow skill for Claude Code
Add custom slash commands for Claude Code:
- /pr command for creating PRs following repo conventions
Add workflow skill with comprehensive documentation:
- Main SKILL.md with core concepts and quick reference
- Reference docs for frameworks, AI agents, patterns, errors, and API
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md serialization docs for custom class support
Add documentation for custom class serialization using @workflow/serde
symbols (WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE) and clarify what can
and cannot be serialized based on recent PR #762.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Fix incorrect API property names in SKILL.md
- Update sleep() example date from 2025 to 2027 (future date)
- Fix getWorkflowMetadata() to use correct properties: workflowRunId,
workflowStartedAt, url (not runId, workflowName)
- Fix getStepMetadata() to use correct property: attempt (not attemptNumber)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Move skills to skill/workflow directory structure
Reorganize skill files to support multiple skills under ./skill/<name>/
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Rename skill to skills (plural)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Refactor skills to use bundled docs from node_modules
- Update SKILL.md to direct agents to search node_modules/workflow/docs/
- Remove manually-maintained reference docs (ai-agents, api-reference, frameworks, patterns)
- Keep common-errors.md for practical troubleshooting
- Add prepack/postpack hooks to bundle docs in npm packages:
- workflow: All docs from docs/content/docs/
- @workflow/ai: AI-specific docs
- @workflow/core: foundations, how-it-works, api-reference
- @workflow/next: Next.js setup docs
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add AI SDK step function guidance to SKILL.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md formatting and add ecosystem section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Align postpack scripts with AI SDK conventions
Use del-cli for postpack cleanup to match the AI SDK's cross-platform
approach. This ensures consistent tooling across both projects.
- Add del-cli ^5.1.0 to root devDependencies
- Update postpack in @workflow/core, @workflow/next, @workflow/ai, and
workflow packages from `rm -rf docs` to `del-cli docs`
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add workflow health command to debugging documentation
Document the CLI health check command for verifying workflow/step
endpoint connectivity during local development and production.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Address PR feedback: standardize on rm -rf and improve SKILL.md
- Remove del-cli dependency, use rm -rf consistently in postpack scripts
- Restructure SKILL.md to lead with "Prefer Step Functions" pattern
- Add sandbox limitations table for workflow-context workarounds
- Consolidate examples and remove duplication
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Consolidate common-errors.md into SKILL.md
Move useful tips (serialization, streaming, debugging) into main skill
file and remove the separate common-errors.md reference file.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
---------
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
2026-01-27 18:41:09 -07:00
|
|
|
"dist",
|
2026-02-06 13:16:48 -08:00
|
|
|
"docs/**/*",
|
|
|
|
|
"runtime.js",
|
|
|
|
|
"runtime.d.ts"
|
2025-10-23 12:07:52 +03:00
|
|
|
],
|
Workflow Skills for npx skills (#842)
* Add workflow skill for Claude Code
Add custom slash commands for Claude Code:
- /pr command for creating PRs following repo conventions
Add workflow skill with comprehensive documentation:
- Main SKILL.md with core concepts and quick reference
- Reference docs for frameworks, AI agents, patterns, errors, and API
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md serialization docs for custom class support
Add documentation for custom class serialization using @workflow/serde
symbols (WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE) and clarify what can
and cannot be serialized based on recent PR #762.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Fix incorrect API property names in SKILL.md
- Update sleep() example date from 2025 to 2027 (future date)
- Fix getWorkflowMetadata() to use correct properties: workflowRunId,
workflowStartedAt, url (not runId, workflowName)
- Fix getStepMetadata() to use correct property: attempt (not attemptNumber)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Move skills to skill/workflow directory structure
Reorganize skill files to support multiple skills under ./skill/<name>/
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Rename skill to skills (plural)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Refactor skills to use bundled docs from node_modules
- Update SKILL.md to direct agents to search node_modules/workflow/docs/
- Remove manually-maintained reference docs (ai-agents, api-reference, frameworks, patterns)
- Keep common-errors.md for practical troubleshooting
- Add prepack/postpack hooks to bundle docs in npm packages:
- workflow: All docs from docs/content/docs/
- @workflow/ai: AI-specific docs
- @workflow/core: foundations, how-it-works, api-reference
- @workflow/next: Next.js setup docs
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add AI SDK step function guidance to SKILL.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md formatting and add ecosystem section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Align postpack scripts with AI SDK conventions
Use del-cli for postpack cleanup to match the AI SDK's cross-platform
approach. This ensures consistent tooling across both projects.
- Add del-cli ^5.1.0 to root devDependencies
- Update postpack in @workflow/core, @workflow/next, @workflow/ai, and
workflow packages from `rm -rf docs` to `del-cli docs`
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add workflow health command to debugging documentation
Document the CLI health check command for verifying workflow/step
endpoint connectivity during local development and production.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Address PR feedback: standardize on rm -rf and improve SKILL.md
- Remove del-cli dependency, use rm -rf consistently in postpack scripts
- Restructure SKILL.md to lead with "Prefer Step Functions" pattern
- Add sandbox limitations table for workflow-context workarounds
- Consolidate examples and remove duplication
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Consolidate common-errors.md into SKILL.md
Move useful tips (serialization, streaming, debugging) into main skill
file and remove the separate common-errors.md reference file.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
---------
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
2026-01-27 18:41:09 -07:00
|
|
|
"directories": {
|
|
|
|
|
"doc": "./docs"
|
|
|
|
|
},
|
2025-10-23 12:07:52 +03:00
|
|
|
"publishConfig": {
|
|
|
|
|
"access": "public"
|
|
|
|
|
},
|
2025-11-03 12:42:40 -08:00
|
|
|
"license": "Apache-2.0",
|
2025-10-23 12:07:52 +03:00
|
|
|
"repository": {
|
|
|
|
|
"type": "git",
|
|
|
|
|
"url": "https://github.com/vercel/workflow.git",
|
|
|
|
|
"directory": "packages/core"
|
|
|
|
|
},
|
|
|
|
|
"exports": {
|
|
|
|
|
".": {
|
|
|
|
|
"types": "./dist/index.d.ts",
|
|
|
|
|
"workflow": "./dist/workflow/index.js",
|
|
|
|
|
"default": "./dist/index.js"
|
|
|
|
|
},
|
2026-02-06 13:16:48 -08:00
|
|
|
"./runtime": {
|
|
|
|
|
"types": "./dist/runtime.d.ts",
|
|
|
|
|
"default": "./dist/runtime.js"
|
|
|
|
|
},
|
2026-04-09 15:10:45 -07:00
|
|
|
"./runtime/run": {
|
|
|
|
|
"types": "./dist/runtime/run.d.ts",
|
|
|
|
|
"default": "./dist/runtime/run.js"
|
|
|
|
|
},
|
2026-02-06 13:16:48 -08:00
|
|
|
"./runtime/start": {
|
|
|
|
|
"types": "./dist/runtime/start.d.ts",
|
|
|
|
|
"default": "./dist/runtime/start.js"
|
|
|
|
|
},
|
|
|
|
|
"./runtime/helpers": {
|
|
|
|
|
"types": "./dist/runtime/helpers.d.ts",
|
|
|
|
|
"default": "./dist/runtime/helpers.js"
|
|
|
|
|
},
|
2026-06-23 16:59:40 -05:00
|
|
|
"./logger": {
|
|
|
|
|
"types": "./dist/logger.d.ts",
|
|
|
|
|
"default": "./dist/logger.js"
|
|
|
|
|
},
|
2026-02-06 13:16:48 -08:00
|
|
|
"./runtime/resume-hook": {
|
|
|
|
|
"types": "./dist/runtime/resume-hook.d.ts",
|
|
|
|
|
"default": "./dist/runtime/resume-hook.js"
|
|
|
|
|
},
|
2026-05-09 13:43:45 +09:00
|
|
|
"./runtime/world-init": {
|
|
|
|
|
"types": "./dist/runtime/world-init.d.ts",
|
|
|
|
|
"workflow": "./dist/workflow/world-init-stub.js",
|
|
|
|
|
"default": "./dist/runtime/world-init.js"
|
|
|
|
|
},
|
2026-02-06 13:16:48 -08:00
|
|
|
"./class-serialization": {
|
|
|
|
|
"types": "./dist/class-serialization.d.ts",
|
|
|
|
|
"default": "./dist/class-serialization.js"
|
|
|
|
|
},
|
|
|
|
|
"./serialization": {
|
|
|
|
|
"types": "./dist/serialization.d.ts",
|
|
|
|
|
"default": "./dist/serialization.js"
|
|
|
|
|
},
|
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
|
|
|
"./serialization-format": {
|
|
|
|
|
"types": "./dist/serialization-format.d.ts",
|
|
|
|
|
"default": "./dist/serialization-format.js"
|
2025-10-23 12:07:52 +03:00
|
|
|
},
|
2026-02-20 14:47:54 -08:00
|
|
|
"./encryption": {
|
|
|
|
|
"types": "./dist/encryption.d.ts",
|
|
|
|
|
"default": "./dist/encryption.js"
|
|
|
|
|
},
|
Friendlier workflow errors (consolidated) (#1849)
* Introduce structured context-violation errors + Ansi renderer
Phase 1: Add Ansi rendering helpers (frame, hint, note, help, code, inline)
to @workflow/errors, and a chalk mock for readable snapshot tests.
Phase 2: Add four context-violation error classes to @workflow/core
(NotInWorkflowContextError, NotInStepContextError,
NotInWorkflowOrStepContextError, UnavailableInWorkflowContextError)
and apply them to all twelve user-facing throw sites so errors now
include docs links and a structured "what/why/fix" frame.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Address review: tighten changeset, implement ansifyName, harden Ansi
- Tighten phase 1 changeset to a single sentence (per pranaygp review) and switch to double-quoted frontmatter (per Copilot + repo convention).
- Implement `ansifyName` to actually apply dim styling to workflow/ / step/ prefixes; add an `Ansi.dim` helper to `@workflow/errors` so callers don't need to import chalk directly.
- Remove the `void getWorkflowMetadata;` workaround in context-errors.ts by dropping the unused value import (we only needed the type and symbol).
- Render the plain-Error throw in `workflow/get-workflow-metadata.ts` with `Ansi.frame` + docs link so the VM path matches the structured-class styling from the sibling step path (still uses a plain Error to avoid the module-init cycle).
- Guard `buildUnderline` against zero-length markers so a stray empty token can't produce a negative `String.repeat` count.
* Structured runtime logger metadata + fold in replay-timeout logging
Adds a `.child()` and `.forRun(runId, workflowName)` child-logger API to
the structured logger so runtime/step code doesn't have to repeat
`workflowRunId`/`workflowName`/`stepId` on every call. Normalizes error
metadata to structured `errorName` / `errorMessage` / `errorStack` fields
instead of ad-hoc `error: err.message` strings, and adds comments to
silent catches that swallow expected idempotency conflicts.
Also folds in the pending changes from #1812 so that PR can be closed:
- Standardize the console prefix to `[workflow-sdk]`.
- Split the replay-timeout log into a warn-while-retrying vs.
error-when-giving-up, and surface the underlying error when we can't
mark a timed-out run as failed.
- Include the error stack in the "Fatal runtime error during workflow
setup" log and in the top-level user-code workflow error log so the
stack surfaces in flattened log drains.
- Drop the `[Workflows] "<runId>" - ` prefix from
`buildWorkflowSuspensionMessage` — the structured logger now attaches
run context.
Supersedes #1812.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Use double-quoted changeset frontmatter per repo convention
* Add SerializationError + apply to user-facing serialization sites
Phase 4 of friendlier errors: introduce a `SerializationError` class with
an optional `hint` and a docs link (workflow-sdk.dev/err/serialization-failed),
and adopt it at every user-facing serialization boundary in @workflow/core:
- Locked ReadableStream at a workflow boundary
- Unregistered class / missing `classId` / missing `WORKFLOW_DESERIALIZE`
- Attempting to return step functions to clients or call workflow functions
directly
- Webhook `respondWith()` called outside a step
- `dehydrate*` / `getSerializeStream` failures (workflow args/return, step
args/return, stream chunks)
Internal invariants (format prefix length checks, unknown format bytes,
missing `STREAM_NAME_SYMBOL`, encryption key/size guards, etc.) now throw
`WorkflowRuntimeError` instead of plain `Error` so the classifier and logger
treat them consistently.
`formatSerializationError` now returns `{ message, hint }` so the hint
fragment can be rendered with the standard SerializationError framing
instead of being baked into the message string.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Use double-quoted changeset frontmatter per repo convention
* Presentation-only user vs SDK error attribution
Add describeError() that derives attribution and class-aware hints from
existing error classes + RUN_ERROR_CODES — no event data changes. Wire into
step failures, max-delivery exhaustion, run failures, and fatal setup errors
so terminal logs include errorAttribution and a hint for known error types.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Address review: describeError accepts precomputed errorCode + instanceof
- `describeError(err, errorCode?)` now accepts an optional precomputed
`RunErrorCode`. `classifyRunError(err)` only narrows to USER_ERROR /
RUNTIME_ERROR, so the REPLAY_TIMEOUT and MAX_DELIVERIES_EXCEEDED branches
were previously unreachable from the step / run failure log sites.
Callers that know the failure category (runtime.ts for replay timeout and
max-deliveries exhaustion) now pass the code in.
- Context-violation checks use `instanceof` against the actual classes from
context-errors.ts instead of a name-string set. Type-safe + survives
class renames.
- Wire the new hints through to the REPLAY_TIMEOUT and MAX_DELIVERIES_EXCEEDED
log sites so those branches actually render a hint now.
- 3 new tests cover the reachable code paths + precomputed-code override.
- Changeset frontmatter switched to double quotes per repo convention.
* Cosmetic consistency pass on remaining bare throws
Internal invariants now use WorkflowRuntimeError so describeError attributes
them to the SDK: missing startedAt, VM generateKey, closure-vars outside
step context, ENOTSUP. defineHook().resume() formats schema validation
failures as a readable list instead of a JSON blob.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Use double-quoted changeset frontmatter per repo convention
* Data-driven describeRunError + expose via @workflow/core/describe-error
Observability renderers read persisted run_failed / step_failed event data,
not live Error instances. describeRunError takes { errorCode, errorName }
and returns the same { attribution, hint } shape as describeError, so the
CLI and web UI can derive user-vs-SDK framing from the event log directly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Friendlier build-time errors: WorkflowBuildError class + applications
Add `WorkflowBuildError` class in `@workflow/errors` with optional `hint`
for an actionable next step, and apply it in `@workflow/builders` at
user-facing sites: failed esbuild phases, unresolved built-in steps, and
empty esbuild output now throw `WorkflowBuildError` with a hint pointing
at the likely fix. Runtime invariants remain plain `Error`.
* Polish friendlier-errors rendering: drop functionName leak, simplify docs link, redirect stack
- Drop the readonly `functionName` param-property on context-error classes so
util.inspect no longer prints a trailing `{ functionName: 'foo()' }` block.
- Replace the `DocLink` ("label: https://…") shape with a plain `DocsUrl`
template-literal type. Error output now renders a single clean line:
`docs: https://…` (new `Ansi.docs` helper) instead of the noisier
"note: Read more about foo(): https://…".
- Add throw helpers (`throwNotInWorkflowContext`, etc.) that call
`Error.captureStackTrace(err, stackStartFn)` on V8 engines so the top frame
of the thrown error points at the user's call site instead of at the gate
function inside the framework. Callers pass themselves as the boundary.
- Refactor `defineHook()` (both root and `/workflow`) to use named function
closures rather than `this.create`/`this.resume`, since the stack redirect
relies on a stable function identity that survives destructuring.
- Update context-errors.test.ts to snapshot the new `docs:` framing and to
add a regression test asserting the top stack frame is the user call site.
* Consolidate friendlier-errors stack: fix ANSI leak + non-retry semantics
Addresses PR review feedback across the 8-phase friendlier-errors stack and
fixes issues surfaced by manual testing (createHook() inside a step):
- ANSI no longer leaks into .message / .stack. Context-violation errors
now store plain text on .message and render the colored framed form
lazily via [util.inspect.custom] / toString(). Structured logs, log
drains, CBOR-serialized events, and JSON payloads no longer contain
raw \x1B[...m bytes.
- Context violations are now fatal. ContextViolationError sets
fatal = true; FatalError.is(err) recognizes any error with a
fatal: true own property. Calling createHook() from a step no longer
burns three retry attempts on a guaranteed-to-fail context violation.
- Ansi helpers moved to @workflow/errors/ansi subpath so imports from
@workflow/errors no longer pull chalk into consumers that only want
error classes (addresses reviewer VaguelySerious).
- Shared redirectStackToCaller helper in packages/core/src/capture-stack.ts,
used by both context-errors.ts and workflow/get-workflow-metadata.ts
(addresses Copilot review on #1849).
- Structured framed content: ContextViolationError now takes a structured
FramedContent (title segments + detail branches) and renders plain/pretty
from the same source of truth.
Tightens the eight existing phase changesets to 1-2 sentences each and adds
four new scoped changesets (errors-ansi-subpath, context-errors-plain-message,
context-errors-fatal, capture-stack-shared) for the followup fixes, so the
final changelog history stays readable.
* test: update step-handler mocks for scoped forRun() logger
The runtime logger now uses .forRun(runId, name, {stepId, stepName})
to attach scope context, so 409-handling log calls no longer repeat
{workflowRunId, stepId} in every metadata bag — those live on the
scoped logger instance. Update the mock to return itself from forRun()
and tighten assertions to check both the log args (errorName/errorMessage)
and the forRun() scope.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Mark SerializationError fatal + route dehydration through step-failure path
SerializationError now carries readonly fatal = true. Step-return
dehydration is wrapped inside the user-code try/catch so that the
resulting error flows through userCodeFailed → step_failed →
FatalError.is() short-circuit instead of bubbling up as HTTP 500 and
triggering a queue retry loop. Retrying a step that returned a non-POJO
is guaranteed to fail the same way, so this saves ~20s and 3 near-
identical error blocks per serialization failure.
* Add logging snapshot tests + manual-test artifacts
Snapshot tests lock in the exact shape of:
- describeError() payloads (attribution, errorCode, hint) for every
classification — plain Error, SerializationError, context-violation,
WorkflowRuntimeError, REPLAY_TIMEOUT, MAX_DELIVERIES_EXCEEDED.
- The scoped-logger call signature for the two canonical runtime
failure paths (fatal-bubble and hit-max-retries), so refactors of
forRun() / child() metadata merging can't silently change what users
see in their log drains.
SerializationError now also has a direct test for readonly fatal=true
+ FatalError.is() recognition.
pr-artifacts/ contains real log-output snapshots from running the
nextjs-turbopack workbench against five error scenarios. These are
reference material for reviewers and are flagged to be removed before
merge.
* Readable step-fatal logs: inline stack + friendly step/workflow names
The step-level fatal-error log used to embed the full stack trace inside
an `errorStack` string field in the metadata object, so util.inspect
rendered it as a quote-escaped, line-continuation blob when the log
hit the terminal — unreadable in practice. Move framing + stack into
the log *message* (matching the workflow-level log in runtime.ts) and
keep the metadata object compact with only the indexable structured
fields (`errorAttribution`, `errorName`, `errorMessage`, `hint`,
IDs). Log drains still get the same keys; humans now see a readable
stack trace.
Also introduce `formatStepName` / `formatWorkflowName` in
`@workflow/utils` that render machine names
(`step//./workflows/1_simple//add`) as `add (./workflows/1_simple)` in
log framings, using the existing `parseStepName` / `parseWorkflowName`
parsers. Applied to step-fatal, hit-max-retries, exceeded-max-retries,
and workflow-threw log sites.
Artifacts in pr-artifacts/ updated to show the new output shape, and
renamed .log → .md since they're Markdown and IDE previews are nicer
that way.
* Opinionated pretty formatter for runtime structured-log metadata
Replace util.inspect's default object dump (which quote-escapes
multi-line stacks and paragraph hints into a single-line JSON-y blob)
with a workflow-aware formatter that composes the entire log line
into a single string passed to console.error / console.warn.
Highlights of the new output:
- Per-run / per-step IDs render with their parsed friendly names so
users see `wrun_… · simple (./workflows/1_simple)` instead of just
the raw `workflowName: 'workflow//./workflows/1_simple//simple'`.
- Color-coded attribution badge (user error red / sdk error magenta)
paired with the error class in bold.
- Hints render as a paragraph under `hint:` rather than a backslash-
`\n`-escaped string.
- Drops redundant fields (errorStack always; errorMessage when it's
already in the parent message) to avoid double-printing.
- Unknown fields fall through as a sorted `key value` tail so we
never silently drop log information.
@workflow/errors/ansi gains bold/red/magenta helpers used by the
formatter. The web / web-shared packages don't consume stderr — they
read structured event payloads from the World event log — so this is
presentation-only at the runtime layer.
* ci(benchmarks): disable pnpm cache for getCommunityWorldsMatrix
The job never runs `pnpm install` (it just calls `node` against a
checked-in script), so the pnpm store path never exists. The post-job
`actions/setup-node@v4` cache-save then fails with `Path Validation
Error: Path(s) specified in the action for caching do(es) not exist`
and red-X's the entire job even though the matrix step succeeded.
The setup-workflow-dev composite already has a `cache-pnpm` opt-out
input for this exact case — wire it through here.
* Address PR review comments: inspect dedup, cause leak, retry-loop tests
- ContextViolationError: util.inspect(err) duplicated every framed detail
line because the stack-tail strip only sliced the first message line.
V8's Error.stack reads `Name: messageLine1\n messageLine2\n at ...`,
so for our multi-line `title\n╰▶ docs: …` messages every detail line
was getting prepended twice (once in the pretty form, once via the
unsliced message tail). Count the actual message lines and slice past
all of them. Repro test asserts `╰▶ docs:` appears exactly once.
- WorkflowError: stop assigning `cause: undefined` as an enumerable own
property when no cause is provided. Subclasses (every error in this PR)
inherit the parent constructor; the unconditional assignment polluted
`util.inspect(err)` output with `{ cause: undefined, … }` on every
no-cause instance. The `super(...)` call already conditionally sets
`.cause` non-enumerably when `options.cause` is provided.
- step-handler.test.ts: add a regression-gate suite that exercises the
fatal-vs-retryable retry-loop wiring directly. Asserts that an error
with `fatal: true` produces exactly one `step_failed` event with no
`step_retrying`, and that a non-fatal `Error` retries via
`step_retrying` on early attempts and emits `step_failed` once the
retry budget is exhausted. Catches the silent-regression case where
`fatal = true` is removed from a context-violation error class but
the `FatalError.is()` unit tests stay green.
* Consolidate changesets + remove pr-artifacts
Address review feedback to drastically shorten the changesets — fold
the 15 file-by-file entries into a single user-facing changeset for
@workflow/core / errors / builders / utils. Also drop the pr-artifacts/
folder (reviewer-only log captures, no longer needed).
* Polish runtime error logging: layout, stack trim, hint consolidation
Five user-driven fixes from manual smoke-testing of #1849:
1. Logger layout. composeLogLine() now puts the structured-fields block
(attribution badge, run/step IDs, error code) **between** the framing
line and the stack body, instead of after it where 30+ lines of stack
buried the most useful information. The framing stays at the top,
stack at the bottom, structured info readable at a glance.
2. Stack trim. Drops framework-internal frames (`node_modules/.pnpm/`,
`node:internal/`, Turbopack-bundled `node_modules__pnpm_*` chunks,
`_next_dist_*` chunks) and caps the surviving frame count at 6
so the stack stays compact even on heavy async wrappers. Suppressed
runs emit one summary line so users know the trim happened.
3. Wrapper-route noise. The nextjs-turbopack workbench's start route
was catching `WorkflowRunFailedError` rejection on
`Promise.race([readLoop(), run.returnValue])` and re-logging it via
`console.error('Error in workflow stream:', error)` plus
`controller.error(error)` — which then triggered Next.js's
`⨯ failed to pipe response` overlay. The SDK already logs the
failure cleanly upstream and the runId is on the response header, so
the wrapper now closes the SSE stream cleanly on
WorkflowRunFailedError.
4. Consistent framed `╰▶ hint:` / `╰▶ docs:` layout for all errors
that carry a hint or docs slug. WorkflowError, SerializationError,
and WorkflowBuildError now share one `appendFramedDetails` helper
matching the box-drawing structure that ContextViolationError
already used. Was: blank-line-separated `Learn more: <url>`. Now:
one tree, indistinguishable from context-violation rendering.
5. Drop the duplicate logger-side `hint` field. Hints now live on the
error message only — actionable hints get serialized into the event
log, rehydrated on the workflow side, and shown in observability
automatically. The previous logger-only hint duplicated stderr but
never made it past the step boundary.
Updated SerializationError hint to point at the foundations doc
("Ensure you're returning workflow serializable types. Check the
serialization docs to see what's serializable:
https://workflow-sdk.dev/docs/foundations/serialization") instead
of the hardcoded `(plain objects, arrays, primitives, …)` list,
which drifted out of sync as the supported types grew. Same hint
reuses for step args, workflow args/return, stream messages, and
any other site that goes through `formatSerializationError`.
Also retitled the retry summary `3 retries` → `3 max retries` since
"3 retries" next to "4 attempts" was ambiguous (already-happened vs.
budget).
* Trim error-card title + drop machine step name from persisted error
- ErrorStackBlock (web observability): show just the first non-empty
trimmed line of the error message in the card title with single-line
truncation. Multi-line messages (`Failed to serialize step return
value\n╰▶ hint: …`) were rendering the entire framed body in the
title, pushing the copy button off-screen and burying the
scannability of the headline. Full message stays in the body via
the stack (V8 prepends `Name: message` to `Error.stack`), so no
information is lost; hover-tooltip exposes the full title text.
- Persisted error message: drop the `Step "step//./.../foo"` machine
name from `Step failed after N retries: …` and `Step exceeded max
retries (…)` strings. Observability already attributes the event
to a specific step via the UI tree, and the CLI logger emits the
friendly `Step foo (./...) hit max retries` framing on its own
line. Embedding the raw `step//./...` machine name in the persisted
message text was duplicate noise.
* Update .changeset/friendlier-errors.md
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
* Update .changeset/pretty-log-format.md
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
* Update SerializationError snapshot tests for slug-less message
The class no longer attaches a slug-based `╰▶ docs:` line — the
foundations URL is embedded directly in the hint via the
`formatSerializationError` helper in @workflow/core. Update the test
expectations accordingly:
- bare-title case is now a single line (no docs link)
- hint case renders one `╰▶ hint: …` branch (no second branch)
* Update serialization.test.ts hint assertions for foundations URL
Four `should throw error for an unsupported type` cases were still
asserting on the old hardcoded type list. Update to the new hint
phrasing that points at the foundations doc, matching the change in
`formatSerializationError` (`packages/core/src/serialization/errors.ts`).
---------
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
2026-05-04 14:04:24 +09:00
|
|
|
"./describe-error": {
|
|
|
|
|
"types": "./dist/describe-error.d.ts",
|
|
|
|
|
"default": "./dist/describe-error.js"
|
|
|
|
|
},
|
2025-10-23 12:07:52 +03:00
|
|
|
"./_workflow": "./dist/workflow/index.js"
|
|
|
|
|
},
|
|
|
|
|
"scripts": {
|
QuickJS engine: host-side, side-effect-free serialization via handles (#3263)
* Add opt-in QuickJS WASM VM engine (WORKFLOW_VM=quickjs) with full event replay
* QuickJS engine: AbortController, setAttributes, terminal drain, turbo-safe requeue, stable PRNG seed
* QuickJS engine: hook.getConflict support, cross-run writable forwarding symbols
* QuickJS engine: stream framing round-trip, bound step proxies, webhook fidelity
* Apply biome fixes to QuickJS engine files
* Address review feedback: anchor source-map strip to end-of-input, use getWorkflowQueueName for conflict requeue, Buffer-free asset decoding, function replacers for payload injection, maxEventsLimit guard
* CI: include generated QuickJS source assets in shared e2e build artifacts
* Fix same-token hook ordering and conflicted-hook disposal in the QuickJS engine
* CI: run both VM engines across all frameworks and worlds; label jobs with the engine
* Fix stack overflow stripping inline source maps from webpack dev bundles; harden step-listing e2e assertions against eventually-consistent reads
* e2e: poll step listings until analytics rows include attempt (optional column can lag terminal status)
* e2e: use --withData to force storage-backed step listings for attempt assertions (analytics listing can omit attempt entirely)
* Sort imports in QuickJS serialization files (biome organizeImports)
* QuickJS engine: resolve the run's full payload-key capability so sealed (encp) hook payloads open
Main's sealed-box work (#3096) makes cross-deployment resumeHook() seal
hook payloads to the target run's published X25519 public key. The shared
start() path publishes that key regardless of engine, so QuickJS runs
receive sealed payloads too — but the QuickJS entrypoint resolved only the
bare symmetric key via importKey(), which cannot open encp envelopes. The
first sealed hook payload wedged the run right after hook_received, timing
out every hook/webhook e2e on Vercel prod (node:vm legs were fine — the
node engine resolves the full capability via memoizeEncryptionKey).
Resolve deriveRunPayloadKeys() in the entrypoint instead and widen the
runtime's key types from CryptoKey to DecryptionKey. Writes stay symmetric
(encrypt() with RunPayloadKeys takes the encr path). Regression test seals
a payload exactly as resumeHook does and round-trips it through the VM.
* Address review: crypto/process parity, loud Intl guards, lazy engine import, VM-leak guard, telemetry namespace, eval-string escaping
- Deterministic crypto.getRandomValues/randomUUID in the VM bootstrap,
drawing from the seeded Math.random (identical sequences to the node
engine's vm/index.ts implementations); all crypto.subtle methods throw
with step-function guidance. process.env exposed as a frozen copy,
matching node.
- Intl: throwing constructors (no ICU in QuickJS), and toLocale*-family
methods (incl. localeCompare) throw when given an explicit locale so
cross-engine divergence is loud instead of silently writing different
values into the event log. No-argument forms keep working.
- runtime.ts lazy-imports the QuickJS entrypoint at dispatch, keeping the
~1.3MB embedded WASM assets out of node-engine deployments.
- runQuickJSWorkflow wraps the per-run phase so an exceptional exit
disposes the VM instead of leaking it in a reused compute instance;
corrected the misleading fail-loud comment (run_failed, not retry);
warn when the event drain loop exhausts its iteration bound.
- Telemetry attributes renamed quickjs.* → workflow.vm.* to stay in the
file's workflow.* namespace.
- Eval-string correlation-id interpolation uses JSON.stringify instead of
quote-only escaping.
- common-vm.test.ts pins the reducer/reviver superset invariant against
common.ts so the duplicated sets can't silently drift.
- Docs enumerate the remaining global-surface differences (subtle.digest,
Intl, WebAssembly, Atomics); quickjs-entrypoint documents the known
precondition-guard gap.
* QuickJS engine: implement resilient resumeHook (hookInput materialization + resumeId dedup)
#1834 made resumeHook() fall back to enqueueing the run with a hookInput
payload when the direct hook_received write fails transiently, with the
runtime materializing the missing event on delivery. Only the node:vm
path implemented it — the QuickJS dispatch returned before the node
block, so the resilient payload was silently dropped and the new e2e
timed out on every quickjs leg.
- runtime.ts threads hookInput into runWorkflowWithQuickJS; the
entrypoint materializes the missing hook_received after loading the
event log (resumeId-keyed dedup, occurredAt from the resumeId ULID,
local eventData substitution for lazy/ref responses, EntityConflict /
HookNotFound handling) — mirroring the node block.
- processEvents drops duplicate hook_received rows sharing a resumeId
(first-in-log wins), matching the node engine's EventsConsumer dedup;
the seen-set lives in the VM heap so it is deterministic per replay.
Verified against the dev server with WORKFLOW_VM=quickjs: the resilient
resume e2e passes and the materialization is observable in the logs; all
27 hook e2e tests green.
* QuickJS engine: inline step execution via live-VM continuation loop + WASM module caching
* Address review: exclusive inline step claims, self-write requeue, in-loop event ceiling
- Inline steps now claim via a lazy step_started carrying the input
(step_created deferred, atomic create-claim in the world), with
ownerMessageId stamped and authoritativeAttempt=1 — a concurrent
invocation racing on the same fresh step loses with
EntityConflictError and skips instead of both bare-starting the step
and double-running the body. This also removes the stepsCreatedByUs
set, whose 'created by us' invariant didn't survive the swallowed
create-race conflict; redelivery backstops now key on hasCreatedEvent.
- dispatchPendingOps' createdAttributeEvent/createdGetConflictHook
signals are consumed again: when the loop exits suspended without ever
reading back a self-written attr_set / getConflict hook_created
(eventually-consistent listing lag), the entrypoint requeues
immediately instead of parking the run awaiting_external with its
unblocking event already written.
- The server-supplied event ceiling is re-checked at the top of every
continuation-loop turn (seenEventIds.size), so a single invocation
fanning out inline can no longer grow the log arbitrarily past the
operator's limit. The quickjs dispatch in runtime.ts converts
MaxEventsExceededError into run_failed / MAX_EVENTS_EXCEEDED — the
guard's throw previously nacked forever, parking runaway runs in
'running'.
- Documented the deliberate decision that the platform function timeout
is the only bound on inline chaining (budget parked per batch),
matching the node engine.
* QuickJS engine: host-side, side-effect-free serialization via handles
(Re-applied onto the review-fixed base; original commits da2723016 +
9814ed9ac squashed.)
Replace the in-VM serde bundle with a host-side codec
(runtime/quickjs-serde.ts) built on quickjs-wasi 3.3's introspection
primitives and devalue 5.9's pluggable stringify/parse operations —
mirroring the node:vm engine's architecture.
Review fixes incorporated:
- reducer/reviver key sets are pinned against codec-devalue-vm's
workflow mode by exhaustiveness tests (exact order for reducers —
first match wins), so the handle-space codec can't silently drift
from the shared value-space sets.
- the devalue entry in minimumReleaseAgeExclude is removed: the exact
version is pinned via the workspace catalog + lockfile, so the
cooldown waiver was unnecessary (verified with both frozen and
regular installs).
- eval-string interpolation inherits the JSON.stringify(cid) hardening
from the base branch.
* Address review: NUL-safe string extraction, deterministic retryAfter, pass-scoped handle disposal, byte-cache lifecycle
- NUL (U+0000) safety across the WASM boundary: handle.toString() routes
through JS_ToCString and silently truncates at the first NUL, and the
C-string key APIs mangle NUL-bearing property keys (drop or collide).
guestString() detects truncation by comparing against the handle's
true guest length and recovers via in-VM JSON.stringify escaping;
shapeOf verifies its fast host-string key list against a guest
Object.keys count (+ duplicate check) and re-extracts through key
handles on mismatch; get/hasOwn route NUL-bearing keys through
length-aware guest string handles. All string funnels (primitives,
symbol descriptions, error fields via chained/own reads, Headers
entries, RegExp source/flags, URL href) go through guestString.
Regression-tested down to the truncate-vs-collide enumeration shapes;
fixes nullByteWorkflow on the quickjs e2e legs.
- RetryableError's absent/invalid retryAfter fallback now reads the
GUEST clock (the deterministic replay clock at the WASI layer) via a
captured Date.now instead of the host wall clock — the in-VM reducer
was replay-stable by construction and the host port silently lost
that.
- Pass-scoped handle disposal: serialize/deserialize sweep every
intermediate handle their pass creates (call/invoke results,
descriptor reads, dups, parse-op constructions), closing the
~one-leaked-handle-per-value-node growth across long-lived inline
sessions. Implemented with module-owned tracking rather than
vm.withScope: the library scope also captures the handles the
host-callback trampoline wraps around C-owned argv pointers, and
disposing those (Map/Set/Headers forEach visitors run mid-pass)
double-frees guest values — observed as WASM memory corruption.
identities is cleared per pass so freed-pointer reuse cannot alias
entries across passes.
- Byte-cache lifecycle: terminal drain now shares the per-VM cache with
the suspension path (re-serializing an op at drain could re-invoke
getters and produce different bytes for what the log treats as one
value), and entries for settled ops — which neither collection filter
can match again — are evicted, bounding the cache by the live pending
set.
* Adopt quickjs-wasi 3.3.1: withScope handle sweeping, real memoryLimit accounting, loud unregistered-callback failures
3.3.1 ships the three fixes this branch surfaced upstream:
- Borrowed host-callback handles (vercel-labs/quickjs-wasi#31): the
trampoline's this/argv handles are scope-exempt, making vm.withScope
safe around host callbacks. The serde's module-owned pass-disposal
apparatus (passDisposal/track/runWithPassDisposal and ~18 track()
wraps) is replaced by withScope in serialize/deserialize — simpler,
and strictly more complete: every handle constructed during the pass
is swept, not just the ones our creation funnels saw. Bench parity
confirmed (within ~10% on the 50k-node extreme case, unchanged
elsewhere; still 2.6-100x over the in-VM codec).
- Real memoryLimit accounting (vercel-labs/quickjs-wasi#33): the
engine's 256 MB VM ceiling now actually bounds retained guest
allocations (usable-size was 0 on wasm32-wasi before, so the limit
never accumulated).
- Unregistered host callbacks throw (vercel-labs/quickjs-wasi#34):
guest calls into missing callbacks fail loud instead of silently
returning undefined — protection this engine wants for
snapshot-restore re-registration bugs.
Also merges origin/main (undici 7.29.0).
* Address review: lossless lone-surrogate string extraction, portable base64
P1 — the guestString length check was insufficient: JS_ToCString has
TWO corruptions (NUL truncation, lone-surrogate -> U+FFFD replacement)
and they can cancel — the replacement expansion offsets the truncation
so the extracted length matches the true guest length. A bare lone
surrogate can also replace 1:1 with no length change at all. Worse,
the JSON.stringify slow path was itself lossy for lone surrogates:
QuickJS passes them through raw, and the C-string extraction of ITS
output corrupts them.
- guestString accepts the fast value only when length matches AND it
contains no U+FFFD (legitimate U+FFFD strings take the loss-free slow
path); the slow path now escapes INSIDE the VM to printable ASCII via
a new captured escapeString intrinsic (WTF-16-safe per-code-unit
\uXXXX escaping), then JSON-parses host-side.
- shapeOf's fast-key acceptance adds a U+FFFD scan alongside the
count/duplicate checks (lone-surrogate keys corrupt with count and
uniqueness intact).
- get()/hasOwn() route keys through guest string handles when they
carry a NUL or an UNPAIRED surrogate (paired surrogates - emoji keys
- encode fine through the C-string APIs; vm.newString is verified
WTF-16-preserving for the handle path).
- Tests: the reviewer's exact length-canceling case, bare lone
surrogates, legit-U+FFFD passthrough, byte parity with the reference
codec, and lone-surrogate/mixed keys.
P2 — the codec's base64 helpers no longer carry an unconditional Node
Buffer dependency: feature-detected Uint8Array.fromBase64/toBase64
when available, Buffer when present, btoa/atob loop otherwise —
keeping WASM-only/non-Node hosts (Cloudflare Workers) viable.
* Adopt quickjs-wasi 3.4.0: delete the NUL/surrogate string machinery
3.4.0 ships lossless string transport (vercel-labs/quickjs-wasi#35 —
found by this PR's review cycle), so the SDK-side detection and escape
machinery is deleted wholesale:
- guestString (length + U+FFFD detection, in-VM escape fallback) — plain
toString() is lossless now
- the escapeString / hasOwnCall / jsonStringify / objectKeys captured
intrinsics
- keyNeedsHandleLookup and the handle-keyed get/hasOwn routing — the
library routes inexpressible keys itself
- shapeOf's guest-key verification pass (count/duplicate/U+FFFD scans) —
enumeration is lossless
Net ~130 lines and four captured intrinsics removed; the serde now uses
the plain quickjs-wasi surface everywhere.
Test honesty fix that 3.4.0 forced: the earlier lone-surrogate
round-trip tests passed only via mutual corruption — the pre-3.4.0
lossy host→guest transport corrupted the guest comparison literals
identically to the wire. With an honest transport they exposed that the
WIRE itself (devalue emits lone surrogates raw; the wire is UTF-8)
degrades lone surrogates to U+FFFD — in the node engine's reference
codec exactly as here, verified. Bug-compatible parity is the
load-bearing property (event logs replay across engines), so those
tests now assert byte parity with the reference codec plus
guest-observed equality with the reference codec's own round trip; NULs
are devalue-escaped and asserted to survive exactly. Wire-level
surrogate preservation is a product-wide devalue/UTF-8 question,
tracked separately from this engine.
2026-08-06 14:47:08 -07:00
|
|
|
"build": "genversion --es6 src/version.ts && node scripts/build-quickjs-assets.js && tsc",
|
2026-01-27 13:15:06 -08:00
|
|
|
"dev": "genversion --es6 src/version.ts && tsc --watch",
|
Workflow Skills for npx skills (#842)
* Add workflow skill for Claude Code
Add custom slash commands for Claude Code:
- /pr command for creating PRs following repo conventions
Add workflow skill with comprehensive documentation:
- Main SKILL.md with core concepts and quick reference
- Reference docs for frameworks, AI agents, patterns, errors, and API
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md serialization docs for custom class support
Add documentation for custom class serialization using @workflow/serde
symbols (WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE) and clarify what can
and cannot be serialized based on recent PR #762.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Fix incorrect API property names in SKILL.md
- Update sleep() example date from 2025 to 2027 (future date)
- Fix getWorkflowMetadata() to use correct properties: workflowRunId,
workflowStartedAt, url (not runId, workflowName)
- Fix getStepMetadata() to use correct property: attempt (not attemptNumber)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Move skills to skill/workflow directory structure
Reorganize skill files to support multiple skills under ./skill/<name>/
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Rename skill to skills (plural)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Refactor skills to use bundled docs from node_modules
- Update SKILL.md to direct agents to search node_modules/workflow/docs/
- Remove manually-maintained reference docs (ai-agents, api-reference, frameworks, patterns)
- Keep common-errors.md for practical troubleshooting
- Add prepack/postpack hooks to bundle docs in npm packages:
- workflow: All docs from docs/content/docs/
- @workflow/ai: AI-specific docs
- @workflow/core: foundations, how-it-works, api-reference
- @workflow/next: Next.js setup docs
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add AI SDK step function guidance to SKILL.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md formatting and add ecosystem section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Align postpack scripts with AI SDK conventions
Use del-cli for postpack cleanup to match the AI SDK's cross-platform
approach. This ensures consistent tooling across both projects.
- Add del-cli ^5.1.0 to root devDependencies
- Update postpack in @workflow/core, @workflow/next, @workflow/ai, and
workflow packages from `rm -rf docs` to `del-cli docs`
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add workflow health command to debugging documentation
Document the CLI health check command for verifying workflow/step
endpoint connectivity during local development and production.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Address PR feedback: standardize on rm -rf and improve SKILL.md
- Remove del-cli dependency, use rm -rf consistently in postpack scripts
- Restructure SKILL.md to lead with "Prefer Step Functions" pattern
- Add sandbox limitations table for workflow-context workarounds
- Consolidate examples and remove duplication
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Consolidate common-errors.md into SKILL.md
Move useful tips (serialization, streaming, debugging) into main skill
file and remove the separate common-errors.md reference file.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
---------
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
2026-01-27 18:41:09 -07:00
|
|
|
"clean": "tsc --build --clean && rm -rf dist src/version.ts docs ||:",
|
2026-08-21 15:59:29 -07:00
|
|
|
"test": "cross-env WORKFLOW_TARGET_WORLD=local vitest run src e2e/utils.test.ts",
|
2025-10-23 12:07:52 +03:00
|
|
|
"test:e2e": "vitest run e2e",
|
Workflow Skills for npx skills (#842)
* Add workflow skill for Claude Code
Add custom slash commands for Claude Code:
- /pr command for creating PRs following repo conventions
Add workflow skill with comprehensive documentation:
- Main SKILL.md with core concepts and quick reference
- Reference docs for frameworks, AI agents, patterns, errors, and API
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md serialization docs for custom class support
Add documentation for custom class serialization using @workflow/serde
symbols (WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE) and clarify what can
and cannot be serialized based on recent PR #762.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Fix incorrect API property names in SKILL.md
- Update sleep() example date from 2025 to 2027 (future date)
- Fix getWorkflowMetadata() to use correct properties: workflowRunId,
workflowStartedAt, url (not runId, workflowName)
- Fix getStepMetadata() to use correct property: attempt (not attemptNumber)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Move skills to skill/workflow directory structure
Reorganize skill files to support multiple skills under ./skill/<name>/
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Rename skill to skills (plural)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Refactor skills to use bundled docs from node_modules
- Update SKILL.md to direct agents to search node_modules/workflow/docs/
- Remove manually-maintained reference docs (ai-agents, api-reference, frameworks, patterns)
- Keep common-errors.md for practical troubleshooting
- Add prepack/postpack hooks to bundle docs in npm packages:
- workflow: All docs from docs/content/docs/
- @workflow/ai: AI-specific docs
- @workflow/core: foundations, how-it-works, api-reference
- @workflow/next: Next.js setup docs
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add AI SDK step function guidance to SKILL.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md formatting and add ecosystem section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Align postpack scripts with AI SDK conventions
Use del-cli for postpack cleanup to match the AI SDK's cross-platform
approach. This ensures consistent tooling across both projects.
- Add del-cli ^5.1.0 to root devDependencies
- Update postpack in @workflow/core, @workflow/next, @workflow/ai, and
workflow packages from `rm -rf docs` to `del-cli docs`
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add workflow health command to debugging documentation
Document the CLI health check command for verifying workflow/step
endpoint connectivity during local development and production.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Address PR feedback: standardize on rm -rf and improve SKILL.md
- Remove del-cli dependency, use rm -rf consistently in postpack scripts
- Restructure SKILL.md to lead with "Prefer Step Functions" pattern
- Add sandbox limitations table for workflow-context workarounds
- Consolidate examples and remove duplication
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Consolidate common-errors.md into SKILL.md
Move useful tips (serialization, streaming, debugging) into main skill
file and remove the separate common-errors.md reference file.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
---------
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
2026-01-27 18:41:09 -07:00
|
|
|
"typecheck": "genversion --es6 src/version.ts && tsc --noEmit",
|
docs: split v4/v5 content trees and fix version switcher end-to-end (#1948)
* docs: split v4/v5 content, fix version switcher end-to-end
## Content restructuring
- Split `docs/content/docs/` into `docs/content/docs/v4/` and
`docs/content/docs/v5/` so each version is a fully independent
content tree with no shared-file coupling
- v4 excludes the four pages that are v5-only (AbortController
cancellation docs and the serializable-abort-controller internal page)
- v5 retains all pages; `preRelease` frontmatter field removed (no
longer needed now that each version is its own folder)
- Removed `AbortController` / `AbortSignal` from v4 serialization page
(section moved to v5 only)
## Fumadocs source
- Added `v4docs` and `v5docs` as separate `defineDocs()` collections in
`source.config.ts`; shared `docsSchema` (no more `preRelease` field)
- `source.ts` exports both `source` (v4, `baseUrl: /docs`) and
`v5Source` (v5, same base URL)
## Version routing
- `version-source.ts` simplified: `filterPreReleaseFromNodes` and
`isPreReleaseUrl` logic removed; v4 tree uses `source`, v5 tree uses
`v5Source` + `rewriteNodeUrls`
- v4 `page.tsx`: removed `preRelease` guard (v4Source has no such pages)
- v5 `page.tsx`: uses `v5Source` for `getPage` / `generateStaticParams`
/ `generateMetadata`; `v5Link` wrapper rewrites `/docs/…` hrefs to
`/v5/docs/…` so inline MDX links stay in the v5 context
## Versioned cookbook
- Added `app/[lang]/v5/cookbook/` layout + page (mirrors v4 but uses
`v5Source`, `rewriteCookbookUrlForVersion`, and `V5CookbookLink`)
- `getCookbookTree` accepts a `versionPrefix` parameter; sidebar URLs
are prefixed accordingly (`/v5/cookbook/…`)
- `cookbook-tree.ts`: added `skipVersions?: string[]` per-recipe field
for version-specific exclusions; `distributed-abort-controller` is
marked `skipVersions: ['v5']`
## Version switcher — state & navigation
- New `VersionProvider` context (`hooks/geistdocs/use-version.tsx`)
backed by `localStorage`: URL is source of truth on versioned pages,
`localStorage` carries the preference across non-versioned pages
(cookbook overview, worlds, etc.)
- `VersionSwitcher` uses `useVersion()` context instead of URL-only
detection; now visible on all pages including cookbook
- `DesktopMenu` and `MobileMenu` use `activeVersion` from context so
the "Docs" and "Cookbook" navbar links resolve to the correct version
prefix on every page
- `buildVersionUrl` expanded to handle `/cookbook/…` paths alongside
`/docs/…`; non-versioned routes (worlds, api) return unchanged
- `switchVersion` does a `HEAD` probe before navigating; falls back to
the versioned cookbook or docs home if the target page doesn't exist
in that version (handles v4-only → v5 and v5-only → v4 cases)
## Cookbook content (v5)
- Rewrote `agent-cancellation` recipe using a single `AbortController`
pattern; removed Hard Cancellation vs Stop Signal two-approach
comparison
- Deleted `distributed-abort-controller` recipe from v5 (native
`AbortController` serialization makes it unnecessary)
- Removed references to distributed-abort-controller from
`cookbook/index.mdx` and `common-patterns/timeouts.mdx`
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(docs): use abortSignal (not signal) in DurableAgent.stream() options
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(docs): update prepack scripts to use versioned content paths
Content moved from docs/content/docs/ to docs/content/docs/v5/ on main
(pre-release channel). Stable branch will use v4/ after backport.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 22:25:15 -07:00
|
|
|
"prepack": "mkdir -p docs && cp -r ../../docs/content/docs/v5/foundations ./docs/ && cp -r ../../docs/content/docs/v5/how-it-works ./docs/ && cp -r ../../docs/content/docs/v5/api-reference/workflow ./docs/api-reference",
|
Workflow Skills for npx skills (#842)
* Add workflow skill for Claude Code
Add custom slash commands for Claude Code:
- /pr command for creating PRs following repo conventions
Add workflow skill with comprehensive documentation:
- Main SKILL.md with core concepts and quick reference
- Reference docs for frameworks, AI agents, patterns, errors, and API
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md serialization docs for custom class support
Add documentation for custom class serialization using @workflow/serde
symbols (WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE) and clarify what can
and cannot be serialized based on recent PR #762.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Fix incorrect API property names in SKILL.md
- Update sleep() example date from 2025 to 2027 (future date)
- Fix getWorkflowMetadata() to use correct properties: workflowRunId,
workflowStartedAt, url (not runId, workflowName)
- Fix getStepMetadata() to use correct property: attempt (not attemptNumber)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Move skills to skill/workflow directory structure
Reorganize skill files to support multiple skills under ./skill/<name>/
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Rename skill to skills (plural)
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Refactor skills to use bundled docs from node_modules
- Update SKILL.md to direct agents to search node_modules/workflow/docs/
- Remove manually-maintained reference docs (ai-agents, api-reference, frameworks, patterns)
- Keep common-errors.md for practical troubleshooting
- Add prepack/postpack hooks to bundle docs in npm packages:
- workflow: All docs from docs/content/docs/
- @workflow/ai: AI-specific docs
- @workflow/core: foundations, how-it-works, api-reference
- @workflow/next: Next.js setup docs
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add AI SDK step function guidance to SKILL.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Update SKILL.md formatting and add ecosystem section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Align postpack scripts with AI SDK conventions
Use del-cli for postpack cleanup to match the AI SDK's cross-platform
approach. This ensures consistent tooling across both projects.
- Add del-cli ^5.1.0 to root devDependencies
- Update postpack in @workflow/core, @workflow/next, @workflow/ai, and
workflow packages from `rm -rf docs` to `del-cli docs`
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Add workflow health command to debugging documentation
Document the CLI health check command for verifying workflow/step
endpoint connectivity during local development and production.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Address PR feedback: standardize on rm -rf and improve SKILL.md
- Remove del-cli dependency, use rm -rf consistently in postpack scripts
- Restructure SKILL.md to lead with "Prefer Step Functions" pattern
- Add sandbox limitations table for workflow-context workarounds
- Consolidate examples and remove duplication
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
* Consolidate common-errors.md into SKILL.md
Move useful tips (serialization, streaming, debugging) into main skill
file and remove the separate common-errors.md reference file.
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
---------
Signed-off-by: johnlindquist <johnlindquist@gmail.com>
Signed-off-by: John Lindquist <johnlindquist@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
2026-01-27 18:41:09 -07:00
|
|
|
"postpack": "rm -rf docs"
|
2025-10-23 12:07:52 +03:00
|
|
|
},
|
|
|
|
|
"dependencies": {
|
2026-06-08 16:29:26 -07:00
|
|
|
"@aws-sdk/credential-provider-web-identity": "3.972.49",
|
2025-11-12 14:26:01 -08:00
|
|
|
"@jridgewell/trace-mapping": "0.3.31",
|
|
|
|
|
"@standard-schema/spec": "1.0.0",
|
|
|
|
|
"@types/ms": "2.1.0",
|
2025-10-23 12:07:52 +03:00
|
|
|
"@vercel/functions": "catalog:",
|
|
|
|
|
"@workflow/errors": "workspace:*",
|
2026-01-19 15:38:19 -08:00
|
|
|
"@workflow/serde": "workspace:*",
|
2025-11-06 13:37:58 -08:00
|
|
|
"@workflow/utils": "workspace:*",
|
2025-10-23 12:07:52 +03:00
|
|
|
"@workflow/world": "workspace:*",
|
|
|
|
|
"@workflow/world-local": "workspace:*",
|
|
|
|
|
"@workflow/world-vercel": "workspace:*",
|
2025-11-12 14:26:01 -08:00
|
|
|
"debug": "4.4.3",
|
feat(core): side-effect-free serialization of workflow VM values (#3257)
* feat(core): side-effect-free serialization of workflow VM values
Serialization runs on the host but inspects values constructed inside the
node:vm sandbox, so ordinary dynamic operations dispatch into the sandbox
realm and execute workflow code: `value.toISOString()`, `Array.from(map)`,
`Object.prototype.toString` (via Symbol.toStringTag), `.source`/`.flags`,
`.href`, view `.buffer`/`.byteOffset`/`.byteLength`, and error
`.message`/`.stack`/`.cause` reads.
That is a determinism hazard. A payload is serialized exactly once and is
never re-serialized on replay, so any workflow-visible side effect it
triggers exists only on the live path — a patched `Date.prototype.toISOString`
that consumes a seeded `Math.random()` draw, for example, shifts every
subsequent draw and diverges from replay.
This makes serialization side-effect free where the data allows it, and
observable where it does not:
- Classification uses engine brand checks (node:util types, internal-slot
probes) instead of `instanceof global.X` and Object.prototype.toString, so
it is immune to Symbol.hasInstance, reassigned sandbox globals, and
Symbol.toStringTag spoofs. An unbranded value claiming a brand-decided tag
is now classified as a plain object instead of being routed into an
extractor that requires the real internal slot (unhardened devalue crashes
on that input).
- Extraction goes through intrinsics captured at module load — host boot,
before any workflow bundle runs — invoked with explicit receivers.
Internal slots are realm-agnostic, so host intrinsics read VM-realm
objects without touching the sandbox's patchable prototypes.
- Property access reads through descriptors, so plain data never invokes
anything.
Where workflow code must run because the data lives behind it — getters,
proxies, custom [WORKFLOW_SERIALIZE] methods, toString() on
toStringTag-branded objects like Temporal polyfills — the execution is
preserved for compatibility and recorded in a new `CodecOptions.guestCodeStats`
sink, surfaced as workflow.serialization.guest_code_{executions,details} span
attributes. Consumers that retain a VM across steps can treat a non-empty
report as "serialization may have perturbed VM state".
Engine-provided accessors are deliberately not reported: V8 defines `stack`
as an own accessor on every Error instance, so reporting it would flag every
serialized error. Nativeness is decided with the captured host
Function.prototype.toString; the bound-function caveat is documented in
hardened.ts.
Requires devalue 5.9.0 for the pluggable `operations` option.
* chore: shorten changeset
* fix(core): close review gaps in hardened serialization
Five correctness fixes, all with repros:
- Callable proxies were treated as engine accessors. V8 returns
`function () { [native code] }` from Function.prototype.toString for a
proxy around a function rather than throwing, so a proxy-wrapped getter
was cached as engine-provided and invoked unreported. Gate on
types.isProxy first.
- Host builtins implemented in JavaScript were reported as workflow code.
Node's DOMException.prototype.message/name are ordinary functions, so
the nativeness test failed and every serialized DOMException reported
two getter executions. They belong to the *host* realm, though, and
workflow code cannot author a host-realm function — so provenance is
now decided by nativeness OR host-realm `Function.prototype`, which are
disjoint and together cover both cases (V8 installs `stack` per realm,
so a VM error's getter is native but VM-realm).
- The extraReducers at the two VM call sites were still unhardened, and
they run on every value the earlier reducers do not claim — which is
exactly where the report has to be complete. `instanceof
global.ReadableStream/WritableStream/Request/Response` consulted
Symbol.hasInstance on the sandbox class (14 invocations for an ordinary
payload once the classes are patched), and AbortController's guard did
a bare `value.signal` read, so a non-enumerable `signal` getter ran
with an empty report. All five now walk the prototype chain and read
through descriptors.
- `__closureVarsFn` was invoked unreported on a purity argument that
nothing checked: the property is reachable from workflow code, which
can replace the compiler-generated function. step.ts now registers the
generated function as trusted when it builds the proxy, so provenance
is verified rather than assumed, and an unrecognized function is
reported.
- The URL/URLSearchParams test patched prototypes of *host* classes
injected into the sandbox, mutating them for the rest of the worker
process. Restored in a finally.
Also, per review:
- `dehydrateStepArguments` / `dehydrateWorkflowReturnValue` take an
optional GuestCodeStats out-param, so a retained-VM gate can consume
the report instead of it being spent on span attributes. The
report-completeness tests use it to exercise the real dehydrate path.
- Every intrinsic capture is now optional. The table is built at module
scope, so a missing member was an import-time crash of @workflow/core
rather than a degraded path; only SharedArrayBuffer was guarded, while
URLSearchParams.prototype.size (Node 19.8+) and the WHATWG classes were
assumed. Absent captures now make the corresponding reducer decline to
match.
- Documented that recording is not prevention (a recorded getter calling
Math.random() still advances the run's seeded PRNG), and that a
`{ kind: 'proxy' }` report implies a silent shape change (a proxied Map
serializes as a plain object).
- Parity coverage extended to DataView, boxed primitives, null-prototype
objects, setter-only properties, DOMException, AggregateError, an
accessor-valued Symbol.toStringTag, both RetryableError retryAfter
paths, and a WORKFLOW_SERIALIZE class instance.
* fix(core): keep identifying proxied host classes
Every Next.js e2e job failed on the two webhook tests: the hook POST
returned 404 because `resumeWebhook` could not serialize its step return
value ("Cannot stringify arbitrary non-POJOs"), so no hook was ever
registered.
The value was a `NextRequest`, which Next.js hands over as a **Proxy**.
`isInstanceOfPrototype` rejected proxies outright, so the Request reducer
answered "not a Request" and devalue fell through to the POJO check. The
reasoning behind rejecting them — that proxied built-ins were never
serializable, because internal-slot reads throw on a proxy receiver — is
true for `Map`/`Date`/`URL`, whose reducers read internal slots, but not
for `Request`/`Response`/streams, whose reducers read ordinary
properties. Next's proxy forwards those with the target as receiver, so
they serialized fine before this PR.
Identification now walks through proxies, matching `instanceof`, and
records the traps rather than suppressing the answer. The three reducers
that do read internal slots (URL, URLSearchParams, Headers) fall back to
the dynamic read when the value is a proxy, so their behavior is exactly
what it was before — including throwing for a bare proxy over a built-in,
which threw before too.
Verified against the real thing: the full nextjs-turbopack e2e suite
(135 tests) passes locally, having reproduced the failure first and
confirmed a reverted `serialization.ts` fixed it.
The regression test uses a receiver-correcting proxy, which is what makes
NextRequest work in practice; a comment records that a bare
`new Proxy(request, {})` throws on undici's private slots with or without
this change.
* fix(core): state what the closure-fn mark proves, and correct stale docs
- `isInstanceOfPrototype`'s JSDoc still described the behavior removed in
8bc462fb5 (proxies rejected without firing traps), which is the opposite
of what it now does.
- The `__closureVarsFn` provenance check proves the function was passed to
`useStep`, not that this package generated it: `useStep` is published on
the sandbox global, so workflow code can call it with a function of its
own and have it marked. Renamed `registerTrustedFunction` /
`isTrustedFunction` to `markUseStepClosureFn` / `isUseStepClosureFn` so
the name states the boundary, and documented the laundering caveat
alongside the existing ones. Marking still earns its keep — reporting
every step that captures a variable would bury the signal — and closing
the gap properly needs a compiler-emitted marker, which is a compiler
change.
- Added the missing coverage for both sides of that check: an unmarked
`__closureVarsFn` is invoked and reported, a marked one is invoked and
not.
- `guestCodeStats` was documented as something a retained-VM gate consumes,
but no runtime caller passes a sink; the executions reach telemetry from
every dehydrate path regardless. Reworded both docs to say that, so the
out-param is not mistaken for wiring that already exists.
2026-08-01 03:11:06 -07:00
|
|
|
"devalue": "5.9.0",
|
2025-10-23 12:07:52 +03:00
|
|
|
"ms": "2.1.3",
|
2025-11-12 14:26:01 -08:00
|
|
|
"nanoid": "5.1.6",
|
QuickJS engine: host-side, side-effect-free serialization via handles (#3263)
* Add opt-in QuickJS WASM VM engine (WORKFLOW_VM=quickjs) with full event replay
* QuickJS engine: AbortController, setAttributes, terminal drain, turbo-safe requeue, stable PRNG seed
* QuickJS engine: hook.getConflict support, cross-run writable forwarding symbols
* QuickJS engine: stream framing round-trip, bound step proxies, webhook fidelity
* Apply biome fixes to QuickJS engine files
* Address review feedback: anchor source-map strip to end-of-input, use getWorkflowQueueName for conflict requeue, Buffer-free asset decoding, function replacers for payload injection, maxEventsLimit guard
* CI: include generated QuickJS source assets in shared e2e build artifacts
* Fix same-token hook ordering and conflicted-hook disposal in the QuickJS engine
* CI: run both VM engines across all frameworks and worlds; label jobs with the engine
* Fix stack overflow stripping inline source maps from webpack dev bundles; harden step-listing e2e assertions against eventually-consistent reads
* e2e: poll step listings until analytics rows include attempt (optional column can lag terminal status)
* e2e: use --withData to force storage-backed step listings for attempt assertions (analytics listing can omit attempt entirely)
* Sort imports in QuickJS serialization files (biome organizeImports)
* QuickJS engine: resolve the run's full payload-key capability so sealed (encp) hook payloads open
Main's sealed-box work (#3096) makes cross-deployment resumeHook() seal
hook payloads to the target run's published X25519 public key. The shared
start() path publishes that key regardless of engine, so QuickJS runs
receive sealed payloads too — but the QuickJS entrypoint resolved only the
bare symmetric key via importKey(), which cannot open encp envelopes. The
first sealed hook payload wedged the run right after hook_received, timing
out every hook/webhook e2e on Vercel prod (node:vm legs were fine — the
node engine resolves the full capability via memoizeEncryptionKey).
Resolve deriveRunPayloadKeys() in the entrypoint instead and widen the
runtime's key types from CryptoKey to DecryptionKey. Writes stay symmetric
(encrypt() with RunPayloadKeys takes the encr path). Regression test seals
a payload exactly as resumeHook does and round-trips it through the VM.
* Address review: crypto/process parity, loud Intl guards, lazy engine import, VM-leak guard, telemetry namespace, eval-string escaping
- Deterministic crypto.getRandomValues/randomUUID in the VM bootstrap,
drawing from the seeded Math.random (identical sequences to the node
engine's vm/index.ts implementations); all crypto.subtle methods throw
with step-function guidance. process.env exposed as a frozen copy,
matching node.
- Intl: throwing constructors (no ICU in QuickJS), and toLocale*-family
methods (incl. localeCompare) throw when given an explicit locale so
cross-engine divergence is loud instead of silently writing different
values into the event log. No-argument forms keep working.
- runtime.ts lazy-imports the QuickJS entrypoint at dispatch, keeping the
~1.3MB embedded WASM assets out of node-engine deployments.
- runQuickJSWorkflow wraps the per-run phase so an exceptional exit
disposes the VM instead of leaking it in a reused compute instance;
corrected the misleading fail-loud comment (run_failed, not retry);
warn when the event drain loop exhausts its iteration bound.
- Telemetry attributes renamed quickjs.* → workflow.vm.* to stay in the
file's workflow.* namespace.
- Eval-string correlation-id interpolation uses JSON.stringify instead of
quote-only escaping.
- common-vm.test.ts pins the reducer/reviver superset invariant against
common.ts so the duplicated sets can't silently drift.
- Docs enumerate the remaining global-surface differences (subtle.digest,
Intl, WebAssembly, Atomics); quickjs-entrypoint documents the known
precondition-guard gap.
* QuickJS engine: implement resilient resumeHook (hookInput materialization + resumeId dedup)
#1834 made resumeHook() fall back to enqueueing the run with a hookInput
payload when the direct hook_received write fails transiently, with the
runtime materializing the missing event on delivery. Only the node:vm
path implemented it — the QuickJS dispatch returned before the node
block, so the resilient payload was silently dropped and the new e2e
timed out on every quickjs leg.
- runtime.ts threads hookInput into runWorkflowWithQuickJS; the
entrypoint materializes the missing hook_received after loading the
event log (resumeId-keyed dedup, occurredAt from the resumeId ULID,
local eventData substitution for lazy/ref responses, EntityConflict /
HookNotFound handling) — mirroring the node block.
- processEvents drops duplicate hook_received rows sharing a resumeId
(first-in-log wins), matching the node engine's EventsConsumer dedup;
the seen-set lives in the VM heap so it is deterministic per replay.
Verified against the dev server with WORKFLOW_VM=quickjs: the resilient
resume e2e passes and the materialization is observable in the logs; all
27 hook e2e tests green.
* QuickJS engine: inline step execution via live-VM continuation loop + WASM module caching
* Address review: exclusive inline step claims, self-write requeue, in-loop event ceiling
- Inline steps now claim via a lazy step_started carrying the input
(step_created deferred, atomic create-claim in the world), with
ownerMessageId stamped and authoritativeAttempt=1 — a concurrent
invocation racing on the same fresh step loses with
EntityConflictError and skips instead of both bare-starting the step
and double-running the body. This also removes the stepsCreatedByUs
set, whose 'created by us' invariant didn't survive the swallowed
create-race conflict; redelivery backstops now key on hasCreatedEvent.
- dispatchPendingOps' createdAttributeEvent/createdGetConflictHook
signals are consumed again: when the loop exits suspended without ever
reading back a self-written attr_set / getConflict hook_created
(eventually-consistent listing lag), the entrypoint requeues
immediately instead of parking the run awaiting_external with its
unblocking event already written.
- The server-supplied event ceiling is re-checked at the top of every
continuation-loop turn (seenEventIds.size), so a single invocation
fanning out inline can no longer grow the log arbitrarily past the
operator's limit. The quickjs dispatch in runtime.ts converts
MaxEventsExceededError into run_failed / MAX_EVENTS_EXCEEDED — the
guard's throw previously nacked forever, parking runaway runs in
'running'.
- Documented the deliberate decision that the platform function timeout
is the only bound on inline chaining (budget parked per batch),
matching the node engine.
* QuickJS engine: host-side, side-effect-free serialization via handles
(Re-applied onto the review-fixed base; original commits da2723016 +
9814ed9ac squashed.)
Replace the in-VM serde bundle with a host-side codec
(runtime/quickjs-serde.ts) built on quickjs-wasi 3.3's introspection
primitives and devalue 5.9's pluggable stringify/parse operations —
mirroring the node:vm engine's architecture.
Review fixes incorporated:
- reducer/reviver key sets are pinned against codec-devalue-vm's
workflow mode by exhaustiveness tests (exact order for reducers —
first match wins), so the handle-space codec can't silently drift
from the shared value-space sets.
- the devalue entry in minimumReleaseAgeExclude is removed: the exact
version is pinned via the workspace catalog + lockfile, so the
cooldown waiver was unnecessary (verified with both frozen and
regular installs).
- eval-string interpolation inherits the JSON.stringify(cid) hardening
from the base branch.
* Address review: NUL-safe string extraction, deterministic retryAfter, pass-scoped handle disposal, byte-cache lifecycle
- NUL (U+0000) safety across the WASM boundary: handle.toString() routes
through JS_ToCString and silently truncates at the first NUL, and the
C-string key APIs mangle NUL-bearing property keys (drop or collide).
guestString() detects truncation by comparing against the handle's
true guest length and recovers via in-VM JSON.stringify escaping;
shapeOf verifies its fast host-string key list against a guest
Object.keys count (+ duplicate check) and re-extracts through key
handles on mismatch; get/hasOwn route NUL-bearing keys through
length-aware guest string handles. All string funnels (primitives,
symbol descriptions, error fields via chained/own reads, Headers
entries, RegExp source/flags, URL href) go through guestString.
Regression-tested down to the truncate-vs-collide enumeration shapes;
fixes nullByteWorkflow on the quickjs e2e legs.
- RetryableError's absent/invalid retryAfter fallback now reads the
GUEST clock (the deterministic replay clock at the WASI layer) via a
captured Date.now instead of the host wall clock — the in-VM reducer
was replay-stable by construction and the host port silently lost
that.
- Pass-scoped handle disposal: serialize/deserialize sweep every
intermediate handle their pass creates (call/invoke results,
descriptor reads, dups, parse-op constructions), closing the
~one-leaked-handle-per-value-node growth across long-lived inline
sessions. Implemented with module-owned tracking rather than
vm.withScope: the library scope also captures the handles the
host-callback trampoline wraps around C-owned argv pointers, and
disposing those (Map/Set/Headers forEach visitors run mid-pass)
double-frees guest values — observed as WASM memory corruption.
identities is cleared per pass so freed-pointer reuse cannot alias
entries across passes.
- Byte-cache lifecycle: terminal drain now shares the per-VM cache with
the suspension path (re-serializing an op at drain could re-invoke
getters and produce different bytes for what the log treats as one
value), and entries for settled ops — which neither collection filter
can match again — are evicted, bounding the cache by the live pending
set.
* Adopt quickjs-wasi 3.3.1: withScope handle sweeping, real memoryLimit accounting, loud unregistered-callback failures
3.3.1 ships the three fixes this branch surfaced upstream:
- Borrowed host-callback handles (vercel-labs/quickjs-wasi#31): the
trampoline's this/argv handles are scope-exempt, making vm.withScope
safe around host callbacks. The serde's module-owned pass-disposal
apparatus (passDisposal/track/runWithPassDisposal and ~18 track()
wraps) is replaced by withScope in serialize/deserialize — simpler,
and strictly more complete: every handle constructed during the pass
is swept, not just the ones our creation funnels saw. Bench parity
confirmed (within ~10% on the 50k-node extreme case, unchanged
elsewhere; still 2.6-100x over the in-VM codec).
- Real memoryLimit accounting (vercel-labs/quickjs-wasi#33): the
engine's 256 MB VM ceiling now actually bounds retained guest
allocations (usable-size was 0 on wasm32-wasi before, so the limit
never accumulated).
- Unregistered host callbacks throw (vercel-labs/quickjs-wasi#34):
guest calls into missing callbacks fail loud instead of silently
returning undefined — protection this engine wants for
snapshot-restore re-registration bugs.
Also merges origin/main (undici 7.29.0).
* Address review: lossless lone-surrogate string extraction, portable base64
P1 — the guestString length check was insufficient: JS_ToCString has
TWO corruptions (NUL truncation, lone-surrogate -> U+FFFD replacement)
and they can cancel — the replacement expansion offsets the truncation
so the extracted length matches the true guest length. A bare lone
surrogate can also replace 1:1 with no length change at all. Worse,
the JSON.stringify slow path was itself lossy for lone surrogates:
QuickJS passes them through raw, and the C-string extraction of ITS
output corrupts them.
- guestString accepts the fast value only when length matches AND it
contains no U+FFFD (legitimate U+FFFD strings take the loss-free slow
path); the slow path now escapes INSIDE the VM to printable ASCII via
a new captured escapeString intrinsic (WTF-16-safe per-code-unit
\uXXXX escaping), then JSON-parses host-side.
- shapeOf's fast-key acceptance adds a U+FFFD scan alongside the
count/duplicate checks (lone-surrogate keys corrupt with count and
uniqueness intact).
- get()/hasOwn() route keys through guest string handles when they
carry a NUL or an UNPAIRED surrogate (paired surrogates - emoji keys
- encode fine through the C-string APIs; vm.newString is verified
WTF-16-preserving for the handle path).
- Tests: the reviewer's exact length-canceling case, bare lone
surrogates, legit-U+FFFD passthrough, byte parity with the reference
codec, and lone-surrogate/mixed keys.
P2 — the codec's base64 helpers no longer carry an unconditional Node
Buffer dependency: feature-detected Uint8Array.fromBase64/toBase64
when available, Buffer when present, btoa/atob loop otherwise —
keeping WASM-only/non-Node hosts (Cloudflare Workers) viable.
* Adopt quickjs-wasi 3.4.0: delete the NUL/surrogate string machinery
3.4.0 ships lossless string transport (vercel-labs/quickjs-wasi#35 —
found by this PR's review cycle), so the SDK-side detection and escape
machinery is deleted wholesale:
- guestString (length + U+FFFD detection, in-VM escape fallback) — plain
toString() is lossless now
- the escapeString / hasOwnCall / jsonStringify / objectKeys captured
intrinsics
- keyNeedsHandleLookup and the handle-keyed get/hasOwn routing — the
library routes inexpressible keys itself
- shapeOf's guest-key verification pass (count/duplicate/U+FFFD scans) —
enumeration is lossless
Net ~130 lines and four captured intrinsics removed; the serde now uses
the plain quickjs-wasi surface everywhere.
Test honesty fix that 3.4.0 forced: the earlier lone-surrogate
round-trip tests passed only via mutual corruption — the pre-3.4.0
lossy host→guest transport corrupted the guest comparison literals
identically to the wire. With an honest transport they exposed that the
WIRE itself (devalue emits lone surrogates raw; the wire is UTF-8)
degrades lone surrogates to U+FFFD — in the node engine's reference
codec exactly as here, verified. Bug-compatible parity is the
load-bearing property (event logs replay across engines), so those
tests now assert byte parity with the reference codec plus
guest-observed equality with the reference codec's own round trip; NULs
are devalue-escaped and asserted to survive exactly. Wire-level
surrogate preservation is a product-wide devalue/UTF-8 question,
tracked separately from this engine.
2026-08-06 14:47:08 -07:00
|
|
|
"quickjs-wasi": "3.4.0",
|
2025-11-12 14:26:01 -08:00
|
|
|
"seedrandom": "3.0.5",
|
2026-04-01 14:32:51 -07:00
|
|
|
"semver": "catalog:",
|
2026-03-05 13:35:23 -08:00
|
|
|
"ulid": "catalog:",
|
2025-10-23 12:07:52 +03:00
|
|
|
"zod": "catalog:"
|
|
|
|
|
},
|
|
|
|
|
"devDependencies": {
|
2025-11-12 14:26:01 -08:00
|
|
|
"@opentelemetry/api": "1.9.0",
|
otel: explicit traceparent injection + linked-trace mode for bounded per-invocation traces (#2363)
* otel: explicit traceparent injection + linked-trace mode for bounded per-invocation traces
- Add WORKFLOW_TRACE_MODE ('linked' default, 'continuous' legacy) to the
workflow and step queue handlers. In linked mode, WORKFLOW_V2/STEP spans
start a new trace root with span links to the incoming delivery context
and the run-origin context, and re-enqueued messages forward the
ORIGINAL run-origin trace carrier unchanged.
- world-vercel now explicitly injects W3C traceparent/tracestate/baggage
headers on outgoing workflow-server HTTP requests from inside the
client span (no-op without an OTEL SDK registered).
- New workflow.trace.mode span attribute; unit tests for both modes and
for header injection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* changeset: call out behavioral telemetry changes of the linked default
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add v5 observability tracing page
Documents OTEL spans/attributes, linked trace mode and WORKFLOW_TRACE_MODE,
span links, context propagation, and the v4 behavior-change callout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* otel: human-friendly span names for workflow and step spans
WORKFLOW_V2/STEP prefixes with full machine names (workflow//./src/...//fn)
become workflow.execute / step.execute / workflow.start with the short
function name. New workflowDisplayName/stepDisplayName helpers in
@workflow/utils handle both raw and queue-sanitized name forms; full names
remain in the workflow.name/step.name attributes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* changeset: merge span-name and linked-trace notes into one changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: update trace-shape prose to renamed span names
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: replace ascii trace diagram with mermaid
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* address review: empty carriers, shared trace helpers, mode warning, name edge cases, consumer span kind
- Treat an empty ({}) trace carrier as absent everywhere the trace-mode
logic branches, so linked mode falls back to a fresh origin instead of
forwarding a useless {} forever; workflow.trace.propagated now reports
whether a usable carrier arrived.
- Extract the duplicated linked-mode logic into shared telemetry helpers
getNextTraceCarrier() and buildInvocationSpanLinks(), used by both the
workflow and step queue handlers; resume-hook now uses
linkToTraceCarrier (gaining the isSpanContextValid guard).
- Warn once per distinct unrecognized WORKFLOW_TRACE_MODE value instead
of silently selecting linked.
- shortNameFromSanitized: map default/__default to the module short name
(mirroring parseName) and document the `$`-sanitization limitation.
- Queue-delivered workflow.execute spans now use the CONSUMER span kind,
matching queue-delivered step.execute spans; docs span table and
changeset updated accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:35:53 -07:00
|
|
|
"@opentelemetry/context-async-hooks": "1.30.1",
|
|
|
|
|
"@opentelemetry/core": "1.30.1",
|
|
|
|
|
"@opentelemetry/sdk-trace-base": "1.30.1",
|
2025-11-12 14:26:01 -08:00
|
|
|
"@types/debug": "4.1.12",
|
2025-10-23 12:07:52 +03:00
|
|
|
"@types/node": "catalog:",
|
2025-11-12 14:26:01 -08:00
|
|
|
"@types/seedrandom": "3.0.8",
|
2026-06-08 16:29:26 -07:00
|
|
|
"@types/semver": "7.7.1",
|
2025-11-12 23:36:45 -08:00
|
|
|
"@workflow/tsconfig": "workspace:*",
|
2026-01-27 13:15:06 -08:00
|
|
|
"cross-env": "10.1.0",
|
2026-07-13 11:33:55 -07:00
|
|
|
"genversion": "3.2.0",
|
|
|
|
|
"typescript": "catalog:"
|
2025-10-23 12:07:52 +03:00
|
|
|
},
|
|
|
|
|
"peerDependencies": {
|
|
|
|
|
"@opentelemetry/api": "1"
|
|
|
|
|
},
|
|
|
|
|
"peerDependenciesMeta": {
|
|
|
|
|
"@opentelemetry/api": {
|
|
|
|
|
"optional": true
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-23 17:39:39 -07:00
|
|
|
}
|