@workflow/web-shared 5.0.0-beta.2 → 5.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.js +1 -1
- package/dist/components/event-list-view.d.ts +16 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +196 -85
- package/dist/components/hook-actions.js +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -1
- package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
- package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts +10 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.js +46 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
- package/dist/components/new-trace-viewer/components/span-markers.d.ts +46 -0
- package/dist/components/new-trace-viewer/components/span-markers.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/span-markers.js +85 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +15 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.js +105 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts +36 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.js +254 -0
- package/dist/components/new-trace-viewer/components/timeline.module.css +46 -0
- package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
- package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +19 -0
- package/dist/components/new-trace-viewer/components/use-row-window.d.ts +34 -0
- package/dist/components/new-trace-viewer/components/use-row-window.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/use-row-window.js +107 -0
- package/dist/components/new-trace-viewer/context.d.ts +15 -0
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/context.js +47 -0
- package/dist/components/new-trace-viewer/icons.d.ts +6 -0
- package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/icons.js +15 -0
- package/dist/components/new-trace-viewer/search.d.ts +19 -0
- package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/search.js +134 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts +11 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.js +462 -0
- package/dist/components/new-trace-viewer/types.d.ts +3 -0
- package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/types.js +2 -0
- package/dist/components/new-trace-viewer/utils.d.ts +61 -0
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/utils.js +345 -0
- package/dist/components/run-trace-view.js +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +129 -102
- package/dist/components/sidebar/attributes-block.d.ts +28 -0
- package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
- package/dist/components/sidebar/attributes-block.js +81 -0
- package/dist/components/sidebar/conversation-view.js +1 -1
- package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
- package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +16 -15
- package/dist/components/sidebar/detail-card.d.ts +5 -6
- package/dist/components/sidebar/detail-card.d.ts.map +1 -1
- package/dist/components/sidebar/detail-card.js +32 -16
- package/dist/components/sidebar/entity-detail-panel.d.ts +1 -3
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +17 -35
- package/dist/components/sidebar/events-list.d.ts +3 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +36 -22
- package/dist/components/sidebar/resolve-hook-modal.js +1 -1
- package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar-data-context.js +19 -0
- package/dist/components/sidebar/span-detail-merge.d.ts +16 -0
- package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
- package/dist/components/sidebar/span-detail-merge.js +53 -0
- package/dist/components/stream-viewer.d.ts +1 -1
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +3 -27
- package/dist/components/trace-viewer/components/map.js +1 -1
- package/dist/components/trace-viewer/components/markers.js +1 -1
- package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/node.js +2 -1
- package/dist/components/trace-viewer/components/search-input.js +1 -1
- package/dist/components/trace-viewer/components/search.js +1 -1
- package/dist/components/trace-viewer/components/span-content.js +1 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
- package/dist/components/trace-viewer/components/span-segments.js +1 -1
- package/dist/components/trace-viewer/components/span-strategies.js +1 -1
- package/dist/components/trace-viewer/components/ui.js +1 -1
- package/dist/components/trace-viewer/components/zoom-button.js +1 -1
- package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
- package/dist/components/trace-viewer/context.js +1 -1
- package/dist/components/trace-viewer/index.js +1 -1
- package/dist/components/trace-viewer/trace-viewer.js +1 -1
- package/dist/components/trace-viewer/trace-viewer.module.css +4 -4
- package/dist/components/trace-viewer/types.js +1 -1
- package/dist/components/trace-viewer/util/constants.js +1 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts +3 -3
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/timing.js +8 -11
- package/dist/components/trace-viewer/util/tree.js +1 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
- package/dist/components/trace-viewer/worker.js +1 -1
- package/dist/components/trace-viewer-new.d.ts +14 -0
- package/dist/components/trace-viewer-new.d.ts.map +1 -0
- package/dist/components/trace-viewer-new.js +21 -0
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +46 -0
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/context-card.d.ts +47 -0
- package/dist/components/ui/context-card.d.ts.map +1 -0
- package/dist/components/ui/context-card.js +471 -0
- package/dist/components/ui/data-inspector.d.ts +22 -0
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +103 -13
- package/dist/components/ui/decrypt-button.js +1 -1
- package/dist/components/ui/error-card.js +1 -1
- package/dist/components/ui/error-stack-block.d.ts +17 -10
- package/dist/components/ui/error-stack-block.d.ts.map +1 -1
- package/dist/components/ui/error-stack-block.js +49 -30
- package/dist/components/ui/icon-button.d.ts +19 -0
- package/dist/components/ui/icon-button.d.ts.map +1 -0
- package/dist/components/ui/icon-button.js +29 -0
- package/dist/components/ui/inspector-theme.d.ts +9 -10
- package/dist/components/ui/inspector-theme.d.ts.map +1 -1
- package/dist/components/ui/inspector-theme.js +39 -72
- package/dist/components/ui/kbd.d.ts +6 -0
- package/dist/components/ui/kbd.d.ts.map +1 -0
- package/dist/components/ui/kbd.js +6 -0
- package/dist/components/ui/load-more-button.js +1 -1
- package/dist/components/ui/menu-dropdown.js +1 -1
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/spinner.js +1 -1
- package/dist/components/ui/timestamp-tooltip.d.ts +34 -1
- package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
- package/dist/components/ui/timestamp-tooltip.js +80 -132
- package/dist/components/ui/tooltip.d.ts +10 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +12 -0
- package/dist/components/workflow-trace-view.d.ts +1 -3
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +11 -13
- package/dist/components/workflow-traces/event-colors.d.ts +1 -0
- package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
- package/dist/components/workflow-traces/event-colors.js +12 -1
- package/dist/components/workflow-traces/trace-colors.js +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +19 -5
- package/dist/components/workflow-traces/trace-time-utils.js +1 -1
- package/dist/hooks/use-dark-mode.js +1 -1
- package/dist/hooks/use-intersection-observer.d.ts +17 -0
- package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
- package/dist/hooks/use-intersection-observer.js +29 -0
- package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
- package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
- package/dist/hooks/use-load-more-on-scroll.js +28 -0
- package/dist/hooks/use-reduced-motion.d.ts +10 -0
- package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
- package/dist/hooks/use-reduced-motion.js +29 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/lib/event-analysis.js +1 -1
- package/dist/lib/event-materialization.js +1 -1
- package/dist/lib/exact-event-search-id.d.ts +25 -0
- package/dist/lib/exact-event-search-id.d.ts.map +1 -0
- package/dist/lib/exact-event-search-id.js +39 -0
- package/dist/lib/hydration.d.ts +6 -0
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +154 -2
- package/dist/lib/stream-display.d.ts +18 -0
- package/dist/lib/stream-display.d.ts.map +1 -0
- package/dist/lib/stream-display.js +30 -0
- package/dist/lib/toast.js +1 -1
- package/dist/lib/trace-builder.d.ts +15 -0
- package/dist/lib/trace-builder.d.ts.map +1 -1
- package/dist/lib/trace-builder.js +29 -3
- package/dist/lib/utils.d.ts +20 -9
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +74 -31
- package/dist/lib/zstd-browser-decoder.d.ts +7 -0
- package/dist/lib/zstd-browser-decoder.d.ts.map +1 -0
- package/dist/lib/zstd-browser-decoder.js +41 -0
- package/dist/styles.css +484 -1
- package/package.json +10 -5
- package/src/components/event-list-view.tsx +279 -104
- package/src/components/index.ts +7 -0
- package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
- package/src/components/new-trace-viewer/components/event-list.tsx +136 -0
- package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
- package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
- package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
- package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
- package/src/components/new-trace-viewer/components/span-markers.tsx +173 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +198 -0
- package/src/components/new-trace-viewer/components/timeline.module.css +46 -0
- package/src/components/new-trace-viewer/components/timeline.tsx +744 -0
- package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +94 -0
- package/src/components/new-trace-viewer/components/use-row-window.ts +140 -0
- package/src/components/new-trace-viewer/context.tsx +90 -0
- package/src/components/new-trace-viewer/icons.tsx +79 -0
- package/src/components/new-trace-viewer/search.ts +178 -0
- package/src/components/new-trace-viewer/trace-viewer.tsx +843 -0
- package/src/components/new-trace-viewer/types.ts +2 -0
- package/src/components/new-trace-viewer/utils.test.ts +182 -0
- package/src/components/new-trace-viewer/utils.ts +519 -0
- package/src/components/sidebar/attribute-panel.tsx +194 -240
- package/src/components/sidebar/attributes-block.tsx +173 -0
- package/src/components/sidebar/copyable-data-block.tsx +49 -27
- package/src/components/sidebar/detail-card.tsx +119 -59
- package/src/components/sidebar/entity-detail-panel.tsx +55 -111
- package/src/components/sidebar/events-list.tsx +85 -96
- package/src/components/sidebar/sidebar-data-context.tsx +62 -0
- package/src/components/sidebar/span-detail-merge.ts +65 -0
- package/src/components/stream-viewer.tsx +18 -40
- package/src/components/trace-viewer/components/node.tsx +3 -2
- package/src/components/trace-viewer/trace-viewer.module.css +4 -4
- package/src/components/trace-viewer/util/timing.ts +7 -11
- package/src/components/trace-viewer-new.tsx +55 -0
- package/src/components/ui/button.tsx +68 -0
- package/src/components/ui/context-card.tsx +784 -0
- package/src/components/ui/data-inspector.tsx +244 -22
- package/src/components/ui/error-stack-block.tsx +84 -55
- package/src/components/ui/icon-button.tsx +53 -0
- package/src/components/ui/inspector-theme.ts +39 -81
- package/src/components/ui/kbd.tsx +21 -0
- package/src/components/ui/timestamp-tooltip.tsx +132 -194
- package/src/components/ui/tooltip.tsx +47 -0
- package/src/components/workflow-trace-view.tsx +19 -15
- package/src/components/workflow-traces/event-colors.ts +12 -0
- package/src/components/workflow-traces/trace-span-construction.ts +24 -5
- package/src/hooks/use-intersection-observer.ts +57 -0
- package/src/hooks/use-load-more-on-scroll.ts +49 -0
- package/src/hooks/use-reduced-motion.ts +34 -0
- package/src/index.ts +9 -0
- package/src/lib/exact-event-search-id.ts +67 -0
- package/src/lib/hydration.ts +166 -1
- package/src/lib/stream-display.ts +48 -0
- package/src/lib/trace-builder.ts +34 -2
- package/src/lib/utils.ts +83 -31
- package/src/lib/zstd-browser-decoder.ts +42 -0
- package/src/styles.css +484 -1
- package/dist/components/error-boundary.js.map +0 -1
- package/dist/components/event-list-view.js.map +0 -1
- package/dist/components/hook-actions.js.map +0 -1
- package/dist/components/index.js.map +0 -1
- package/dist/components/run-trace-view.js.map +0 -1
- package/dist/components/sidebar/attribute-panel.js.map +0 -1
- package/dist/components/sidebar/conversation-view.js.map +0 -1
- package/dist/components/sidebar/copyable-data-block.js.map +0 -1
- package/dist/components/sidebar/detail-card.js.map +0 -1
- package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
- package/dist/components/sidebar/events-list.js.map +0 -1
- package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
- package/dist/components/stream-viewer.js.map +0 -1
- package/dist/components/trace-viewer/components/map.js.map +0 -1
- package/dist/components/trace-viewer/components/markers.js.map +0 -1
- package/dist/components/trace-viewer/components/node.js.map +0 -1
- package/dist/components/trace-viewer/components/search-input.js.map +0 -1
- package/dist/components/trace-viewer/components/search.js.map +0 -1
- package/dist/components/trace-viewer/components/span-content.js.map +0 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
- package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
- package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
- package/dist/components/trace-viewer/components/ui.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
- package/dist/components/trace-viewer/context.js.map +0 -1
- package/dist/components/trace-viewer/index.js.map +0 -1
- package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
- package/dist/components/trace-viewer/types.js.map +0 -1
- package/dist/components/trace-viewer/util/constants.js.map +0 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
- package/dist/components/trace-viewer/util/timing.js.map +0 -1
- package/dist/components/trace-viewer/util/tree.js.map +0 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
- package/dist/components/trace-viewer/worker.js.map +0 -1
- package/dist/components/ui/alert.js.map +0 -1
- package/dist/components/ui/card.js.map +0 -1
- package/dist/components/ui/data-inspector.js.map +0 -1
- package/dist/components/ui/decrypt-button.js.map +0 -1
- package/dist/components/ui/error-card.js.map +0 -1
- package/dist/components/ui/error-stack-block.js.map +0 -1
- package/dist/components/ui/inspector-theme.js.map +0 -1
- package/dist/components/ui/load-more-button.js.map +0 -1
- package/dist/components/ui/menu-dropdown.js.map +0 -1
- package/dist/components/ui/skeleton.js.map +0 -1
- package/dist/components/ui/spinner.js.map +0 -1
- package/dist/components/ui/timestamp-tooltip.js.map +0 -1
- package/dist/components/workflow-trace-view.js.map +0 -1
- package/dist/components/workflow-traces/event-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
- package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
- package/dist/hooks/use-dark-mode.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/event-analysis.js.map +0 -1
- package/dist/lib/event-materialization.js.map +0 -1
- package/dist/lib/hydration.js.map +0 -1
- package/dist/lib/toast.js.map +0 -1
- package/dist/lib/trace-builder.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
|
@@ -17,6 +17,11 @@ import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
|
|
17
17
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
18
18
|
import { createPortal } from 'react-dom';
|
|
19
19
|
import { useToast } from '../lib/toast';
|
|
20
|
+
import {
|
|
21
|
+
buildTrace,
|
|
22
|
+
filterSpanRawEvents,
|
|
23
|
+
type TraceWithMeta,
|
|
24
|
+
} from '../lib/trace-builder';
|
|
20
25
|
import { ErrorBoundary } from './error-boundary';
|
|
21
26
|
import {
|
|
22
27
|
EntityDetailPanel,
|
|
@@ -36,7 +41,6 @@ import {
|
|
|
36
41
|
getCustomSpanClassName,
|
|
37
42
|
getCustomSpanEventClassName,
|
|
38
43
|
} from './workflow-traces/trace-colors';
|
|
39
|
-
import { buildTrace, type TraceWithMeta } from '../lib/trace-builder';
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* While a run is live, continuously grow root.duration and rescale so the
|
|
@@ -776,7 +780,6 @@ export const WorkflowTraceViewer = ({
|
|
|
776
780
|
encryptionKey,
|
|
777
781
|
onDecrypt,
|
|
778
782
|
isDecrypting = false,
|
|
779
|
-
hasEncryptedData = false,
|
|
780
783
|
}: {
|
|
781
784
|
run: WorkflowRun;
|
|
782
785
|
events: Event[];
|
|
@@ -819,8 +822,6 @@ export const WorkflowTraceViewer = ({
|
|
|
819
822
|
onDecrypt?: () => void;
|
|
820
823
|
/** Whether the encryption key is currently being fetched */
|
|
821
824
|
isDecrypting?: boolean;
|
|
822
|
-
/** Run-level hint: the run contains encrypted data (from probe). */
|
|
823
|
-
hasEncryptedData?: boolean;
|
|
824
825
|
}) => {
|
|
825
826
|
const toast = useToast();
|
|
826
827
|
const [selectedSpan, setSelectedSpan] = useState<SelectedSpanInfo | null>(
|
|
@@ -865,12 +866,14 @@ export const WorkflowTraceViewer = ({
|
|
|
865
866
|
const handleSelectionChange = useCallback(
|
|
866
867
|
(info: SelectedSpanInfo | null) => {
|
|
867
868
|
if (info) {
|
|
868
|
-
// Filter raw events
|
|
869
|
-
//
|
|
870
|
-
|
|
871
|
-
const rawEvents =
|
|
872
|
-
|
|
873
|
-
|
|
869
|
+
// Filter raw events for the selected span: child spans match on
|
|
870
|
+
// correlationId (stepId/hookId), the run root span gets run-level
|
|
871
|
+
// events. This bypasses the trace worker pipeline entirely.
|
|
872
|
+
const rawEvents = filterSpanRawEvents(
|
|
873
|
+
events,
|
|
874
|
+
info.resource,
|
|
875
|
+
info.spanId
|
|
876
|
+
);
|
|
874
877
|
setSelectedSpan({ ...info, rawEvents });
|
|
875
878
|
} else {
|
|
876
879
|
setSelectedSpan(null);
|
|
@@ -884,8 +887,10 @@ export const WorkflowTraceViewer = ({
|
|
|
884
887
|
const correlationId = selectedSpan?.spanId;
|
|
885
888
|
if (!correlationId) return;
|
|
886
889
|
|
|
887
|
-
const nextRawEvents =
|
|
888
|
-
|
|
890
|
+
const nextRawEvents = filterSpanRawEvents(
|
|
891
|
+
events,
|
|
892
|
+
selectedSpan?.resource,
|
|
893
|
+
correlationId
|
|
889
894
|
);
|
|
890
895
|
setSelectedSpan((prev) => {
|
|
891
896
|
if (!prev || prev.spanId !== correlationId) {
|
|
@@ -907,7 +912,7 @@ export const WorkflowTraceViewer = ({
|
|
|
907
912
|
rawEvents: nextRawEvents,
|
|
908
913
|
};
|
|
909
914
|
});
|
|
910
|
-
}, [events, selectedSpan?.spanId]);
|
|
915
|
+
}, [events, selectedSpan?.spanId, selectedSpan?.resource]);
|
|
911
916
|
|
|
912
917
|
// Reset selected span when navigating to a different run
|
|
913
918
|
useEffect(() => {
|
|
@@ -977,7 +982,7 @@ export const WorkflowTraceViewer = ({
|
|
|
977
982
|
}
|
|
978
983
|
|
|
979
984
|
return (
|
|
980
|
-
<div className="relative w-full h-full flex">
|
|
985
|
+
<div className="relative w-full h-full flex flex-row">
|
|
981
986
|
{/* Timeline (takes remaining space) */}
|
|
982
987
|
<div className="flex-1 min-w-0 relative">
|
|
983
988
|
<TraceViewerContextProvider
|
|
@@ -1164,7 +1169,6 @@ export const WorkflowTraceViewer = ({
|
|
|
1164
1169
|
onDecrypt={onDecrypt}
|
|
1165
1170
|
isDecrypting={isDecrypting}
|
|
1166
1171
|
selectedSpan={selectedSpan}
|
|
1167
|
-
hasEncryptedData={hasEncryptedData}
|
|
1168
1172
|
/>
|
|
1169
1173
|
</ErrorBoundary>
|
|
1170
1174
|
</div>
|
|
@@ -22,6 +22,7 @@ export interface EventColorPalette {
|
|
|
22
22
|
* - Red for failures (step_failed, run_failed)
|
|
23
23
|
* - Orange/yellow for retries (step_retrying)
|
|
24
24
|
* - Purple for webhook-related events
|
|
25
|
+
* - Teal for attribute changes (attr_set)
|
|
25
26
|
* - Blue otherwise (default)
|
|
26
27
|
*/
|
|
27
28
|
export function getEventColor(
|
|
@@ -64,6 +65,17 @@ export function getEventColor(
|
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
// Attribute changes - Teal
|
|
69
|
+
if (eventType === 'attr_set') {
|
|
70
|
+
return {
|
|
71
|
+
color: 'var(--ds-teal-900)',
|
|
72
|
+
background: 'var(--ds-teal-200)',
|
|
73
|
+
border: 'var(--ds-teal-500)',
|
|
74
|
+
text: 'var(--ds-teal-900)',
|
|
75
|
+
secondary: 'var(--ds-gray-900)',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
// Default - Blue
|
|
68
80
|
return {
|
|
69
81
|
color: 'var(--ds-blue-600)',
|
|
@@ -26,6 +26,7 @@ const MARKER_EVENT_TYPES: Set<Event['eventType']> = new Set([
|
|
|
26
26
|
'run_failed',
|
|
27
27
|
'wait_created',
|
|
28
28
|
'wait_completed',
|
|
29
|
+
'attr_set',
|
|
29
30
|
]);
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -59,7 +60,7 @@ export const waitEventsToWaitEntity = (
|
|
|
59
60
|
waitId: string;
|
|
60
61
|
runId: string;
|
|
61
62
|
createdAt: Date;
|
|
62
|
-
resumeAt
|
|
63
|
+
resumeAt?: Date;
|
|
63
64
|
completedAt?: Date;
|
|
64
65
|
} | null => {
|
|
65
66
|
const startEvent = events.find((event) => event.eventType === 'wait_created');
|
|
@@ -73,7 +74,9 @@ export const waitEventsToWaitEntity = (
|
|
|
73
74
|
waitId: startEvent.correlationId,
|
|
74
75
|
runId: startEvent.runId,
|
|
75
76
|
createdAt: startEvent.createdAt,
|
|
76
|
-
resumeAt: startEvent.eventData?.resumeAt
|
|
77
|
+
resumeAt: startEvent.eventData?.resumeAt
|
|
78
|
+
? new Date(startEvent.eventData.resumeAt)
|
|
79
|
+
: undefined,
|
|
77
80
|
completedAt: completedEvent?.createdAt,
|
|
78
81
|
};
|
|
79
82
|
};
|
|
@@ -81,13 +84,29 @@ export const waitEventsToWaitEntity = (
|
|
|
81
84
|
/**
|
|
82
85
|
* Converts a workflow Wait to an OpenTelemetry Span
|
|
83
86
|
*/
|
|
84
|
-
export function waitToSpan(
|
|
87
|
+
export function waitToSpan(
|
|
88
|
+
events: Event[],
|
|
89
|
+
maxEndTime: Date,
|
|
90
|
+
fallbackEndTime = maxEndTime
|
|
91
|
+
): Span | null {
|
|
85
92
|
const wait = waitEventsToWaitEntity(events);
|
|
86
93
|
if (!wait) {
|
|
87
94
|
return null;
|
|
88
95
|
}
|
|
89
96
|
const startTime = wait.createdAt;
|
|
90
|
-
const
|
|
97
|
+
const startMs = startTime.getTime();
|
|
98
|
+
let endTime = wait.completedAt;
|
|
99
|
+
if (!endTime) {
|
|
100
|
+
const fallbackCap =
|
|
101
|
+
wait.resumeAt && wait.resumeAt.getTime() < fallbackEndTime.getTime()
|
|
102
|
+
? wait.resumeAt
|
|
103
|
+
: fallbackEndTime;
|
|
104
|
+
endTime =
|
|
105
|
+
maxEndTime.getTime() > startMs &&
|
|
106
|
+
maxEndTime.getTime() < fallbackCap.getTime()
|
|
107
|
+
? maxEndTime
|
|
108
|
+
: fallbackCap;
|
|
109
|
+
}
|
|
91
110
|
const start = dateToOtelTime(startTime);
|
|
92
111
|
const end = dateToOtelTime(endTime);
|
|
93
112
|
const duration = calculateDuration(startTime, endTime);
|
|
@@ -296,7 +315,7 @@ export function hookToSpan(hookEvents: Event[], maxEndTime: Date): Span | null {
|
|
|
296
315
|
|
|
297
316
|
return {
|
|
298
317
|
spanId: String(hook.hookId),
|
|
299
|
-
name: String(hook.hookId),
|
|
318
|
+
name: hook.token ?? String(hook.hookId),
|
|
300
319
|
kind: 1, // INTERNAL span kind
|
|
301
320
|
resource: 'hook',
|
|
302
321
|
library: WORKFLOW_LIBRARY,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
interface UseIntersectionObserverArgs
|
|
7
|
+
extends Omit<IntersectionObserverInit, 'root'> {
|
|
8
|
+
/**
|
|
9
|
+
* Scroll container the target is observed within. Defaults to the viewport.
|
|
10
|
+
* Passed as a ref so it resolves after commit (when the node exists).
|
|
11
|
+
*/
|
|
12
|
+
rootRef?: RefObject<Element | null>;
|
|
13
|
+
freezeOnceVisible?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the latest IntersectionObserverEntry for `elementRef`.
|
|
18
|
+
* Ported from vercel/front's `@vercel/hooks` `useIntersectionObserver`, with
|
|
19
|
+
* `root` threaded as a ref so we can observe inside a custom scroll container.
|
|
20
|
+
*/
|
|
21
|
+
export function useIntersectionObserver(
|
|
22
|
+
elementRef: RefObject<Element | null>,
|
|
23
|
+
{
|
|
24
|
+
threshold = 0,
|
|
25
|
+
rootRef,
|
|
26
|
+
rootMargin = '0%',
|
|
27
|
+
freezeOnceVisible = false,
|
|
28
|
+
}: UseIntersectionObserverArgs = {}
|
|
29
|
+
): IntersectionObserverEntry | undefined {
|
|
30
|
+
const [entry, setEntry] = useState<IntersectionObserverEntry>();
|
|
31
|
+
|
|
32
|
+
const frozen = entry?.isIntersecting && freezeOnceVisible;
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const node = elementRef?.current;
|
|
36
|
+
|
|
37
|
+
const hasIOSupport = !!window.IntersectionObserver;
|
|
38
|
+
|
|
39
|
+
if (!hasIOSupport || frozen || !node) return;
|
|
40
|
+
|
|
41
|
+
const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
|
|
42
|
+
setEntry(entry);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const observer = new IntersectionObserver(updateEntry, {
|
|
46
|
+
threshold,
|
|
47
|
+
root: rootRef?.current ?? null,
|
|
48
|
+
rootMargin,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
observer.observe(node);
|
|
52
|
+
|
|
53
|
+
return () => observer.disconnect();
|
|
54
|
+
}, [elementRef, threshold, rootRef, rootMargin, frozen]);
|
|
55
|
+
|
|
56
|
+
return entry;
|
|
57
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
import { useEffect, useRef } from 'react';
|
|
5
|
+
import { useIntersectionObserver } from './use-intersection-observer';
|
|
6
|
+
|
|
7
|
+
interface UseLoadMoreOnScrollOptions {
|
|
8
|
+
hasMore: boolean;
|
|
9
|
+
isLoadingMore: boolean;
|
|
10
|
+
/** Scroll container the sentinel lives in. Defaults to the viewport. */
|
|
11
|
+
rootRef?: RefObject<Element | null>;
|
|
12
|
+
rootMargin?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Triggers `loadMore` when a sentinel element scrolls into view.
|
|
17
|
+
* Returns a ref to attach to an invisible sentinel `<div>` placed after the
|
|
18
|
+
* list content. Ported from vercel/front's `@vercel/hooks`
|
|
19
|
+
* `useLoadMoreOnScroll`.
|
|
20
|
+
*
|
|
21
|
+
* Because it keys off `isIntersecting`/`isLoadingMore`, it self-continues:
|
|
22
|
+
* if the sentinel is still in view once a page finishes loading, the next
|
|
23
|
+
* page is requested automatically.
|
|
24
|
+
*/
|
|
25
|
+
export function useLoadMoreOnScroll(
|
|
26
|
+
loadMore: () => void,
|
|
27
|
+
{
|
|
28
|
+
hasMore,
|
|
29
|
+
isLoadingMore,
|
|
30
|
+
rootRef,
|
|
31
|
+
rootMargin = '400px',
|
|
32
|
+
}: UseLoadMoreOnScrollOptions
|
|
33
|
+
) {
|
|
34
|
+
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const entry = useIntersectionObserver(sentinelRef, { rootRef, rootMargin });
|
|
36
|
+
|
|
37
|
+
const loadMoreRef = useRef(loadMore);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
loadMoreRef.current = loadMore;
|
|
40
|
+
}, [loadMore]);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (entry?.isIntersecting && hasMore && !isLoadingMore) {
|
|
44
|
+
loadMoreRef.current();
|
|
45
|
+
}
|
|
46
|
+
}, [entry?.isIntersecting, hasMore, isLoadingMore]);
|
|
47
|
+
|
|
48
|
+
return sentinelRef;
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
const QUERY = '(prefers-reduced-motion: reduce)';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook that detects whether the user has requested reduced motion and reacts
|
|
9
|
+
* to changes. Mirrors the `prefers-reduced-motion: reduce` media query so
|
|
10
|
+
* components can skip or shorten animations for users with motion
|
|
11
|
+
* sensitivities (e.g. vestibular disorders).
|
|
12
|
+
*
|
|
13
|
+
* @returns `true` if the user prefers reduced motion, `false` otherwise
|
|
14
|
+
*/
|
|
15
|
+
export const useReducedMotion = (): boolean => {
|
|
16
|
+
const [reduced, setReduced] = useState(() => {
|
|
17
|
+
if (typeof window === 'undefined' || !window.matchMedia) return false;
|
|
18
|
+
return window.matchMedia(QUERY).matches;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (typeof window === 'undefined' || !window.matchMedia) return;
|
|
23
|
+
|
|
24
|
+
const media = window.matchMedia(QUERY);
|
|
25
|
+
const onChange = (): void => setReduced(media.matches);
|
|
26
|
+
|
|
27
|
+
media.addEventListener('change', onChange);
|
|
28
|
+
onChange();
|
|
29
|
+
|
|
30
|
+
return () => media.removeEventListener('change', onChange);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return reduced;
|
|
34
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,13 @@ export {
|
|
|
14
14
|
waitEventsToWaitEntity,
|
|
15
15
|
} from './components/workflow-traces/trace-span-construction';
|
|
16
16
|
export type { EventAnalysis } from './lib/event-analysis';
|
|
17
|
+
export {
|
|
18
|
+
parseExactWorkflowSearchId,
|
|
19
|
+
looksLikeWorkflowIdSearchInput,
|
|
20
|
+
type ExactWorkflowSearchId,
|
|
21
|
+
type ExactWorkflowSearchIdKind,
|
|
22
|
+
type ExactIdSearchResult,
|
|
23
|
+
} from './lib/exact-event-search-id';
|
|
17
24
|
export {
|
|
18
25
|
analyzeEvents,
|
|
19
26
|
hasPendingHooksFromEvents,
|
|
@@ -50,12 +57,14 @@ export {
|
|
|
50
57
|
STREAM_REF_TYPE,
|
|
51
58
|
truncateId,
|
|
52
59
|
} from './lib/hydration';
|
|
60
|
+
export type { DecodedStreamChunkSource } from './lib/stream-display';
|
|
53
61
|
export type { ToastAdapter } from './lib/toast';
|
|
54
62
|
export { ToastProvider, useToast } from './lib/toast';
|
|
55
63
|
export type { StreamStep } from './lib/utils';
|
|
56
64
|
export {
|
|
57
65
|
extractConversation,
|
|
58
66
|
formatDuration,
|
|
67
|
+
formatDurationPrecise,
|
|
59
68
|
identifyStreamSteps,
|
|
60
69
|
isDoStreamStep,
|
|
61
70
|
} from './lib/utils';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Event } from '@workflow/world';
|
|
2
|
+
|
|
3
|
+
const WORKFLOW_ULID_BODY = '[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}';
|
|
4
|
+
|
|
5
|
+
const STEP_ID_PATTERN = new RegExp(`^step_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
6
|
+
const WAIT_ID_PATTERN = new RegExp(`^wait_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
7
|
+
const HOOK_ID_PATTERN = new RegExp(`^hook_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
8
|
+
const EVENT_ID_PATTERN = new RegExp(`^evnt_(${WORKFLOW_ULID_BODY})$`, 'i');
|
|
9
|
+
|
|
10
|
+
const WORKFLOW_ID_PREFIX_PATTERN = /^(step_|wait_|hook_|evnt_|wrun_)/i;
|
|
11
|
+
|
|
12
|
+
export type ExactWorkflowSearchIdKind = 'step' | 'wait' | 'hook' | 'event';
|
|
13
|
+
|
|
14
|
+
export type ExactWorkflowSearchId = {
|
|
15
|
+
kind: ExactWorkflowSearchIdKind;
|
|
16
|
+
id: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ExactIdSearchResult =
|
|
20
|
+
| { status: 'ok'; events: Event[]; truncated?: boolean }
|
|
21
|
+
| { status: 'not_found' }
|
|
22
|
+
| { status: 'error'; message: string };
|
|
23
|
+
|
|
24
|
+
function matchPrefixedId(
|
|
25
|
+
pattern: RegExp,
|
|
26
|
+
prefix: 'step' | 'wait' | 'hook' | 'evnt',
|
|
27
|
+
kind: ExactWorkflowSearchIdKind,
|
|
28
|
+
query: string
|
|
29
|
+
): ExactWorkflowSearchId | null {
|
|
30
|
+
const match = query.match(pattern);
|
|
31
|
+
if (!match) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return { kind, id: `${prefix}_${match[1].toUpperCase()}` };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns a parsed workflow ID when `query` is a full step, wait, hook, or event ID.
|
|
39
|
+
* Partial IDs and run IDs (`wrun_`) are ignored. ULID bodies are matched case-insensitively
|
|
40
|
+
* and normalized to uppercase in the returned ID.
|
|
41
|
+
*/
|
|
42
|
+
export function parseExactWorkflowSearchId(
|
|
43
|
+
query: string
|
|
44
|
+
): ExactWorkflowSearchId | null {
|
|
45
|
+
const trimmed = query.trim();
|
|
46
|
+
if (!trimmed) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
matchPrefixedId(STEP_ID_PATTERN, 'step', 'step', trimmed) ??
|
|
52
|
+
matchPrefixedId(WAIT_ID_PATTERN, 'wait', 'wait', trimmed) ??
|
|
53
|
+
matchPrefixedId(HOOK_ID_PATTERN, 'hook', 'hook', trimmed) ??
|
|
54
|
+
matchPrefixedId(EVENT_ID_PATTERN, 'evnt', 'event', trimmed)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** True when input looks like the user is attempting an ID search (including partial). */
|
|
59
|
+
export function looksLikeWorkflowIdSearchInput(query: string): boolean {
|
|
60
|
+
const trimmed = query.trim();
|
|
61
|
+
if (!WORKFLOW_ID_PREFIX_PATTERN.test(trimmed)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
// Distinguish IDs (contain digits) from event-type strings like step_started.
|
|
65
|
+
// Assumes workflow event types do not include digits in their names.
|
|
66
|
+
return /\d/.test(trimmed);
|
|
67
|
+
}
|
package/src/lib/hydration.ts
CHANGED
|
@@ -58,12 +58,60 @@ function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
|
58
58
|
// Web revivers (browser-safe, no Buffer dependency)
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Build a reviver for one of the built-in `Error` subclasses (e.g.
|
|
63
|
+
* `TypeError`, `RangeError`). The constructor for the named subclass is
|
|
64
|
+
* resolved off `globalThis` at call time so the produced instance has the
|
|
65
|
+
* correct prototype chain in the consumer realm. Falls back to a generic
|
|
66
|
+
* `Error` (with `name` set) if the global isn't available, which keeps the
|
|
67
|
+
* o11y UI rendering even on exotic browsers.
|
|
68
|
+
*
|
|
69
|
+
* `cause` is passed through `ErrorOptions` to the constructor when present,
|
|
70
|
+
* matching `getCommonRevivers` in `@workflow/core` so the resulting `cause`
|
|
71
|
+
* property has the same semantics (non-enumerable, set by the engine) as a
|
|
72
|
+
* freshly thrown Error in the consumer realm. The `'cause' in value` check
|
|
73
|
+
* preserves the distinction between "no cause" and "cause is undefined".
|
|
74
|
+
*/
|
|
75
|
+
function makeWebErrorSubclassReviver(
|
|
76
|
+
name:
|
|
77
|
+
| 'EvalError'
|
|
78
|
+
| 'RangeError'
|
|
79
|
+
| 'ReferenceError'
|
|
80
|
+
| 'SyntaxError'
|
|
81
|
+
| 'TypeError'
|
|
82
|
+
| 'URIError'
|
|
83
|
+
) {
|
|
84
|
+
return (value: { message: string; stack?: string; cause?: unknown }) => {
|
|
85
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
86
|
+
const Ctor = (globalThis as Record<string, any>)[name] as
|
|
87
|
+
| ErrorConstructor
|
|
88
|
+
| undefined;
|
|
89
|
+
let error: Error;
|
|
90
|
+
if (typeof Ctor === 'function') {
|
|
91
|
+
error = new Ctor(value.message, opts);
|
|
92
|
+
} else {
|
|
93
|
+
// Fallback path: no built-in subclass available (exotic env). Construct
|
|
94
|
+
// a plain Error with the right `name` and copy `cause` manually since
|
|
95
|
+
// the base Error constructor is what we actually called.
|
|
96
|
+
error = Object.assign(new Error(value.message, opts), { name });
|
|
97
|
+
}
|
|
98
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
99
|
+
return error;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
61
103
|
/**
|
|
62
104
|
* Get the web-specific revivers for hydrating serialized data.
|
|
63
105
|
*
|
|
64
106
|
* Uses `atob()` for base64 decoding (no Node.js Buffer dependency).
|
|
65
107
|
* All types are revived as real instances (Date, Map, Set, URL,
|
|
66
108
|
* URLSearchParams, Headers, Error, etc.).
|
|
109
|
+
*
|
|
110
|
+
* NOTE: this set must mirror the keys in `SerializableSpecial` (see
|
|
111
|
+
* `@workflow/core/serialization/types`). Any reducer key added on the
|
|
112
|
+
* serialization side that isn't covered here will cause `devalue.unflatten`
|
|
113
|
+
* to throw `Unknown type X`, which `hydrateResourceIO` swallows and
|
|
114
|
+
* surfaces as a "Failed to load resource details" banner in the o11y UI.
|
|
67
115
|
*/
|
|
68
116
|
export function getWebRevivers(): Revivers {
|
|
69
117
|
function reviveArrayBuffer(value: string): ArrayBuffer {
|
|
@@ -83,10 +131,120 @@ export function getWebRevivers(): Revivers {
|
|
|
83
131
|
BigUint64Array: (value: string) =>
|
|
84
132
|
new BigUint64Array(reviveArrayBuffer(value)),
|
|
85
133
|
Date: (value) => new Date(value),
|
|
134
|
+
|
|
135
|
+
// Error family. The reducer side (see
|
|
136
|
+
// `packages/core/src/serialization/reducers/common.ts`) emits a tagged
|
|
137
|
+
// entry for each built-in Error subclass plus the workflow-specific
|
|
138
|
+
// `FatalError` / `RetryableError` / `HookConflictError` /
|
|
139
|
+
// `RuntimeDecryptionError` and `AggregateError`. Without
|
|
140
|
+
// matching revivers here, `devalue.unflatten` throws "Unknown type X"
|
|
141
|
+
// — which surfaces in the web o11y UI as "Failed to load resource
|
|
142
|
+
// details: Unknown type FatalError".
|
|
86
143
|
Error: (value) => {
|
|
144
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
145
|
+
const error = new Error(value.message, opts);
|
|
146
|
+
error.name = value.name;
|
|
147
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
148
|
+
return error;
|
|
149
|
+
},
|
|
150
|
+
EvalError: makeWebErrorSubclassReviver('EvalError'),
|
|
151
|
+
RangeError: makeWebErrorSubclassReviver('RangeError'),
|
|
152
|
+
ReferenceError: makeWebErrorSubclassReviver('ReferenceError'),
|
|
153
|
+
SyntaxError: makeWebErrorSubclassReviver('SyntaxError'),
|
|
154
|
+
TypeError: makeWebErrorSubclassReviver('TypeError'),
|
|
155
|
+
URIError: makeWebErrorSubclassReviver('URIError'),
|
|
156
|
+
AggregateError: (value) => {
|
|
157
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
158
|
+
const Ctor = (
|
|
159
|
+
globalThis as { AggregateError?: AggregateErrorConstructor }
|
|
160
|
+
).AggregateError;
|
|
161
|
+
const error =
|
|
162
|
+
typeof Ctor === 'function'
|
|
163
|
+
? new Ctor(value.errors, value.message, opts)
|
|
164
|
+
: Object.assign(new Error(value.message, opts), {
|
|
165
|
+
name: 'AggregateError',
|
|
166
|
+
errors: value.errors,
|
|
167
|
+
});
|
|
168
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
169
|
+
return error;
|
|
170
|
+
},
|
|
171
|
+
// `FatalError` and `RetryableError` are not built-in browser globals,
|
|
172
|
+
// so we can't resolve a constructor from globalThis. The web o11y UI
|
|
173
|
+
// doesn't need `instanceof FatalError` to pass (no user code runs
|
|
174
|
+
// here) — it just needs `name`, `message`, `stack`, and any extra
|
|
175
|
+
// enumerable fields to render. Construct a plain `Error` with `name`
|
|
176
|
+
// set; ObjectInspector reads `constructor.name` for the displayed
|
|
177
|
+
// class label, but we don't have the real class, so we emit a tagged
|
|
178
|
+
// Error whose `name` field carries the class identity. This matches
|
|
179
|
+
// how the existing base `Error` reviver presents unknown subclasses.
|
|
180
|
+
FatalError: (value) => {
|
|
181
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
182
|
+
const error = new Error(value.message, opts);
|
|
183
|
+
error.name = 'FatalError';
|
|
184
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
185
|
+
return error;
|
|
186
|
+
},
|
|
187
|
+
HookConflictError: (value) => {
|
|
188
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
189
|
+
const error = new Error(value.message, opts) as Error & {
|
|
190
|
+
token?: string;
|
|
191
|
+
conflictingRunId?: string;
|
|
192
|
+
};
|
|
193
|
+
error.name = 'HookConflictError';
|
|
194
|
+
error.token = value.token;
|
|
195
|
+
if (value.conflictingRunId !== undefined) {
|
|
196
|
+
error.conflictingRunId = value.conflictingRunId;
|
|
197
|
+
}
|
|
198
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
199
|
+
return error;
|
|
200
|
+
},
|
|
201
|
+
RetryableError: (value) => {
|
|
202
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
203
|
+
const error = new Error(value.message, opts) as Error & {
|
|
204
|
+
retryAfter?: Date;
|
|
205
|
+
};
|
|
206
|
+
error.name = 'RetryableError';
|
|
207
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
208
|
+
// `retryAfter` is serialized as an epoch ms number (see the runtime
|
|
209
|
+
// RetryableError reducer for the rationale around realm-safety).
|
|
210
|
+
// Rehydrate as a Date so o11y consumers can render it directly.
|
|
211
|
+
// Guard against payloads from older runtime versions that predate
|
|
212
|
+
// the field — without this check, `new Date(undefined)` would
|
|
213
|
+
// produce an Invalid Date rather than omitting the property.
|
|
214
|
+
if (value.retryAfter != null) {
|
|
215
|
+
error.retryAfter = new Date(value.retryAfter);
|
|
216
|
+
}
|
|
217
|
+
return error;
|
|
218
|
+
},
|
|
219
|
+
RuntimeDecryptionError: (value) => {
|
|
220
|
+
const opts = 'cause' in value ? { cause: value.cause } : undefined;
|
|
221
|
+
const error = new Error(value.message, opts) as Error & {
|
|
222
|
+
context?: unknown;
|
|
223
|
+
};
|
|
224
|
+
error.name = 'RuntimeDecryptionError';
|
|
225
|
+
if (value.context !== undefined) {
|
|
226
|
+
error.context = value.context;
|
|
227
|
+
}
|
|
228
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
229
|
+
return error;
|
|
230
|
+
},
|
|
231
|
+
DOMException: (value) => {
|
|
232
|
+
// Modern browsers and Node 18+ expose `DOMException` on globalThis.
|
|
233
|
+
// `AbortController.abort()` with no argument synthesizes one as the
|
|
234
|
+
// signal's reason, so this is a common payload for any aborted step.
|
|
235
|
+
const G = globalThis as { DOMException?: typeof DOMException };
|
|
236
|
+
if (typeof G.DOMException === 'function') {
|
|
237
|
+
const e = new G.DOMException(value.message, value.name);
|
|
238
|
+
if (value.stack !== undefined) e.stack = value.stack;
|
|
239
|
+
if ('cause' in value) (e as { cause?: unknown }).cause = value.cause;
|
|
240
|
+
return e;
|
|
241
|
+
}
|
|
87
242
|
const error = new Error(value.message);
|
|
88
243
|
error.name = value.name;
|
|
89
|
-
error.stack = value.stack;
|
|
244
|
+
if (value.stack !== undefined) error.stack = value.stack;
|
|
245
|
+
if ('cause' in value) {
|
|
246
|
+
(error as Error & { cause?: unknown }).cause = value.cause;
|
|
247
|
+
}
|
|
90
248
|
return error;
|
|
91
249
|
},
|
|
92
250
|
Float32Array: (value: string) => new Float32Array(reviveArrayBuffer(value)),
|
|
@@ -318,6 +476,13 @@ export async function hydrateResourceIOWithKey<T>(
|
|
|
318
476
|
'@workflow/core/serialization-format'
|
|
319
477
|
);
|
|
320
478
|
const { importKey } = await import('@workflow/core/encryption');
|
|
479
|
+
// Payloads may be zstd-compressed (the Web DecompressionStream has no zstd);
|
|
480
|
+
// register the WASM-backed browser decoder before hydrating. Idempotent and
|
|
481
|
+
// lazy — the WASM is only compiled when a zstd payload is actually decoded.
|
|
482
|
+
const { ensureZstdDecoderRegistered } = await import(
|
|
483
|
+
'./zstd-browser-decoder.js'
|
|
484
|
+
);
|
|
485
|
+
ensureZstdDecoderRegistered();
|
|
321
486
|
const cryptoKey = await importKey(key);
|
|
322
487
|
const revivers = getRevivers();
|
|
323
488
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface DecodedStreamChunkSource {
|
|
2
|
+
type: string;
|
|
3
|
+
encoding: 'utf-8';
|
|
4
|
+
rawSummary: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface FormattedStreamChunkDisplay {
|
|
8
|
+
text: string;
|
|
9
|
+
decodedFrom?: DecodedStreamChunkSource;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function summarizeArrayBufferView(value: ArrayBufferView): string {
|
|
13
|
+
const ta = value as unknown as {
|
|
14
|
+
length: number;
|
|
15
|
+
constructor: { name: string };
|
|
16
|
+
} & ArrayLike<number>;
|
|
17
|
+
const name = ta.constructor.name;
|
|
18
|
+
const preview = Array.from(
|
|
19
|
+
{ length: Math.min(ta.length, 8) },
|
|
20
|
+
(_, i) => ta[i]
|
|
21
|
+
);
|
|
22
|
+
const suffix = ta.length > 8 ? ', …' : '';
|
|
23
|
+
return `${name}(${ta.length}) [${preview.join(', ')}${suffix}]`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Decode a typed array as UTF-8 text when valid, otherwise return a compact
|
|
28
|
+
* raw-byte summary. Used by `DataInspector`'s `collapseRefs` pipeline so
|
|
29
|
+
* hydrated `Uint8Array` chunks (e.g. AI SDK text deltas) render as readable
|
|
30
|
+
* text while still exposing the underlying byte layout.
|
|
31
|
+
*/
|
|
32
|
+
export function formatArrayBufferViewForDisplay(
|
|
33
|
+
value: ArrayBufferView
|
|
34
|
+
): FormattedStreamChunkDisplay {
|
|
35
|
+
try {
|
|
36
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(value);
|
|
37
|
+
return {
|
|
38
|
+
text,
|
|
39
|
+
decodedFrom: {
|
|
40
|
+
type: value.constructor.name,
|
|
41
|
+
encoding: 'utf-8',
|
|
42
|
+
rawSummary: summarizeArrayBufferView(value),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
} catch {
|
|
46
|
+
return { text: summarizeArrayBufferView(value) };
|
|
47
|
+
}
|
|
48
|
+
}
|