Commit Graph

4 Commits

Author SHA1 Message Date
Pranay Prakash 02681dce4a feat(core): add hook.dispose() method to release hook tokens early (#1181)
* feat(core): add hook.dispose() method to release hook tokens early

Add a `dispose()` method to the Hook interface that allows workflows to
explicitly release hook tokens for reuse by other workflows while the
current workflow is still running. This enables handoff patterns where
one workflow can transfer a hook token to another workflow.

- Add `dispose()` method to Hook interface in create-hook.ts
- Implement dispose functionality in workflow/hook.ts
- Add HookDisposedInvocationQueueItem to global.ts
- Handle hook_disposed events in suspension-handler.ts
- Update documentation in hooks.mdx and create-hook.mdx
- Add e2e test for hook token reuse after explicit disposal

https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH

* feat(core): implement TypeScript Disposable spec for hooks

Add Symbol.dispose to Hook interface to support the TC39 Explicit Resource
Management proposal. This allows hooks to be used with the `using` keyword
for automatic disposal when exiting scope.

https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH

* docs: prefer `using` keyword for hooks and webhooks

Update all documentation and e2e tests to use the `using` keyword as the
recommended approach for creating hooks and webhooks. This leverages the
TC39 Explicit Resource Management proposal for automatic disposal.

- Update e2e tests to use `using` syntax
- Update foundational hooks guide to recommend `using`
- Update create-hook API reference with `using` examples
- Update create-webhook API reference with `using` examples
- Update example workflow to use `using`

https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH

* chore: simplify `using` examples in docs and e2e tests

Remove unnecessary block scopes and excessive comments about automatic
disposal. Block scopes are only used when early disposal is relevant
(like in the handoff test).

https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH

* docs: add brief explanation of `using` in first examples

Add a one-liner explaining the `using` keyword in the intro examples
of the API reference docs, so new users understand the syntax.

https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH

* fix: address PR review comments

- Restore code highlights (`[!code highlight]`) that were unintentionally
  removed from pre-existing doc examples
- Move `using` explanation from prose to inline code comment in intro
  examples
- Add `{/* @skip-typecheck */}` to incomplete manual dispose() snippet
- Add 409 (conflict/duplicate) error handling for hook_disposed events
  in suspension handler to handle workflow re-invocation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: polyfill Symbol.dispose in workflow VM context

The workflow VM sandbox doesn't have Symbol.dispose/Symbol.asyncDispose
available, causing `using` keyword to fail with "Symbol.dispose is not
defined" at runtime. Add polyfill in the VM context creation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use VM's Symbol.dispose for hook disposable support

The workflow VM sandbox has its own Symbol object with a polyfilled
Symbol.dispose. The hook object was using the host's Symbol.dispose,
which is a different symbol instance. The SWC-compiled `using` keyword
looks up the VM's Symbol.dispose on the object, causing "Object not
disposable" errors.

Fix by setting Symbol.dispose on the hook object dynamically using the
VM's globalThis.Symbol.dispose from the orchestrator context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert webhookWorkflow to create all webhooks upfront

The webhookWorkflow e2e test creates 3 webhooks that must all exist
before the test sends HTTP requests. Using `using` with sequential
creation meant only the first webhook existed at the first suspension
point. Revert to `const` since all webhooks need to be created upfront.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: eliminate hook_disposed queue item, use flag pattern for disposal

Instead of adding a separate HookDisposedInvocationQueueItem to the
queue on dispose, keep the HookInvocationQueueItem throughout the
hook lifecycle and track state with flags (hasCreatedEvent, disposed).
A closure variable (hasDisposedEvent) makes disposeHook() a pure no-op
on replay, avoiding redundant server calls and 409 errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: delete hook from invocations queue on hook_disposed terminal event

Match the pattern used by steps (step_completed/step_failed) and waits
(wait_completed) where the queue item is removed on the terminal event.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: verify dispose() is safe when called twice after replay

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add comprehensive edge case tests for hook disposal

- WorkflowSuspension counts disposed hooks separately from active hooks
- Dispose after hook_created replay produces correct suspension
- Dispose before first suspension (needs both create + dispose)
- Multiple hooks where only one is disposed
- Dispose on a conflicted hook is safe (no crash)
- Symbol.dispose calls disposeHook correctly (using keyword pattern)
- Iterator break without dispose keeps hook alive in queue
- Await after dispose on first invocation triggers suspension

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove unsafe cast by adding Symbol.dispose to hook object literal

Add [Symbol.dispose] directly to the hook object so it satisfies the
Hook<T> type without `as unknown as`. The VM's Symbol.dispose is still
added separately when it differs from the host's.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address remaining Copilot review comments

- Rename test to match new behavior (hooks stay in queue, not removed)
- Use neutral "processed" verb in WorkflowSuspension message when
  mixed item types are present
- Remove extends Disposable from Hook interface to avoid requiring
  lib.esnext.disposable in downstream consumers (explicit
  [Symbol.dispose]() method is still declared on the interface)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: drain pending promises in disposeHook to prevent orphaned awaits

When dispose() is called while a promise is pending (e.g., iterator
suspended on yield await this, or direct await hook after dispose),
the promise would hang forever since the event consumer will never
deliver another hook_received. Now disposeHook() clears the promises
array and triggers a WorkflowSuspension so the runtime processes the
disposal cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove workflow from changeset, keep only @workflow/core

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 12:59:54 -08:00
Nathan Rajlich f2ab6ee179 Fix Nest workbench app build (#865)
* Use proper OpenAI import

```
Error: ../example/workflows/5_hooks.ts(36,22): error TS2351: This expression is not constructable.
  Type 'typeof import("/vercel/path0/node_modules/.pnpm/openai@6.1.0_ws@8.18.3_zod@4.1.11/node_modules/openai/index")' has no construct signatures.
```

* .
2026-01-27 00:21:33 -08:00
Pranay Prakash 3aee6d7efb Port Peter's Workbench UI to nextjs-turbopack (#458)
* Port Peter's Workspace UI

* Add symlinks for webpack workbench to share UI with turbopack

- Add symlinks for components, hooks, lib directories
- Add symlinks for app/workflows and app/api/workflows
- Install UI dependencies in webpack workbench package.json
- Both webpack and turbopack workbenches now share the same UI code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Use allWorkflows from @/_workflows instead of custom examples.ts

- Update workflow definitions to include workflowFile field
- Change start route to use allWorkflows like trigger route
- Remove unused examples.ts file
- This allows accessing workflows from any workflow file, not just 99_e2e

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Dynamically generate workflow definitions from allWorkflows

- Remove manual workflow definitions, now auto-generated from _workflows.ts
- Add workflowFile path display in UI (tooltip and fallback description)
- Sort workflows by file name first, then by workflow name
- Fix duplicate key issue by using workflowFile:name as key
- Make description optional since it's inferred from file path

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add default arguments map for workflows that require parameters

- Create DEFAULT_ARGS_MAP to provide sensible defaults for workflows with parameters
- Includes defaults for: addTenWorkflow, hookWorkflow, webhookWorkflow, hookCleanupTestWorkflow, closureVariableWorkflow
- Workflows without entries in the map get empty args array

This allows all workflows to be started from the UI without needing manual argument input for now.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Fix terminal log colors for better visibility on black background

- Change info logs from text-primary (black in light mode) to text-cyan-400
- Change error logs from text-destructive to text-red-400
- Update stream logs to text-blue-400
- Update result logs to text-green-400
- Change prefix color from text-gray-500 to text-gray-400

All colors now work properly with the terminal's black background regardless of light/dark mode.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Remove toTitleCase transformation, use raw workflow names

- Display workflow names exactly as they are in code (e.g., "addTenWorkflow")
- Remove the toTitleCase helper function
- Simpler and more direct mapping from code to UI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Use monospace font for workflow names

- Apply font-mono to workflow displayName in both card and tooltip
- Makes workflow names more readable and consistent with code style

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Filter out step functions using workflowId property

- Check for 'workflowId' property added by compiler to identify workflow functions
- More reliable than checking function name endings
- Only functions with "use workflow" directive have workflowId property
- Prevents step functions from appearing in the UI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add symlinks for remaining workflow files and dependencies

- Add symlinks for 2_control_flow.ts, 4_ai.ts, 5_hooks.ts to both workbenches
- Install openai and mixpart dependencies needed by these workflows
- Update DEFAULT_ARGS_MAP with arguments for new workflows:
  - ai: sample weather prompt
  - agent: sample weather prompt for Muscat
  - handleUserSignup: example email address
- All 9 workflow files now available in both workbenches

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Group workflows by file with section headers in UI

- Group workflows by workflowFile and render each group separately
- Add section headers showing the file path for each group
- Use monospace font for file path headers
- Increase spacing between sections (space-y-4)
- Improves visual organization when browsing workflows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Show default arguments instead of file path in workflow cards

- Display default arguments in the card description line (if present)
- Remove redundant file path since it's now shown in section headers
- Use monospace font for argument display
- Workflows without arguments show no description line

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Display default arguments with multi-line formatting

- Use JSON.stringify with indentation (null, 2) for better readability
- Change from <p> to <pre> with whitespace-pre-wrap
- Arguments now display across multiple lines when needed
- Easier to read complex arguments like webhook tokens

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Add workflow runtime duration display with millisecond precision

- Add formatDuration helper to calculate and format runtime
- Show duration in ms, seconds, or minutes based on length
- Formats: "123ms", "5.234s", "2m 15.678s"
- Display duration in monospace font below end time
- Only shown when invocation has completed (endTime exists)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

* Update default arguments to match e2e test values

- Use arguments from packages/core/e2e/e2e.test.ts for accuracy
- addTenWorkflow: 123 (instead of 5) to match test expectations
- closureVariableWorkflow: 7 (instead of 42) to match test expectations
- Use Math.random().toString(36) for hook/webhook tokens (matches test pattern)
- Add comment referencing e2e test source

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-28 21:43:27 -08:00
Gal Schlezinger 4ca9a3edbd Introducing Workflow DevKit
build durable, resilient, and observable workflows.

Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Adrian <me@adriandlam.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
Co-authored-by: Vercel Release Bot <88769842+vercel-release-bot@users.noreply.github.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
Co-authored-by: Manuel Muñoz Solera <mamuso@mamuso.net>
Co-authored-by: Garrett <garrett.tolbert@vercel.com>
Co-authored-by: Lars Grammel <lars.grammel@gmail.com>
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
Co-authored-by: Tom Dale <tom@tomdale.net>
Co-authored-by: Vishal Yathish <135551666+visyat@users.noreply.github.com>
Co-authored-by: josh <144584931+dancer@users.noreply.github.com>
2025-10-23 12:07:52 +03:00