workflow 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -194,6 +194,10 @@ This avoids replaying potentially thousands of chunks and lets the UI render fas
194
194
  When using a negative `initialStartIndex`, the reconnection endpoint **must** return the `x-workflow-stream-tail-index` header (as shown in [Step 2](#add-a-stream-reconnection-endpoint) above). The transport uses this header to compute absolute chunk positions so that retries after a disconnect resume from the correct position. If the header is missing, the transport falls back to `startIndex: 0` (replaying the entire stream) and logs a warning.
195
195
  </Callout>
196
196
 
197
+ ### Mid-part resumes
198
+
199
+ A workflow stream is a flat sequence of chunks, but the AI SDK's UI protocol groups chunks into logical parts (`text-*`, `reasoning-*`, `tool-input-*`) that must be opened with a `*-start` before any `*-delta` or `*-end`. A non-zero `startIndex` can land in the middle of an open part. See [`WorkflowChatTransport` → Mid-part resumes](/docs/api-reference/workflow-ai/workflow-chat-transport#mid-part-resumes) for how this is handled and an example of rewinding to a step boundary on the server.
200
+
197
201
  ## Related Documentation
198
202
 
199
203
  - [`WorkflowChatTransport` API Reference](/docs/api-reference/workflow-ai/workflow-chat-transport) - Full configuration options
@@ -65,7 +65,7 @@ export default Hook;`}
65
65
 
66
66
  The returned `Hook` object also implements `AsyncIterable<T>`, which allows you to iterate over incoming payloads using `for await...of` syntax.
67
67
 
68
- Use `hook.getConflict()` to check whether the hook token is already claimed by another active hook, without waiting for hook payload data. Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the registration, then resolves with `null` once `hook_created` is recorded, or with `{ runId }` identifying the conflicting run if another active hook already owns the same token.
68
+ Use `hook.getConflict()` (available starting in `workflow@4.5.0`) to check whether the hook token is already claimed by another active hook, without waiting for hook payload data. Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the registration, then resolves with `null` once `hook_created` is recorded, or with `{ runId }` identifying the conflicting run if another active hook already owns the same token.
69
69
 
70
70
  ## Examples
71
71
 
@@ -116,7 +116,7 @@ export async function slackBotWorkflow(channelId: string) {
116
116
 
117
117
  ### Detecting Token Conflicts
118
118
 
119
- Use `hook.getConflict()` when the workflow needs to claim a hook token before doing other work, but does not need a payload yet:
119
+ Use `hook.getConflict()` (available starting in `workflow@4.5.0`) when the workflow needs to claim a hook token before doing other work, but does not need a payload yet:
120
120
 
121
121
  ```typescript lineNumbers
122
122
  import { createHook } from "workflow";
@@ -254,6 +254,45 @@ export default function ChatWithCustomConfig() {
254
254
  }
255
255
  ```
256
256
 
257
+ ## Mid-part resumes
258
+
259
+ A workflow stream is a flat sequence of chunks, but the AI SDK's UI protocol groups chunks into logical parts: a `text-start` opens a text part that subsequent `text-delta`s extend and a `text-end` closes, and the same shape applies to `reasoning-*` and `tool-input-*`. The AI SDK client enforces that grammar — a `reasoning-delta` whose `reasoning-start` was never seen throws and breaks the chat.
260
+
261
+ A non-zero `startIndex` (in particular a negative `initialStartIndex`) resolves to a chunk offset with no awareness of those part boundaries, so it can land in the middle of an open part. When that happens, `WorkflowChatTransport` will **drop chunks that reference a part it didn't see a start for** and log a one-time warning. The chat keeps working, but any partial part overlapping the resume cursor is discarded. Tool calls are an exception: `tool-input-available` / `tool-input-error` chunks are self-contained (they carry the full input), so a tool call is recovered as soon as one of those chunks appears in the resumed window — only its streamed input deltas are lost.
262
+
263
+ To preserve those partial parts, rewind to a step boundary on the server before returning the readable. `start-step` / `finish-step` chunks are the natural seams — no UI part is ever open across them. Sketch:
264
+
265
+ {/*@skip-typecheck: incomplete code sample*/}
266
+
267
+ ```typescript title="app/api/chat/[id]/stream/route.ts"
268
+ const run = getRun(id);
269
+ const tailIndex = await run.getReadable().getTailIndex();
270
+
271
+ let resolved = startIndex < 0
272
+ ? Math.max(0, tailIndex + 1 + startIndex)
273
+ : startIndex;
274
+
275
+ if (startIndex !== 0) {
276
+ // Walk back from `resolved` to the most recent start-step (or chunk 0),
277
+ // capping the lookback so a single huge step can't trigger an unbounded scan.
278
+ const LOOKBACK = 200;
279
+ const probe = run.getReadable({ startIndex: Math.max(0, resolved - LOOKBACK) });
280
+ let i = Math.max(0, resolved - LOOKBACK);
281
+ let lastBoundary = i;
282
+ for await (const chunk of probe as unknown as AsyncIterable<{ type: string }>) {
283
+ if (i >= resolved) break;
284
+ if (chunk.type === "start-step") lastBoundary = i;
285
+ i++;
286
+ }
287
+ resolved = lastBoundary;
288
+ }
289
+
290
+ return createUIMessageStreamResponse({
291
+ stream: run.getReadable({ startIndex: resolved }),
292
+ headers: { "x-workflow-stream-tail-index": String(tailIndex) },
293
+ });
294
+ ```
295
+
257
296
  ## See Also
258
297
 
259
298
  - [DurableAgent](/docs/api-reference/workflow-ai/durable-agent) - Building durable AI agents within workflows
@@ -97,6 +97,8 @@ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
97
97
 
98
98
  <Callout type="info">
99
99
  The `deploymentId` option is currently a Vercel-specific feature. Other Worlds may implement this option differently to match their own deployment runtimes, and the World spec may rename it from `deploymentId` to `version` in a future SDK version. On Vercel, `"latest"` resolves to the most recent deployment matching your current environment — the same production target for production deployments, or the same git branch for preview deployments.
100
+
101
+ In Worlds without atomic, immutable deployments (such as local development or self-hosted Postgres), there is no notion of multiple deployments to resolve between, so `deploymentId: "latest"` has no effect: the SDK logs a warning and the run targets the current deployment. This means a workflow that opts into `"latest"` on Vercel still runs unchanged in local development.
100
102
  </Callout>
101
103
 
102
104
  <Callout type="warn">
@@ -31,11 +31,35 @@ export default withWorkflow(nextConfig, workflowConfig); // [!code highlight]
31
31
  If a package in `serverExternalPackages` contains workflow code (`"use step"`,
32
32
  `"use workflow"`, or serialization classes), `withWorkflow()` automatically
33
33
  removes it from `serverExternalPackages` for the current build and prints a
34
- warning. This ensures the package still gets transformed by the Workflow
35
- compiler. Remove that package from `serverExternalPackages` in your
34
+ warning. Workflow still compiles the package so its directives are transformed.
35
+ Remove that package from `serverExternalPackages` in your
36
36
  `next.config` to silence the warning.
37
37
  </Callout>
38
38
 
39
+ ### Workflow Discovery in Next.js
40
+
41
+ `withWorkflow()` discovers workflows by scanning your Next.js entrypoints — App
42
+ Router `route`, `page`, and `layout` files (under `app/` or `src/app/`) and any
43
+ file under `pages/` or `src/pages/` — for `start()` calls imported from
44
+ `workflow/api`. The workflow and step files themselves can live anywhere (for
45
+ example `src/workflows/`); they are discovered transitively through imports, as
46
+ long as a `start()` call in an entrypoint statically reaches them.
47
+
48
+ <Callout type="info">
49
+ Call `start()` from server-side entrypoints, including Route Handlers and Server
50
+ Actions. Don't call workflow functions directly — that bypasses the workflow
51
+ runtime.
52
+ </Callout>
53
+
54
+ ### Next.js Server Actions and `"use server"`
55
+
56
+ Don't put a top-level `"use server"` directive in modules imported by workflow
57
+ or step functions. Workflow transformation wraps imported modules in synchronous
58
+ initializers, and Next.js rejects a `"use server"` directive inside that wrapper
59
+ with errors like `Server Actions must be async functions`. Keep `"use server"`
60
+ on the files that define your Server Actions, and move shared logic into
61
+ separate modules that don't carry the directive.
62
+
39
63
  ### Monorepos and Workspace Imports
40
64
 
41
65
  By default, Next.js detects the correct workspace root automatically. If your Next.js app lives in a subdirectory such as `apps/web` and workspace resolution is not working correctly, you can set `outputFileTracingRoot` as a workaround:
@@ -68,7 +92,6 @@ const nextConfig: NextConfig = {};
68
92
 
69
93
  export default withWorkflow(nextConfig, {
70
94
  workflows: {
71
- lazyDiscovery: true,
72
95
  local: {
73
96
  port: 4000,
74
97
  },
@@ -78,7 +101,6 @@ export default withWorkflow(nextConfig, {
78
101
 
79
102
  | Option | Type | Default | Description |
80
103
  | --- | --- | --- | --- |
81
- | `workflows.lazyDiscovery` | `boolean` | `false` | When `true`, defers workflow discovery until files are requested instead of scanning eagerly at startup. Useful for large projects where startup time matters. |
82
104
  | `workflows.local.port` | `number` | — | Overrides the `PORT` environment variable for local development. Has no effect when deployed to Vercel. |
83
105
 
84
106
  <Callout type="info">
@@ -31,10 +31,42 @@ WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
31
31
 
32
32
  Run the migration script to create the necessary tables in your database. Ensure `WORKFLOW_POSTGRES_URL` is set when running this command:
33
33
 
34
+ <Tabs items={["npm", "pnpm", "Yarn", "Bun"]}>
35
+
36
+ <Tab value="npm">
37
+
38
+ ```bash
39
+ npx --package=@workflow/world-postgres bootstrap
40
+ ```
41
+
42
+ </Tab>
43
+
44
+ <Tab value="pnpm">
45
+
46
+ ```bash
47
+ pnpm dlx --package @workflow/world-postgres bootstrap
48
+ ```
49
+
50
+ </Tab>
51
+
52
+ <Tab value="Yarn">
53
+
54
+ ```bash
55
+ yarn dlx --package @workflow/world-postgres bootstrap
56
+ ```
57
+
58
+ </Tab>
59
+
60
+ <Tab value="Bun">
61
+
34
62
  ```bash
35
- npx workflow-postgres-setup
63
+ bunx --package @workflow/world-postgres bootstrap
36
64
  ```
37
65
 
66
+ </Tab>
67
+
68
+ </Tabs>
69
+
38
70
  <Callout type="info">
39
71
  The migration is idempotent and can safely be run as a post-deployment lifecycle script.
40
72
  </Callout>
@@ -87,7 +87,7 @@ The key points:
87
87
 
88
88
  ### Checking for Token Conflicts
89
89
 
90
- Sometimes you need to know that a hook token has been claimed, but you do not want to wait for external data yet. Await `hook.getConflict()` for that:
90
+ Sometimes you need to know that a hook token has been claimed, but you do not want to wait for external data yet. Await `hook.getConflict()` (available starting in `workflow@4.5.0`) for that:
91
91
 
92
92
  ```typescript lineNumbers
93
93
  import { createHook } from "workflow";
@@ -42,6 +42,19 @@ npx workflow inspect runs --web
42
42
 
43
43
  ![Workflow SDK Web UI](/o11y-ui.png)
44
44
 
45
+ To share a link to a specific run without opening a browser, use the `--url`
46
+ flag. It prints the dashboard deep link to stdout and exits (no browser, no
47
+ local server) — useful for scripts, PR comments, or automation. Add `--json` to
48
+ get `{ "url": "..." }`.
49
+
50
+ ```bash
51
+ # Print the deep-link URL for a run (no browser, no server)
52
+ npx workflow inspect run <run_id> --url
53
+
54
+ # Vercel runs: add the backend (and --env preview for preview deployments)
55
+ npx workflow inspect run <run_id> --backend vercel --url
56
+ ```
57
+
45
58
  ## Backends
46
59
 
47
60
  The Workflow SDK CLI can inspect data from any [World](/docs/deploying). By default, it inspects data in your local development environment. For example, if you are using Next.js to develop workflows locally, the
@@ -27,6 +27,7 @@ When enabled, the module:
27
27
  - Registers the workflow runtime routes under `/.well-known/workflow/v1/`.
28
28
  - Serves a redirect to the local observability dashboard at `/_workflow` in development.
29
29
  - Configures Vercel function rules (queue triggers and `maxDuration`) for the workflow routes when deploying to Vercel.
30
+ - Uses Nitro's `workspaceDir` as the workflow project root so monorepo apps can import sibling workspace packages without extra workflow config.
30
31
 
31
32
  ## Module Options
32
33
 
@@ -25,6 +25,7 @@ When enabled, the module:
25
25
  - Registers the [`workflow/nitro`](/docs/api-reference/workflow-nitro) module on Nuxt's Nitro server, which transforms `"use workflow"` and `"use step"` directives, builds the workflow bundles, and registers the workflow runtime routes under `/.well-known/workflow/v1/`.
26
26
  - Configures Vite to bundle (rather than externalize) the Workflow SDK packages in SSR mode so workflow code is transformed correctly.
27
27
  - Enables the `workflow` TypeScript plugin by default for IDE IntelliSense.
28
+ - Uses Nuxt/Nitro's detected `workspaceDir` so monorepo apps can import sibling workspace packages without extra workflow config.
28
29
 
29
30
  ## Module Options
30
31
 
@@ -27,6 +27,7 @@ When enabled, the module:
27
27
  - Registers the workflow runtime routes under `/.well-known/workflow/v1/`.
28
28
  - Serves a redirect to the local observability dashboard at `/_workflow` in development.
29
29
  - Configures Vercel function rules (queue triggers and `maxDuration`) for the workflow routes when deploying to Vercel.
30
+ - Uses Nitro's `workspaceDir` as the workflow project root so monorepo apps can import sibling workspace packages without extra workflow config.
30
31
 
31
32
  ## Module Options
32
33
 
@@ -25,6 +25,7 @@ When enabled, the module:
25
25
  - Registers the [`workflow/nitro`](/docs/api-reference/workflow-nitro) module on Nuxt's Nitro server, which transforms `"use workflow"` and `"use step"` directives, builds the workflow bundles, and registers the workflow runtime routes under `/.well-known/workflow/v1/`.
26
26
  - Configures Vite to bundle (rather than externalize) the Workflow SDK packages in SSR mode so workflow code is transformed correctly.
27
27
  - Enables the `workflow` TypeScript plugin by default for IDE IntelliSense.
28
+ - Uses Nuxt/Nitro's detected `workspaceDir` so monorepo apps can import sibling workspace packages without extra workflow config.
28
29
 
29
30
  ## Module Options
30
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -57,18 +57,18 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "ms": "2.1.3",
60
- "@workflow/astro": "4.0.10",
61
- "@workflow/cli": "4.2.10",
62
- "@workflow/core": "4.5.0",
60
+ "@workflow/astro": "4.0.11",
61
+ "@workflow/cli": "4.3.0",
62
+ "@workflow/core": "4.6.0",
63
63
  "@workflow/errors": "4.1.4",
64
64
  "@workflow/typescript-plugin": "4.0.3",
65
65
  "@workflow/utils": "4.1.3",
66
- "@workflow/next": "4.0.11",
67
- "@workflow/nest": "0.0.10",
68
- "@workflow/nitro": "4.1.1",
69
- "@workflow/nuxt": "4.0.11",
70
- "@workflow/sveltekit": "4.0.10",
71
- "@workflow/rollup": "4.0.10"
66
+ "@workflow/next": "4.1.0",
67
+ "@workflow/nest": "4.0.12",
68
+ "@workflow/nitro": "4.1.2",
69
+ "@workflow/nuxt": "4.0.12",
70
+ "@workflow/sveltekit": "4.0.11",
71
+ "@workflow/rollup": "4.0.11"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@types/ms": "2.1.0",