Commit Graph

11 Commits

Author SHA1 Message Date
Mitul Shah e181f64b72 Align Streams UI with trace viewer (#3197)
* Align streams UI with trace viewer

Co-authored-by: Cursor <cursoragent@cursor.com>

* cleanupp

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 08:00:45 -07:00
Nathan Rajlich e8bc7d6aad feat: decrypt sealed payloads in the dashboard and CLI (#3146)
* feat: decrypt sealed payloads in the dashboard and CLI

Without this, any payload another run sealed to this one renders as a lock
icon with no way to open it — a visible regression for anyone debugging a
run that received a cross-deployment hook resumption. The user is entitled
to read the data and has already supplied the key; only the plumbing was
missing.

`hydrateDataWithKey` now delegates to the envelope layer, which dispatches
on the format prefix, instead of unconditionally running AES-GCM. All four
o11y key-resolution sites (web-shared hydration, the web stream reader,
and both CLI `--decrypt` paths) resolve the full capability rather than
just the symmetric key. Each already had the raw 32 bytes in hand, so this
costs one extra derivation and no additional requests.

A caller that supplies only a symmetric key still gets the ciphertext
placeholder for sealed payloads rather than a decryption error, since that
key never could have opened them.

**Browser bundling.** The obvious import for the new helper is
`@workflow/core/serialization`, but that module graph reaches `node:util`
and `node:async_hooks` and cannot be bundled for the browser — which is
what `@workflow/core/serialization-format` exists to avoid. The key
helpers are re-exported from that browser-safe entrypoint instead, and the
two browser consumers import from there; the CLI keeps the direct import
since it runs on Node. Verified by walking the built import graph: the
entrypoint reaches 6 modules and zero Node built-ins.

Unrelated: `pnpm --filter @workflow/web build` currently fails on `main`
too (`reducers/common.js` importing `node:util`). Turbo caching had been
hiding it; touching core caused a cache miss that surfaced it. Not
addressed here.

* review: narrow the o11y decrypt key type and dedupe an import

- `hydrateDataWithKey` accepted `PayloadKey`, which includes `SealTarget`.
  A seal target holds only a public key, so it can open neither scheme —
  passing one compiled fine and then always failed at runtime. Added a
  `DecryptionKey` alias (`CryptoKey | RunPayloadKeys`) and narrowed the
  signature, so that misuse is now a compile error. A `@ts-expect-error`
  test pins the guarantee.
- `hydrateResourceIOAsync` dynamically imported
  `@workflow/core/serialization-format` twice. Destructure both bindings
  from the single existing import instead.

* review: record @workflow/web in the changeset

This PR changes the dashboard's stream reader
(`packages/web/app/lib/hooks/use-stream-reader.ts`) so it dispatches on the
envelope format and can read sealed (`encp`) frames, but the changeset listed
only core, web-shared and cli.

`@workflow/web` is published, so without an entry the change would still ship —
just as an incidental dependency bump, with nothing in that package's release
notes explaining that sealed-stream decryption landed.
2026-07-28 00:36:15 +00:00
Karthik Kalyan 9ea125427f Decode UTF-8 stream chunks (#1852)
* Decode typed array stream chunks

* Render decoded stream bytes with raw view

* Render decoded bytes in data inspector

* Use generic byte inspector for streams

* review feedback: narrow stream-display exports, fix tab a11y, add collapseRefs tests

- Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay
  exports; keep only the formatArrayBufferViewForDisplay path actually used
  by DataInspector.
- Replace broken role=tablist/role=tab on the Decoded/Bytes switcher
  with aria-pressed toggle-button semantics.
- Export collapseRefs/isBytesDisplay and add regression tests covering
  typed-array detection (top-level, nested in object/array/Map/Set,
  DataView exclusion).

* Replace eval with JSON.parse in serialization revive helper (#1848)

* Replace eval with JSON.parse in serialization revive helper

devalue.stringify() always produces valid JSON — special values
(undefined, NaN, Infinity, -0) are encoded as negative integer
sentinels. JSON.parse yields the same flattened array form that
unflatten() expects, without the eval anti-pattern (VULN-918).

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

* Drop redundant workflow package from changeset

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Add e2e test for UTF-8 parseable stream chunks

Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended,
CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and
asserts each chunk round-trips through TextDecoder({ fatal: true }).
Exercises the same decode path the web inspector relies on for
typed-array stream values.

Made-with: Cursor

---------

Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 09:22:05 -07:00
Karthik Kalyan c488877727 [web] Switch web package stream reader to getStreamChunks (#1542)
* fix: switch web stream reader from readFromStream to getStreamChunksix

* add decrypt button for stream

* add decrypt button for stream

* add polling for streams tab

* add polling for streams tab

* add jsdoc

* address review comments
2026-03-30 13:11:49 -07:00
Karthik Kalyan bb86b695c2 [web] Fix Streams tab crash when decrypting large stream chunks (#1515)
* optimize stream tab

* add changeset
2026-03-24 19:00:32 -07:00
Karthik Kalyan 2b80e2de35 [web] Support legacy newline-delimited stream format in useStreamReader (#1473)
* fix stream tab for legacy runs

* fix stream tab for legacy runs

* Apply suggestion from @VaguelySerious

Signed-off-by: Peter Wielander <mittgfu@gmail.com>

---------

Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
2026-03-23 09:51:28 -07:00
Nathan Rajlich 97932d3086 fix: thread runId through stream inspection for encryption key resolution (#1277)
CLI showStream:
- Requires --run with --decrypt for encrypted stream decryption
- Warns when --decrypt is used without --run

Web stream reading:
- readStreamServerAction accepts runId parameter for key resolution
- Stream API route reads runId from query param
- readStream client function passes runId to the API route
- useStreamReader hook accepts and passes runId
- run-detail-view passes runId to useStreamReader

Removes getRunIdFromStreamId helper (stream IDs don't always share
the run's ULID, e.g. streams serialized across step/workflow boundaries).
2026-03-05 23:36:36 +00:00
Karthik Kalyan 1c1157340d [builders][web-shared] Improvements to o11y and fixes to graph generation code path (#1031)
* [workflow o11y] rebase on latest main and keep targeted UI/builders changes

Rebase the branch intent onto latest main by preserving web-shared UI refactors and builders base-builder updates while taking main for hydration and data-fetching behavior elsewhere.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [workflow o11y] align web-shared hydration revivers with main

Revert the hydration reviver delta for URL, URLSearchParams, and Headers so web-shared matches main behavior while keeping the targeted UI/builders-only scope on this branch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [workflow o11y] restore PR #1017 detail-panel decoupling

Bring the web-shared trace/detail panel files back in sync with main so PR #1017 behavior is preserved and not regressed on this branch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [workflow o11y] restore PR #1018 react-inspector sidebar updates

Bring web-shared o11y rendering files back in sync with main so ObjectInspector-based sidebar rendering and related UI behavior from PR #1018 remain intact on this branch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [workflow o11y] keep custom viewers and apply inspector rendering

Preserve the branch-specific event list and stream viewer UX while applying react-inspector rendering to payload/chunk data so complex hydrated values render correctly without reverting custom UI behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [workflow o11y] trace viewer UX improvements and Geist alignment

- Add live tick animation, context menu, and cancel run support from PR #984
- Decouple side panel styling to use Geist design tokens (inline styles)
- Fix sleep span detail panel showing events instead of wait entity attributes
- Fix stream viewer flickering by removing unstable object deps
- Remove Chunks/Output toggle from stream viewer, show only chunks
- Add resolve hook modal, wake-up sleep, and cancel run plumbing
- Add loading skeleton for stream viewer

Co-authored-by: Cursor <cursoragent@cursor.com>

* Bug fixes

* Bug fixes

* Bug fixes

* Bug fixes

* Bug fixes

* Bug fixes

* Bug fixes

* Bug fixes

* Bug fixes

* Bug fixes

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 13:27:16 -08:00
Nathan Rajlich 8a53c3fa3d Use react-inspector for data rendering in o11y sidebar (#1018)
## Summary

Replace the custom `JSON.stringify`-based data renderer with [`react-inspector`](https://www.npmjs.com/package/react-inspector)'s `ObjectInspector` component for proper display of all JavaScript types in the o11y sidebar panel.

## Before

Data was rendered via `JSON.stringify(value, null, 2)` in a `<pre>` block. Non-plain-object types like `Map`, `Set`, `URLSearchParams`, `Date`, `Error`, `RegExp`, and typed arrays all rendered as `{}` or lost their type information.

## After

Uses `react-inspector` (the same rendering engine as Chrome DevTools) to display data as expandable tree views with proper type labels:
- `URLSearchParams {size: 12}` with entries visible
- `Map(3)` / `Set(5)` with contents
- `Date "2026-02-11T..."` 
- `Error {message: "...", stack: "..."}` 
- `Uint8Array(32)` with byte values

## Implementation

- Replace `JsonBlock` function with `DataInspector` component using `ObjectInspector`
- Custom theme with transparent background and design system CSS variables (`--ds-gray-*`, `--font-mono`)
- Dark/light mode support via `useDarkMode` hook
- `StreamRef` and `ClassInstanceRef` objects still render as custom components (clickable stream links and styled class cards)
- Expand level defaults to 2 for a good overview without overwhelming

## Dependencies
- Added `react-inspector@9` to `@workflow/web-shared`
2026-02-12 17:37:50 -08:00
Nathan Rajlich c56dc3848e Extract browser-safe serialization format and move hydration client-side (#1015)
## Summary

Split the serialization/deserialization logic into environment-specific layers so data hydration can happen client-side in the browser. This is a prerequisite for e2e encryption where decryption keys are only available in the browser.

## Architecture

### Layer 1: `@workflow/core/serialization-format` (new, browser-safe)
- Format prefix encoding/decoding (`devl`, future `encr`, etc.)
- Generic `hydrateData()` dispatch — handles Uint8Array (v2 binary), legacy arrays (v1), and plain values
- `hydrateResourceIO(resource, revivers)` resource-type dispatcher (step/hook/event/workflow field mapping)
- `ClassInstanceRef` (plain data class, no `node:util` dependency)
- `StreamRef`, type guards (`isStreamRef`, `isStreamId`, `isClassInstanceRef`), utility functions (`extractStreamIds`, `truncateId`)
- Shared `observabilityRevivers` for stream/class/step display overrides
- 36 unit tests covering all of the above

### Layer 2: Environment-specific revivers
- **`@workflow/web-shared`** (`lib/hydration.ts`) — browser-safe revivers using `atob()` for base64, real `URLSearchParams`/`Headers`/`URL` instances, `ClassInstanceRef` for UI rendering
- **`@workflow/cli`** (`lib/inspect/hydration.ts`) — Node.js revivers using `Buffer.from()` for base64, `CLIClassInstanceRef` with `util.inspect.custom` for CLI output

Each module exports a pre-bound `hydrateResourceIO(resource)` that uses its environment's revivers.

### Removed: `@workflow/core/observability`
- Deleted `observability.ts` and `observability.test.ts` entirely (no remaining consumers)
- Removed `"./observability"` export from `@workflow/core/package.json`
- Removed the `workflow` package's `internal/observability.ts` re-export
- All functionality has been split between `serialization-format.ts` (shared types/utilities) and the environment-specific hydration modules

## Web package changes
- Server passes raw world data through without hydration (CBOR preserves `Uint8Array`)
- Client calls `hydrateResourceIO` from `@workflow/web-shared` after receiving CBOR-decoded data
- No Vite `node:*` stubs needed since `@workflow/core/serialization-format` is browser-safe
- Optimized: event hydration finds the matching event before hydrating (instead of hydrating all)
- Reduced server log noise from handled API errors (4xx errors no longer logged)

## Packages affected
- `@workflow/core` — new `serialization-format` export (with tests), removed `observability` export
- `@workflow/web-shared` — new `lib/hydration.ts` with browser-safe revivers
- `@workflow/cli` — new `lib/inspect/hydration.ts` with Node.js revivers, updated `output.ts` import
- `@workflow/web` — client-side hydration, removed server-side hydration
- `workflow` — removed `internal/observability.ts` re-export
2026-02-12 16:58:19 -08:00
Nathan Rajlich 7653e6bfdb Migrate @workflow/web from Next.js to React Router v7 (#1005)
## Summary

- Replace Next.js App Router with React Router v7.13.0 framework mode (Vite-based), eliminating the large `next` dependency from the web, CLI, and workflow metapackages
- Serve the web UI in-process from the CLI via Express instead of spawning `next start` as a child process
- Switch RPC transport from JSON to CBOR to preserve binary data types across the wire
- Replace `nuqs` URL state management with React Router's `useSearchParams`
- Replace Next.js server actions with an RPC resource route (`/api/rpc`) and a thin CBOR-based client

## Motivation

The `next` package is ~300MB installed and was the single largest dependency in the monorepo. It also required spawning a separate child process from the CLI to run the o11y web server, adding complexity around process lifecycle management, port readiness polling, and environment variable forwarding.

With React Router framework mode, the web package builds to a standard Express-compatible server bundle that the CLI can import and serve directly in its own process.

## What changed

**Framework swap (`@workflow/web`):**
- `next.config.ts` / `postcss.config.mjs` → `react-router.config.ts` / `vite.config.ts`
- `src/` directory → `app/` directory (React Router convention)
- `src/app/layout.tsx` + `layout-client.tsx` → `app/root.tsx`
- `src/app/page.tsx` → `app/routes/home.tsx`
- `src/app/run/[runId]/page.tsx` → `app/routes/run-detail.tsx`
- Path alias `@/` → `~/`
- Removed all `'use client'` / `'use server'` directives

**Data transport:**
- Server actions → RPC resource route at `/api/rpc` with CBOR encoding
- CBOR preserves `Uint8Array` and other binary types natively (no base64 overhead)
- Stream reading → dedicated `/api/stream/:streamId` resource route

**URL state:**
- `nuqs` (`useQueryState`) → `useSearchParams` from `react-router`

**Fonts:**
- `next/font/google` → Geist `.woff2` files referenced directly from `node_modules/geist` via `@font-face` in CSS

**CLI integration (`@workflow/cli`):**
- `import('@workflow/web/server').then(m => m.startServer(port))`
- No child process, no readiness polling, no cleanup handlers

**Radix UI compatibility:**
- `onSubmit` preventDefault on `AlertDialogContent` and `SheetContent` to prevent Radix's internal `<form method="dialog">` from triggering React Router route actions
- Catch-all action on root route for any stray POSTs

## Dependencies removed
- `next`, `swr`, `nuqs`, `@tailwindcss/postcss`

## Dependencies added
- `react-router` / `@react-router/dev` / `@react-router/node` / `@react-router/express` (all `7.13.0`)
- `express`, `vite`, `@tailwindcss/vite`, `cbor-x`, `isbot`, `cross-env`
- `geist` (devDep)
2026-02-12 16:30:51 -08:00