From eb4d5e8655e2d3b47c4eca2aa508ac1159beb7a2 Mon Sep 17 00:00:00 2001 From: johnlindquist Date: Fri, 23 Jan 2026 11:51:34 -0800 Subject: [PATCH 01/12] 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 Signed-off-by: John Lindquist --- .changeset/fresh-suns-wave.md | 2 + skills/SKILL.md | 245 +++++++++++++++ skills/references/ai-agents.md | 228 ++++++++++++++ skills/references/api-reference.md | 434 ++++++++++++++++++++++++++ skills/references/errors.md | 296 ++++++++++++++++++ skills/references/frameworks.md | 235 ++++++++++++++ skills/references/patterns.md | 480 +++++++++++++++++++++++++++++ 7 files changed, 1920 insertions(+) create mode 100644 .changeset/fresh-suns-wave.md create mode 100644 skills/SKILL.md create mode 100644 skills/references/ai-agents.md create mode 100644 skills/references/api-reference.md create mode 100644 skills/references/errors.md create mode 100644 skills/references/frameworks.md create mode 100644 skills/references/patterns.md diff --git a/.changeset/fresh-suns-wave.md b/.changeset/fresh-suns-wave.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/fresh-suns-wave.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/skills/SKILL.md b/skills/SKILL.md new file mode 100644 index 0000000000..ef81d7dda6 --- /dev/null +++ b/skills/SKILL.md @@ -0,0 +1,245 @@ +--- +name: workflow +description: Creates durable, resumable workflows using Vercel's Workflow DevKit. Use when building workflows that need to survive restarts, pause for external events, retry on failure, or coordinate multi-step operations over time. Triggers on mentions of "workflow", "durable functions", "resumable", "workflow devkit", or step-based orchestration. +--- + +# Workflow DevKit + +TypeScript framework for building durable, resumable functions. Workflows survive crashes, deployments, and can pause for days/months. + +**Website:** https://useworkflow.dev | **GitHub:** https://github.com/vercel/workflow + +## Installation + +```bash +npm i workflow +# or: pnpm add workflow | yarn add workflow | bun add workflow +``` + +**Version Note:** The Workflow DevKit is in beta. Consider pinning to a specific version (e.g., `workflow@4.0.1-beta.44`) rather than `"latest"` to avoid unexpected breaking changes. + +## Core Concepts + +### Directives + +```typescript +"use workflow"; // First line - makes async function durable +"use step"; // First line - makes function a cached, retryable unit +``` + +### Workflow Function + +Orchestrates steps, survives restarts, replays deterministically: + +```typescript +import { sleep } from "workflow"; + +export async function onboardUser(email: string) { + "use workflow"; + + const user = await createUser(email); // Step 1 + await sendWelcomeEmail(user); // Step 2 + await sleep("3 days"); // Pause (no resources consumed) + await sendFollowupEmail(user); // Step 3 + return { userId: user.id }; +} +``` + +### Step Function + +Has full Node.js access, auto-retries on failure: + +```typescript +async function createUser(email: string) { + "use step"; + return await db.users.create({ email }); +} +``` + +## Essential APIs + +### sleep - Pause Workflow + +```typescript +import { sleep } from "workflow"; + +await sleep("5s"); // 5 seconds +await sleep("10m"); // 10 minutes +await sleep("1h"); // 1 hour +await sleep("7 days"); // 7 days +await sleep(5000); // milliseconds +await sleep(new Date("2025-01-01")); // specific date +``` + +### createHook - Wait for External Input + +```typescript +import { createHook } from "workflow"; + +const hook = createHook<{ approved: boolean }>(); +console.log("Token:", hook.token); // Send to external system +const result = await hook; // Pauses until resumed +``` + +Resume externally: +```typescript +import { resumeHook } from "workflow/api"; +await resumeHook(token, { approved: true }); +``` + +### createWebhook - HTTP Callback Endpoint + +```typescript +import { createWebhook } from "workflow"; + +const webhook = createWebhook(); +await sendToExternalService(webhook.url); // Auto-generated URL +const request = await webhook; // Pauses until HTTP POST +const data = await request.json(); +``` + +### start - Trigger Workflow + +```typescript +import { start } from "workflow/api"; + +const run = await start(onboardUser, ["user@example.com"]); +console.log(run.runId); // Returns immediately +``` + +### Error Handling + +```typescript +import { FatalError, RetryableError } from "workflow"; + +// Stop retries permanently +throw new FatalError("User not found"); + +// Retry with delay +throw new RetryableError("Rate limited", { retryAfter: "5m" }); +``` + +### Parallel Execution + +```typescript +// Execute steps concurrently +const [user, settings, history] = await Promise.all([ + fetchUser(userId), + fetchSettings(userId), + fetchHistory(userId), +]); + +// Race: first to complete wins (useful for timeouts) +const result = await Promise.race([ + processTask(taskId), + (async () => { await sleep("30s"); return { timeout: true }; })(), +]); +``` + +## Fetch in Workflows + +Workflows run sandboxed. Import fetch from workflow: + +```typescript +import { fetch } from "workflow"; + +export async function myWorkflow() { + "use workflow"; + globalThis.fetch = fetch; // Enable for libraries (AI SDK) + + const response = await fetch("https://api.example.com/data"); +} +``` + +## Streaming (Steps Only) + +```typescript +import { getWritable } from "workflow"; + +async function streamData(items: string[]) { + "use step"; + const writer = getWritable(); + for (const item of items) { + await writer.write({ item }); + } + writer.releaseLock(); +} +``` + +## Metadata & Idempotency + +### Workflow Metadata + +Get context about the current workflow run: + +```typescript +import { getWorkflowMetadata } from "workflow"; + +export async function myWorkflow() { + "use workflow"; + const { runId, workflowName } = getWorkflowMetadata(); + console.log(`Run ${runId} of ${workflowName}`); +} +``` + +### Step Metadata for Idempotency + +Use stepId for external API calls to prevent duplicate operations: + +```typescript +import { getStepMetadata } from "workflow"; + +async function chargeCard(amount: number) { + "use step"; + const { stepId, attemptNumber } = getStepMetadata(); + await stripe.charges.create( + { amount }, + { idempotencyKey: `charge:${stepId}` } + ); +} +``` + +## What Cannot Be Serialized + +- Functions/callbacks - define logic in steps +- Class instances with methods - use plain objects +- Symbols, WeakMap, WeakSet +- Node.js modules (fs, http, crypto) - use in steps only + +## Run Status + +`pending | running | completed | failed | cancelled` + +## References + +- **Framework Setup:** See [frameworks.md](references/frameworks.md) for Next.js, Express, Hono, Vite, Astro, Fastify, Nitro, Nuxt, SvelteKit +- **AI Agents:** See [ai-agents.md](references/ai-agents.md) for DurableAgent and streaming patterns +- **Advanced Patterns:** See [patterns.md](references/patterns.md) for hooks, webhooks, control flow, batching, streaming +- **Common Errors:** See [errors.md](references/errors.md) for troubleshooting +- **API Reference:** See [api-reference.md](references/api-reference.md) for complete API + +## Local Development + +```bash +# Terminal 1: Start your app on default port +npm run dev + +# Terminal 2: Open dashboard (connects to port 3000) +npx workflow web + +# If app runs on different port: +npx workflow web --app-url http://localhost:3001 +``` + +Dashboard at `http://localhost:3456` shows workflows and runs. If runs don't appear, restart dashboard after app is running. + +## Quick Checklist + +- [ ] Add `"use workflow"` as first line of workflow function +- [ ] Add `"use step"` as first line of step functions +- [ ] Import `fetch` from `workflow` and assign to `globalThis.fetch` +- [ ] Use `sleep()` instead of setTimeout +- [ ] Move Node.js module usage to step functions +- [ ] Use `FatalError` for permanent failures, `RetryableError` for retries +- [ ] Configure framework (withWorkflow, nitro module, etc.) +- [ ] Run dashboard with correct `--app-url` if not using port 3000 diff --git a/skills/references/ai-agents.md b/skills/references/ai-agents.md new file mode 100644 index 0000000000..1dd21368e1 --- /dev/null +++ b/skills/references/ai-agents.md @@ -0,0 +1,228 @@ +# AI Agents with Workflow + +Build durable AI agents that survive restarts and stream responses. + +## Installation + +```bash +npm i @workflow/ai ai +``` + +Supports AI SDK v5 and v6. + +## DurableAgent + +Creates AI agents that maintain state across workflow steps: + +```typescript +import { DurableAgent } from "@workflow/ai/agent"; +import { fetch, getWritable } from "workflow"; +import { z } from "zod"; + +const agent = new DurableAgent({ + model: "anthropic/claude-haiku-4.5", + system: "You are a helpful assistant.", + temperature: 0.7, + maxSteps: 10, + tools: { + searchWeb: { + description: "Search the web for information", + parameters: z.object({ query: z.string() }), + execute: async ({ query }) => { + "use step"; + return await webSearch(query); + }, + }, + getWeather: { + description: "Get current weather", + parameters: z.object({ city: z.string() }), + execute: async ({ city }) => { + "use step"; + return await weatherAPI.get(city); + }, + }, + }, +}); + +export async function chatWorkflow(messages: Message[]) { + "use workflow"; + globalThis.fetch = fetch; // Required for AI SDK + + const writable = getWritable(); + const result = await agent.stream({ + messages, + writable, + }); + + return result.messages; +} +``` + +## DurableAgent Options + +| Option | Type | Description | +|--------|------|-------------| +| `model` | string | LLM identifier (e.g., "anthropic/claude-haiku-4.5") | +| `system` | string | System prompt | +| `temperature` | number | Generation parameter (0-1) | +| `tools` | object | Tool definitions with execute functions | +| `toolChoice` | string | "auto" \| "required" \| "none" \| specific tool | +| `maxSteps` | number | Max LLM iterations | + +## Callbacks + +```typescript +const agent = new DurableAgent({ + model: "anthropic/claude-haiku-4.5", + onFinish: (result) => console.log("Done:", result), + onError: (error) => console.error("Error:", error), + onAbort: () => console.log("Aborted"), + onStepFinish: (step) => console.log("Step:", step), +}); +``` + +## Streaming AI Responses + +### In Workflow + +```typescript +export async function streamingWorkflow(prompt: string) { + "use workflow"; + globalThis.fetch = fetch; + + const writable = getWritable(); + await agent.stream({ messages: [{ role: "user", content: prompt }], writable }); +} +``` + +### Reading Stream (Client) + +```typescript +import { start } from "workflow/api"; + +const run = await start(streamingWorkflow, ["Hello"]); + +for await (const chunk of run.readable) { + console.log(chunk); +} +``` + +### Resumable Streams + +```typescript +// Resume from specific position +const readable = run.getReadable({ startIndex: lastIndex }); + +for await (const chunk of readable) { + process.stdout.write(chunk); +} +``` + +## WorkflowChatTransport + +Transport layer for AI SDK with automatic reconnection: + +```typescript +import { WorkflowChatTransport } from "@workflow/ai"; + +const transport = new WorkflowChatTransport({ + maxConsecutiveErrors: 3, + onChatSendMessage: (response) => { + // Extract workflow run ID from header + const runId = response.headers.get("x-workflow-run-id"); + }, + onChatEnd: ({ chatId, chunkIndex }) => { + console.log("Chat ended at chunk:", chunkIndex); + }, +}); +``` + +## Piping AI SDK Responses + +```typescript +async function generateWithAI(prompt: string) { + "use step"; + + const writable = getWritable(); + const response = await generateText({ prompt, stream: true }); + + await response.pipeThrough(writable); +} +``` + +## Human-in-the-Loop AI + +Combine AI with human approval: + +```typescript +export async function contentApproval(topic: string) { + "use workflow"; + globalThis.fetch = fetch; + + // AI generates draft + const draft = await generateDraft(topic); + + // Wait for human review + const hook = createHook<{ approved: boolean; feedback?: string }>(); + await notifyEditor(draft.id, hook.token); + + const review = await hook; + + if (review.approved) { + await publish(draft); + return { status: "published", draft }; + } else { + // AI revises based on feedback + const revised = await reviseDraft(draft, review.feedback); + return { status: "revised", draft: revised }; + } +} + +async function generateDraft(topic: string) { + "use step"; + const writable = getWritable(); + return await agent.stream({ + messages: [{ role: "user", content: `Write about: ${topic}` }], + writable, + }); +} +``` + +## Multi-Day AI Agents + +Agents that operate over extended periods: + +```typescript +export async function longRunningAgent(task: string) { + "use workflow"; + globalThis.fetch = fetch; + + // Initial processing + const plan = await createPlan(task); + + for (const step of plan.steps) { + await executeStep(step); + + // Wait for external systems + await sleep("1h"); + + // Check status + const status = await checkProgress(step); + if (status.needsReview) { + const hook = createHook<{ proceed: boolean }>(); + await notifyTeam(step, hook.token); + const decision = await hook; + if (!decision.proceed) break; + } + } + + return await generateReport(plan); +} +``` + +## Important Notes + +1. **Always set globalThis.fetch** - AI SDK requires it +2. **Tools must use "use step"** - For durability and retry +3. **Use getWritable() in steps only** - Cannot stream from workflow function +4. **Release writer locks** - Call `writer.releaseLock()` after writing diff --git a/skills/references/api-reference.md b/skills/references/api-reference.md new file mode 100644 index 0000000000..a50f46225f --- /dev/null +++ b/skills/references/api-reference.md @@ -0,0 +1,434 @@ +# Complete API Reference + +## workflow package + +### sleep(duration) + +Pause workflow without consuming resources. + +```typescript +import { sleep } from "workflow"; + +await sleep("5s"); // String duration +await sleep("10m"); // 10 minutes +await sleep("1h"); // 1 hour +await sleep("7 days"); // 7 days +await sleep(5000); // Milliseconds +await sleep(new Date("2025-12-31")); // Until specific date +``` + +**Duration formats:** `${number}${'ms'|'s'|'m'|'h'|'d'}` + +### fetch(url, options) + +HTTP request with automatic retry. Use instead of global fetch. + +```typescript +import { fetch } from "workflow"; + +export async function myWorkflow() { + "use workflow"; + globalThis.fetch = fetch; // Enable for libraries + + const response = await fetch("https://api.example.com/data"); + const data = await response.json(); +} +``` + +### createHook(options?) + +Create suspension point for external input. + +```typescript +import { createHook } from "workflow"; + +const hook = createHook<{ approved: boolean }>(); +// hook.token - string identifier for resumption +const result = await hook; // Pauses until resumed +``` + +**Options:** +- `token?: string` - Custom deterministic token +- `metadata?: any` - Attached metadata + +**Returns:** `Hook` - PromiseLike & AsyncIterable + +### defineHook(config) + +Type-safe hook with schema validation. + +```typescript +import { defineHook } from "workflow"; +import { z } from "zod"; + +const myHook = defineHook({ + schema: z.object({ + id: z.string(), + value: z.number(), + }), +}); + +// Create hook +const hook = myHook.create({ token: `custom:${id}` }); + +// Resume with validation +await myHook.resume(`custom:${id}`, { id: "123", value: 42 }); +``` + +### createWebhook(options?) + +Create HTTP webhook endpoint. + +```typescript +import { createWebhook } from "workflow"; + +const webhook = createWebhook(); +console.log(webhook.url); // Auto-generated URL +const request = await webhook; // Pauses until HTTP POST +``` + +**Options:** +- `token?: string` - Custom token +- `metadata?: any` - Attached metadata +- `respondWith?: Response | "manual"` - Response mode + +**Returns:** `Webhook` - PromiseLike & AsyncIterable with `.url` + +### getWritable(options?) + +Access stream writer (steps only). + +```typescript +import { getWritable } from "workflow"; + +async function streamStep() { + "use step"; + + const writer = getWritable(); + await writer.write({ data: "chunk" }); + writer.releaseLock(); +} +``` + +**Options:** +- `namespace?: string` - Stream namespace + +**Returns:** `WritableStream` + +### getWorkflowMetadata() + +Get workflow run context. + +```typescript +import { getWorkflowMetadata } from "workflow"; + +export async function myWorkflow() { + "use workflow"; + + const { runId, workflowName } = getWorkflowMetadata(); +} +``` + +### getStepMetadata() + +Get step execution context. + +```typescript +import { getStepMetadata } from "workflow"; + +async function myStep() { + "use step"; + + const { stepId, attemptNumber } = getStepMetadata(); + // Use stepId for idempotency keys +} +``` + +### FatalError + +Stop retries permanently. + +```typescript +import { FatalError } from "workflow"; + +throw new FatalError("User not found"); +throw new FatalError("Invalid input", { cause: originalError }); +``` + +### RetryableError + +Trigger retry with delay. + +```typescript +import { RetryableError } from "workflow"; + +throw new RetryableError("Rate limited", { retryAfter: "5m" }); +throw new RetryableError("Retry", { retryAfter: 5000 }); +throw new RetryableError("Retry", { retryAfter: new Date("2025-01-01") }); +``` + +--- + +## workflow/api package + +### start(workflow, args, options?) + +Start workflow run. + +```typescript +import { start } from "workflow/api"; + +const run = await start(myWorkflow, ["arg1", "arg2"]); +console.log(run.runId); + +// With options +const run = await start(myWorkflow, ["arg1"], { + runId: "custom-id", +}); +``` + +**Returns:** `Run` object + +### Run object + +```typescript +interface Run { + runId: string; + + // Get current status + get status(): Promise; + + // Get return value (waits for completion) + get returnValue(): Promise; + + // Cancel the run + cancel(): Promise; + + // Get readable stream + getReadable(options?: { namespace?: string }): ReadableStream; +} +``` + +### getRun(runId) + +Get existing run by ID. + +```typescript +import { getRun } from "workflow/api"; + +const run = getRun("run-id"); +const status = await run.status; +const result = await run.returnValue; +``` + +### resumeHook(token, data) + +Resume workflow via hook. + +```typescript +import { resumeHook } from "workflow/api"; + +const result = await resumeHook(token, { approved: true }); +// result.runId, result.hookId +``` + +### resumeWebhook(token, request) + +Resume workflow via webhook. + +```typescript +import { resumeWebhook } from "workflow/api"; + +await resumeWebhook(token, incomingRequest); +``` + +### getHookByToken(token) + +Get hook metadata. + +```typescript +import { getHookByToken } from "workflow/api"; + +const hook = await getHookByToken(token); +// hook.status: 'pending' | 'received' | 'disposed' +``` + +### getWorld() + +Access low-level infrastructure. + +```typescript +import { getWorld } from "workflow/api"; + +const world = getWorld(); +const runs = await world.runs.list({ pagination: { cursor } }); +``` + +--- + +## Types + +### RunStatus + +```typescript +type RunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'; +``` + +### EventType + +```typescript +type EventType = + | 'step_completed' | 'step_failed' | 'step_retrying' | 'step_started' + | 'hook_created' | 'hook_received' | 'hook_disposed' + | 'wait_created' | 'wait_completed' + | 'workflow_completed' | 'workflow_failed' | 'workflow_started'; +``` + +### Serializable Types + +```typescript +type Serializable = + | string | number | boolean | null | undefined | bigint + | Serializable[] + | { [key: string]: Serializable } + | Date | URL | RegExp | URLSearchParams + | Map | Set + | Response | Request | Headers + | ArrayBuffer | Uint8Array | Int8Array | Float64Array // all typed arrays + | ReadableStream | WritableStream; +``` + +--- + +## Framework Exports + +### workflow/next + +```typescript +import { withWorkflow } from "workflow/next"; + +export default withWorkflow(nextConfig); +``` + +### workflow/vite + +```typescript +import { workflow } from "workflow/vite"; + +export default defineConfig({ + plugins: [workflow()], +}); +``` + +### workflow/astro + +```typescript +import { workflow } from "workflow/astro"; + +export default defineConfig({ + integrations: [workflow()], +}); +``` + +### workflow/nitro + +```typescript +// nitro.config.ts +export default defineNitroConfig({ + modules: ["workflow/nitro"], +}); +``` + +### workflow/nuxt + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ["workflow/nuxt"], +}); +``` + +### workflow/sveltekit + +```typescript +import { workflowPlugin } from "workflow/sveltekit"; + +export default { + plugins: [sveltekit(), workflowPlugin()], +}; +``` + +--- + +## @workflow/ai + +### DurableAgent + +```typescript +import { DurableAgent } from "@workflow/ai/agent"; + +const agent = new DurableAgent({ + model: string, + system?: string, + temperature?: number, + tools?: Record, + toolChoice?: "auto" | "required" | "none" | string, + maxSteps?: number, + onFinish?: (result) => void, + onError?: (error) => void, + onAbort?: () => void, + onStepFinish?: (step) => void, +}); + +await agent.stream({ messages, writable }); +``` + +### WorkflowChatTransport + +```typescript +import { WorkflowChatTransport } from "@workflow/ai"; + +const transport = new WorkflowChatTransport({ + maxConsecutiveErrors?: number, + onChatSendMessage?: (response) => void, + onChatEnd?: ({ chatId, chunkIndex }) => void, +}); +``` + +--- + +## Worlds + +### @workflow/world-local + +Local development with filesystem storage. + +```typescript +// Auto-used in development +// Data in .workflow-data/ or .next/workflow-data/ +``` + +### @workflow/world-vercel + +Production on Vercel with durable queues. + +```typescript +// Auto-used when deployed to Vercel +// No configuration needed +``` + +### @workflow/world-postgres + +Self-hosted PostgreSQL backend. + +```typescript +import { PostgresWorld } from "@workflow/world-postgres"; +``` + +### @workflow/world-testing + +Testing utilities. + +```typescript +import { TestWorld } from "@workflow/world-testing"; +``` diff --git a/skills/references/errors.md b/skills/references/errors.md new file mode 100644 index 0000000000..d038dbb3f8 --- /dev/null +++ b/skills/references/errors.md @@ -0,0 +1,296 @@ +# Common Errors and Solutions + +## Quick Reference + +| Error | Cause | Solution | +|-------|-------|----------| +| `fetch-in-workflow` | Using global fetch | Import from `workflow`, assign to `globalThis.fetch` | +| `timeout-in-workflow` | setTimeout/setInterval | Use `sleep()` instead | +| `serialization-failed` | Non-serializable data | Use supported types, plain objects | +| `start-invalid-workflow-function` | Missing directive or config | Add `'use workflow'`, check `withWorkflow()` | +| `node-js-module-in-workflow` | Node.js modules in workflow | Move to step function | +| `webhook-invalid-respond-with-value` | Wrong respondWith | Use `"manual"`, `Response`, or `undefined` | +| `webhook-response-not-sent` | Manual mode, no response | Call `request.respondWith()` | +| Dashboard shows no runs | Port mismatch or dashboard started first | Restart dashboard, use `--app-url` flag | +| 500 error on API route | Unused workflow imports | Only import APIs you actually use | + +## Import Only What You Need + +**Symptom:** API route returns 500 error silently. + +**Cause:** Importing workflow APIs (like `fetch`, `sleep`, `FatalError`) that aren't used can cause initialization errors. + +**Solution:** Only import what you actually use: +```typescript +// BAD: Importing unused APIs +import { sleep, createHook, FatalError, fetch } from "workflow"; +// If you don't use sleep, FatalError, or fetch, remove them + +// GOOD: Import only what's needed +import { createHook } from "workflow"; +``` + +## fetch-in-workflow + +**Error:** Global `fetch` is unavailable in workflows. + +**Cause:** Workflows run in a sandboxed environment. + +**Solution:** +```typescript +import { fetch } from "workflow"; + +export async function myWorkflow() { + "use workflow"; + globalThis.fetch = fetch; // Required for libraries (AI SDK) + + const response = await fetch("https://api.example.com/data"); + const data = await response.json(); +} +``` + +## timeout-in-workflow + +**Error:** `setTimeout` or `setInterval` unavailable. + +**Cause:** These are non-deterministic and break replay. + +**Solution:** +```typescript +import { sleep } from "workflow"; + +// Instead of setTimeout +await sleep("5s"); + +// Polling pattern instead of setInterval +while (true) { + const status = await checkStatus(id); + if (status === "ready") break; + await sleep("30s"); +} +``` + +## serialization-failed + +**Error:** Data cannot be serialized. + +**Cause:** Functions, class instances, Symbols, WeakMap/WeakSet. + +**Solution:** +```typescript +// BAD: Function in data +const config = { callback: () => console.log("hi") }; + +// GOOD: Configuration data, logic in steps +const config = { logLevel: "info" }; + +async function processWithLogging(data: any, logLevel: string) { + "use step"; + if (logLevel === "info") console.log(data); + return process(data); +} +``` + +**Supported types:** +- Primitives: string, number, boolean, null, undefined, bigint +- Objects: plain objects, arrays, Date, RegExp, URL, URLSearchParams +- Collections: Map, Set, Headers +- Binary: ArrayBuffer, typed arrays (Uint8Array, etc.) +- Web: Request, Response, ReadableStream, WritableStream + +## start-invalid-workflow-function + +**Error:** Invalid workflow function. + +**Cause:** Missing `"use workflow"` directive or framework config. + +**Solution:** +```typescript +// 1. Add directive as FIRST statement +export async function myWorkflow(input: string) { + "use workflow"; // Must be first + // ... workflow code +} + +// 2. Check framework config +// next.config.ts +import { withWorkflow } from "workflow/next"; +export default withWorkflow({ /* config */ }); + +// nitro.config.ts +export default defineNitroConfig({ + modules: ["workflow/nitro"], +}); +``` + +## node-js-module-in-workflow + +**Error:** Node.js module unavailable in workflow. + +**Cause:** Workflows run sandboxed for determinism. + +**Restricted:** fs, path, http, https, net, dns, child_process, cluster, os, crypto, stream + +**Solution:** Move Node.js code to step functions: +```typescript +// BAD: Node.js in workflow +export async function badWorkflow() { + "use workflow"; + const fs = require("fs"); // Error! +} + +// GOOD: Node.js in step +async function readFile(path: string) { + "use step"; + const fs = require("fs"); + return fs.readFileSync(path, "utf-8"); +} + +export async function goodWorkflow(filePath: string) { + "use workflow"; + const content = await readFile(filePath); // Works! +} +``` + +## webhook-invalid-respond-with-value + +**Error:** Invalid `respondWith` value. + +**Solution:** +```typescript +// Valid options: +const webhook = createWebhook(); // Default: auto 202 +const webhook = createWebhook({ respondWith: "manual" }); +const webhook = createWebhook({ respondWith: new Response("OK") }); +``` + +## webhook-response-not-sent + +**Error:** Manual mode but `respondWith()` not called. + +**Solution:** +```typescript +const webhook = createWebhook({ respondWith: "manual" }); +const request = await webhook; + +await processRequest(request); + +// MUST call respondWith in manual mode +await request.respondWith(new Response("OK", { status: 200 })); +``` + +## Step Retry Exhaustion + +**Symptom:** Step fails after max retries. + +**Default retry:** 5-10 attempts with exponential backoff. + +**Solution:** Handle expected failures explicitly: +```typescript +async function callAPI() { + "use step"; + + const res = await fetch("..."); + + // Don't retry on client errors + if (res.status >= 400 && res.status < 500) { + throw new FatalError(`Client error: ${res.status}`); + } + + // Custom retry delay + if (res.status === 429) { + throw new RetryableError("Rate limited", { retryAfter: "5m" }); + } + + return res; +} +``` + +## Streaming Errors + +**Symptom:** Stream not working. + +**Solution:** +```typescript +// 1. Only use in steps, not workflow +async function streamData() { + "use step"; // Required! + + const writer = getWritable(); + await writer.write(data); + writer.releaseLock(); // 2. Release lock! +} + +// 3. Close when done +const writer = getWritable(); +await writer.write(lastData); +writer.releaseLock(); +await getWritable().close(); // Signal completion +``` + +## Hook Token Not Found + +**Error:** Invalid or expired hook token. + +**Cause:** Token doesn't exist, workflow completed, or token already consumed. + +**Solution:** +```typescript +import { resumeHook, getHookByToken } from "workflow/api"; + +// Check hook status before resuming +const hookInfo = await getHookByToken(token); +if (!hookInfo || hookInfo.status !== "pending") { + throw new Error("Hook not available"); +} + +await resumeHook(token, data); +``` + +## Dashboard Not Showing Runs + +**Symptom:** `npx workflow web` shows workflows but no runs, even after triggering. + +**Causes:** +1. Dashboard not connected to your app's port +2. App running on non-default port (not 3000) +3. Dashboard started before app +4. Multiple dev servers running on different ports + +**Solution:** + +```bash +# 1. Kill any stray dev servers +pkill -f "next dev" 2>/dev/null + +# 2. Start your app on port 3000 +npm run dev + +# 3. In another terminal, restart the dashboard +pkill -f "workflow web" 2>/dev/null +npx workflow web + +# 4. If using a different port, specify it: +npx workflow web --app-url http://localhost:3001 +``` + +**Verify connection:** +```bash +# Check your app responds to workflow endpoints +curl -s http://localhost:3000/.well-known/workflow/v1/flow \ + -X POST -H "Content-Type: application/json" +# Should return: {"error": "Missing required headers"} (not 404) +``` + +**Local storage locations:** +- Next.js: `.next/workflow-data/` +- Other frameworks: `.workflow-data/` + +## Debugging Tips + +1. **Use npx workflow web** - Visual dashboard for runs (ensure correct port) +2. **Check run status** - `getRun(runId).status` +3. **Review event log** - Shows step executions, hooks, sleeps +4. **Test locally first** - Local World for development +5. **Add logging in steps** - Steps have full console access +6. **Verify port** - Ensure dashboard connects to correct app port diff --git a/skills/references/frameworks.md b/skills/references/frameworks.md new file mode 100644 index 0000000000..a05b6e4c8a --- /dev/null +++ b/skills/references/frameworks.md @@ -0,0 +1,235 @@ +# Framework Setup + +## Table of Contents +- [Next.js](#nextjs) +- [Express](#express) +- [Hono](#hono) +- [Vite](#vite) +- [Astro](#astro) +- [Fastify](#fastify) +- [Nitro](#nitro) +- [Nuxt](#nuxt) +- [SvelteKit](#sveltekit) + +## Next.js + +```bash +npm create next-app@latest my-app && cd my-app && npm i workflow +``` + +**next.config.ts:** +```typescript +import type { NextConfig } from "next"; +import { withWorkflow } from "workflow/next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default withWorkflow(nextConfig); +``` + +**tsconfig.json** (optional IDE support): +```json +{ "compilerOptions": { "plugins": [{ "name": "workflow" }] } } +``` + +**API Route:** +```typescript +// app/api/signup/route.ts +import { start } from "workflow/api"; +import { handleUserSignup } from "@/workflows/user-signup"; + +export async function POST(req: Request) { + const { email } = await req.json(); + await start(handleUserSignup, [email]); + return Response.json({ message: "Workflow started" }); +} +``` + +## Express + +```bash +mkdir my-app && cd my-app && npm init -y +npm i workflow express nitro rollup && npm i -D @types/express +``` + +**nitro.config.ts:** +```typescript +import { defineNitroConfig } from "nitro/config"; + +export default defineNitroConfig({ + modules: ["workflow/nitro"], + vercel: { entryFormat: "node" }, + routes: { "/**": { handler: "./src/index.ts", format: "node" } }, +}); +``` + +**src/index.ts:** +```typescript +import express from "express"; +import { start } from "workflow/api"; + +const app = express(); +app.use(express.json()); + +app.post("/api/signup", async (req, res) => { + await start(handleUserSignup, [req.body.email]); + return res.json({ message: "Workflow started" }); +}); + +export default app; +``` + +## Hono + +```bash +npm create hono@latest my-app -- --template=nodejs +cd my-app && npm i workflow nitro rollup +``` + +**nitro.config.ts:** +```typescript +import { defineConfig } from "nitro"; + +export default defineConfig({ + modules: ["workflow/nitro"], + routes: { "/**": "./src/index.ts" } +}); +``` + +## Vite + +```bash +npm create vite@latest my-app -- --template react-ts +cd my-app && npm i workflow nitro rollup +``` + +**vite.config.ts:** +```typescript +import { defineConfig } from "vite"; +import { nitro } from "nitro/plugin"; +import { workflow } from "workflow/vite"; + +export default defineConfig({ + plugins: [nitro({ serverDir: "." }), workflow()], +}); +``` + +## Astro + +```bash +npm create astro@latest my-app -- --template minimal --install --yes +cd my-app && npm i workflow +``` + +**astro.config.mjs:** +```typescript +import { defineConfig } from "astro/config"; +import { workflow } from "workflow/astro"; + +export default defineConfig({ integrations: [workflow()] }); +``` + +**src/pages/api/signup.ts:** +```typescript +import type { APIRoute } from "astro"; +import { start } from "workflow/api"; + +export const POST: APIRoute = async ({ request }) => { + const { email } = await request.json(); + await start(handleUserSignup, [email]); + return Response.json({ message: "Workflow started" }); +}; +``` + +## Fastify + +```bash +mkdir my-app && cd my-app && npm init -y +npm i workflow fastify nitro rollup && npm i -D @types/node typescript +``` + +**nitro.config.ts:** +```typescript +import { defineNitroConfig } from "nitro/config"; +export default defineNitroConfig({ modules: ["workflow/nitro"] }); +``` + +## Nitro + +```bash +npx create-nitro-app my-app && cd my-app && npm i workflow +``` + +**nitro.config.ts:** +```typescript +export default defineConfig({ + serverDir: "./server", + modules: ["workflow/nitro"], +}); +``` + +## Nuxt + +```bash +npm create nuxt@latest my-app && cd my-app && npm i workflow +``` + +**nuxt.config.ts:** +```typescript +export default defineNuxtConfig({ + modules: ["workflow/nuxt"], + compatibilityDate: "latest", +}); +``` + +## SvelteKit + +```bash +npx sv create my-app --template=minimal --types=ts --no-add-ones +cd my-app && npm i workflow +``` + +**vite.config.ts:** +```typescript +import { sveltekit } from "@sveltejs/kit/vite"; +import { workflowPlugin } from "workflow/sveltekit"; + +export default { plugins: [sveltekit(), workflowPlugin()] }; +``` + +## Run and Inspect + +```bash +# Start your app on the default port (3000) +npm run dev + +# In a separate terminal, open the dashboard +npx workflow web +``` + +**Important:** The dashboard (`npx workflow web`) connects to your app at `http://localhost:3000` by default. If your app runs on a different port, specify it: + +```bash +# If your app is on port 3001 +npx workflow web --app-url http://localhost:3001 +``` + +The dashboard opens at `http://localhost:3456` and shows: +- **Workflows tab:** Lists registered workflow functions +- **Runs tab:** Shows triggered workflow runs with status and events + +**Troubleshooting:** If runs don't appear: +1. Ensure your app is running on the expected port +2. Restart the dashboard after starting your app +3. Check that workflows are being triggered (API returns `runId`) +4. Workflow data is stored in `.next/workflow-data/` (Next.js) or `.workflow-data/` + +## HTTP Endpoints (Auto-generated) + +| Endpoint | Purpose | +|----------|---------| +| `POST /.well-known/workflow/v1/flow` | Execute workflow | +| `POST /.well-known/workflow/v1/step` | Execute step | +| `POST /.well-known/workflow/v1/webhook/:token` | Deliver webhook | diff --git a/skills/references/patterns.md b/skills/references/patterns.md new file mode 100644 index 0000000000..b59f2cf56d --- /dev/null +++ b/skills/references/patterns.md @@ -0,0 +1,480 @@ +# Advanced Workflow Patterns + +## Table of Contents +- [Hooks](#hooks) +- [Webhooks](#webhooks) +- [Control Flow](#control-flow) +- [Batching](#batching) +- [Streaming](#streaming) +- [Error Handling](#error-handling) +- [Real-World Examples](#real-world-examples) + +## Hooks + +### Basic Hook + +```typescript +import { createHook } from "workflow"; + +export async function approvalWorkflow() { + "use workflow"; + + const hook = createHook<{ approved: boolean; comment: string }>(); + console.log("Send approval to:", hook.token); + + const result = await hook; // Pauses until resumed + return result.approved ? "Approved" : "Rejected"; +} +``` + +### Custom Deterministic Tokens + +Reconstruct tokens from external data: + +```typescript +const hook = createHook({ + token: `slack:${channelId}:${threadTs}` +}); + +// External handler reconstructs token: +await resumeHook(`slack:${channelId}:${threadTs}`, slackEvent); +``` + +### Multiple Events (AsyncIterable) + +```typescript +export async function dataCollectionWorkflow() { + "use workflow"; + + const hook = createHook<{ value: number; done?: boolean }>(); + const values: number[] = []; + + for await (const payload of hook) { + values.push(payload.value); + if (payload.done) break; + } + + return { total: values.reduce((a, b) => a + b, 0), count: values.length }; +} +``` + +### Type-Safe Hooks with Schema + +```typescript +import { defineHook } from "workflow"; +import { z } from "zod"; + +const approvalHook = defineHook({ + schema: z.object({ + requestId: z.string(), + approved: z.boolean(), + approvedBy: z.string(), + comment: z.string().transform((v) => v.trim()), + }), +}); + +export async function documentApproval(documentId: string) { + "use workflow"; + + const hook = approvalHook.create({ token: `approval:${documentId}` }); + const approval = await hook; + + return approval.approved; +} + +// Resume with validation (throws on invalid data) +await approvalHook.resume(`approval:${documentId}`, approvalData); +``` + +Supported validators: Zod, Valibot, ArkType, Effect Schema + +## Webhooks + +### Basic Webhook + +```typescript +import { createWebhook } from "workflow"; + +export async function paymentWorkflow(orderId: string) { + "use workflow"; + + const webhook = createWebhook(); + await notifyPaymentProvider(orderId, webhook.url); + + const request = await webhook; // Pauses until HTTP POST + const data = await request.json(); + + if (data.status === "completed") { + await fulfillOrder(orderId); + } +} +``` + +### Manual Response Mode + +```typescript +const webhook = createWebhook({ respondWith: "manual" }); +const request = await webhook; + +await processRequest(request); + +// Must respond when using manual mode +await request.respondWith(new Response("OK", { status: 200 })); +``` + +### Automatic Custom Response + +```typescript +const webhook = createWebhook({ + respondWith: new Response("Received", { status: 200 }) +}); +``` + +### Multiple Webhook Events + +```typescript +for await (const request of webhook) { + const data = await request.json(); + await processEvent(data); + if (data.final) break; +} +``` + +### Hooks vs Webhooks + +| Feature | Hooks | Webhooks | +|---------|-------|----------| +| Data format | Any serializable | HTTP Request | +| URL | Manual token | Auto `webhook.url` | +| Response | N/A | Auto or manual | +| Use case | Custom integrations | HTTP callbacks | + +## Control Flow + +### Parallel Steps (Promise.all) + +```typescript +export async function parallelWorkflow(urls: string[]) { + "use workflow"; + + // Execute steps in parallel + const results = await Promise.all( + urls.map(url => fetchAndProcess(url)) + ); + + return results; +} +``` + +### Race Condition (Promise.race) + +First to complete wins - useful for timeouts or competitive execution: + +```typescript +export async function raceWorkflow(taskId: string) { + "use workflow"; + + // Race between task completion and timeout + const result = await Promise.race([ + (async () => { + const data = await processTask(taskId); + return { status: "completed", data }; + })(), + (async () => { + await sleep("30s"); + return { status: "timeout" }; + })(), + ]); + + if (result.status === "timeout") { + await notifyTimeout(taskId); + } + + return result; +} +``` + +### Conditional Branching + +```typescript +export async function conditionalWorkflow(userId: string) { + "use workflow"; + + const user = await getUser(userId); + + if (user.tier === "premium") { + await processPremium(user); + } else { + await processStandard(user); + } +} +``` + +### Polling Pattern + +```typescript +export async function pollUntilReady(jobId: string) { + "use workflow"; + + while (true) { + const status = await checkStatus(jobId); + if (status === "ready") return await getResult(jobId); + if (status === "failed") throw new FatalError("Job failed"); + await sleep("30s"); + } +} +``` + +### Retry with Backoff + +```typescript +export async function retryWorkflow(data: any) { + "use workflow"; + + let attempts = 0; + const maxAttempts = 5; + + while (attempts < maxAttempts) { + try { + return await processData(data); + } catch (error) { + attempts++; + if (attempts >= maxAttempts) throw error; + await sleep(`${Math.pow(2, attempts)}s`); // Exponential backoff + } + } +} +``` + +## Batching + +### Item-Level Steps (Granular Retry) + +Each item in array is a separate step - if one fails, only that item retries: + +```typescript +export async function itemLevelBatch(items: string[]) { + "use workflow"; + + const results = await Promise.all( + items.map(async (item) => { + return await processItem(item); // Each is a separate step + }) + ); + + return results; +} + +async function processItem(item: string) { + "use step"; + // If this fails, only this item retries + return await externalAPI.process(item); +} +``` + +### Batch-Level Steps (Atomic Retry) + +Entire batch is one step - if any item fails, whole batch retries: + +```typescript +export async function batchLevelWorkflow(items: string[]) { + "use workflow"; + + const results = await processBatch(items); + return results; +} + +async function processBatch(items: string[]) { + "use step"; + // If ANY item fails, entire batch retries + return await Promise.all( + items.map(item => externalAPI.process(item)) + ); +} +``` + +### Chunked Batching + +Process large arrays in smaller chunks: + +```typescript +export async function chunkedWorkflow(items: string[]) { + "use workflow"; + + const chunkSize = 10; + const results: any[] = []; + + for (let i = 0; i < items.length; i += chunkSize) { + const chunk = items.slice(i, i + chunkSize); + const chunkResults = await processChunk(chunk); + results.push(...chunkResults); + } + + return results; +} + +async function processChunk(chunk: string[]) { + "use step"; + return await Promise.all(chunk.map(item => process(item))); +} +``` + +## Streaming + +### Namespaced Streams + +```typescript +async function processWithLogs(data: any) { + "use step"; + + const logsWriter = getWritable({ namespace: "logs" }); + const metricsWriter = getWritable({ namespace: "metrics" }); + + await logsWriter.write({ level: "info", msg: "Starting" }); + await metricsWriter.write({ event: "start", timestamp: Date.now() }); + + const result = await process(data); + + await logsWriter.write({ level: "info", msg: "Done" }); + logsWriter.releaseLock(); + metricsWriter.releaseLock(); + + return result; +} +``` + +### Binary Data + +```typescript +async function writeBinary(data: string) { + "use step"; + + const writer = getWritable(); + const encoder = new TextEncoder(); + await writer.write(encoder.encode(data)); + writer.releaseLock(); +} +``` + +## Error Handling + +### Categorizing Errors + +```typescript +async function callExternalAPI(url: string) { + "use step"; + + const res = await fetch(url); + + // Permanent failures + if (res.status === 401 || res.status === 403) { + throw new FatalError(`Auth error: ${res.status}`); + } + + // Retryable with delay + if (res.status === 429) { + const retryAfter = res.headers.get("Retry-After"); + throw new RetryableError("Rate limited", { + retryAfter: retryAfter ? parseInt(retryAfter) * 1000 : "5m" + }); + } + + // Server errors - auto retry + if (res.status >= 500) { + throw new Error(`Server error: ${res.status}`); + } + + return res; +} +``` + +### Workflow-Level Error Handling + +```typescript +export async function resilientWorkflow(input: any) { + "use workflow"; + + try { + const result = await riskyOperation(input); + return { success: true, result }; + } catch (error) { + await notifyAdmin(error); + await cleanup(input); + return { success: false, error: error.message }; + } +} +``` + +## Real-World Examples + +### Onboarding Drip Campaign + +```typescript +export async function userOnboarding(email: string) { + "use workflow"; + + await sendWelcomeEmail(email); + await sleep("3 days"); + await sendTipsEmail(email); + await sleep("7 days"); + await sendFeedbackRequest(email); +} +``` + +### Churn Prevention + +```typescript +export async function churnPrevention(userId: string) { + "use workflow"; + + await sleep("7 days"); + if (!await isActive(userId)) { + await sendReEngagementEmail(userId); + await sleep("3 days"); + if (!await isActive(userId)) { + await offerDiscount(userId); + } + } +} +``` + +### Order Processing + +```typescript +export async function processOrder(orderId: string) { + "use workflow"; + + const payment = await chargeCard(orderId); + + // Wait for warehouse webhook + const webhook = createWebhook(); + await notifyWarehouse(orderId, webhook.url); + const shipment = await webhook; + + await notifyCustomer(orderId, await shipment.json()); + return { orderId, status: "shipped" }; +} +``` + +### Multi-Step Approval + +```typescript +export async function multiApproval(requestId: string) { + "use workflow"; + + const managers = await getApprovers(requestId); + + for (const manager of managers) { + const hook = createHook<{ approved: boolean }>(); + await notifyManager(manager, requestId, hook.token); + + const decision = await hook; + if (!decision.approved) { + return { status: "rejected", rejectedBy: manager }; + } + } + + await executeRequest(requestId); + return { status: "approved" }; +} +``` From a38334149b339ccd348635c516faaa6aa2e38097 Mon Sep 17 00:00:00 2001 From: johnlindquist Date: Fri, 23 Jan 2026 12:39:25 -0800 Subject: [PATCH 02/12] 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 --- skills/SKILL.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/skills/SKILL.md b/skills/SKILL.md index ef81d7dda6..2f186bb696 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -199,10 +199,30 @@ async function chargeCard(amount: number) { } ``` +## Custom Class Serialization + +Class instances can be serialized by implementing static methods: + +```typescript +import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; + +class Point { + constructor(public x: number, public y: number) {} + + static [WORKFLOW_SERIALIZE](instance: Point) { + return { x: instance.x, y: instance.y }; + } + + static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) { + return new Point(data.x, data.y); + } +} +``` + ## What Cannot Be Serialized -- Functions/callbacks - define logic in steps -- Class instances with methods - use plain objects +- Functions/callbacks (except step functions) - define logic in steps +- Class instances without custom serialization methods - Symbols, WeakMap, WeakSet - Node.js modules (fs, http, crypto) - use in steps only From 1335d00ae3741b58736e4f113909841a6e4c9e59 Mon Sep 17 00:00:00 2001 From: johnlindquist Date: Fri, 23 Jan 2026 12:42:43 -0800 Subject: [PATCH 03/12] 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 --- skills/SKILL.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skills/SKILL.md b/skills/SKILL.md index 2f186bb696..5bd905265c 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -68,7 +68,7 @@ await sleep("10m"); // 10 minutes await sleep("1h"); // 1 hour await sleep("7 days"); // 7 days await sleep(5000); // milliseconds -await sleep(new Date("2025-01-01")); // specific date +await sleep(new Date("2027-01-01")); // specific date ``` ### createHook - Wait for External Input @@ -177,8 +177,8 @@ import { getWorkflowMetadata } from "workflow"; export async function myWorkflow() { "use workflow"; - const { runId, workflowName } = getWorkflowMetadata(); - console.log(`Run ${runId} of ${workflowName}`); + const { workflowRunId, workflowStartedAt, url } = getWorkflowMetadata(); + console.log(`Run ${workflowRunId} started at ${workflowStartedAt}`); } ``` @@ -191,7 +191,7 @@ import { getStepMetadata } from "workflow"; async function chargeCard(amount: number) { "use step"; - const { stepId, attemptNumber } = getStepMetadata(); + const { stepId, attempt, stepStartedAt } = getStepMetadata(); await stripe.charges.create( { amount }, { idempotencyKey: `charge:${stepId}` } From 151c2cd1581a8431abd8c3a4a33be5f04867b600 Mon Sep 17 00:00:00 2001 From: johnlindquist Date: Fri, 23 Jan 2026 13:01:05 -0800 Subject: [PATCH 04/12] Move skills to skill/workflow directory structure Reorganize skill files to support multiple skills under ./skill// Signed-off-by: John Lindquist --- {skills => skill/workflow}/SKILL.md | 0 {skills => skill/workflow}/references/ai-agents.md | 0 {skills => skill/workflow}/references/api-reference.md | 0 {skills => skill/workflow}/references/errors.md | 0 {skills => skill/workflow}/references/frameworks.md | 0 {skills => skill/workflow}/references/patterns.md | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {skills => skill/workflow}/SKILL.md (100%) rename {skills => skill/workflow}/references/ai-agents.md (100%) rename {skills => skill/workflow}/references/api-reference.md (100%) rename {skills => skill/workflow}/references/errors.md (100%) rename {skills => skill/workflow}/references/frameworks.md (100%) rename {skills => skill/workflow}/references/patterns.md (100%) diff --git a/skills/SKILL.md b/skill/workflow/SKILL.md similarity index 100% rename from skills/SKILL.md rename to skill/workflow/SKILL.md diff --git a/skills/references/ai-agents.md b/skill/workflow/references/ai-agents.md similarity index 100% rename from skills/references/ai-agents.md rename to skill/workflow/references/ai-agents.md diff --git a/skills/references/api-reference.md b/skill/workflow/references/api-reference.md similarity index 100% rename from skills/references/api-reference.md rename to skill/workflow/references/api-reference.md diff --git a/skills/references/errors.md b/skill/workflow/references/errors.md similarity index 100% rename from skills/references/errors.md rename to skill/workflow/references/errors.md diff --git a/skills/references/frameworks.md b/skill/workflow/references/frameworks.md similarity index 100% rename from skills/references/frameworks.md rename to skill/workflow/references/frameworks.md diff --git a/skills/references/patterns.md b/skill/workflow/references/patterns.md similarity index 100% rename from skills/references/patterns.md rename to skill/workflow/references/patterns.md From 5f4e294039dcaf725f8b50e7b81cb320c4d485a9 Mon Sep 17 00:00:00 2001 From: johnlindquist Date: Fri, 23 Jan 2026 13:12:34 -0800 Subject: [PATCH 05/12] Rename skill to skills (plural) Signed-off-by: John Lindquist --- {skill => skills}/workflow/SKILL.md | 0 {skill => skills}/workflow/references/ai-agents.md | 0 {skill => skills}/workflow/references/api-reference.md | 0 {skill => skills}/workflow/references/errors.md | 0 {skill => skills}/workflow/references/frameworks.md | 0 {skill => skills}/workflow/references/patterns.md | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {skill => skills}/workflow/SKILL.md (100%) rename {skill => skills}/workflow/references/ai-agents.md (100%) rename {skill => skills}/workflow/references/api-reference.md (100%) rename {skill => skills}/workflow/references/errors.md (100%) rename {skill => skills}/workflow/references/frameworks.md (100%) rename {skill => skills}/workflow/references/patterns.md (100%) diff --git a/skill/workflow/SKILL.md b/skills/workflow/SKILL.md similarity index 100% rename from skill/workflow/SKILL.md rename to skills/workflow/SKILL.md diff --git a/skill/workflow/references/ai-agents.md b/skills/workflow/references/ai-agents.md similarity index 100% rename from skill/workflow/references/ai-agents.md rename to skills/workflow/references/ai-agents.md diff --git a/skill/workflow/references/api-reference.md b/skills/workflow/references/api-reference.md similarity index 100% rename from skill/workflow/references/api-reference.md rename to skills/workflow/references/api-reference.md diff --git a/skill/workflow/references/errors.md b/skills/workflow/references/errors.md similarity index 100% rename from skill/workflow/references/errors.md rename to skills/workflow/references/errors.md diff --git a/skill/workflow/references/frameworks.md b/skills/workflow/references/frameworks.md similarity index 100% rename from skill/workflow/references/frameworks.md rename to skills/workflow/references/frameworks.md diff --git a/skill/workflow/references/patterns.md b/skills/workflow/references/patterns.md similarity index 100% rename from skill/workflow/references/patterns.md rename to skills/workflow/references/patterns.md From facfc0fdc99e30364f96c12b2f355eaf7702b715 Mon Sep 17 00:00:00 2001 From: johnlindquist Date: Fri, 23 Jan 2026 14:43:34 -0800 Subject: [PATCH 06/12] 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 --- packages/ai/package.json | 12 +- packages/core/package.json | 12 +- packages/next/package.json | 10 +- packages/workflow/package.json | 12 +- skills/workflow/SKILL.md | 279 ++-------- skills/workflow/references/ai-agents.md | 228 --------- skills/workflow/references/api-reference.md | 434 ---------------- .../{errors.md => common-errors.md} | 7 +- skills/workflow/references/frameworks.md | 235 --------- skills/workflow/references/patterns.md | 480 ------------------ 10 files changed, 86 insertions(+), 1623 deletions(-) delete mode 100644 skills/workflow/references/ai-agents.md delete mode 100644 skills/workflow/references/api-reference.md rename skills/workflow/references/{errors.md => common-errors.md} (98%) delete mode 100644 skills/workflow/references/frameworks.md delete mode 100644 skills/workflow/references/patterns.md diff --git a/packages/ai/package.json b/packages/ai/package.json index c519fb5651..d28121e38d 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -5,8 +5,12 @@ "type": "module", "main": "dist/index.js", "files": [ - "dist" + "dist", + "docs/**/*" ], + "directories": { + "doc": "./docs" + }, "publishConfig": { "access": "public" }, @@ -53,8 +57,10 @@ }, "scripts": { "build": "tsc", - "clean": "tsc --build --clean", - "test": "vitest run" + "clean": "tsc --build --clean && rm -rf docs ||:", + "test": "vitest run", + "prepack": "mkdir -p docs && cp -r ../../docs/content/docs/ai ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow-ai ./docs/", + "postpack": "rm -rf docs" }, "devDependencies": { "@workflow/tsconfig": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index 60820a83fb..cb6f38dc0e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -5,8 +5,12 @@ "type": "module", "main": "dist/index.js", "files": [ - "dist" + "dist", + "docs/**/*" ], + "directories": { + "doc": "./docs" + }, "publishConfig": { "access": "public" }, @@ -37,10 +41,12 @@ "scripts": { "build": "tsc", "dev": "tsc --watch", - "clean": "tsc --build --clean && rm -r dist ||:", + "clean": "tsc --build --clean && rm -rf dist docs ||:", "test": "cross-env WORKFLOW_TARGET_WORLD=local vitest run src", "test:e2e": "vitest run e2e", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "prepack": "mkdir -p docs && cp -r ../../docs/content/docs/foundations ./docs/ && cp -r ../../docs/content/docs/how-it-works ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow ./docs/api-reference", + "postpack": "rm -rf docs" }, "dependencies": { "@aws-sdk/credential-provider-web-identity": "3.609.0", diff --git a/packages/next/package.json b/packages/next/package.json index b028cd9733..68529cca84 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -5,8 +5,12 @@ "type": "commonjs", "main": "dist/index.js", "files": [ - "dist" + "dist", + "docs/**/*" ], + "directories": { + "doc": "./docs" + }, "publishConfig": { "access": "public" }, @@ -24,7 +28,9 @@ "scripts": { "build": "tsc", "dev": "tsc --watch", - "clean": "tsc --build --clean && rm -rf dist" + "clean": "tsc --build --clean && rm -rf dist docs", + "prepack": "mkdir -p docs && cp ../../docs/content/docs/getting-started/next.mdx ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow-next ./docs/api-reference", + "postpack": "rm -rf docs" }, "dependencies": { "@swc/core": "catalog:", diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 60850a70bf..35281f995d 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -10,8 +10,12 @@ }, "files": [ "dist", - "bin" + "bin", + "docs/**/*" ], + "directories": { + "doc": "./docs" + }, "publishConfig": { "access": "public" }, @@ -48,10 +52,12 @@ }, "scripts": { "build": "tsc && chmod +x bin/run.js", - "clean": "tsc --build --clean && rm -r dist ||:", + "clean": "tsc --build --clean && rm -rf dist docs ||:", "dev": "tsc --watch", "test": "vitest run src", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "prepack": "cp -r ../../docs/content/docs ./docs", + "postpack": "rm -rf docs" }, "dependencies": { "@workflow/astro": "workspace:*", diff --git a/skills/workflow/SKILL.md b/skills/workflow/SKILL.md index 5bd905265c..889255fee9 100644 --- a/skills/workflow/SKILL.md +++ b/skills/workflow/SKILL.md @@ -1,265 +1,76 @@ --- name: workflow description: Creates durable, resumable workflows using Vercel's Workflow DevKit. Use when building workflows that need to survive restarts, pause for external events, retry on failure, or coordinate multi-step operations over time. Triggers on mentions of "workflow", "durable functions", "resumable", "workflow devkit", or step-based orchestration. +metadata: + author: Vercel Inc. + version: '1.0' --- -# Workflow DevKit +## Workflow DevKit Documentation -TypeScript framework for building durable, resumable functions. Workflows survive crashes, deployments, and can pause for days/months. +When you need up-to-date information about the Workflow DevKit: -**Website:** https://useworkflow.dev | **GitHub:** https://github.com/vercel/workflow +### Search Bundled Documentation -## Installation +Search the bundled documentation in `node_modules/workflow/docs/`: -```bash -npm i workflow -# or: pnpm add workflow | yarn add workflow | bun add workflow -``` - -**Version Note:** The Workflow DevKit is in beta. Consider pinning to a specific version (e.g., `workflow@4.0.1-beta.44`) rather than `"latest"` to avoid unexpected breaking changes. - -## Core Concepts - -### Directives - -```typescript -"use workflow"; // First line - makes async function durable -"use step"; // First line - makes function a cached, retryable unit -``` - -### Workflow Function - -Orchestrates steps, survives restarts, replays deterministically: - -```typescript -import { sleep } from "workflow"; - -export async function onboardUser(email: string) { - "use workflow"; - - const user = await createUser(email); // Step 1 - await sendWelcomeEmail(user); // Step 2 - await sleep("3 days"); // Pause (no resources consumed) - await sendFollowupEmail(user); // Step 3 - return { userId: user.id }; -} -``` - -### Step Function - -Has full Node.js access, auto-retries on failure: - -```typescript -async function createUser(email: string) { - "use step"; - return await db.users.create({ email }); -} -``` +1. **Find docs**: `glob "node_modules/workflow/docs/**/*.mdx"` +2. **Search content**: `grep "your query" node_modules/workflow/docs/` -## Essential APIs +Documentation structure in `node_modules/workflow/docs/`: -### sleep - Pause Workflow +- `getting-started/` - Framework setup (next.mdx, express.mdx, hono.mdx, etc.) +- `foundations/` - Core concepts (workflows-and-steps.mdx, hooks.mdx, streaming.mdx, etc.) +- `api-reference/workflow/` - API docs (sleep.mdx, create-hook.mdx, fatal-error.mdx, etc.) +- `api-reference/workflow-api/` - Client API (start.mdx, get-run.mdx, resume-hook.mdx, etc.) +- `ai/` - AI SDK integration docs +- `errors/` - Error code documentation -```typescript -import { sleep } from "workflow"; +Related packages also include bundled docs: -await sleep("5s"); // 5 seconds -await sleep("10m"); // 10 minutes -await sleep("1h"); // 1 hour -await sleep("7 days"); // 7 days -await sleep(5000); // milliseconds -await sleep(new Date("2027-01-01")); // specific date -``` +- `@workflow/ai`: `node_modules/@workflow/ai/docs/` - DurableAgent and AI integration +- `@workflow/core`: `node_modules/@workflow/core/docs/` - Core runtime (foundations, how-it-works) +- `@workflow/next`: `node_modules/@workflow/next/docs/` - Next.js integration -### createHook - Wait for External Input +**When in doubt, update to the latest version of the Workflow DevKit.** -```typescript -import { createHook } from "workflow"; +### Official Resources -const hook = createHook<{ approved: boolean }>(); -console.log("Token:", hook.token); // Send to external system -const result = await hook; // Pauses until resumed -``` +- **Website**: https://useworkflow.dev +- **GitHub**: https://github.com/vercel/workflow -Resume externally: -```typescript -import { resumeHook } from "workflow/api"; -await resumeHook(token, { approved: true }); -``` - -### createWebhook - HTTP Callback Endpoint - -```typescript -import { createWebhook } from "workflow"; - -const webhook = createWebhook(); -await sendToExternalService(webhook.url); // Auto-generated URL -const request = await webhook; // Pauses until HTTP POST -const data = await request.json(); -``` +### Quick Reference -### start - Trigger Workflow +**Directives:** ```typescript -import { start } from "workflow/api"; - -const run = await start(onboardUser, ["user@example.com"]); -console.log(run.runId); // Returns immediately +"use workflow"; // First line - makes async function durable +"use step"; // First line - makes function a cached, retryable unit ``` -### Error Handling +**Essential imports:** ```typescript +// Workflow primitives +import { sleep, fetch, createHook, createWebhook, getWritable } from "workflow"; import { FatalError, RetryableError } from "workflow"; +import { getWorkflowMetadata, getStepMetadata } from "workflow"; -// Stop retries permanently -throw new FatalError("User not found"); - -// Retry with delay -throw new RetryableError("Rate limited", { retryAfter: "5m" }); -``` - -### Parallel Execution - -```typescript -// Execute steps concurrently -const [user, settings, history] = await Promise.all([ - fetchUser(userId), - fetchSettings(userId), - fetchHistory(userId), -]); - -// Race: first to complete wins (useful for timeouts) -const result = await Promise.race([ - processTask(taskId), - (async () => { await sleep("30s"); return { timeout: true }; })(), -]); -``` - -## Fetch in Workflows - -Workflows run sandboxed. Import fetch from workflow: - -```typescript -import { fetch } from "workflow"; - -export async function myWorkflow() { - "use workflow"; - globalThis.fetch = fetch; // Enable for libraries (AI SDK) - - const response = await fetch("https://api.example.com/data"); -} -``` - -## Streaming (Steps Only) - -```typescript -import { getWritable } from "workflow"; - -async function streamData(items: string[]) { - "use step"; - const writer = getWritable(); - for (const item of items) { - await writer.write({ item }); - } - writer.releaseLock(); -} -``` - -## Metadata & Idempotency - -### Workflow Metadata - -Get context about the current workflow run: - -```typescript -import { getWorkflowMetadata } from "workflow"; - -export async function myWorkflow() { - "use workflow"; - const { workflowRunId, workflowStartedAt, url } = getWorkflowMetadata(); - console.log(`Run ${workflowRunId} started at ${workflowStartedAt}`); -} -``` - -### Step Metadata for Idempotency - -Use stepId for external API calls to prevent duplicate operations: - -```typescript -import { getStepMetadata } from "workflow"; - -async function chargeCard(amount: number) { - "use step"; - const { stepId, attempt, stepStartedAt } = getStepMetadata(); - await stripe.charges.create( - { amount }, - { idempotencyKey: `charge:${stepId}` } - ); -} -``` - -## Custom Class Serialization - -Class instances can be serialized by implementing static methods: - -```typescript -import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; - -class Point { - constructor(public x: number, public y: number) {} - - static [WORKFLOW_SERIALIZE](instance: Point) { - return { x: instance.x, y: instance.y }; - } - - static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) { - return new Point(data.x, data.y); - } -} -``` - -## What Cannot Be Serialized - -- Functions/callbacks (except step functions) - define logic in steps -- Class instances without custom serialization methods -- Symbols, WeakMap, WeakSet -- Node.js modules (fs, http, crypto) - use in steps only - -## Run Status - -`pending | running | completed | failed | cancelled` - -## References - -- **Framework Setup:** See [frameworks.md](references/frameworks.md) for Next.js, Express, Hono, Vite, Astro, Fastify, Nitro, Nuxt, SvelteKit -- **AI Agents:** See [ai-agents.md](references/ai-agents.md) for DurableAgent and streaming patterns -- **Advanced Patterns:** See [patterns.md](references/patterns.md) for hooks, webhooks, control flow, batching, streaming -- **Common Errors:** See [errors.md](references/errors.md) for troubleshooting -- **API Reference:** See [api-reference.md](references/api-reference.md) for complete API - -## Local Development - -```bash -# Terminal 1: Start your app on default port -npm run dev - -# Terminal 2: Open dashboard (connects to port 3000) -npx workflow web +// API operations +import { start, getRun, resumeHook, resumeWebhook } from "workflow/api"; -# If app runs on different port: -npx workflow web --app-url http://localhost:3001 +// Framework integrations +import { withWorkflow } from "workflow/next"; +import { workflow } from "workflow/vite"; +import { workflow } from "workflow/astro"; +// Or use modules: ["workflow/nitro"] for Nitro/Nuxt ``` -Dashboard at `http://localhost:3456` shows workflows and runs. If runs don't appear, restart dashboard after app is running. +**Critical workflow rules:** -## Quick Checklist +1. Always set `globalThis.fetch = fetch` when using AI SDK or HTTP libraries +2. Use `sleep()` instead of setTimeout/setInterval +3. Move Node.js modules (fs, crypto, etc.) to step functions +4. Use `FatalError` for permanent failures, `RetryableError` for retries -- [ ] Add `"use workflow"` as first line of workflow function -- [ ] Add `"use step"` as first line of step functions -- [ ] Import `fetch` from `workflow` and assign to `globalThis.fetch` -- [ ] Use `sleep()` instead of setTimeout -- [ ] Move Node.js module usage to step functions -- [ ] Use `FatalError` for permanent failures, `RetryableError` for retries -- [ ] Configure framework (withWorkflow, nitro module, etc.) -- [ ] Run dashboard with correct `--app-url` if not using port 3000 +For common errors and troubleshooting, see [Common Errors Reference](references/common-errors.md). diff --git a/skills/workflow/references/ai-agents.md b/skills/workflow/references/ai-agents.md deleted file mode 100644 index 1dd21368e1..0000000000 --- a/skills/workflow/references/ai-agents.md +++ /dev/null @@ -1,228 +0,0 @@ -# AI Agents with Workflow - -Build durable AI agents that survive restarts and stream responses. - -## Installation - -```bash -npm i @workflow/ai ai -``` - -Supports AI SDK v5 and v6. - -## DurableAgent - -Creates AI agents that maintain state across workflow steps: - -```typescript -import { DurableAgent } from "@workflow/ai/agent"; -import { fetch, getWritable } from "workflow"; -import { z } from "zod"; - -const agent = new DurableAgent({ - model: "anthropic/claude-haiku-4.5", - system: "You are a helpful assistant.", - temperature: 0.7, - maxSteps: 10, - tools: { - searchWeb: { - description: "Search the web for information", - parameters: z.object({ query: z.string() }), - execute: async ({ query }) => { - "use step"; - return await webSearch(query); - }, - }, - getWeather: { - description: "Get current weather", - parameters: z.object({ city: z.string() }), - execute: async ({ city }) => { - "use step"; - return await weatherAPI.get(city); - }, - }, - }, -}); - -export async function chatWorkflow(messages: Message[]) { - "use workflow"; - globalThis.fetch = fetch; // Required for AI SDK - - const writable = getWritable(); - const result = await agent.stream({ - messages, - writable, - }); - - return result.messages; -} -``` - -## DurableAgent Options - -| Option | Type | Description | -|--------|------|-------------| -| `model` | string | LLM identifier (e.g., "anthropic/claude-haiku-4.5") | -| `system` | string | System prompt | -| `temperature` | number | Generation parameter (0-1) | -| `tools` | object | Tool definitions with execute functions | -| `toolChoice` | string | "auto" \| "required" \| "none" \| specific tool | -| `maxSteps` | number | Max LLM iterations | - -## Callbacks - -```typescript -const agent = new DurableAgent({ - model: "anthropic/claude-haiku-4.5", - onFinish: (result) => console.log("Done:", result), - onError: (error) => console.error("Error:", error), - onAbort: () => console.log("Aborted"), - onStepFinish: (step) => console.log("Step:", step), -}); -``` - -## Streaming AI Responses - -### In Workflow - -```typescript -export async function streamingWorkflow(prompt: string) { - "use workflow"; - globalThis.fetch = fetch; - - const writable = getWritable(); - await agent.stream({ messages: [{ role: "user", content: prompt }], writable }); -} -``` - -### Reading Stream (Client) - -```typescript -import { start } from "workflow/api"; - -const run = await start(streamingWorkflow, ["Hello"]); - -for await (const chunk of run.readable) { - console.log(chunk); -} -``` - -### Resumable Streams - -```typescript -// Resume from specific position -const readable = run.getReadable({ startIndex: lastIndex }); - -for await (const chunk of readable) { - process.stdout.write(chunk); -} -``` - -## WorkflowChatTransport - -Transport layer for AI SDK with automatic reconnection: - -```typescript -import { WorkflowChatTransport } from "@workflow/ai"; - -const transport = new WorkflowChatTransport({ - maxConsecutiveErrors: 3, - onChatSendMessage: (response) => { - // Extract workflow run ID from header - const runId = response.headers.get("x-workflow-run-id"); - }, - onChatEnd: ({ chatId, chunkIndex }) => { - console.log("Chat ended at chunk:", chunkIndex); - }, -}); -``` - -## Piping AI SDK Responses - -```typescript -async function generateWithAI(prompt: string) { - "use step"; - - const writable = getWritable(); - const response = await generateText({ prompt, stream: true }); - - await response.pipeThrough(writable); -} -``` - -## Human-in-the-Loop AI - -Combine AI with human approval: - -```typescript -export async function contentApproval(topic: string) { - "use workflow"; - globalThis.fetch = fetch; - - // AI generates draft - const draft = await generateDraft(topic); - - // Wait for human review - const hook = createHook<{ approved: boolean; feedback?: string }>(); - await notifyEditor(draft.id, hook.token); - - const review = await hook; - - if (review.approved) { - await publish(draft); - return { status: "published", draft }; - } else { - // AI revises based on feedback - const revised = await reviseDraft(draft, review.feedback); - return { status: "revised", draft: revised }; - } -} - -async function generateDraft(topic: string) { - "use step"; - const writable = getWritable(); - return await agent.stream({ - messages: [{ role: "user", content: `Write about: ${topic}` }], - writable, - }); -} -``` - -## Multi-Day AI Agents - -Agents that operate over extended periods: - -```typescript -export async function longRunningAgent(task: string) { - "use workflow"; - globalThis.fetch = fetch; - - // Initial processing - const plan = await createPlan(task); - - for (const step of plan.steps) { - await executeStep(step); - - // Wait for external systems - await sleep("1h"); - - // Check status - const status = await checkProgress(step); - if (status.needsReview) { - const hook = createHook<{ proceed: boolean }>(); - await notifyTeam(step, hook.token); - const decision = await hook; - if (!decision.proceed) break; - } - } - - return await generateReport(plan); -} -``` - -## Important Notes - -1. **Always set globalThis.fetch** - AI SDK requires it -2. **Tools must use "use step"** - For durability and retry -3. **Use getWritable() in steps only** - Cannot stream from workflow function -4. **Release writer locks** - Call `writer.releaseLock()` after writing diff --git a/skills/workflow/references/api-reference.md b/skills/workflow/references/api-reference.md deleted file mode 100644 index a50f46225f..0000000000 --- a/skills/workflow/references/api-reference.md +++ /dev/null @@ -1,434 +0,0 @@ -# Complete API Reference - -## workflow package - -### sleep(duration) - -Pause workflow without consuming resources. - -```typescript -import { sleep } from "workflow"; - -await sleep("5s"); // String duration -await sleep("10m"); // 10 minutes -await sleep("1h"); // 1 hour -await sleep("7 days"); // 7 days -await sleep(5000); // Milliseconds -await sleep(new Date("2025-12-31")); // Until specific date -``` - -**Duration formats:** `${number}${'ms'|'s'|'m'|'h'|'d'}` - -### fetch(url, options) - -HTTP request with automatic retry. Use instead of global fetch. - -```typescript -import { fetch } from "workflow"; - -export async function myWorkflow() { - "use workflow"; - globalThis.fetch = fetch; // Enable for libraries - - const response = await fetch("https://api.example.com/data"); - const data = await response.json(); -} -``` - -### createHook(options?) - -Create suspension point for external input. - -```typescript -import { createHook } from "workflow"; - -const hook = createHook<{ approved: boolean }>(); -// hook.token - string identifier for resumption -const result = await hook; // Pauses until resumed -``` - -**Options:** -- `token?: string` - Custom deterministic token -- `metadata?: any` - Attached metadata - -**Returns:** `Hook` - PromiseLike & AsyncIterable - -### defineHook(config) - -Type-safe hook with schema validation. - -```typescript -import { defineHook } from "workflow"; -import { z } from "zod"; - -const myHook = defineHook({ - schema: z.object({ - id: z.string(), - value: z.number(), - }), -}); - -// Create hook -const hook = myHook.create({ token: `custom:${id}` }); - -// Resume with validation -await myHook.resume(`custom:${id}`, { id: "123", value: 42 }); -``` - -### createWebhook(options?) - -Create HTTP webhook endpoint. - -```typescript -import { createWebhook } from "workflow"; - -const webhook = createWebhook(); -console.log(webhook.url); // Auto-generated URL -const request = await webhook; // Pauses until HTTP POST -``` - -**Options:** -- `token?: string` - Custom token -- `metadata?: any` - Attached metadata -- `respondWith?: Response | "manual"` - Response mode - -**Returns:** `Webhook` - PromiseLike & AsyncIterable with `.url` - -### getWritable(options?) - -Access stream writer (steps only). - -```typescript -import { getWritable } from "workflow"; - -async function streamStep() { - "use step"; - - const writer = getWritable(); - await writer.write({ data: "chunk" }); - writer.releaseLock(); -} -``` - -**Options:** -- `namespace?: string` - Stream namespace - -**Returns:** `WritableStream` - -### getWorkflowMetadata() - -Get workflow run context. - -```typescript -import { getWorkflowMetadata } from "workflow"; - -export async function myWorkflow() { - "use workflow"; - - const { runId, workflowName } = getWorkflowMetadata(); -} -``` - -### getStepMetadata() - -Get step execution context. - -```typescript -import { getStepMetadata } from "workflow"; - -async function myStep() { - "use step"; - - const { stepId, attemptNumber } = getStepMetadata(); - // Use stepId for idempotency keys -} -``` - -### FatalError - -Stop retries permanently. - -```typescript -import { FatalError } from "workflow"; - -throw new FatalError("User not found"); -throw new FatalError("Invalid input", { cause: originalError }); -``` - -### RetryableError - -Trigger retry with delay. - -```typescript -import { RetryableError } from "workflow"; - -throw new RetryableError("Rate limited", { retryAfter: "5m" }); -throw new RetryableError("Retry", { retryAfter: 5000 }); -throw new RetryableError("Retry", { retryAfter: new Date("2025-01-01") }); -``` - ---- - -## workflow/api package - -### start(workflow, args, options?) - -Start workflow run. - -```typescript -import { start } from "workflow/api"; - -const run = await start(myWorkflow, ["arg1", "arg2"]); -console.log(run.runId); - -// With options -const run = await start(myWorkflow, ["arg1"], { - runId: "custom-id", -}); -``` - -**Returns:** `Run` object - -### Run object - -```typescript -interface Run { - runId: string; - - // Get current status - get status(): Promise; - - // Get return value (waits for completion) - get returnValue(): Promise; - - // Cancel the run - cancel(): Promise; - - // Get readable stream - getReadable(options?: { namespace?: string }): ReadableStream; -} -``` - -### getRun(runId) - -Get existing run by ID. - -```typescript -import { getRun } from "workflow/api"; - -const run = getRun("run-id"); -const status = await run.status; -const result = await run.returnValue; -``` - -### resumeHook(token, data) - -Resume workflow via hook. - -```typescript -import { resumeHook } from "workflow/api"; - -const result = await resumeHook(token, { approved: true }); -// result.runId, result.hookId -``` - -### resumeWebhook(token, request) - -Resume workflow via webhook. - -```typescript -import { resumeWebhook } from "workflow/api"; - -await resumeWebhook(token, incomingRequest); -``` - -### getHookByToken(token) - -Get hook metadata. - -```typescript -import { getHookByToken } from "workflow/api"; - -const hook = await getHookByToken(token); -// hook.status: 'pending' | 'received' | 'disposed' -``` - -### getWorld() - -Access low-level infrastructure. - -```typescript -import { getWorld } from "workflow/api"; - -const world = getWorld(); -const runs = await world.runs.list({ pagination: { cursor } }); -``` - ---- - -## Types - -### RunStatus - -```typescript -type RunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'; -``` - -### EventType - -```typescript -type EventType = - | 'step_completed' | 'step_failed' | 'step_retrying' | 'step_started' - | 'hook_created' | 'hook_received' | 'hook_disposed' - | 'wait_created' | 'wait_completed' - | 'workflow_completed' | 'workflow_failed' | 'workflow_started'; -``` - -### Serializable Types - -```typescript -type Serializable = - | string | number | boolean | null | undefined | bigint - | Serializable[] - | { [key: string]: Serializable } - | Date | URL | RegExp | URLSearchParams - | Map | Set - | Response | Request | Headers - | ArrayBuffer | Uint8Array | Int8Array | Float64Array // all typed arrays - | ReadableStream | WritableStream; -``` - ---- - -## Framework Exports - -### workflow/next - -```typescript -import { withWorkflow } from "workflow/next"; - -export default withWorkflow(nextConfig); -``` - -### workflow/vite - -```typescript -import { workflow } from "workflow/vite"; - -export default defineConfig({ - plugins: [workflow()], -}); -``` - -### workflow/astro - -```typescript -import { workflow } from "workflow/astro"; - -export default defineConfig({ - integrations: [workflow()], -}); -``` - -### workflow/nitro - -```typescript -// nitro.config.ts -export default defineNitroConfig({ - modules: ["workflow/nitro"], -}); -``` - -### workflow/nuxt - -```typescript -// nuxt.config.ts -export default defineNuxtConfig({ - modules: ["workflow/nuxt"], -}); -``` - -### workflow/sveltekit - -```typescript -import { workflowPlugin } from "workflow/sveltekit"; - -export default { - plugins: [sveltekit(), workflowPlugin()], -}; -``` - ---- - -## @workflow/ai - -### DurableAgent - -```typescript -import { DurableAgent } from "@workflow/ai/agent"; - -const agent = new DurableAgent({ - model: string, - system?: string, - temperature?: number, - tools?: Record, - toolChoice?: "auto" | "required" | "none" | string, - maxSteps?: number, - onFinish?: (result) => void, - onError?: (error) => void, - onAbort?: () => void, - onStepFinish?: (step) => void, -}); - -await agent.stream({ messages, writable }); -``` - -### WorkflowChatTransport - -```typescript -import { WorkflowChatTransport } from "@workflow/ai"; - -const transport = new WorkflowChatTransport({ - maxConsecutiveErrors?: number, - onChatSendMessage?: (response) => void, - onChatEnd?: ({ chatId, chunkIndex }) => void, -}); -``` - ---- - -## Worlds - -### @workflow/world-local - -Local development with filesystem storage. - -```typescript -// Auto-used in development -// Data in .workflow-data/ or .next/workflow-data/ -``` - -### @workflow/world-vercel - -Production on Vercel with durable queues. - -```typescript -// Auto-used when deployed to Vercel -// No configuration needed -``` - -### @workflow/world-postgres - -Self-hosted PostgreSQL backend. - -```typescript -import { PostgresWorld } from "@workflow/world-postgres"; -``` - -### @workflow/world-testing - -Testing utilities. - -```typescript -import { TestWorld } from "@workflow/world-testing"; -``` diff --git a/skills/workflow/references/errors.md b/skills/workflow/references/common-errors.md similarity index 98% rename from skills/workflow/references/errors.md rename to skills/workflow/references/common-errors.md index d038dbb3f8..7bcf20f485 100644 --- a/skills/workflow/references/errors.md +++ b/skills/workflow/references/common-errors.md @@ -1,4 +1,9 @@ -# Common Errors and Solutions +--- +title: Common Errors +description: Reference for common Workflow DevKit errors and how to resolve them. +--- + +# Common Errors ## Quick Reference diff --git a/skills/workflow/references/frameworks.md b/skills/workflow/references/frameworks.md deleted file mode 100644 index a05b6e4c8a..0000000000 --- a/skills/workflow/references/frameworks.md +++ /dev/null @@ -1,235 +0,0 @@ -# Framework Setup - -## Table of Contents -- [Next.js](#nextjs) -- [Express](#express) -- [Hono](#hono) -- [Vite](#vite) -- [Astro](#astro) -- [Fastify](#fastify) -- [Nitro](#nitro) -- [Nuxt](#nuxt) -- [SvelteKit](#sveltekit) - -## Next.js - -```bash -npm create next-app@latest my-app && cd my-app && npm i workflow -``` - -**next.config.ts:** -```typescript -import type { NextConfig } from "next"; -import { withWorkflow } from "workflow/next"; - -const nextConfig: NextConfig = { - /* config options here */ -}; - -export default withWorkflow(nextConfig); -``` - -**tsconfig.json** (optional IDE support): -```json -{ "compilerOptions": { "plugins": [{ "name": "workflow" }] } } -``` - -**API Route:** -```typescript -// app/api/signup/route.ts -import { start } from "workflow/api"; -import { handleUserSignup } from "@/workflows/user-signup"; - -export async function POST(req: Request) { - const { email } = await req.json(); - await start(handleUserSignup, [email]); - return Response.json({ message: "Workflow started" }); -} -``` - -## Express - -```bash -mkdir my-app && cd my-app && npm init -y -npm i workflow express nitro rollup && npm i -D @types/express -``` - -**nitro.config.ts:** -```typescript -import { defineNitroConfig } from "nitro/config"; - -export default defineNitroConfig({ - modules: ["workflow/nitro"], - vercel: { entryFormat: "node" }, - routes: { "/**": { handler: "./src/index.ts", format: "node" } }, -}); -``` - -**src/index.ts:** -```typescript -import express from "express"; -import { start } from "workflow/api"; - -const app = express(); -app.use(express.json()); - -app.post("/api/signup", async (req, res) => { - await start(handleUserSignup, [req.body.email]); - return res.json({ message: "Workflow started" }); -}); - -export default app; -``` - -## Hono - -```bash -npm create hono@latest my-app -- --template=nodejs -cd my-app && npm i workflow nitro rollup -``` - -**nitro.config.ts:** -```typescript -import { defineConfig } from "nitro"; - -export default defineConfig({ - modules: ["workflow/nitro"], - routes: { "/**": "./src/index.ts" } -}); -``` - -## Vite - -```bash -npm create vite@latest my-app -- --template react-ts -cd my-app && npm i workflow nitro rollup -``` - -**vite.config.ts:** -```typescript -import { defineConfig } from "vite"; -import { nitro } from "nitro/plugin"; -import { workflow } from "workflow/vite"; - -export default defineConfig({ - plugins: [nitro({ serverDir: "." }), workflow()], -}); -``` - -## Astro - -```bash -npm create astro@latest my-app -- --template minimal --install --yes -cd my-app && npm i workflow -``` - -**astro.config.mjs:** -```typescript -import { defineConfig } from "astro/config"; -import { workflow } from "workflow/astro"; - -export default defineConfig({ integrations: [workflow()] }); -``` - -**src/pages/api/signup.ts:** -```typescript -import type { APIRoute } from "astro"; -import { start } from "workflow/api"; - -export const POST: APIRoute = async ({ request }) => { - const { email } = await request.json(); - await start(handleUserSignup, [email]); - return Response.json({ message: "Workflow started" }); -}; -``` - -## Fastify - -```bash -mkdir my-app && cd my-app && npm init -y -npm i workflow fastify nitro rollup && npm i -D @types/node typescript -``` - -**nitro.config.ts:** -```typescript -import { defineNitroConfig } from "nitro/config"; -export default defineNitroConfig({ modules: ["workflow/nitro"] }); -``` - -## Nitro - -```bash -npx create-nitro-app my-app && cd my-app && npm i workflow -``` - -**nitro.config.ts:** -```typescript -export default defineConfig({ - serverDir: "./server", - modules: ["workflow/nitro"], -}); -``` - -## Nuxt - -```bash -npm create nuxt@latest my-app && cd my-app && npm i workflow -``` - -**nuxt.config.ts:** -```typescript -export default defineNuxtConfig({ - modules: ["workflow/nuxt"], - compatibilityDate: "latest", -}); -``` - -## SvelteKit - -```bash -npx sv create my-app --template=minimal --types=ts --no-add-ones -cd my-app && npm i workflow -``` - -**vite.config.ts:** -```typescript -import { sveltekit } from "@sveltejs/kit/vite"; -import { workflowPlugin } from "workflow/sveltekit"; - -export default { plugins: [sveltekit(), workflowPlugin()] }; -``` - -## Run and Inspect - -```bash -# Start your app on the default port (3000) -npm run dev - -# In a separate terminal, open the dashboard -npx workflow web -``` - -**Important:** The dashboard (`npx workflow web`) connects to your app at `http://localhost:3000` by default. If your app runs on a different port, specify it: - -```bash -# If your app is on port 3001 -npx workflow web --app-url http://localhost:3001 -``` - -The dashboard opens at `http://localhost:3456` and shows: -- **Workflows tab:** Lists registered workflow functions -- **Runs tab:** Shows triggered workflow runs with status and events - -**Troubleshooting:** If runs don't appear: -1. Ensure your app is running on the expected port -2. Restart the dashboard after starting your app -3. Check that workflows are being triggered (API returns `runId`) -4. Workflow data is stored in `.next/workflow-data/` (Next.js) or `.workflow-data/` - -## HTTP Endpoints (Auto-generated) - -| Endpoint | Purpose | -|----------|---------| -| `POST /.well-known/workflow/v1/flow` | Execute workflow | -| `POST /.well-known/workflow/v1/step` | Execute step | -| `POST /.well-known/workflow/v1/webhook/:token` | Deliver webhook | diff --git a/skills/workflow/references/patterns.md b/skills/workflow/references/patterns.md deleted file mode 100644 index b59f2cf56d..0000000000 --- a/skills/workflow/references/patterns.md +++ /dev/null @@ -1,480 +0,0 @@ -# Advanced Workflow Patterns - -## Table of Contents -- [Hooks](#hooks) -- [Webhooks](#webhooks) -- [Control Flow](#control-flow) -- [Batching](#batching) -- [Streaming](#streaming) -- [Error Handling](#error-handling) -- [Real-World Examples](#real-world-examples) - -## Hooks - -### Basic Hook - -```typescript -import { createHook } from "workflow"; - -export async function approvalWorkflow() { - "use workflow"; - - const hook = createHook<{ approved: boolean; comment: string }>(); - console.log("Send approval to:", hook.token); - - const result = await hook; // Pauses until resumed - return result.approved ? "Approved" : "Rejected"; -} -``` - -### Custom Deterministic Tokens - -Reconstruct tokens from external data: - -```typescript -const hook = createHook({ - token: `slack:${channelId}:${threadTs}` -}); - -// External handler reconstructs token: -await resumeHook(`slack:${channelId}:${threadTs}`, slackEvent); -``` - -### Multiple Events (AsyncIterable) - -```typescript -export async function dataCollectionWorkflow() { - "use workflow"; - - const hook = createHook<{ value: number; done?: boolean }>(); - const values: number[] = []; - - for await (const payload of hook) { - values.push(payload.value); - if (payload.done) break; - } - - return { total: values.reduce((a, b) => a + b, 0), count: values.length }; -} -``` - -### Type-Safe Hooks with Schema - -```typescript -import { defineHook } from "workflow"; -import { z } from "zod"; - -const approvalHook = defineHook({ - schema: z.object({ - requestId: z.string(), - approved: z.boolean(), - approvedBy: z.string(), - comment: z.string().transform((v) => v.trim()), - }), -}); - -export async function documentApproval(documentId: string) { - "use workflow"; - - const hook = approvalHook.create({ token: `approval:${documentId}` }); - const approval = await hook; - - return approval.approved; -} - -// Resume with validation (throws on invalid data) -await approvalHook.resume(`approval:${documentId}`, approvalData); -``` - -Supported validators: Zod, Valibot, ArkType, Effect Schema - -## Webhooks - -### Basic Webhook - -```typescript -import { createWebhook } from "workflow"; - -export async function paymentWorkflow(orderId: string) { - "use workflow"; - - const webhook = createWebhook(); - await notifyPaymentProvider(orderId, webhook.url); - - const request = await webhook; // Pauses until HTTP POST - const data = await request.json(); - - if (data.status === "completed") { - await fulfillOrder(orderId); - } -} -``` - -### Manual Response Mode - -```typescript -const webhook = createWebhook({ respondWith: "manual" }); -const request = await webhook; - -await processRequest(request); - -// Must respond when using manual mode -await request.respondWith(new Response("OK", { status: 200 })); -``` - -### Automatic Custom Response - -```typescript -const webhook = createWebhook({ - respondWith: new Response("Received", { status: 200 }) -}); -``` - -### Multiple Webhook Events - -```typescript -for await (const request of webhook) { - const data = await request.json(); - await processEvent(data); - if (data.final) break; -} -``` - -### Hooks vs Webhooks - -| Feature | Hooks | Webhooks | -|---------|-------|----------| -| Data format | Any serializable | HTTP Request | -| URL | Manual token | Auto `webhook.url` | -| Response | N/A | Auto or manual | -| Use case | Custom integrations | HTTP callbacks | - -## Control Flow - -### Parallel Steps (Promise.all) - -```typescript -export async function parallelWorkflow(urls: string[]) { - "use workflow"; - - // Execute steps in parallel - const results = await Promise.all( - urls.map(url => fetchAndProcess(url)) - ); - - return results; -} -``` - -### Race Condition (Promise.race) - -First to complete wins - useful for timeouts or competitive execution: - -```typescript -export async function raceWorkflow(taskId: string) { - "use workflow"; - - // Race between task completion and timeout - const result = await Promise.race([ - (async () => { - const data = await processTask(taskId); - return { status: "completed", data }; - })(), - (async () => { - await sleep("30s"); - return { status: "timeout" }; - })(), - ]); - - if (result.status === "timeout") { - await notifyTimeout(taskId); - } - - return result; -} -``` - -### Conditional Branching - -```typescript -export async function conditionalWorkflow(userId: string) { - "use workflow"; - - const user = await getUser(userId); - - if (user.tier === "premium") { - await processPremium(user); - } else { - await processStandard(user); - } -} -``` - -### Polling Pattern - -```typescript -export async function pollUntilReady(jobId: string) { - "use workflow"; - - while (true) { - const status = await checkStatus(jobId); - if (status === "ready") return await getResult(jobId); - if (status === "failed") throw new FatalError("Job failed"); - await sleep("30s"); - } -} -``` - -### Retry with Backoff - -```typescript -export async function retryWorkflow(data: any) { - "use workflow"; - - let attempts = 0; - const maxAttempts = 5; - - while (attempts < maxAttempts) { - try { - return await processData(data); - } catch (error) { - attempts++; - if (attempts >= maxAttempts) throw error; - await sleep(`${Math.pow(2, attempts)}s`); // Exponential backoff - } - } -} -``` - -## Batching - -### Item-Level Steps (Granular Retry) - -Each item in array is a separate step - if one fails, only that item retries: - -```typescript -export async function itemLevelBatch(items: string[]) { - "use workflow"; - - const results = await Promise.all( - items.map(async (item) => { - return await processItem(item); // Each is a separate step - }) - ); - - return results; -} - -async function processItem(item: string) { - "use step"; - // If this fails, only this item retries - return await externalAPI.process(item); -} -``` - -### Batch-Level Steps (Atomic Retry) - -Entire batch is one step - if any item fails, whole batch retries: - -```typescript -export async function batchLevelWorkflow(items: string[]) { - "use workflow"; - - const results = await processBatch(items); - return results; -} - -async function processBatch(items: string[]) { - "use step"; - // If ANY item fails, entire batch retries - return await Promise.all( - items.map(item => externalAPI.process(item)) - ); -} -``` - -### Chunked Batching - -Process large arrays in smaller chunks: - -```typescript -export async function chunkedWorkflow(items: string[]) { - "use workflow"; - - const chunkSize = 10; - const results: any[] = []; - - for (let i = 0; i < items.length; i += chunkSize) { - const chunk = items.slice(i, i + chunkSize); - const chunkResults = await processChunk(chunk); - results.push(...chunkResults); - } - - return results; -} - -async function processChunk(chunk: string[]) { - "use step"; - return await Promise.all(chunk.map(item => process(item))); -} -``` - -## Streaming - -### Namespaced Streams - -```typescript -async function processWithLogs(data: any) { - "use step"; - - const logsWriter = getWritable({ namespace: "logs" }); - const metricsWriter = getWritable({ namespace: "metrics" }); - - await logsWriter.write({ level: "info", msg: "Starting" }); - await metricsWriter.write({ event: "start", timestamp: Date.now() }); - - const result = await process(data); - - await logsWriter.write({ level: "info", msg: "Done" }); - logsWriter.releaseLock(); - metricsWriter.releaseLock(); - - return result; -} -``` - -### Binary Data - -```typescript -async function writeBinary(data: string) { - "use step"; - - const writer = getWritable(); - const encoder = new TextEncoder(); - await writer.write(encoder.encode(data)); - writer.releaseLock(); -} -``` - -## Error Handling - -### Categorizing Errors - -```typescript -async function callExternalAPI(url: string) { - "use step"; - - const res = await fetch(url); - - // Permanent failures - if (res.status === 401 || res.status === 403) { - throw new FatalError(`Auth error: ${res.status}`); - } - - // Retryable with delay - if (res.status === 429) { - const retryAfter = res.headers.get("Retry-After"); - throw new RetryableError("Rate limited", { - retryAfter: retryAfter ? parseInt(retryAfter) * 1000 : "5m" - }); - } - - // Server errors - auto retry - if (res.status >= 500) { - throw new Error(`Server error: ${res.status}`); - } - - return res; -} -``` - -### Workflow-Level Error Handling - -```typescript -export async function resilientWorkflow(input: any) { - "use workflow"; - - try { - const result = await riskyOperation(input); - return { success: true, result }; - } catch (error) { - await notifyAdmin(error); - await cleanup(input); - return { success: false, error: error.message }; - } -} -``` - -## Real-World Examples - -### Onboarding Drip Campaign - -```typescript -export async function userOnboarding(email: string) { - "use workflow"; - - await sendWelcomeEmail(email); - await sleep("3 days"); - await sendTipsEmail(email); - await sleep("7 days"); - await sendFeedbackRequest(email); -} -``` - -### Churn Prevention - -```typescript -export async function churnPrevention(userId: string) { - "use workflow"; - - await sleep("7 days"); - if (!await isActive(userId)) { - await sendReEngagementEmail(userId); - await sleep("3 days"); - if (!await isActive(userId)) { - await offerDiscount(userId); - } - } -} -``` - -### Order Processing - -```typescript -export async function processOrder(orderId: string) { - "use workflow"; - - const payment = await chargeCard(orderId); - - // Wait for warehouse webhook - const webhook = createWebhook(); - await notifyWarehouse(orderId, webhook.url); - const shipment = await webhook; - - await notifyCustomer(orderId, await shipment.json()); - return { orderId, status: "shipped" }; -} -``` - -### Multi-Step Approval - -```typescript -export async function multiApproval(requestId: string) { - "use workflow"; - - const managers = await getApprovers(requestId); - - for (const manager of managers) { - const hook = createHook<{ approved: boolean }>(); - await notifyManager(manager, requestId, hook.token); - - const decision = await hook; - if (!decision.approved) { - return { status: "rejected", rejectedBy: manager }; - } - } - - await executeRequest(requestId); - return { status: "approved" }; -} -``` From db6c97ba03fab25762624150b2fd03e628e539e5 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Mon, 26 Jan 2026 19:05:41 -0700 Subject: [PATCH 07/12] Add AI SDK step function guidance to SKILL.md Co-Authored-By: Claude Opus 4.5 Signed-off-by: John Lindquist --- skills/workflow/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/skills/workflow/SKILL.md b/skills/workflow/SKILL.md index 889255fee9..27f2621a7a 100644 --- a/skills/workflow/SKILL.md +++ b/skills/workflow/SKILL.md @@ -72,5 +72,6 @@ import { workflow } from "workflow/astro"; 2. Use `sleep()` instead of setTimeout/setInterval 3. Move Node.js modules (fs, crypto, etc.) to step functions 4. Use `FatalError` for permanent failures, `RetryableError` for retries +5. Use `use step` with AI SDK methods to avoid issues with OIDC For common errors and troubleshooting, see [Common Errors Reference](references/common-errors.md). From 3230d705a78884cd147147821aebfe2036c22e88 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 27 Jan 2026 09:28:49 -0700 Subject: [PATCH 08/12] Update SKILL.md formatting and add ecosystem section Co-Authored-By: Claude Opus 4.5 Signed-off-by: John Lindquist --- skills/workflow/SKILL.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/skills/workflow/SKILL.md b/skills/workflow/SKILL.md index 27f2621a7a..fc0545ac16 100644 --- a/skills/workflow/SKILL.md +++ b/skills/workflow/SKILL.md @@ -66,7 +66,7 @@ import { workflow } from "workflow/astro"; // Or use modules: ["workflow/nitro"] for Nitro/Nuxt ``` -**Critical workflow rules:** +## Critical workflow rules 1. Always set `globalThis.fetch = fetch` when using AI SDK or HTTP libraries 2. Use `sleep()` instead of setTimeout/setInterval @@ -74,4 +74,10 @@ import { workflow } from "workflow/astro"; 4. Use `FatalError` for permanent failures, `RetryableError` for retries 5. Use `use step` with AI SDK methods to avoid issues with OIDC -For common errors and troubleshooting, see [Common Errors Reference](references/common-errors.md). +## Ecosystem + +Workflow tooling is optimized to be used with the AI SDK, Vercel's AI Gateway, and Vercel OIDC + +## Errors and Troubleshooting + +Common errors and troubleshooting are documented here: [Common Errors Reference](references/common-errors.md). From 75b0e0582c392550c7db5bd76f6c887076560e7c Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 27 Jan 2026 09:51:50 -0700 Subject: [PATCH 09/12] 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 --- package.json | 1 + packages/ai/package.json | 2 +- packages/core/package.json | 2 +- packages/next/package.json | 2 +- packages/workflow/package.json | 2 +- pnpm-lock.yaml | 369 ++++++++++++++++++++++++++++++--- 6 files changed, 347 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index f482372c66..82c5b13814 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@changesets/changelog-github": "0.5.2", "@changesets/cli": "^2.29.5", "@vitest/coverage-v8": "catalog:", + "del-cli": "^5.1.0", "esbuild": "catalog:", "husky": "^9.1.7", "lint-staged": "^16.1.0", diff --git a/packages/ai/package.json b/packages/ai/package.json index d28121e38d..324023b0db 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -60,7 +60,7 @@ "clean": "tsc --build --clean && rm -rf docs ||:", "test": "vitest run", "prepack": "mkdir -p docs && cp -r ../../docs/content/docs/ai ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow-ai ./docs/", - "postpack": "rm -rf docs" + "postpack": "del-cli docs" }, "devDependencies": { "@workflow/tsconfig": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index cb6f38dc0e..4f8164fdf6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -46,7 +46,7 @@ "test:e2e": "vitest run e2e", "typecheck": "tsc --noEmit", "prepack": "mkdir -p docs && cp -r ../../docs/content/docs/foundations ./docs/ && cp -r ../../docs/content/docs/how-it-works ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow ./docs/api-reference", - "postpack": "rm -rf docs" + "postpack": "del-cli docs" }, "dependencies": { "@aws-sdk/credential-provider-web-identity": "3.609.0", diff --git a/packages/next/package.json b/packages/next/package.json index 68529cca84..74bec8811a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -30,7 +30,7 @@ "dev": "tsc --watch", "clean": "tsc --build --clean && rm -rf dist docs", "prepack": "mkdir -p docs && cp ../../docs/content/docs/getting-started/next.mdx ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow-next ./docs/api-reference", - "postpack": "rm -rf docs" + "postpack": "del-cli docs" }, "dependencies": { "@swc/core": "catalog:", diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 35281f995d..b99da0be3f 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -57,7 +57,7 @@ "test": "vitest run src", "typecheck": "tsc --noEmit", "prepack": "cp -r ../../docs/content/docs ./docs", - "postpack": "rm -rf docs" + "postpack": "del-cli docs" }, "dependencies": { "@workflow/astro": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9eba6bd98c..cb449e31db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@vitest/coverage-v8': specifier: 'catalog:' version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + del-cli: + specifier: ^5.1.0 + version: 5.1.0 esbuild: specifier: 'catalog:' version: 0.25.12 @@ -755,7 +758,7 @@ importers: version: link:../tsconfig nuxt: specifier: 4.0.0 - version: 4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) + version: 4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) packages/rollup: dependencies: @@ -1774,7 +1777,7 @@ importers: version: 4.2.0 nuxt: specifier: ^4.1.3 - version: 4.1.3(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) + version: 4.1.3(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) openai: specifier: ^6.6.0 version: 6.6.0(ws@8.18.3)(zod@4.1.11) @@ -6560,6 +6563,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -7023,6 +7029,10 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + aggregate-error@4.0.1: + resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} + engines: {node: '>=12'} + ai@5.0.104: resolution: {integrity: sha512-MZOkL9++nY5PfkpWKBR3Rv+Oygxpb9S16ctv8h91GvrSif7UnNEdPMVZe3bUyMd2djxf0AtBk/csBixP0WwWZQ==} engines: {node: '>=18'} @@ -7130,6 +7140,10 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -7397,6 +7411,14 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase-keys@7.0.2: + resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} + engines: {node: '>=12'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + camelcase@8.0.0: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} @@ -7499,6 +7521,10 @@ packages: resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==} engines: {node: '>=10'} + clean-stack@4.2.0: + resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} + engines: {node: '>=12'} + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -8029,6 +8055,18 @@ packages: decache@4.6.2: resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decamelize@5.0.1: + resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} + engines: {node: '>=10'} + decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} @@ -8076,6 +8114,15 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + del-cli@5.1.0: + resolution: {integrity: sha512-xwMeh2acluWeccsfzE7VLsG3yTr7nWikbfw+xhMnpRrF15pGSkw+3/vJZWlGoE4I86UiLRNHicmKt4tkIX9Jtg==} + engines: {node: '>=14.16'} + hasBin: true + + del@7.1.0: + resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} + engines: {node: '>=14.16'} + delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} @@ -8842,6 +8889,9 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -9073,6 +9123,10 @@ packages: engines: {node: 20 || >=22} hasBin: true + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} @@ -9089,6 +9143,10 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + globby@14.1.0: resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} engines: {node: '>=18'} @@ -9132,6 +9190,10 @@ packages: hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -9213,6 +9275,10 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -9325,10 +9391,18 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + index-to-position@1.2.0: resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} engines: {node: '>=18'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -9454,10 +9528,18 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-path-cwd@3.0.0: + resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-path-inside@4.0.0: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -9974,6 +10056,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lucide-react@0.469.0: resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} peerDependencies: @@ -10021,6 +10107,14 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -10110,6 +10204,14 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + meow@10.1.5: + resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -10321,6 +10423,10 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -10602,6 +10708,10 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + normalize-package-data@6.0.2: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} @@ -10902,6 +11012,10 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} + p-map@5.5.0: + resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} + engines: {node: '>=12'} + p-map@7.0.4: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} @@ -10991,6 +11105,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -11447,6 +11565,10 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + quote-unquote@1.0.0: resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==} @@ -11604,6 +11726,14 @@ packages: resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} engines: {node: '>=18'} + read-pkg-up@8.0.0: + resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} + engines: {node: '>=12'} + + read-pkg@6.0.0: + resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} + engines: {node: '>=12'} + read-pkg@9.0.1: resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} engines: {node: '>=18'} @@ -11658,6 +11788,10 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redent@4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -11827,6 +11961,11 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -12035,6 +12174,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -12214,6 +12357,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -12457,6 +12604,10 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + trim-newlines@4.1.1: + resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} + engines: {node: '>=12'} + triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} @@ -12556,6 +12707,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -13446,6 +13601,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -13460,6 +13618,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -15712,7 +15874,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/vite-builder@4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1)': + '@nuxt/vite-builder@4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1)': dependencies: '@nuxt/kit': 4.0.0(magicast@0.3.5) '@rollup/plugin-replace': 6.0.2(rollup@4.53.2) @@ -15742,7 +15904,7 @@ snapshots: unenv: 2.0.0-rc.24 vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - vite-plugin-checker: 0.10.3(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + vite-plugin-checker: 0.10.3(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) vue: 3.5.22(typescript@5.9.3) vue-bundle-renderer: 2.2.0 transitivePeerDependencies: @@ -15797,7 +15959,7 @@ snapshots: - xml2js - yaml - '@nuxt/vite-builder@4.1.3(@biomejs/biome@2.3.3)(@types/node@22.19.0)(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1)': + '@nuxt/vite-builder@4.1.3(@biomejs/biome@2.3.3)(@types/node@22.19.0)(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1)': dependencies: '@nuxt/kit': 4.1.3(magicast@0.3.5) '@rollup/plugin-replace': 6.0.2(rollup@4.53.2) @@ -15826,7 +15988,7 @@ snapshots: unenv: 2.0.0-rc.24 vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - vite-plugin-checker: 0.11.0(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + vite-plugin-checker: 0.11.0(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) vue: 3.5.22(typescript@5.9.3) vue-bundle-renderer: 2.2.0 transitivePeerDependencies: @@ -19245,6 +19407,8 @@ snapshots: '@types/mime@1.3.5': {} + '@types/minimist@1.2.5': {} + '@types/ms@0.7.34': {} '@types/ms@2.1.0': {} @@ -19602,14 +19766,6 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - '@vitest/mocker@3.2.4(vite@7.1.12(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 @@ -19869,6 +20025,11 @@ snapshots: agent-base@7.1.4: {} + aggregate-error@4.0.1: + dependencies: + clean-stack: 4.2.0 + indent-string: 5.0.0 + ai@5.0.104(zod@4.1.11): dependencies: '@ai-sdk/gateway': 2.0.17(zod@4.1.11) @@ -19994,6 +20155,8 @@ snapshots: array-union@2.1.0: {} + arrify@1.0.1: {} + asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -20477,6 +20640,15 @@ snapshots: callsites@3.1.0: {} + camelcase-keys@7.0.2: + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 + + camelcase@6.3.0: {} + camelcase@8.0.0: {} caniuse-api@3.0.0: @@ -20583,6 +20755,10 @@ snapshots: dependencies: escape-string-regexp: 4.0.0 + clean-stack@4.2.0: + dependencies: + escape-string-regexp: 5.0.0 + cli-boxes@3.0.0: {} cli-cursor@5.0.0: @@ -21109,6 +21285,15 @@ snapshots: dependencies: callsite: 1.0.0 + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + decamelize@5.0.1: {} + decimal.js-light@2.5.1: {} decode-named-character-reference@1.2.0: @@ -21148,6 +21333,22 @@ snapshots: defu@6.1.4: {} + del-cli@5.1.0: + dependencies: + del: 7.1.0 + meow: 10.1.5 + + del@7.1.0: + dependencies: + globby: 13.2.2 + graceful-fs: 4.2.11 + is-glob: 4.0.3 + is-path-cwd: 3.0.0 + is-path-inside: 4.0.0 + p-map: 5.5.0 + rimraf: 3.0.2 + slash: 4.0.0 + delaunator@5.0.1: dependencies: robust-predicates: 3.0.2 @@ -21920,7 +22121,6 @@ snapshots: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - optional: true find-up@7.0.0: dependencies: @@ -22008,6 +22208,8 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true @@ -22267,6 +22469,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.1 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -22285,6 +22496,14 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + globby@13.2.2: + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 4.0.0 + globby@14.1.0: dependencies: '@sindresorhus/merge-streams': 2.3.0 @@ -22343,6 +22562,8 @@ snapshots: hachure-fill@0.5.2: {} + hard-rejection@2.1.0: {} + has-flag@4.0.0: {} has-flag@5.0.1: {} @@ -22518,6 +22739,10 @@ snapshots: hookable@5.5.3: {} + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -22615,8 +22840,15 @@ snapshots: indent-string@4.0.0: {} + indent-string@5.0.0: {} + index-to-position@1.2.0: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} ini@1.3.8: @@ -22715,8 +22947,12 @@ snapshots: is-number@7.0.0: {} + is-path-cwd@3.0.0: {} + is-path-inside@4.0.0: {} + is-plain-obj@1.1.0: {} + is-plain-obj@2.1.0: {} is-plain-obj@4.1.0: {} @@ -23113,7 +23349,6 @@ snapshots: locate-path@6.0.0: dependencies: p-locate: 5.0.0 - optional: true locate-path@7.2.0: dependencies: @@ -23181,6 +23416,10 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lucide-react@0.469.0(react@19.1.0): dependencies: react: 19.1.0 @@ -23241,6 +23480,10 @@ snapshots: dependencies: semver: 7.7.3 + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -23450,6 +23693,24 @@ snapshots: media-typer@1.1.0: {} + meow@10.1.5: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.1.1 + type-fest: 1.4.0 + yargs-parser: 20.2.9 + + meow@13.2.0: + optional: true + merge-descriptors@2.0.0: {} merge-options@3.0.4: @@ -23835,6 +24096,12 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + minimist@1.2.8: {} minipass@7.1.2: {} @@ -24417,6 +24684,13 @@ snapshots: dependencies: abbrev: 3.0.1 + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.16.1 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 @@ -24454,7 +24728,7 @@ snapshots: next: 16.0.10(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-router: 7.10.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - nuxt@4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1): + nuxt@4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1): dependencies: '@nuxt/cli': 3.29.3(magicast@0.3.5) '@nuxt/devalue': 2.0.2 @@ -24462,7 +24736,7 @@ snapshots: '@nuxt/kit': 4.0.0(magicast@0.3.5) '@nuxt/schema': 4.0.0 '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1) + '@nuxt/vite-builder': 4.0.0(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1) '@unhead/vue': 2.0.19(vue@3.5.22(typescript@5.9.3)) '@vue/shared': 3.5.22 c12: 3.3.1(magicast@0.3.5) @@ -24580,7 +24854,7 @@ snapshots: - xml2js - yaml - nuxt@4.1.3(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1): + nuxt@4.1.3(@biomejs/biome@2.3.3)(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/blob@2.0.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1): dependencies: '@nuxt/cli': 3.29.3(magicast@0.3.5) '@nuxt/devalue': 2.0.2 @@ -24588,7 +24862,7 @@ snapshots: '@nuxt/kit': 4.1.3(magicast@0.3.5) '@nuxt/schema': 4.1.3 '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 4.1.3(@biomejs/biome@2.3.3)(@types/node@22.19.0)(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1) + '@nuxt/vite-builder': 4.1.3(@biomejs/biome@2.3.3)(@types/node@22.19.0)(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.53.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1) '@unhead/vue': 2.0.19(vue@3.5.22(typescript@5.9.3)) '@vue/shared': 3.5.22 c12: 3.3.1(magicast@0.3.5) @@ -25017,7 +25291,6 @@ snapshots: p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - optional: true p-limit@4.0.0: dependencies: @@ -25034,7 +25307,6 @@ snapshots: p-locate@5.0.0: dependencies: p-limit: 3.1.0 - optional: true p-locate@6.0.0: dependencies: @@ -25042,6 +25314,10 @@ snapshots: p-map@2.1.0: {} + p-map@5.5.0: + dependencies: + aggregate-error: 4.0.1 + p-map@7.0.4: {} p-queue@8.1.1: @@ -25134,6 +25410,8 @@ snapshots: path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -25592,6 +25870,8 @@ snapshots: quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} + quote-unquote@1.0.0: {} radix-ui@1.4.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): @@ -25844,6 +26124,19 @@ snapshots: read-pkg: 9.0.1 type-fest: 4.41.0 + read-pkg-up@8.0.0: + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 + + read-pkg@6.0.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + read-pkg@9.0.1: dependencies: '@types/normalize-package-data': 2.4.4 @@ -25937,6 +26230,11 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + redent@4.0.0: + dependencies: + indent-string: 5.0.0 + strip-indent: 4.1.1 + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -26171,6 +26469,10 @@ snapshots: rfdc@1.4.1: {} + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + robust-predicates@3.0.2: {} rollup-plugin-dts@6.2.3(rollup@4.53.2)(typescript@5.9.3): @@ -26457,6 +26759,8 @@ snapshots: slash@3.0.0: {} + slash@4.0.0: {} + slash@5.1.0: {} slice-ansi@5.0.0: @@ -26684,6 +26988,8 @@ snapshots: strip-final-newline@4.0.0: {} + strip-indent@4.1.1: {} + strip-json-comments@2.0.1: optional: true @@ -26981,6 +27287,8 @@ snapshots: trim-lines@3.0.1: {} + trim-newlines@4.1.1: {} + triple-beam@1.4.1: {} trough@2.2.0: {} @@ -27062,6 +27370,8 @@ snapshots: type-fest@0.21.3: {} + type-fest@1.4.0: {} + type-fest@4.41.0: {} type-fest@5.1.0: @@ -27576,7 +27886,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.10.3(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): + vite-plugin-checker@0.10.3(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): dependencies: '@babel/code-frame': 7.27.1 chokidar: 4.0.3 @@ -27591,10 +27901,11 @@ snapshots: optionalDependencies: '@biomejs/biome': 2.3.3 eslint: 9.38.0(jiti@2.6.1) + meow: 13.2.0 optionator: 0.9.4 typescript: 5.9.3 - vite-plugin-checker@0.11.0(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): + vite-plugin-checker@0.11.0(@biomejs/biome@2.3.3)(eslint@9.38.0(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): dependencies: '@babel/code-frame': 7.27.1 chokidar: 4.0.3 @@ -27608,6 +27919,7 @@ snapshots: optionalDependencies: '@biomejs/biome': 2.3.3 eslint: 9.38.0(jiti@2.6.1) + meow: 13.2.0 optionator: 0.9.4 typescript: 5.9.3 @@ -27732,7 +28044,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -27987,12 +28299,16 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yallist@5.0.0: {} yaml@2.8.0: {} yaml@2.8.1: {} + yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -28010,8 +28326,7 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - yocto-queue@0.1.0: - optional: true + yocto-queue@0.1.0: {} yocto-queue@1.2.1: {} From 965fec407d0b32e301820ad54ed6d7418d43f68f Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 27 Jan 2026 10:06:03 -0700 Subject: [PATCH 10/12] 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 --- skills/workflow/references/common-errors.md | 28 +++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/skills/workflow/references/common-errors.md b/skills/workflow/references/common-errors.md index 7bcf20f485..8b29c37e07 100644 --- a/skills/workflow/references/common-errors.md +++ b/skills/workflow/references/common-errors.md @@ -281,7 +281,20 @@ npx workflow web --app-url http://localhost:3001 **Verify connection:** ```bash -# Check your app responds to workflow endpoints +# Check Local World backend health (workflow and step endpoints) +npx workflow health + +# Check specific endpoint +npx workflow health --endpoint workflow +npx workflow health --endpoint step + +# Specify port if not using default 3000 +npx workflow health --port 3001 + +# Check Vercel backend health +npx workflow health --backend vercel --project my-project --team my-team + +# Or manually check your app responds to workflow endpoints curl -s http://localhost:3000/.well-known/workflow/v1/flow \ -X POST -H "Content-Type: application/json" # Should return: {"error": "Missing required headers"} (not 404) @@ -293,9 +306,10 @@ curl -s http://localhost:3000/.well-known/workflow/v1/flow \ ## Debugging Tips -1. **Use npx workflow web** - Visual dashboard for runs (ensure correct port) -2. **Check run status** - `getRun(runId).status` -3. **Review event log** - Shows step executions, hooks, sleeps -4. **Test locally first** - Local World for development -5. **Add logging in steps** - Steps have full console access -6. **Verify port** - Ensure dashboard connects to correct app port +1. **Run `npx workflow health`** - Verifies workflow/step endpoints are reachable (use `--port` for non-default ports, `--backend vercel` for production) +2. **Use `npx workflow web`** - Visual dashboard for runs (ensure correct port) +3. **Check run status** - `getRun(runId).status` +4. **Review event log** - Shows step executions, hooks, sleeps +5. **Test locally first** - Local World for development +6. **Add logging in steps** - Steps have full console access +7. **Verify port** - Ensure dashboard connects to correct app port From 6c16cf2debf8e0e3ec8f6a6423b4062391b8fce9 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 27 Jan 2026 12:10:27 -0700 Subject: [PATCH 11/12] 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 --- package.json | 1 - packages/ai/package.json | 2 +- packages/core/package.json | 2 +- packages/next/package.json | 2 +- packages/workflow/package.json | 2 +- pnpm-lock.yaml | 326 +-------------------------------- skills/workflow/SKILL.md | 82 +++++++-- 7 files changed, 81 insertions(+), 336 deletions(-) diff --git a/package.json b/package.json index 82c5b13814..f482372c66 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "@changesets/changelog-github": "0.5.2", "@changesets/cli": "^2.29.5", "@vitest/coverage-v8": "catalog:", - "del-cli": "^5.1.0", "esbuild": "catalog:", "husky": "^9.1.7", "lint-staged": "^16.1.0", diff --git a/packages/ai/package.json b/packages/ai/package.json index 324023b0db..d28121e38d 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -60,7 +60,7 @@ "clean": "tsc --build --clean && rm -rf docs ||:", "test": "vitest run", "prepack": "mkdir -p docs && cp -r ../../docs/content/docs/ai ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow-ai ./docs/", - "postpack": "del-cli docs" + "postpack": "rm -rf docs" }, "devDependencies": { "@workflow/tsconfig": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index 4f8164fdf6..cb6f38dc0e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -46,7 +46,7 @@ "test:e2e": "vitest run e2e", "typecheck": "tsc --noEmit", "prepack": "mkdir -p docs && cp -r ../../docs/content/docs/foundations ./docs/ && cp -r ../../docs/content/docs/how-it-works ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow ./docs/api-reference", - "postpack": "del-cli docs" + "postpack": "rm -rf docs" }, "dependencies": { "@aws-sdk/credential-provider-web-identity": "3.609.0", diff --git a/packages/next/package.json b/packages/next/package.json index 74bec8811a..68529cca84 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -30,7 +30,7 @@ "dev": "tsc --watch", "clean": "tsc --build --clean && rm -rf dist docs", "prepack": "mkdir -p docs && cp ../../docs/content/docs/getting-started/next.mdx ./docs/ && cp -r ../../docs/content/docs/api-reference/workflow-next ./docs/api-reference", - "postpack": "del-cli docs" + "postpack": "rm -rf docs" }, "dependencies": { "@swc/core": "catalog:", diff --git a/packages/workflow/package.json b/packages/workflow/package.json index b99da0be3f..35281f995d 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -57,7 +57,7 @@ "test": "vitest run src", "typecheck": "tsc --noEmit", "prepack": "cp -r ../../docs/content/docs ./docs", - "postpack": "del-cli docs" + "postpack": "rm -rf docs" }, "dependencies": { "@workflow/astro": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb449e31db..8f378ca283 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,9 +66,6 @@ importers: '@vitest/coverage-v8': specifier: 'catalog:' version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) - del-cli: - specifier: ^5.1.0 - version: 5.1.0 esbuild: specifier: 'catalog:' version: 0.25.12 @@ -6563,9 +6560,6 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -7029,10 +7023,6 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - aggregate-error@4.0.1: - resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} - engines: {node: '>=12'} - ai@5.0.104: resolution: {integrity: sha512-MZOkL9++nY5PfkpWKBR3Rv+Oygxpb9S16ctv8h91GvrSif7UnNEdPMVZe3bUyMd2djxf0AtBk/csBixP0WwWZQ==} engines: {node: '>=18'} @@ -7140,10 +7130,6 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -7411,14 +7397,6 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase-keys@7.0.2: - resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} - engines: {node: '>=12'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - camelcase@8.0.0: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} @@ -7521,10 +7499,6 @@ packages: resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==} engines: {node: '>=10'} - clean-stack@4.2.0: - resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} - engines: {node: '>=12'} - cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -8055,18 +8029,6 @@ packages: decache@4.6.2: resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - decamelize@5.0.1: - resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} - engines: {node: '>=10'} - decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} @@ -8114,15 +8076,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - del-cli@5.1.0: - resolution: {integrity: sha512-xwMeh2acluWeccsfzE7VLsG3yTr7nWikbfw+xhMnpRrF15pGSkw+3/vJZWlGoE4I86UiLRNHicmKt4tkIX9Jtg==} - engines: {node: '>=14.16'} - hasBin: true - - del@7.1.0: - resolution: {integrity: sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==} - engines: {node: '>=14.16'} - delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} @@ -8889,9 +8842,6 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -9123,10 +9073,6 @@ packages: engines: {node: 20 || >=22} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} @@ -9143,10 +9089,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - globby@14.1.0: resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} engines: {node: '>=18'} @@ -9190,10 +9132,6 @@ packages: hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -9275,10 +9213,6 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -9391,18 +9325,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - index-to-position@1.2.0: resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} engines: {node: '>=18'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -9528,18 +9454,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-cwd@3.0.0: - resolution: {integrity: sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-path-inside@4.0.0: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -10056,10 +9974,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - lucide-react@0.469.0: resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} peerDependencies: @@ -10107,14 +10021,6 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -10204,10 +10110,6 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - meow@10.1.5: - resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -10423,10 +10325,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -10708,10 +10606,6 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - normalize-package-data@6.0.2: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} @@ -11012,10 +10906,6 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} - p-map@5.5.0: - resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} - engines: {node: '>=12'} - p-map@7.0.4: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} @@ -11105,10 +10995,6 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -11565,10 +11451,6 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - quote-unquote@1.0.0: resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==} @@ -11726,14 +11608,6 @@ packages: resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} engines: {node: '>=18'} - read-pkg-up@8.0.0: - resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} - engines: {node: '>=12'} - - read-pkg@6.0.0: - resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} - engines: {node: '>=12'} - read-pkg@9.0.1: resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} engines: {node: '>=18'} @@ -11788,10 +11662,6 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} - redent@4.0.0: - resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} - engines: {node: '>=12'} - redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -11961,11 +11831,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -12174,10 +12039,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} - slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -12357,10 +12218,6 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-indent@4.1.1: - resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} - engines: {node: '>=12'} - strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -12604,10 +12461,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - trim-newlines@4.1.1: - resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} - engines: {node: '>=12'} - triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} @@ -12707,10 +12560,6 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - type-fest@1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} - type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -13601,9 +13450,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -13618,10 +13464,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -19407,8 +19249,6 @@ snapshots: '@types/mime@1.3.5': {} - '@types/minimist@1.2.5': {} - '@types/ms@0.7.34': {} '@types/ms@2.1.0': {} @@ -20025,11 +19865,6 @@ snapshots: agent-base@7.1.4: {} - aggregate-error@4.0.1: - dependencies: - clean-stack: 4.2.0 - indent-string: 5.0.0 - ai@5.0.104(zod@4.1.11): dependencies: '@ai-sdk/gateway': 2.0.17(zod@4.1.11) @@ -20155,8 +19990,6 @@ snapshots: array-union@2.1.0: {} - arrify@1.0.1: {} - asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -20640,15 +20473,6 @@ snapshots: callsites@3.1.0: {} - camelcase-keys@7.0.2: - dependencies: - camelcase: 6.3.0 - map-obj: 4.3.0 - quick-lru: 5.1.1 - type-fest: 1.4.0 - - camelcase@6.3.0: {} - camelcase@8.0.0: {} caniuse-api@3.0.0: @@ -20755,10 +20579,6 @@ snapshots: dependencies: escape-string-regexp: 4.0.0 - clean-stack@4.2.0: - dependencies: - escape-string-regexp: 5.0.0 - cli-boxes@3.0.0: {} cli-cursor@5.0.0: @@ -21285,15 +21105,6 @@ snapshots: dependencies: callsite: 1.0.0 - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - - decamelize@5.0.1: {} - decimal.js-light@2.5.1: {} decode-named-character-reference@1.2.0: @@ -21333,22 +21144,6 @@ snapshots: defu@6.1.4: {} - del-cli@5.1.0: - dependencies: - del: 7.1.0 - meow: 10.1.5 - - del@7.1.0: - dependencies: - globby: 13.2.2 - graceful-fs: 4.2.11 - is-glob: 4.0.3 - is-path-cwd: 3.0.0 - is-path-inside: 4.0.0 - p-map: 5.5.0 - rimraf: 3.0.2 - slash: 4.0.0 - delaunator@5.0.1: dependencies: robust-predicates: 3.0.2 @@ -22121,6 +21916,7 @@ snapshots: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 + optional: true find-up@7.0.0: dependencies: @@ -22208,8 +22004,6 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -22469,15 +22263,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.1 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -22496,14 +22281,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@13.2.2: - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 4.0.0 - globby@14.1.0: dependencies: '@sindresorhus/merge-streams': 2.3.0 @@ -22562,8 +22339,6 @@ snapshots: hachure-fill@0.5.2: {} - hard-rejection@2.1.0: {} - has-flag@4.0.0: {} has-flag@5.0.1: {} @@ -22739,10 +22514,6 @@ snapshots: hookable@5.5.3: {} - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -22840,15 +22611,8 @@ snapshots: indent-string@4.0.0: {} - indent-string@5.0.0: {} - index-to-position@1.2.0: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - inherits@2.0.4: {} ini@1.3.8: @@ -22947,12 +22711,8 @@ snapshots: is-number@7.0.0: {} - is-path-cwd@3.0.0: {} - is-path-inside@4.0.0: {} - is-plain-obj@1.1.0: {} - is-plain-obj@2.1.0: {} is-plain-obj@4.1.0: {} @@ -23349,6 +23109,7 @@ snapshots: locate-path@6.0.0: dependencies: p-locate: 5.0.0 + optional: true locate-path@7.2.0: dependencies: @@ -23416,10 +23177,6 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - lucide-react@0.469.0(react@19.1.0): dependencies: react: 19.1.0 @@ -23480,10 +23237,6 @@ snapshots: dependencies: semver: 7.7.3 - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -23693,21 +23446,6 @@ snapshots: media-typer@1.1.0: {} - meow@10.1.5: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 7.0.2 - decamelize: 5.0.1 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 8.0.0 - redent: 4.0.0 - trim-newlines: 4.1.1 - type-fest: 1.4.0 - yargs-parser: 20.2.9 - meow@13.2.0: optional: true @@ -24096,12 +23834,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - minimist@1.2.8: {} minipass@7.1.2: {} @@ -24684,13 +24416,6 @@ snapshots: dependencies: abbrev: 3.0.1 - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.16.1 - semver: 7.7.3 - validate-npm-package-license: 3.0.4 - normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 @@ -25291,6 +25016,7 @@ snapshots: p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + optional: true p-limit@4.0.0: dependencies: @@ -25307,6 +25033,7 @@ snapshots: p-locate@5.0.0: dependencies: p-limit: 3.1.0 + optional: true p-locate@6.0.0: dependencies: @@ -25314,10 +25041,6 @@ snapshots: p-map@2.1.0: {} - p-map@5.5.0: - dependencies: - aggregate-error: 4.0.1 - p-map@7.0.4: {} p-queue@8.1.1: @@ -25410,8 +25133,6 @@ snapshots: path-exists@5.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -25870,8 +25591,6 @@ snapshots: quick-format-unescaped@4.0.4: {} - quick-lru@5.1.1: {} - quote-unquote@1.0.0: {} radix-ui@1.4.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): @@ -26124,19 +25843,6 @@ snapshots: read-pkg: 9.0.1 type-fest: 4.41.0 - read-pkg-up@8.0.0: - dependencies: - find-up: 5.0.0 - read-pkg: 6.0.0 - type-fest: 1.4.0 - - read-pkg@6.0.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 3.0.3 - parse-json: 5.2.0 - type-fest: 1.4.0 - read-pkg@9.0.1: dependencies: '@types/normalize-package-data': 2.4.4 @@ -26230,11 +25936,6 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 - redent@4.0.0: - dependencies: - indent-string: 5.0.0 - strip-indent: 4.1.1 - redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -26469,10 +26170,6 @@ snapshots: rfdc@1.4.1: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - robust-predicates@3.0.2: {} rollup-plugin-dts@6.2.3(rollup@4.53.2)(typescript@5.9.3): @@ -26759,8 +26456,6 @@ snapshots: slash@3.0.0: {} - slash@4.0.0: {} - slash@5.1.0: {} slice-ansi@5.0.0: @@ -26988,8 +26683,6 @@ snapshots: strip-final-newline@4.0.0: {} - strip-indent@4.1.1: {} - strip-json-comments@2.0.1: optional: true @@ -27287,8 +26980,6 @@ snapshots: trim-lines@3.0.1: {} - trim-newlines@4.1.1: {} - triple-beam@1.4.1: {} trough@2.2.0: {} @@ -27370,8 +27061,6 @@ snapshots: type-fest@0.21.3: {} - type-fest@1.4.0: {} - type-fest@4.41.0: {} type-fest@5.1.0: @@ -28299,16 +27988,12 @@ snapshots: yallist@3.1.1: {} - yallist@4.0.0: {} - yallist@5.0.0: {} yaml@2.8.0: {} yaml@2.8.1: {} - yargs-parser@20.2.9: {} - yargs-parser@21.1.1: {} yargs@17.7.2: @@ -28326,7 +28011,8 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - yocto-queue@0.1.0: {} + yocto-queue@0.1.0: + optional: true yocto-queue@1.2.1: {} diff --git a/skills/workflow/SKILL.md b/skills/workflow/SKILL.md index fc0545ac16..32cd13ea3a 100644 --- a/skills/workflow/SKILL.md +++ b/skills/workflow/SKILL.md @@ -6,11 +6,12 @@ metadata: version: '1.0' --- -## Workflow DevKit Documentation +## *CRITICAL*: Always Use Correct `workflow` Documentation -When you need up-to-date information about the Workflow DevKit: +Your knowledge of `workflow` is outdated. -### Search Bundled Documentation +The `workflow` documentation outlined below matches the installed version of the Workflow DevKit. +Follow these instructions before starting on any `workflow`-related tasks: Search the bundled documentation in `node_modules/workflow/docs/`: @@ -66,17 +67,76 @@ import { workflow } from "workflow/astro"; // Or use modules: ["workflow/nitro"] for Nitro/Nuxt ``` -## Critical workflow rules +## Prefer Step Functions to Avoid Sandbox Errors -1. Always set `globalThis.fetch = fetch` when using AI SDK or HTTP libraries -2. Use `sleep()` instead of setTimeout/setInterval -3. Move Node.js modules (fs, crypto, etc.) to step functions -4. Use `FatalError` for permanent failures, `RetryableError` for retries -5. Use `use step` with AI SDK methods to avoid issues with OIDC +`"use workflow"` functions run in a sandboxed VM. `"use step"` functions have **full Node.js access**. Put your logic in steps and use the workflow function purely for orchestration. -## Ecosystem +```typescript +// Steps have full Node.js and npm access +async function fetchUserData(userId: string) { + "use step"; + const response = await fetch(`https://api.example.com/users/${userId}`); + return response.json(); +} + +async function processWithAI(data: any) { + "use step"; + // AI SDK works in steps without workarounds + return await generateText({ + model: openai("gpt-4"), + prompt: `Process: ${JSON.stringify(data)}`, + }); +} + +// Workflow orchestrates steps - no sandbox issues +export async function dataProcessingWorkflow(userId: string) { + "use workflow"; + const data = await fetchUserData(userId); + const processed = await processWithAI(data); + return { success: true, processed }; +} +``` + +**Benefits:** Steps have automatic retry, results are persisted for replay, and no sandbox restrictions. + +## Workflow Sandbox Limitations + +When you logic directly in a workflow function (not in a step), these restrictions apply: + +| Limitation | Workaround | +|------------|------------| +| No `fetch()` | `import { fetch } from "workflow"` then `globalThis.fetch = fetch` | +| No `setTimeout`/`setInterval` | Use `sleep("5s")` from `"workflow"` | +| No Node.js modules (fs, crypto, etc.) | Move to a step function | + +**Example - Using fetch in workflow context:** + +```typescript +import { fetch } from "workflow"; -Workflow tooling is optimized to be used with the AI SDK, Vercel's AI Gateway, and Vercel OIDC +export async function myWorkflow() { + "use workflow"; + globalThis.fetch = fetch; // Required for AI SDK and HTTP libraries + // Now generateText() and other libraries work +} +``` + +**Note:** `DurableAgent` from `@workflow/ai` handles the fetch assignment automatically. + +## Error Handling + +Use `FatalError` for permanent failures (no retry), `RetryableError` for transient failures: + +```typescript +import { FatalError, RetryableError } from "workflow"; + +if (res.status >= 400 && res.status < 500) { + throw new FatalError(`Client error: ${res.status}`); +} +if (res.status === 429) { + throw new RetryableError("Rate limited", { retryAfter: "5m" }); +} +``` ## Errors and Troubleshooting From 13aca8228bd6d1fd87180c2580752c15cc79f0ef Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 27 Jan 2026 12:17:16 -0700 Subject: [PATCH 12/12] 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 --- skills/workflow/SKILL.md | 44 ++- skills/workflow/references/common-errors.md | 315 -------------------- 2 files changed, 41 insertions(+), 318 deletions(-) delete mode 100644 skills/workflow/references/common-errors.md diff --git a/skills/workflow/SKILL.md b/skills/workflow/SKILL.md index 32cd13ea3a..c0c313dc9e 100644 --- a/skills/workflow/SKILL.md +++ b/skills/workflow/SKILL.md @@ -101,7 +101,7 @@ export async function dataProcessingWorkflow(userId: string) { ## Workflow Sandbox Limitations -When you logic directly in a workflow function (not in a step), these restrictions apply: +When you need logic directly in a workflow function (not in a step), these restrictions apply: | Limitation | Workaround | |------------|------------| @@ -138,6 +138,44 @@ if (res.status === 429) { } ``` -## Errors and Troubleshooting +## Serialization -Common errors and troubleshooting are documented here: [Common Errors Reference](references/common-errors.md). +All data passed to/from workflows and steps must be serializable. + +**Supported types:** string, number, boolean, null, undefined, bigint, plain objects, arrays, Date, RegExp, URL, URLSearchParams, Map, Set, Headers, ArrayBuffer, typed arrays, Request, Response, ReadableStream, WritableStream. + +**Not supported:** Functions, class instances, Symbols, WeakMap/WeakSet. Pass data, not callbacks. + +## Streaming + +Use `getWritable()` in step functions to stream data: + +```typescript +async function streamData() { + "use step"; // Required - streaming only works in steps + const writer = getWritable(); + await writer.write(data); + writer.releaseLock(); // Always release the lock +} + +// Close when done +await getWritable().close(); +``` + +## Debugging + +```bash +# Check workflow endpoints are reachable +npx workflow health +npx workflow health --port 3001 # Non-default port + +# Visual dashboard for runs +npx workflow web +npx workflow web --app-url http://localhost:3001 + +# CLI inspection (for agents) +npx workflow inspect runs +npx workflow inspect run +``` + +**Tip:** Only import workflow APIs you actually use. Unused imports can cause 500 errors. diff --git a/skills/workflow/references/common-errors.md b/skills/workflow/references/common-errors.md deleted file mode 100644 index 8b29c37e07..0000000000 --- a/skills/workflow/references/common-errors.md +++ /dev/null @@ -1,315 +0,0 @@ ---- -title: Common Errors -description: Reference for common Workflow DevKit errors and how to resolve them. ---- - -# Common Errors - -## Quick Reference - -| Error | Cause | Solution | -|-------|-------|----------| -| `fetch-in-workflow` | Using global fetch | Import from `workflow`, assign to `globalThis.fetch` | -| `timeout-in-workflow` | setTimeout/setInterval | Use `sleep()` instead | -| `serialization-failed` | Non-serializable data | Use supported types, plain objects | -| `start-invalid-workflow-function` | Missing directive or config | Add `'use workflow'`, check `withWorkflow()` | -| `node-js-module-in-workflow` | Node.js modules in workflow | Move to step function | -| `webhook-invalid-respond-with-value` | Wrong respondWith | Use `"manual"`, `Response`, or `undefined` | -| `webhook-response-not-sent` | Manual mode, no response | Call `request.respondWith()` | -| Dashboard shows no runs | Port mismatch or dashboard started first | Restart dashboard, use `--app-url` flag | -| 500 error on API route | Unused workflow imports | Only import APIs you actually use | - -## Import Only What You Need - -**Symptom:** API route returns 500 error silently. - -**Cause:** Importing workflow APIs (like `fetch`, `sleep`, `FatalError`) that aren't used can cause initialization errors. - -**Solution:** Only import what you actually use: -```typescript -// BAD: Importing unused APIs -import { sleep, createHook, FatalError, fetch } from "workflow"; -// If you don't use sleep, FatalError, or fetch, remove them - -// GOOD: Import only what's needed -import { createHook } from "workflow"; -``` - -## fetch-in-workflow - -**Error:** Global `fetch` is unavailable in workflows. - -**Cause:** Workflows run in a sandboxed environment. - -**Solution:** -```typescript -import { fetch } from "workflow"; - -export async function myWorkflow() { - "use workflow"; - globalThis.fetch = fetch; // Required for libraries (AI SDK) - - const response = await fetch("https://api.example.com/data"); - const data = await response.json(); -} -``` - -## timeout-in-workflow - -**Error:** `setTimeout` or `setInterval` unavailable. - -**Cause:** These are non-deterministic and break replay. - -**Solution:** -```typescript -import { sleep } from "workflow"; - -// Instead of setTimeout -await sleep("5s"); - -// Polling pattern instead of setInterval -while (true) { - const status = await checkStatus(id); - if (status === "ready") break; - await sleep("30s"); -} -``` - -## serialization-failed - -**Error:** Data cannot be serialized. - -**Cause:** Functions, class instances, Symbols, WeakMap/WeakSet. - -**Solution:** -```typescript -// BAD: Function in data -const config = { callback: () => console.log("hi") }; - -// GOOD: Configuration data, logic in steps -const config = { logLevel: "info" }; - -async function processWithLogging(data: any, logLevel: string) { - "use step"; - if (logLevel === "info") console.log(data); - return process(data); -} -``` - -**Supported types:** -- Primitives: string, number, boolean, null, undefined, bigint -- Objects: plain objects, arrays, Date, RegExp, URL, URLSearchParams -- Collections: Map, Set, Headers -- Binary: ArrayBuffer, typed arrays (Uint8Array, etc.) -- Web: Request, Response, ReadableStream, WritableStream - -## start-invalid-workflow-function - -**Error:** Invalid workflow function. - -**Cause:** Missing `"use workflow"` directive or framework config. - -**Solution:** -```typescript -// 1. Add directive as FIRST statement -export async function myWorkflow(input: string) { - "use workflow"; // Must be first - // ... workflow code -} - -// 2. Check framework config -// next.config.ts -import { withWorkflow } from "workflow/next"; -export default withWorkflow({ /* config */ }); - -// nitro.config.ts -export default defineNitroConfig({ - modules: ["workflow/nitro"], -}); -``` - -## node-js-module-in-workflow - -**Error:** Node.js module unavailable in workflow. - -**Cause:** Workflows run sandboxed for determinism. - -**Restricted:** fs, path, http, https, net, dns, child_process, cluster, os, crypto, stream - -**Solution:** Move Node.js code to step functions: -```typescript -// BAD: Node.js in workflow -export async function badWorkflow() { - "use workflow"; - const fs = require("fs"); // Error! -} - -// GOOD: Node.js in step -async function readFile(path: string) { - "use step"; - const fs = require("fs"); - return fs.readFileSync(path, "utf-8"); -} - -export async function goodWorkflow(filePath: string) { - "use workflow"; - const content = await readFile(filePath); // Works! -} -``` - -## webhook-invalid-respond-with-value - -**Error:** Invalid `respondWith` value. - -**Solution:** -```typescript -// Valid options: -const webhook = createWebhook(); // Default: auto 202 -const webhook = createWebhook({ respondWith: "manual" }); -const webhook = createWebhook({ respondWith: new Response("OK") }); -``` - -## webhook-response-not-sent - -**Error:** Manual mode but `respondWith()` not called. - -**Solution:** -```typescript -const webhook = createWebhook({ respondWith: "manual" }); -const request = await webhook; - -await processRequest(request); - -// MUST call respondWith in manual mode -await request.respondWith(new Response("OK", { status: 200 })); -``` - -## Step Retry Exhaustion - -**Symptom:** Step fails after max retries. - -**Default retry:** 5-10 attempts with exponential backoff. - -**Solution:** Handle expected failures explicitly: -```typescript -async function callAPI() { - "use step"; - - const res = await fetch("..."); - - // Don't retry on client errors - if (res.status >= 400 && res.status < 500) { - throw new FatalError(`Client error: ${res.status}`); - } - - // Custom retry delay - if (res.status === 429) { - throw new RetryableError("Rate limited", { retryAfter: "5m" }); - } - - return res; -} -``` - -## Streaming Errors - -**Symptom:** Stream not working. - -**Solution:** -```typescript -// 1. Only use in steps, not workflow -async function streamData() { - "use step"; // Required! - - const writer = getWritable(); - await writer.write(data); - writer.releaseLock(); // 2. Release lock! -} - -// 3. Close when done -const writer = getWritable(); -await writer.write(lastData); -writer.releaseLock(); -await getWritable().close(); // Signal completion -``` - -## Hook Token Not Found - -**Error:** Invalid or expired hook token. - -**Cause:** Token doesn't exist, workflow completed, or token already consumed. - -**Solution:** -```typescript -import { resumeHook, getHookByToken } from "workflow/api"; - -// Check hook status before resuming -const hookInfo = await getHookByToken(token); -if (!hookInfo || hookInfo.status !== "pending") { - throw new Error("Hook not available"); -} - -await resumeHook(token, data); -``` - -## Dashboard Not Showing Runs - -**Symptom:** `npx workflow web` shows workflows but no runs, even after triggering. - -**Causes:** -1. Dashboard not connected to your app's port -2. App running on non-default port (not 3000) -3. Dashboard started before app -4. Multiple dev servers running on different ports - -**Solution:** - -```bash -# 1. Kill any stray dev servers -pkill -f "next dev" 2>/dev/null - -# 2. Start your app on port 3000 -npm run dev - -# 3. In another terminal, restart the dashboard -pkill -f "workflow web" 2>/dev/null -npx workflow web - -# 4. If using a different port, specify it: -npx workflow web --app-url http://localhost:3001 -``` - -**Verify connection:** -```bash -# Check Local World backend health (workflow and step endpoints) -npx workflow health - -# Check specific endpoint -npx workflow health --endpoint workflow -npx workflow health --endpoint step - -# Specify port if not using default 3000 -npx workflow health --port 3001 - -# Check Vercel backend health -npx workflow health --backend vercel --project my-project --team my-team - -# Or manually check your app responds to workflow endpoints -curl -s http://localhost:3000/.well-known/workflow/v1/flow \ - -X POST -H "Content-Type: application/json" -# Should return: {"error": "Missing required headers"} (not 404) -``` - -**Local storage locations:** -- Next.js: `.next/workflow-data/` -- Other frameworks: `.workflow-data/` - -## Debugging Tips - -1. **Run `npx workflow health`** - Verifies workflow/step endpoints are reachable (use `--port` for non-default ports, `--backend vercel` for production) -2. **Use `npx workflow web`** - Visual dashboard for runs (ensure correct port) -3. **Check run status** - `getRun(runId).status` -4. **Review event log** - Shows step executions, hooks, sleeps -5. **Test locally first** - Local World for development -6. **Add logging in steps** - Steps have full console access -7. **Verify port** - Ensure dashboard connects to correct app port