@workflow/web-shared 5.0.0-beta.3 → 5.0.0-beta.4

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.
Files changed (70) hide show
  1. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -1
  2. package/dist/components/new-trace-viewer/components/event-list.js +7 -4
  3. package/dist/components/new-trace-viewer/components/event-list.js.map +1 -1
  4. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
  5. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  6. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  7. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +1 -0
  8. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
  9. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  10. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  11. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +1 -0
  12. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
  13. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  14. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
  15. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +1 -0
  16. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
  17. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  18. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  19. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +1 -0
  20. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
  21. package/dist/components/new-trace-viewer/components/split-pane.js +2 -3
  22. package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -1
  23. package/dist/components/new-trace-viewer/components/timeline.d.ts +10 -11
  24. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  25. package/dist/components/new-trace-viewer/components/timeline.js +57 -53
  26. package/dist/components/new-trace-viewer/components/timeline.js.map +1 -1
  27. package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
  28. package/dist/components/new-trace-viewer/context.js +5 -7
  29. package/dist/components/new-trace-viewer/context.js.map +1 -1
  30. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -1
  31. package/dist/components/new-trace-viewer/detail-panel.js +2 -1
  32. package/dist/components/new-trace-viewer/detail-panel.js.map +1 -1
  33. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  34. package/dist/components/new-trace-viewer/trace-viewer.js +37 -17
  35. package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -1
  36. package/dist/components/new-trace-viewer/utils.d.ts +3 -8
  37. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  38. package/dist/components/new-trace-viewer/utils.js +12 -65
  39. package/dist/components/new-trace-viewer/utils.js.map +1 -1
  40. package/dist/components/trace-viewer/util/timing.d.ts +1 -1
  41. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  42. package/dist/components/trace-viewer/util/timing.js +5 -8
  43. package/dist/components/trace-viewer/util/timing.js.map +1 -1
  44. package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
  45. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  46. package/dist/components/workflow-traces/trace-span-construction.js +16 -3
  47. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  48. package/dist/lib/trace-builder.d.ts.map +1 -1
  49. package/dist/lib/trace-builder.js +2 -1
  50. package/dist/lib/trace-builder.js.map +1 -1
  51. package/dist/lib/utils.d.ts +9 -7
  52. package/dist/lib/utils.d.ts.map +1 -1
  53. package/dist/lib/utils.js +27 -27
  54. package/dist/lib/utils.js.map +1 -1
  55. package/package.json +2 -2
  56. package/src/components/new-trace-viewer/components/event-list.tsx +27 -20
  57. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  58. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  59. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  60. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  61. package/src/components/new-trace-viewer/components/split-pane.tsx +2 -3
  62. package/src/components/new-trace-viewer/components/timeline.tsx +119 -115
  63. package/src/components/new-trace-viewer/context.tsx +7 -9
  64. package/src/components/new-trace-viewer/detail-panel.tsx +2 -1
  65. package/src/components/new-trace-viewer/trace-viewer.tsx +120 -60
  66. package/src/components/new-trace-viewer/utils.ts +19 -93
  67. package/src/components/trace-viewer/util/timing.ts +5 -9
  68. package/src/components/workflow-traces/trace-span-construction.ts +22 -4
  69. package/src/lib/trace-builder.ts +2 -1
  70. package/src/lib/utils.ts +29 -29
@@ -6,7 +6,7 @@ export declare function getHighResInMs([seconds, nanoseconds]: [
6
6
  ]): number;
7
7
  export declare function getMsInHighRes(ms: number): [number, number];
8
8
  /**
9
- * Formats a duration for timeline display (compact single-unit format).
9
+ * Formats a duration for timeline display (compact format).
10
10
  * @deprecated Use formatDuration(ms, true) instead.
11
11
  */
12
12
  export declare const formatDurationForTimeline: (ms: number) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../../../src/components/trace-viewer/util/timing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,wBAAgB,cAAc,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE;IACrD,MAAM;IACN,MAAM;CACP,GAAG,MAAM,CAET;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAE3D;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GAAI,IAAI,MAAM,KAAG,MAC7B,CAAC;AAO3B,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM3D"}
1
+ {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../../../src/components/trace-viewer/util/timing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,wBAAgB,cAAc,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE;IACrD,MAAM;IACN,MAAM;CACP,GAAG,MAAM,CAET;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAE3D;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GAAI,IAAI,MAAM,KAAG,MAC7B,CAAC;AAE3B,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM3D"}
@@ -7,19 +7,16 @@ export function getMsInHighRes(ms) {
7
7
  return [Math.floor(ms / 1000), (ms % 1000) * 1000];
8
8
  }
9
9
  /**
10
- * Formats a duration for timeline display (compact single-unit format).
10
+ * Formats a duration for timeline display (compact format).
11
11
  * @deprecated Use formatDuration(ms, true) instead.
12
12
  */
13
13
  export const formatDurationForTimeline = (ms) => formatDuration(ms, true);
14
- const timeSelectionFormatter = new Intl.NumberFormat(undefined, {
15
- minimumFractionDigits: 2,
16
- maximumFractionDigits: 2,
17
- });
18
14
  export function formatTimeSelection(ms) {
19
- if (ms >= 1000) {
20
- return `${timeSelectionFormatter.format(ms / 1000)}s`;
15
+ const roundedMs = Math.round(ms);
16
+ if (roundedMs < 1000) {
17
+ return `${roundedMs}ms`;
21
18
  }
22
- return `${timeSelectionFormatter.format(ms)}ms`;
19
+ return `${Math.round(ms / 1000)}s`;
23
20
  }
24
21
  /**
25
22
  * Format an epoch-millisecond timestamp as a local wall-clock time.
@@ -1 +1 @@
1
- {"version":3,"file":"timing.js","sourceRoot":"","sources":["../../../../src/components/trace-viewer/util/timing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,MAAM,UAAU,cAAc,CAAC,CAAC,OAAO,EAAE,WAAW,CAGnD;IACC,OAAO,OAAO,GAAG,IAAI,GAAG,WAAW,GAAG,GAAG,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,EAAU,EAAU,EAAE,CAC9D,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AAE3B,MAAM,sBAAsB,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE;IAC9D,qBAAqB,EAAE,CAAC;IACxB,qBAAqB,EAAE,CAAC;CACzB,CAAC,CAAC;AAEH,MAAM,UAAU,mBAAmB,CAAC,EAAU;IAC5C,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;QACf,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC;IACxD,CAAC;IACD,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["import { formatDuration } from '../../../lib/utils';\n\nexport { formatDuration };\n\nexport function getHighResInMs([seconds, nanoseconds]: [\n number,\n number,\n]): number {\n return seconds * 1000 + nanoseconds / 1e6;\n}\n\nexport function getMsInHighRes(ms: number): [number, number] {\n return [Math.floor(ms / 1000), (ms % 1000) * 1000];\n}\n\n/**\n * Formats a duration for timeline display (compact single-unit format).\n * @deprecated Use formatDuration(ms, true) instead.\n */\nexport const formatDurationForTimeline = (ms: number): string =>\n formatDuration(ms, true);\n\nconst timeSelectionFormatter = new Intl.NumberFormat(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n});\n\nexport function formatTimeSelection(ms: number): string {\n if (ms >= 1000) {\n return `${timeSelectionFormatter.format(ms / 1000)}s`;\n }\n return `${timeSelectionFormatter.format(ms)}ms`;\n}\n\n/**\n * Format an epoch-millisecond timestamp as a local wall-clock time.\n * Returns a compact HH:MM:SS.mmm string (24-hour format).\n */\nexport function formatWallClockTime(epochMs: number): string {\n const d = new Date(epochMs);\n const h = String(d.getHours()).padStart(2, '0');\n const m = String(d.getMinutes()).padStart(2, '0');\n const s = String(d.getSeconds()).padStart(2, '0');\n return `${h}:${m}:${s}`;\n}\n"]}
1
+ {"version":3,"file":"timing.js","sourceRoot":"","sources":["../../../../src/components/trace-viewer/util/timing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,MAAM,UAAU,cAAc,CAAC,CAAC,OAAO,EAAE,WAAW,CAGnD;IACC,OAAO,OAAO,GAAG,IAAI,GAAG,WAAW,GAAG,GAAG,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,EAAU,EAAU,EAAE,CAC9D,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AAE3B,MAAM,UAAU,mBAAmB,CAAC,EAAU;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjC,IAAI,SAAS,GAAG,IAAI,EAAE,CAAC;QACrB,OAAO,GAAG,SAAS,IAAI,CAAC;IAC1B,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1B,CAAC","sourcesContent":["import { formatDuration } from '../../../lib/utils';\n\nexport { formatDuration };\n\nexport function getHighResInMs([seconds, nanoseconds]: [\n number,\n number,\n]): number {\n return seconds * 1000 + nanoseconds / 1e6;\n}\n\nexport function getMsInHighRes(ms: number): [number, number] {\n return [Math.floor(ms / 1000), (ms % 1000) * 1000];\n}\n\n/**\n * Formats a duration for timeline display (compact format).\n * @deprecated Use formatDuration(ms, true) instead.\n */\nexport const formatDurationForTimeline = (ms: number): string =>\n formatDuration(ms, true);\n\nexport function formatTimeSelection(ms: number): string {\n const roundedMs = Math.round(ms);\n if (roundedMs < 1000) {\n return `${roundedMs}ms`;\n }\n return `${Math.round(ms / 1000)}s`;\n}\n\n/**\n * Format an epoch-millisecond timestamp as a local wall-clock time.\n * Returns a compact HH:MM:SS.mmm string (24-hour format).\n */\nexport function formatWallClockTime(epochMs: number): string {\n const d = new Date(epochMs);\n const h = String(d.getHours()).padStart(2, '0');\n const m = String(d.getMinutes()).padStart(2, '0');\n const s = String(d.getSeconds()).padStart(2, '0');\n return `${h}:${m}:${s}`;\n}\n"]}
@@ -16,13 +16,13 @@ export declare const waitEventsToWaitEntity: (events: Event[]) => {
16
16
  waitId: string;
17
17
  runId: string;
18
18
  createdAt: Date;
19
- resumeAt: Date;
19
+ resumeAt?: Date;
20
20
  completedAt?: Date;
21
21
  } | null;
22
22
  /**
23
23
  * Converts a workflow Wait to an OpenTelemetry Span
24
24
  */
25
- export declare function waitToSpan(events: Event[], maxEndTime: Date): Span | null;
25
+ export declare function waitToSpan(events: Event[], maxEndTime: Date, fallbackEndTime?: Date): Span | null;
26
26
  export declare const stepEventsToStepEntity: (events: Event[]) => {
27
27
  stepId: string;
28
28
  runId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"trace-span-construction.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-traces/trace-span-construction.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAI7D,eAAO,MAAM,gBAAgB;;;CAG5B,CAAC;AAiBF;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,WAAW,UAAO,GACjB,SAAS,EAAE,CAgBb;AAED,eAAO,MAAM,sBAAsB,GACjC,QAAQ,KAAK,EAAE,KACd;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,IAAI,CAAC;IACf,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB,GAAG,IAeH,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CA6BzE;AAED,eAAO,MAAM,sBAAsB,GACjC,QAAQ,KAAK,EAAE,KACd;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IA2DH,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAsD7E;AAED,eAAO,MAAM,sBAAsB,GACjC,QAAQ,KAAK,EAAE,KACd;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB,GAAG,IAuBH,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CA6B7E;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,KAAK,EAAE,EAClB,OAAO,CAAC,EAAE,IAAI,GACb,IAAI,CAuCN"}
1
+ {"version":3,"file":"trace-span-construction.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-traces/trace-span-construction.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAI7D,eAAO,MAAM,gBAAgB;;;CAG5B,CAAC;AAiBF;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,WAAW,UAAO,GACjB,SAAS,EAAE,CAgBb;AAED,eAAO,MAAM,sBAAsB,GACjC,QAAQ,KAAK,EAAE,KACd;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB,GAAG,IAiBH,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,KAAK,EAAE,EACf,UAAU,EAAE,IAAI,EAChB,eAAe,OAAa,GAC3B,IAAI,GAAG,IAAI,CAyCb;AAED,eAAO,MAAM,sBAAsB,GACjC,QAAQ,KAAK,EAAE,KACd;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IA2DH,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAsD7E;AAED,eAAO,MAAM,sBAAsB,GACjC,QAAQ,KAAK,EAAE,KACd;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB,GAAG,IAuBH,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CA6B7E;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,KAAK,EAAE,EAClB,OAAO,CAAC,EAAE,IAAI,GACb,IAAI,CAuCN"}
@@ -51,20 +51,33 @@ export const waitEventsToWaitEntity = (events) => {
51
51
  waitId: startEvent.correlationId,
52
52
  runId: startEvent.runId,
53
53
  createdAt: startEvent.createdAt,
54
- resumeAt: startEvent.eventData?.resumeAt,
54
+ resumeAt: startEvent.eventData?.resumeAt
55
+ ? new Date(startEvent.eventData.resumeAt)
56
+ : undefined,
55
57
  completedAt: completedEvent?.createdAt,
56
58
  };
57
59
  };
58
60
  /**
59
61
  * Converts a workflow Wait to an OpenTelemetry Span
60
62
  */
61
- export function waitToSpan(events, maxEndTime) {
63
+ export function waitToSpan(events, maxEndTime, fallbackEndTime = maxEndTime) {
62
64
  const wait = waitEventsToWaitEntity(events);
63
65
  if (!wait) {
64
66
  return null;
65
67
  }
66
68
  const startTime = wait.createdAt;
67
- const endTime = wait.completedAt ?? maxEndTime;
69
+ const startMs = startTime.getTime();
70
+ let endTime = wait.completedAt;
71
+ if (!endTime) {
72
+ const fallbackCap = wait.resumeAt && wait.resumeAt.getTime() < fallbackEndTime.getTime()
73
+ ? wait.resumeAt
74
+ : fallbackEndTime;
75
+ endTime =
76
+ maxEndTime.getTime() > startMs &&
77
+ maxEndTime.getTime() < fallbackCap.getTime()
78
+ ? maxEndTime
79
+ : fallbackCap;
80
+ }
68
81
  const start = dateToOtelTime(startTime);
69
82
  const end = dateToOtelTime(endTime);
70
83
  const duration = calculateDuration(startTime, endTime);
@@ -1 +1 @@
1
- {"version":3,"file":"trace-span-construction.js","sourceRoot":"","sources":["../../../src/components/workflow-traces/trace-span-construction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Functions for constructing OpenTelemetry spans from workflow entities\n */\n\nimport { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';\nimport type { Event, WorkflowRun } from '@workflow/world';\nimport type { Span, SpanEvent } from '../trace-viewer/types';\nimport { shouldShowVerticalLine } from './event-colors';\nimport { calculateDuration, dateToOtelTime } from './trace-time-utils';\n\nexport const WORKFLOW_LIBRARY = {\n name: 'workflow-development-kit',\n version: '4.0.0',\n};\n\n/**\n * Event types that should be displayed as visual markers in the trace viewer\n */\nconst MARKER_EVENT_TYPES: Set<Event['eventType']> = new Set([\n 'hook_created',\n 'hook_received',\n 'hook_disposed',\n 'step_started',\n 'step_retrying',\n 'step_failed',\n 'run_failed',\n 'wait_created',\n 'wait_completed',\n]);\n\n/**\n * Convert workflow events to span events\n * Only includes events that should be displayed as markers\n */\nexport function convertEventsToSpanEvents(\n events: Event[],\n filterTypes = true\n): SpanEvent[] {\n return events\n .filter((event) =>\n filterTypes ? MARKER_EVENT_TYPES.has(event.eventType) : true\n )\n .map((event) => ({\n name: event.eventType,\n timestamp: dateToOtelTime(event.createdAt),\n attributes: {\n eventId: event.eventId,\n correlationId: event.correlationId,\n eventData: 'eventData' in event ? event.eventData : undefined,\n },\n // Control whether to show vertical line in timeline\n showVerticalLine: shouldShowVerticalLine(event.eventType),\n }));\n}\n\nexport const waitEventsToWaitEntity = (\n events: Event[]\n): {\n waitId: string;\n runId: string;\n createdAt: Date;\n resumeAt: Date;\n completedAt?: Date;\n} | null => {\n const startEvent = events.find((event) => event.eventType === 'wait_created');\n if (!startEvent) {\n return null;\n }\n const completedEvent = events.find(\n (event) => event.eventType === 'wait_completed'\n );\n return {\n waitId: startEvent.correlationId,\n runId: startEvent.runId,\n createdAt: startEvent.createdAt,\n resumeAt: startEvent.eventData?.resumeAt,\n completedAt: completedEvent?.createdAt,\n };\n};\n\n/**\n * Converts a workflow Wait to an OpenTelemetry Span\n */\nexport function waitToSpan(events: Event[], maxEndTime: Date): Span | null {\n const wait = waitEventsToWaitEntity(events);\n if (!wait) {\n return null;\n }\n const startTime = wait.createdAt;\n const endTime = wait.completedAt ?? maxEndTime;\n const start = dateToOtelTime(startTime);\n const end = dateToOtelTime(endTime);\n const duration = calculateDuration(startTime, endTime);\n const spanEvents = convertEventsToSpanEvents(events, false);\n return {\n spanId: wait.waitId,\n name: 'sleep',\n kind: 1, // INTERNAL span kind\n resource: 'sleep',\n library: WORKFLOW_LIBRARY,\n status: { code: 0 },\n traceFlags: 1,\n attributes: {\n resource: 'sleep' as const,\n data: wait, // wait is a plain object built from events, no non-cloneable types\n },\n links: [],\n events: spanEvents,\n duration,\n startTime: start,\n endTime: end,\n };\n}\n\nexport const stepEventsToStepEntity = (\n events: Event[]\n): {\n stepId: string;\n runId: string;\n stepName: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n attempt: number;\n createdAt: Date;\n updatedAt: Date;\n startedAt?: Date;\n completedAt?: Date;\n specVersion?: number;\n} | null => {\n const createdEvent = events.find(\n (event) => event.eventType === 'step_created'\n );\n\n // V1 runs don't emit step_created events. Fall back to the earliest event\n // in the group so we can still build a step span.\n const anchorEvent = createdEvent ?? events[0];\n if (!anchorEvent) {\n return null;\n }\n\n // Walk events in order to derive status, attempt count, and timestamps.\n // Handles both step_retrying and consecutive step_started as retry signals.\n let status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' =\n 'pending';\n let attempt = 0;\n let startedAt: Date | undefined;\n let completedAt: Date | undefined;\n\n for (const e of events) {\n switch (e.eventType) {\n case 'step_started':\n status = 'running';\n attempt += 1;\n if (!startedAt) startedAt = e.createdAt;\n completedAt = undefined;\n break;\n case 'step_completed':\n status = 'completed';\n completedAt = e.createdAt;\n break;\n case 'step_failed':\n status = 'failed';\n completedAt = e.createdAt;\n break;\n case 'step_retrying':\n status = 'pending';\n completedAt = undefined;\n break;\n }\n }\n\n // Ensure at least attempt 1 if we never saw step_started\n if (attempt === 0) attempt = 1;\n\n const lastEvent = events[events.length - 1];\n return {\n stepId: anchorEvent.correlationId ?? '',\n runId: anchorEvent.runId,\n stepName: createdEvent?.eventData?.stepName ?? '',\n status,\n attempt,\n createdAt: anchorEvent.createdAt,\n updatedAt: lastEvent?.createdAt ?? anchorEvent.createdAt,\n startedAt,\n completedAt,\n specVersion: anchorEvent.specVersion,\n };\n};\n\n/**\n * Converts step events to an OpenTelemetry Span\n */\nexport function stepToSpan(stepEvents: Event[], maxEndTime: Date): Span | null {\n const step = stepEventsToStepEntity(stepEvents);\n if (!step) {\n return null;\n }\n const parsedName = parseStepName(String(step.stepName));\n\n const attributes = {\n resource: 'step' as const,\n data: step,\n };\n\n const resource = 'step';\n const endTime = new Date(step.completedAt ?? maxEndTime);\n\n // Include ALL correlated events on the span so the sidebar detail view\n // can display them. The timeline uses the `showVerticalLine` flag to\n // determine which events appear as markers.\n const events = convertEventsToSpanEvents(stepEvents, false);\n\n // Use createdAt as span start time, with activeStartTime for when execution began\n // This allows visualization of the \"queued\" period before execution\n const spanStartTime = new Date(step.createdAt);\n let activeStartTime = step.startedAt ? new Date(step.startedAt) : undefined;\n const firstStartEvent = stepEvents.find(\n (event) => event.eventType === 'step_started'\n );\n if (firstStartEvent) {\n // `step.startedAt` is the server-side creation timestamp, and `event.createdAt` is\n // the client-side creation timestamp. For now, to align the event marker with the\n // line we show for step.startedAt, we overwrite here to always use client-side time.\n activeStartTime = new Date(firstStartEvent.createdAt);\n }\n\n return {\n spanId: String(step.stepId),\n name: parsedName?.shortName ?? String(step.stepName),\n kind: 1, // INTERNAL span kind\n resource,\n library: WORKFLOW_LIBRARY,\n status: { code: 0 },\n traceFlags: 1,\n attributes,\n links: [],\n events,\n startTime: dateToOtelTime(spanStartTime),\n endTime: dateToOtelTime(endTime),\n duration: calculateDuration(spanStartTime, endTime),\n // Only set activeStartTime if it differs from startTime (i.e., there was a queued period)\n activeStartTime:\n activeStartTime && activeStartTime.getTime() > spanStartTime.getTime()\n ? dateToOtelTime(activeStartTime)\n : undefined,\n };\n}\n\nexport const hookEventsToHookEntity = (\n events: Event[]\n): {\n hookId: string;\n runId: string;\n token?: string;\n createdAt: Date;\n receivedCount: number;\n lastReceivedAt?: Date;\n disposedAt?: Date;\n} | null => {\n const createdEvent = events.find(\n (event) => event.eventType === 'hook_created'\n );\n if (!createdEvent) {\n return null;\n }\n const receivedEvents = events.filter(\n (event) => event.eventType === 'hook_received'\n );\n const disposedEvents = events.filter(\n (event) => event.eventType === 'hook_disposed'\n );\n const lastReceivedEvent = receivedEvents.at(-1);\n return {\n hookId: createdEvent.correlationId,\n runId: createdEvent.runId,\n token: createdEvent.eventData?.token,\n createdAt: createdEvent.createdAt,\n receivedCount: receivedEvents.length,\n lastReceivedAt: lastReceivedEvent?.createdAt || undefined,\n disposedAt: disposedEvents.at(-1)?.createdAt || undefined,\n };\n};\n\n/**\n * Converts a workflow Hook to an OpenTelemetry Span\n */\nexport function hookToSpan(hookEvents: Event[], maxEndTime: Date): Span | null {\n const hook = hookEventsToHookEntity(hookEvents);\n if (!hook) {\n return null;\n }\n\n // Convert hook-related events to span events\n const events = convertEventsToSpanEvents(hookEvents, false);\n\n const endTime = hook.disposedAt || maxEndTime;\n\n return {\n spanId: String(hook.hookId),\n name: String(hook.hookId),\n kind: 1, // INTERNAL span kind\n resource: 'hook',\n library: WORKFLOW_LIBRARY,\n status: { code: 1 },\n traceFlags: 1,\n attributes: {\n resource: 'hook' as const,\n data: hook,\n },\n links: [],\n events,\n startTime: dateToOtelTime(hook.createdAt),\n endTime: dateToOtelTime(endTime),\n duration: calculateDuration(hook.createdAt, endTime),\n };\n}\n\n/**\n * Creates a root span for the workflow run\n */\nexport function runToSpan(\n run: WorkflowRun,\n runEvents: Event[],\n nowTime?: Date\n): Span {\n const now = nowTime ?? new Date();\n\n // Only embed identification fields — not the full object with\n // input/output/error which may contain non-cloneable types.\n const { input: _i, output: _o, error: _e, ...runIdentity } = run;\n const attributes = {\n resource: 'run' as const,\n data: runIdentity,\n };\n\n // Use createdAt as span start time, with activeStartTime for when execution began\n const spanStartTime = new Date(run.createdAt);\n const activeStartTime = run.startedAt ? new Date(run.startedAt) : undefined;\n const endTime = run.completedAt ?? now;\n\n // Convert run-level events to span events\n const events = convertEventsToSpanEvents(runEvents, false);\n\n return {\n spanId: String(run.runId),\n name: String(parseWorkflowName(run.workflowName)?.shortName ?? '?'),\n kind: 1, // INTERNAL span kind\n resource: 'run',\n library: WORKFLOW_LIBRARY,\n status: { code: 0 },\n traceFlags: 1,\n attributes,\n links: [],\n events,\n startTime: dateToOtelTime(spanStartTime),\n endTime: dateToOtelTime(endTime),\n duration: calculateDuration(spanStartTime, endTime),\n // Only set activeStartTime if it differs from startTime (i.e., there was a queued period)\n activeStartTime:\n activeStartTime && activeStartTime.getTime() > spanStartTime.getTime()\n ? dateToOtelTime(activeStartTime)\n : undefined,\n };\n}\n"]}
1
+ {"version":3,"file":"trace-span-construction.js","sourceRoot":"","sources":["../../../src/components/workflow-traces/trace-span-construction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Functions for constructing OpenTelemetry spans from workflow entities\n */\n\nimport { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';\nimport type { Event, WorkflowRun } from '@workflow/world';\nimport type { Span, SpanEvent } from '../trace-viewer/types';\nimport { shouldShowVerticalLine } from './event-colors';\nimport { calculateDuration, dateToOtelTime } from './trace-time-utils';\n\nexport const WORKFLOW_LIBRARY = {\n name: 'workflow-development-kit',\n version: '4.0.0',\n};\n\n/**\n * Event types that should be displayed as visual markers in the trace viewer\n */\nconst MARKER_EVENT_TYPES: Set<Event['eventType']> = new Set([\n 'hook_created',\n 'hook_received',\n 'hook_disposed',\n 'step_started',\n 'step_retrying',\n 'step_failed',\n 'run_failed',\n 'wait_created',\n 'wait_completed',\n]);\n\n/**\n * Convert workflow events to span events\n * Only includes events that should be displayed as markers\n */\nexport function convertEventsToSpanEvents(\n events: Event[],\n filterTypes = true\n): SpanEvent[] {\n return events\n .filter((event) =>\n filterTypes ? MARKER_EVENT_TYPES.has(event.eventType) : true\n )\n .map((event) => ({\n name: event.eventType,\n timestamp: dateToOtelTime(event.createdAt),\n attributes: {\n eventId: event.eventId,\n correlationId: event.correlationId,\n eventData: 'eventData' in event ? event.eventData : undefined,\n },\n // Control whether to show vertical line in timeline\n showVerticalLine: shouldShowVerticalLine(event.eventType),\n }));\n}\n\nexport const waitEventsToWaitEntity = (\n events: Event[]\n): {\n waitId: string;\n runId: string;\n createdAt: Date;\n resumeAt?: Date;\n completedAt?: Date;\n} | null => {\n const startEvent = events.find((event) => event.eventType === 'wait_created');\n if (!startEvent) {\n return null;\n }\n const completedEvent = events.find(\n (event) => event.eventType === 'wait_completed'\n );\n return {\n waitId: startEvent.correlationId,\n runId: startEvent.runId,\n createdAt: startEvent.createdAt,\n resumeAt: startEvent.eventData?.resumeAt\n ? new Date(startEvent.eventData.resumeAt)\n : undefined,\n completedAt: completedEvent?.createdAt,\n };\n};\n\n/**\n * Converts a workflow Wait to an OpenTelemetry Span\n */\nexport function waitToSpan(\n events: Event[],\n maxEndTime: Date,\n fallbackEndTime = maxEndTime\n): Span | null {\n const wait = waitEventsToWaitEntity(events);\n if (!wait) {\n return null;\n }\n const startTime = wait.createdAt;\n const startMs = startTime.getTime();\n let endTime = wait.completedAt;\n if (!endTime) {\n const fallbackCap =\n wait.resumeAt && wait.resumeAt.getTime() < fallbackEndTime.getTime()\n ? wait.resumeAt\n : fallbackEndTime;\n endTime =\n maxEndTime.getTime() > startMs &&\n maxEndTime.getTime() < fallbackCap.getTime()\n ? maxEndTime\n : fallbackCap;\n }\n const start = dateToOtelTime(startTime);\n const end = dateToOtelTime(endTime);\n const duration = calculateDuration(startTime, endTime);\n const spanEvents = convertEventsToSpanEvents(events, false);\n return {\n spanId: wait.waitId,\n name: 'sleep',\n kind: 1, // INTERNAL span kind\n resource: 'sleep',\n library: WORKFLOW_LIBRARY,\n status: { code: 0 },\n traceFlags: 1,\n attributes: {\n resource: 'sleep' as const,\n data: wait, // wait is a plain object built from events, no non-cloneable types\n },\n links: [],\n events: spanEvents,\n duration,\n startTime: start,\n endTime: end,\n };\n}\n\nexport const stepEventsToStepEntity = (\n events: Event[]\n): {\n stepId: string;\n runId: string;\n stepName: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n attempt: number;\n createdAt: Date;\n updatedAt: Date;\n startedAt?: Date;\n completedAt?: Date;\n specVersion?: number;\n} | null => {\n const createdEvent = events.find(\n (event) => event.eventType === 'step_created'\n );\n\n // V1 runs don't emit step_created events. Fall back to the earliest event\n // in the group so we can still build a step span.\n const anchorEvent = createdEvent ?? events[0];\n if (!anchorEvent) {\n return null;\n }\n\n // Walk events in order to derive status, attempt count, and timestamps.\n // Handles both step_retrying and consecutive step_started as retry signals.\n let status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' =\n 'pending';\n let attempt = 0;\n let startedAt: Date | undefined;\n let completedAt: Date | undefined;\n\n for (const e of events) {\n switch (e.eventType) {\n case 'step_started':\n status = 'running';\n attempt += 1;\n if (!startedAt) startedAt = e.createdAt;\n completedAt = undefined;\n break;\n case 'step_completed':\n status = 'completed';\n completedAt = e.createdAt;\n break;\n case 'step_failed':\n status = 'failed';\n completedAt = e.createdAt;\n break;\n case 'step_retrying':\n status = 'pending';\n completedAt = undefined;\n break;\n }\n }\n\n // Ensure at least attempt 1 if we never saw step_started\n if (attempt === 0) attempt = 1;\n\n const lastEvent = events[events.length - 1];\n return {\n stepId: anchorEvent.correlationId ?? '',\n runId: anchorEvent.runId,\n stepName: createdEvent?.eventData?.stepName ?? '',\n status,\n attempt,\n createdAt: anchorEvent.createdAt,\n updatedAt: lastEvent?.createdAt ?? anchorEvent.createdAt,\n startedAt,\n completedAt,\n specVersion: anchorEvent.specVersion,\n };\n};\n\n/**\n * Converts step events to an OpenTelemetry Span\n */\nexport function stepToSpan(stepEvents: Event[], maxEndTime: Date): Span | null {\n const step = stepEventsToStepEntity(stepEvents);\n if (!step) {\n return null;\n }\n const parsedName = parseStepName(String(step.stepName));\n\n const attributes = {\n resource: 'step' as const,\n data: step,\n };\n\n const resource = 'step';\n const endTime = new Date(step.completedAt ?? maxEndTime);\n\n // Include ALL correlated events on the span so the sidebar detail view\n // can display them. The timeline uses the `showVerticalLine` flag to\n // determine which events appear as markers.\n const events = convertEventsToSpanEvents(stepEvents, false);\n\n // Use createdAt as span start time, with activeStartTime for when execution began\n // This allows visualization of the \"queued\" period before execution\n const spanStartTime = new Date(step.createdAt);\n let activeStartTime = step.startedAt ? new Date(step.startedAt) : undefined;\n const firstStartEvent = stepEvents.find(\n (event) => event.eventType === 'step_started'\n );\n if (firstStartEvent) {\n // `step.startedAt` is the server-side creation timestamp, and `event.createdAt` is\n // the client-side creation timestamp. For now, to align the event marker with the\n // line we show for step.startedAt, we overwrite here to always use client-side time.\n activeStartTime = new Date(firstStartEvent.createdAt);\n }\n\n return {\n spanId: String(step.stepId),\n name: parsedName?.shortName ?? String(step.stepName),\n kind: 1, // INTERNAL span kind\n resource,\n library: WORKFLOW_LIBRARY,\n status: { code: 0 },\n traceFlags: 1,\n attributes,\n links: [],\n events,\n startTime: dateToOtelTime(spanStartTime),\n endTime: dateToOtelTime(endTime),\n duration: calculateDuration(spanStartTime, endTime),\n // Only set activeStartTime if it differs from startTime (i.e., there was a queued period)\n activeStartTime:\n activeStartTime && activeStartTime.getTime() > spanStartTime.getTime()\n ? dateToOtelTime(activeStartTime)\n : undefined,\n };\n}\n\nexport const hookEventsToHookEntity = (\n events: Event[]\n): {\n hookId: string;\n runId: string;\n token?: string;\n createdAt: Date;\n receivedCount: number;\n lastReceivedAt?: Date;\n disposedAt?: Date;\n} | null => {\n const createdEvent = events.find(\n (event) => event.eventType === 'hook_created'\n );\n if (!createdEvent) {\n return null;\n }\n const receivedEvents = events.filter(\n (event) => event.eventType === 'hook_received'\n );\n const disposedEvents = events.filter(\n (event) => event.eventType === 'hook_disposed'\n );\n const lastReceivedEvent = receivedEvents.at(-1);\n return {\n hookId: createdEvent.correlationId,\n runId: createdEvent.runId,\n token: createdEvent.eventData?.token,\n createdAt: createdEvent.createdAt,\n receivedCount: receivedEvents.length,\n lastReceivedAt: lastReceivedEvent?.createdAt || undefined,\n disposedAt: disposedEvents.at(-1)?.createdAt || undefined,\n };\n};\n\n/**\n * Converts a workflow Hook to an OpenTelemetry Span\n */\nexport function hookToSpan(hookEvents: Event[], maxEndTime: Date): Span | null {\n const hook = hookEventsToHookEntity(hookEvents);\n if (!hook) {\n return null;\n }\n\n // Convert hook-related events to span events\n const events = convertEventsToSpanEvents(hookEvents, false);\n\n const endTime = hook.disposedAt || maxEndTime;\n\n return {\n spanId: String(hook.hookId),\n name: String(hook.hookId),\n kind: 1, // INTERNAL span kind\n resource: 'hook',\n library: WORKFLOW_LIBRARY,\n status: { code: 1 },\n traceFlags: 1,\n attributes: {\n resource: 'hook' as const,\n data: hook,\n },\n links: [],\n events,\n startTime: dateToOtelTime(hook.createdAt),\n endTime: dateToOtelTime(endTime),\n duration: calculateDuration(hook.createdAt, endTime),\n };\n}\n\n/**\n * Creates a root span for the workflow run\n */\nexport function runToSpan(\n run: WorkflowRun,\n runEvents: Event[],\n nowTime?: Date\n): Span {\n const now = nowTime ?? new Date();\n\n // Only embed identification fields — not the full object with\n // input/output/error which may contain non-cloneable types.\n const { input: _i, output: _o, error: _e, ...runIdentity } = run;\n const attributes = {\n resource: 'run' as const,\n data: runIdentity,\n };\n\n // Use createdAt as span start time, with activeStartTime for when execution began\n const spanStartTime = new Date(run.createdAt);\n const activeStartTime = run.startedAt ? new Date(run.startedAt) : undefined;\n const endTime = run.completedAt ?? now;\n\n // Convert run-level events to span events\n const events = convertEventsToSpanEvents(runEvents, false);\n\n return {\n spanId: String(run.runId),\n name: String(parseWorkflowName(run.workflowName)?.shortName ?? '?'),\n kind: 1, // INTERNAL span kind\n resource: 'run',\n library: WORKFLOW_LIBRARY,\n status: { code: 0 },\n traceFlags: 1,\n attributes,\n links: [],\n events,\n startTime: dateToOtelTime(spanStartTime),\n endTime: dateToOtelTime(endTime),\n duration: calculateDuration(spanStartTime, endTime),\n // Only set activeStartTime if it differs from startTime (i.e., there was a queued period)\n activeStartTime:\n activeStartTime && activeStartTime.getTime() > spanStartTime.getTime()\n ? dateToOtelTime(activeStartTime)\n : undefined,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"trace-builder.d.ts","sourceRoot":"","sources":["../../src/lib/trace-builder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kCAAkC,CAAC;AAc7D,eAAO,MAAM,WAAW,GAAI,WAAW,MAAM,YAAkC,CAAC;AAEhF,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,mDACkB,CAAC;AAEjE,eAAO,MAAM,oBAAoB,GAAI,WAAW,MAAM,oEAGvB,CAAC;AAMhC,MAAM,MAAM,aAAa,GAAG;IAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACrC,cAAc,EAAE,KAAK,EAAE,CAAC;IACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAClC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;CAClC,CAAC;AAeF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,CAgCvE;AAqED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAAE,CAAC;IAClE,iEAAiE;IACjE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,KAAK,EAAE,EACf,GAAG,EAAE,IAAI,GACR,aAAa,CA6Bf"}
1
+ {"version":3,"file":"trace-builder.d.ts","sourceRoot":"","sources":["../../src/lib/trace-builder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kCAAkC,CAAC;AAc7D,eAAO,MAAM,WAAW,GAAI,WAAW,MAAM,YAAkC,CAAC;AAEhF,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,mDACkB,CAAC;AAEjE,eAAO,MAAM,oBAAoB,GAAI,WAAW,MAAM,oEAGvB,CAAC;AAMhC,MAAM,MAAM,aAAa,GAAG;IAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACrC,cAAc,EAAE,KAAK,EAAE,CAAC;IACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAClC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;CAClC,CAAC;AAeF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,CAgCvE;AAsED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAAE,CAAC;IAClE,iEAAiE;IACjE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,KAAK,EAAE,EACf,GAAG,EAAE,IAAI,GACR,aAAa,CA6Bf"}
@@ -72,6 +72,7 @@ function buildSpans(run, groupedEvents, now, latestKnownTime) {
72
72
  // unknown territory. Even when the run is completed, we may not have loaded
73
73
  // all events yet, so always use the latest event we've actually seen.
74
74
  const childMaxEnd = latestKnownTime;
75
+ const runMaxEnd = run.completedAt ?? now;
75
76
  const stepSpans = Array.from(groupedEvents.eventsByStepId.values())
76
77
  .map((events) => stepToSpan(events, childMaxEnd))
77
78
  .filter((span) => span !== null);
@@ -79,7 +80,7 @@ function buildSpans(run, groupedEvents, now, latestKnownTime) {
79
80
  .map((events) => hookToSpan(events, childMaxEnd))
80
81
  .filter((span) => span !== null);
81
82
  const waitSpans = Array.from(groupedEvents.timerEvents.values())
82
- .map((events) => waitToSpan(events, childMaxEnd))
83
+ .map((events) => waitToSpan(events, childMaxEnd, runMaxEnd))
83
84
  .filter((span) => span !== null);
84
85
  return {
85
86
  runSpan: runToSpan(run, groupedEvents.runLevelEvents, now),
@@ -1 +1 @@
1
- {"version":3,"file":"trace-builder.js","sourceRoot":"","sources":["../../src/lib/trace-builder.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Builds a complete trace from a WorkflowRun and its Events.\n *\n * This module groups raw events by correlation ID and entity type,\n * converts each group into an OpenTelemetry-style Span, and returns\n * a fully-formed Trace ready for the trace viewer.\n */\n\nimport type { Event, WorkflowRun } from '@workflow/world';\nimport type { Span } from '../components/trace-viewer/types';\nimport {\n hookToSpan,\n runToSpan,\n stepToSpan,\n waitToSpan,\n WORKFLOW_LIBRARY,\n} from '../components/workflow-traces/trace-span-construction';\nimport { otelTimeToMs } from '../components/workflow-traces/trace-time-utils';\n\n// ---------------------------------------------------------------------------\n// Event type classifiers\n// ---------------------------------------------------------------------------\n\nexport const isStepEvent = (eventType: string) => eventType.startsWith('step_');\n\nexport const isTimerEvent = (eventType: string) =>\n eventType === 'wait_created' || eventType === 'wait_completed';\n\nexport const isHookLifecycleEvent = (eventType: string) =>\n eventType === 'hook_received' ||\n eventType === 'hook_created' ||\n eventType === 'hook_disposed';\n\n// ---------------------------------------------------------------------------\n// Event grouping\n// ---------------------------------------------------------------------------\n\nexport type GroupedEvents = {\n eventsByStepId: Map<string, Event[]>;\n runLevelEvents: Event[];\n timerEvents: Map<string, Event[]>;\n hookEvents: Map<string, Event[]>;\n};\n\nfunction pushEvent(\n map: Map<string, Event[]>,\n correlationId: string,\n event: Event\n) {\n const existing = map.get(correlationId);\n if (existing) {\n existing.push(event);\n return;\n }\n map.set(correlationId, [event]);\n}\n\nexport function groupEventsByCorrelation(events: Event[]): GroupedEvents {\n const eventsByStepId = new Map<string, Event[]>();\n const runLevelEvents: Event[] = [];\n const timerEvents = new Map<string, Event[]>();\n const hookEvents = new Map<string, Event[]>();\n\n for (const event of events) {\n const correlationId = event.correlationId;\n if (!correlationId) {\n runLevelEvents.push(event);\n continue;\n }\n\n if (isTimerEvent(event.eventType)) {\n pushEvent(timerEvents, correlationId, event);\n continue;\n }\n\n if (isHookLifecycleEvent(event.eventType)) {\n pushEvent(hookEvents, correlationId, event);\n continue;\n }\n\n if (isStepEvent(event.eventType)) {\n pushEvent(eventsByStepId, correlationId, event);\n continue;\n }\n\n runLevelEvents.push(event);\n }\n\n return { eventsByStepId, runLevelEvents, timerEvents, hookEvents };\n}\n\n// ---------------------------------------------------------------------------\n// Trace construction\n// ---------------------------------------------------------------------------\n\n/**\n * Computes the latest known event time from the events list.\n * Active (in-progress) child spans are capped at this time instead of `now`,\n * so they only extend as far as our data goes.\n */\nfunction computeLatestKnownTime(events: Event[], run: WorkflowRun): Date {\n let latest = new Date(run.createdAt).getTime();\n for (const event of events) {\n const t = new Date(event.createdAt).getTime();\n if (t > latest) latest = t;\n }\n return new Date(latest);\n}\n\nfunction buildSpans(\n run: WorkflowRun,\n groupedEvents: GroupedEvents,\n now: Date,\n latestKnownTime: Date\n) {\n // Active child spans cap at latestKnownTime so they don't extend into\n // unknown territory. Even when the run is completed, we may not have loaded\n // all events yet, so always use the latest event we've actually seen.\n const childMaxEnd = latestKnownTime;\n\n const stepSpans = Array.from(groupedEvents.eventsByStepId.values())\n .map((events) => stepToSpan(events, childMaxEnd))\n .filter((span): span is Span => span !== null);\n\n const hookSpans = Array.from(groupedEvents.hookEvents.values())\n .map((events) => hookToSpan(events, childMaxEnd))\n .filter((span): span is Span => span !== null);\n\n const waitSpans = Array.from(groupedEvents.timerEvents.values())\n .map((events) => waitToSpan(events, childMaxEnd))\n .filter((span): span is Span => span !== null);\n\n return {\n runSpan: runToSpan(run, groupedEvents.runLevelEvents, now),\n spans: [...stepSpans, ...hookSpans, ...waitSpans],\n };\n}\n\nfunction cascadeSpans(runSpan: Span, spans: Span[]) {\n const sortedSpans = [\n runSpan,\n ...spans.slice().sort((a, b) => {\n const aStart = otelTimeToMs(a.startTime);\n const bStart = otelTimeToMs(b.startTime);\n return aStart - bStart;\n }),\n ];\n\n return sortedSpans.map((span, index) => {\n const parentSpanId =\n index === 0 ? undefined : String(sortedSpans[index - 1].spanId);\n return {\n ...span,\n parentSpanId,\n };\n });\n}\n\nexport interface TraceWithMeta {\n traceId: string;\n rootSpanId: string;\n spans: Span[];\n resources: { name: string; attributes: Record<string, string> }[];\n /** Duration in ms from trace start to the latest known event. */\n knownDurationMs: number;\n}\n\nexport function buildTrace(\n run: WorkflowRun,\n events: Event[],\n now: Date\n): TraceWithMeta {\n const groupedEvents = groupEventsByCorrelation(events);\n const latestKnownTime = computeLatestKnownTime(events, run);\n const { runSpan, spans } = buildSpans(\n run,\n groupedEvents,\n now,\n latestKnownTime\n );\n const sortedCascadingSpans = cascadeSpans(runSpan, spans);\n\n // Compute known duration relative to trace start\n const traceStartMs = otelTimeToMs(runSpan.startTime);\n const knownDurationMs = latestKnownTime.getTime() - traceStartMs;\n\n return {\n traceId: run.runId,\n rootSpanId: run.runId,\n spans: sortedCascadingSpans,\n resources: [\n {\n name: 'workflow',\n attributes: {\n 'service.name': WORKFLOW_LIBRARY.name,\n },\n },\n ],\n knownDurationMs: Math.max(0, knownDurationMs),\n };\n}\n"]}
1
+ {"version":3,"file":"trace-builder.js","sourceRoot":"","sources":["../../src/lib/trace-builder.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Builds a complete trace from a WorkflowRun and its Events.\n *\n * This module groups raw events by correlation ID and entity type,\n * converts each group into an OpenTelemetry-style Span, and returns\n * a fully-formed Trace ready for the trace viewer.\n */\n\nimport type { Event, WorkflowRun } from '@workflow/world';\nimport type { Span } from '../components/trace-viewer/types';\nimport {\n hookToSpan,\n runToSpan,\n stepToSpan,\n waitToSpan,\n WORKFLOW_LIBRARY,\n} from '../components/workflow-traces/trace-span-construction';\nimport { otelTimeToMs } from '../components/workflow-traces/trace-time-utils';\n\n// ---------------------------------------------------------------------------\n// Event type classifiers\n// ---------------------------------------------------------------------------\n\nexport const isStepEvent = (eventType: string) => eventType.startsWith('step_');\n\nexport const isTimerEvent = (eventType: string) =>\n eventType === 'wait_created' || eventType === 'wait_completed';\n\nexport const isHookLifecycleEvent = (eventType: string) =>\n eventType === 'hook_received' ||\n eventType === 'hook_created' ||\n eventType === 'hook_disposed';\n\n// ---------------------------------------------------------------------------\n// Event grouping\n// ---------------------------------------------------------------------------\n\nexport type GroupedEvents = {\n eventsByStepId: Map<string, Event[]>;\n runLevelEvents: Event[];\n timerEvents: Map<string, Event[]>;\n hookEvents: Map<string, Event[]>;\n};\n\nfunction pushEvent(\n map: Map<string, Event[]>,\n correlationId: string,\n event: Event\n) {\n const existing = map.get(correlationId);\n if (existing) {\n existing.push(event);\n return;\n }\n map.set(correlationId, [event]);\n}\n\nexport function groupEventsByCorrelation(events: Event[]): GroupedEvents {\n const eventsByStepId = new Map<string, Event[]>();\n const runLevelEvents: Event[] = [];\n const timerEvents = new Map<string, Event[]>();\n const hookEvents = new Map<string, Event[]>();\n\n for (const event of events) {\n const correlationId = event.correlationId;\n if (!correlationId) {\n runLevelEvents.push(event);\n continue;\n }\n\n if (isTimerEvent(event.eventType)) {\n pushEvent(timerEvents, correlationId, event);\n continue;\n }\n\n if (isHookLifecycleEvent(event.eventType)) {\n pushEvent(hookEvents, correlationId, event);\n continue;\n }\n\n if (isStepEvent(event.eventType)) {\n pushEvent(eventsByStepId, correlationId, event);\n continue;\n }\n\n runLevelEvents.push(event);\n }\n\n return { eventsByStepId, runLevelEvents, timerEvents, hookEvents };\n}\n\n// ---------------------------------------------------------------------------\n// Trace construction\n// ---------------------------------------------------------------------------\n\n/**\n * Computes the latest known event time from the events list.\n * Active (in-progress) child spans are capped at this time instead of `now`,\n * so they only extend as far as our data goes.\n */\nfunction computeLatestKnownTime(events: Event[], run: WorkflowRun): Date {\n let latest = new Date(run.createdAt).getTime();\n for (const event of events) {\n const t = new Date(event.createdAt).getTime();\n if (t > latest) latest = t;\n }\n return new Date(latest);\n}\n\nfunction buildSpans(\n run: WorkflowRun,\n groupedEvents: GroupedEvents,\n now: Date,\n latestKnownTime: Date\n) {\n // Active child spans cap at latestKnownTime so they don't extend into\n // unknown territory. Even when the run is completed, we may not have loaded\n // all events yet, so always use the latest event we've actually seen.\n const childMaxEnd = latestKnownTime;\n const runMaxEnd = run.completedAt ?? now;\n\n const stepSpans = Array.from(groupedEvents.eventsByStepId.values())\n .map((events) => stepToSpan(events, childMaxEnd))\n .filter((span): span is Span => span !== null);\n\n const hookSpans = Array.from(groupedEvents.hookEvents.values())\n .map((events) => hookToSpan(events, childMaxEnd))\n .filter((span): span is Span => span !== null);\n\n const waitSpans = Array.from(groupedEvents.timerEvents.values())\n .map((events) => waitToSpan(events, childMaxEnd, runMaxEnd))\n .filter((span): span is Span => span !== null);\n\n return {\n runSpan: runToSpan(run, groupedEvents.runLevelEvents, now),\n spans: [...stepSpans, ...hookSpans, ...waitSpans],\n };\n}\n\nfunction cascadeSpans(runSpan: Span, spans: Span[]) {\n const sortedSpans = [\n runSpan,\n ...spans.slice().sort((a, b) => {\n const aStart = otelTimeToMs(a.startTime);\n const bStart = otelTimeToMs(b.startTime);\n return aStart - bStart;\n }),\n ];\n\n return sortedSpans.map((span, index) => {\n const parentSpanId =\n index === 0 ? undefined : String(sortedSpans[index - 1].spanId);\n return {\n ...span,\n parentSpanId,\n };\n });\n}\n\nexport interface TraceWithMeta {\n traceId: string;\n rootSpanId: string;\n spans: Span[];\n resources: { name: string; attributes: Record<string, string> }[];\n /** Duration in ms from trace start to the latest known event. */\n knownDurationMs: number;\n}\n\nexport function buildTrace(\n run: WorkflowRun,\n events: Event[],\n now: Date\n): TraceWithMeta {\n const groupedEvents = groupEventsByCorrelation(events);\n const latestKnownTime = computeLatestKnownTime(events, run);\n const { runSpan, spans } = buildSpans(\n run,\n groupedEvents,\n now,\n latestKnownTime\n );\n const sortedCascadingSpans = cascadeSpans(runSpan, spans);\n\n // Compute known duration relative to trace start\n const traceStartMs = otelTimeToMs(runSpan.startTime);\n const knownDurationMs = latestKnownTime.getTime() - traceStartMs;\n\n return {\n traceId: run.runId,\n rootSpanId: run.runId,\n spans: sortedCascadingSpans,\n resources: [\n {\n name: 'workflow',\n attributes: {\n 'service.name': WORKFLOW_LIBRARY.name,\n },\n },\n ],\n knownDurationMs: Math.max(0, knownDurationMs),\n };\n}\n"]}
@@ -6,17 +6,19 @@ export declare function cn(...inputs: ClassValue[]): string;
6
6
  * Formats a duration in milliseconds to a human-readable string.
7
7
  *
8
8
  * @param ms - Duration in milliseconds
9
- * @param compact - If true, returns a single-unit format (e.g., "0.38s", "2.5m").
10
- * If false (default), returns multi-part format (e.g., "1m 13.21s", "2d 5h 3m 12.00s").
9
+ * @param compact - If true, returns a compact format (e.g., "380ms", "2m 30s").
10
+ * If false (default), returns multi-part format (e.g., "1m 13s", "2d 5h 3m 12s").
11
11
  *
12
12
  * Compact format (timeline markers):
13
- * - < 1m: shows seconds (e.g., "0.38s", "45s")
14
- * - < 1h: shows minutes (e.g., "2.5m")
15
- * - >= 1h: shows hours (e.g., "2.5h")
13
+ * - < 1s: shows milliseconds (e.g., "380ms")
14
+ * - < 1m: shows seconds (e.g., "45s")
15
+ * - < 1h: shows minutes and seconds (e.g., "2m 30s")
16
+ * - >= 1h: shows hours and minutes (e.g., "2h 30m")
16
17
  *
17
18
  * Full format:
18
- * - < 1m: shows seconds (e.g., "0.38s", "2.71s")
19
- * - >= 1m: shows decomposed format with fractional seconds (e.g., "1m 13.21s", "2m 13.04s")
19
+ * - < 1s: shows milliseconds (e.g., "380ms")
20
+ * - < 1m: shows seconds (e.g., "45s")
21
+ * - >= 1m: shows decomposed format with whole seconds (e.g., "1m 13s", "2m 13s")
20
22
  */
21
23
  export declare function formatDuration(ms: number, compact?: boolean): string;
22
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AACvC,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,MAAM,CAAC;AAG7C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,MAAM,CAyClE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,GACf,MAAM,CAKR;AAMD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,OAAO,GAAG,YAAY,EAAE,GAAG,IAAI,CAmB7E;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,YAAY,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,CAc/D"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AACvC,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,MAAM,CAAC;AAG7C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,MAAM,CAgDlE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,GACf,MAAM,CAKR;AAMD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,OAAO,GAAG,YAAY,EAAE,GAAG,IAAI,CAmB7E;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,YAAY,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,CAc/D"}
package/dist/lib/utils.js CHANGED
@@ -3,13 +3,6 @@ import { twMerge } from 'tailwind-merge';
3
3
  export function cn(...inputs) {
4
4
  return twMerge(clsx(inputs));
5
5
  }
6
- // const compactFormatter = new Intl.NumberFormat(undefined, {
7
- // maximumFractionDigits: 2,
8
- // });
9
- const secondsFormatter = new Intl.NumberFormat(undefined, {
10
- minimumFractionDigits: 2,
11
- maximumFractionDigits: 2,
12
- });
13
6
  const MS_IN_SECOND = 1000;
14
7
  const MS_IN_MINUTE = 60 * MS_IN_SECOND;
15
8
  const MS_IN_HOUR = 60 * MS_IN_MINUTE;
@@ -18,41 +11,48 @@ const MS_IN_DAY = 24 * MS_IN_HOUR;
18
11
  * Formats a duration in milliseconds to a human-readable string.
19
12
  *
20
13
  * @param ms - Duration in milliseconds
21
- * @param compact - If true, returns a single-unit format (e.g., "0.38s", "2.5m").
22
- * If false (default), returns multi-part format (e.g., "1m 13.21s", "2d 5h 3m 12.00s").
14
+ * @param compact - If true, returns a compact format (e.g., "380ms", "2m 30s").
15
+ * If false (default), returns multi-part format (e.g., "1m 13s", "2d 5h 3m 12s").
23
16
  *
24
17
  * Compact format (timeline markers):
25
- * - < 1m: shows seconds (e.g., "0.38s", "45s")
26
- * - < 1h: shows minutes (e.g., "2.5m")
27
- * - >= 1h: shows hours (e.g., "2.5h")
18
+ * - < 1s: shows milliseconds (e.g., "380ms")
19
+ * - < 1m: shows seconds (e.g., "45s")
20
+ * - < 1h: shows minutes and seconds (e.g., "2m 30s")
21
+ * - >= 1h: shows hours and minutes (e.g., "2h 30m")
28
22
  *
29
23
  * Full format:
30
- * - < 1m: shows seconds (e.g., "0.38s", "2.71s")
31
- * - >= 1m: shows decomposed format with fractional seconds (e.g., "1m 13.21s", "2m 13.04s")
24
+ * - < 1s: shows milliseconds (e.g., "380ms")
25
+ * - < 1m: shows seconds (e.g., "45s")
26
+ * - >= 1m: shows decomposed format with whole seconds (e.g., "1m 13s", "2m 13s")
32
27
  */
33
28
  export function formatDuration(ms, compact = false) {
34
29
  if (ms === 0) {
35
30
  return '0s';
36
31
  }
37
- if (ms < MS_IN_MINUTE) {
38
- return `${secondsFormatter.format(ms / MS_IN_SECOND)}s`;
32
+ if (ms < MS_IN_SECOND) {
33
+ const roundedMs = Math.round(ms);
34
+ return roundedMs < MS_IN_SECOND ? `${roundedMs}ms` : '1s';
35
+ }
36
+ const roundedMs = Math.round(ms / MS_IN_SECOND) * MS_IN_SECOND;
37
+ if (roundedMs < MS_IN_MINUTE) {
38
+ return `${Math.floor(roundedMs / MS_IN_SECOND)}s`;
39
39
  }
40
40
  // Compact format: multi-unit without decimals (e.g. "8m 20s", "2h 30m")
41
41
  if (compact) {
42
- if (ms < MS_IN_HOUR) {
43
- const m = Math.floor(ms / MS_IN_MINUTE);
44
- const s = Math.floor((ms % MS_IN_MINUTE) / MS_IN_SECOND);
42
+ if (roundedMs < MS_IN_HOUR) {
43
+ const m = Math.floor(roundedMs / MS_IN_MINUTE);
44
+ const s = Math.floor((roundedMs % MS_IN_MINUTE) / MS_IN_SECOND);
45
45
  return s > 0 ? `${m}m ${s}s` : `${m}m`;
46
46
  }
47
- const h = Math.floor(ms / MS_IN_HOUR);
48
- const m = Math.floor((ms % MS_IN_HOUR) / MS_IN_MINUTE);
47
+ const h = Math.floor(roundedMs / MS_IN_HOUR);
48
+ const m = Math.floor((roundedMs % MS_IN_HOUR) / MS_IN_MINUTE);
49
49
  return m > 0 ? `${h}h ${m}m` : `${h}h`;
50
50
  }
51
- // Full format: decompose into larger units + fractional seconds
52
- const days = Math.floor(ms / MS_IN_DAY);
53
- const hours = Math.floor((ms % MS_IN_DAY) / MS_IN_HOUR);
54
- const minutes = Math.floor((ms % MS_IN_HOUR) / MS_IN_MINUTE);
55
- const seconds = (ms % MS_IN_MINUTE) / MS_IN_SECOND;
51
+ // Full format: decompose into larger units + whole seconds.
52
+ const days = Math.floor(roundedMs / MS_IN_DAY);
53
+ const hours = Math.floor((roundedMs % MS_IN_DAY) / MS_IN_HOUR);
54
+ const minutes = Math.floor((roundedMs % MS_IN_HOUR) / MS_IN_MINUTE);
55
+ const seconds = Math.floor((roundedMs % MS_IN_MINUTE) / MS_IN_SECOND);
56
56
  const parts = [];
57
57
  if (days > 0) {
58
58
  parts.push(`${days}d`);
@@ -63,7 +63,7 @@ export function formatDuration(ms, compact = false) {
63
63
  if (minutes > 0) {
64
64
  parts.push(`${minutes}m`);
65
65
  }
66
- parts.push(`${secondsFormatter.format(seconds)}s`);
66
+ parts.push(`${seconds}s`);
67
67
  return parts.join(' ');
68
68
  }
69
69
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Step } from '@workflow/world';\nimport type { ModelMessage } from 'ai';\nimport { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n// const compactFormatter = new Intl.NumberFormat(undefined, {\n// maximumFractionDigits: 2,\n// });\n\nconst secondsFormatter = new Intl.NumberFormat(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n});\n\nconst MS_IN_SECOND = 1000;\nconst MS_IN_MINUTE = 60 * MS_IN_SECOND;\nconst MS_IN_HOUR = 60 * MS_IN_MINUTE;\nconst MS_IN_DAY = 24 * MS_IN_HOUR;\n\n/**\n * Formats a duration in milliseconds to a human-readable string.\n *\n * @param ms - Duration in milliseconds\n * @param compact - If true, returns a single-unit format (e.g., \"0.38s\", \"2.5m\").\n * If false (default), returns multi-part format (e.g., \"1m 13.21s\", \"2d 5h 3m 12.00s\").\n *\n * Compact format (timeline markers):\n * - < 1m: shows seconds (e.g., \"0.38s\", \"45s\")\n * - < 1h: shows minutes (e.g., \"2.5m\")\n * - >= 1h: shows hours (e.g., \"2.5h\")\n *\n * Full format:\n * - < 1m: shows seconds (e.g., \"0.38s\", \"2.71s\")\n * - >= 1m: shows decomposed format with fractional seconds (e.g., \"1m 13.21s\", \"2m 13.04s\")\n */\nexport function formatDuration(ms: number, compact = false): string {\n if (ms === 0) {\n return '0s';\n }\n\n if (ms < MS_IN_MINUTE) {\n return `${secondsFormatter.format(ms / MS_IN_SECOND)}s`;\n }\n\n // Compact format: multi-unit without decimals (e.g. \"8m 20s\", \"2h 30m\")\n if (compact) {\n if (ms < MS_IN_HOUR) {\n const m = Math.floor(ms / MS_IN_MINUTE);\n const s = Math.floor((ms % MS_IN_MINUTE) / MS_IN_SECOND);\n return s > 0 ? `${m}m ${s}s` : `${m}m`;\n }\n const h = Math.floor(ms / MS_IN_HOUR);\n const m = Math.floor((ms % MS_IN_HOUR) / MS_IN_MINUTE);\n return m > 0 ? `${h}h ${m}m` : `${h}h`;\n }\n\n // Full format: decompose into larger units + fractional seconds\n const days = Math.floor(ms / MS_IN_DAY);\n const hours = Math.floor((ms % MS_IN_DAY) / MS_IN_HOUR);\n const minutes = Math.floor((ms % MS_IN_HOUR) / MS_IN_MINUTE);\n const seconds = (ms % MS_IN_MINUTE) / MS_IN_SECOND;\n\n const parts: string[] = [];\n\n if (days > 0) {\n parts.push(`${days}d`);\n }\n if (hours > 0) {\n parts.push(`${hours}h`);\n }\n if (minutes > 0) {\n parts.push(`${minutes}m`);\n }\n parts.push(`${secondsFormatter.format(seconds)}s`);\n\n return parts.join(' ');\n}\n\n/**\n * Returns a formatted pagination display string\n * @param currentPage - The current page number\n * @param totalPages - The total number of pages visited so far\n * @param hasMore - Whether there are more pages available\n * @returns Formatted string like \"Page 1 of 3+\" or \"Page 2 of 2\"\n */\nexport function getPaginationDisplay(\n currentPage: number,\n totalPages: number,\n hasMore: boolean\n): string {\n if (hasMore) {\n return `Page ${currentPage} of ${totalPages}+`;\n }\n return `Page ${currentPage} of ${totalPages}`;\n}\n\n// ============================================================================\n// Durable Agent Utilities\n// ============================================================================\n\n/**\n * Check if a step is a doStreamStep (LLM call with conversation input)\n */\nexport function isDoStreamStep(stepName: string): boolean {\n return stepName.endsWith('//doStreamStep');\n}\n\n/**\n * Extract the conversation from a hydrated doStreamStep input.\n * doStreamStep signature: (conversationPrompt, model, writable, tools, options)\n * So input[0] is the conversation.\n */\nexport function extractConversation(stepInput: unknown): ModelMessage[] | null {\n if (!Array.isArray(stepInput) || stepInput.length === 0) {\n return null;\n }\n\n const firstArg = stepInput[0];\n\n if (!Array.isArray(firstArg)) {\n return null;\n }\n\n // Validate it looks like ModelMessage[]\n if (\n !firstArg.every((msg) => msg && typeof msg === 'object' && 'role' in msg)\n ) {\n return null;\n }\n\n return firstArg as ModelMessage[];\n}\n\n/**\n * A doStreamStep with its conversation input extracted\n */\nexport interface StreamStep {\n stepId: string;\n stepName: string;\n displayName: string;\n conversation: ModelMessage[];\n}\n\n/**\n * Identifies all stream steps (doStreamStep) in a run and extracts their conversations.\n */\nexport function identifyStreamSteps(steps: Step[]): StreamStep[] {\n return steps\n .filter((step) => isDoStreamStep(step.stepName))\n .map((step) => {\n const functionName = step.stepName.split('//').pop() ?? 'unknown';\n const conversation = extractConversation(step.input) ?? [];\n\n return {\n stepId: step.stepId,\n stepName: step.stepName,\n displayName: functionName,\n conversation,\n };\n });\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Step } from '@workflow/world';\nimport type { ModelMessage } from 'ai';\nimport { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nconst MS_IN_SECOND = 1000;\nconst MS_IN_MINUTE = 60 * MS_IN_SECOND;\nconst MS_IN_HOUR = 60 * MS_IN_MINUTE;\nconst MS_IN_DAY = 24 * MS_IN_HOUR;\n\n/**\n * Formats a duration in milliseconds to a human-readable string.\n *\n * @param ms - Duration in milliseconds\n * @param compact - If true, returns a compact format (e.g., \"380ms\", \"2m 30s\").\n * If false (default), returns multi-part format (e.g., \"1m 13s\", \"2d 5h 3m 12s\").\n *\n * Compact format (timeline markers):\n * - < 1s: shows milliseconds (e.g., \"380ms\")\n * - < 1m: shows seconds (e.g., \"45s\")\n * - < 1h: shows minutes and seconds (e.g., \"2m 30s\")\n * - >= 1h: shows hours and minutes (e.g., \"2h 30m\")\n *\n * Full format:\n * - < 1s: shows milliseconds (e.g., \"380ms\")\n * - < 1m: shows seconds (e.g., \"45s\")\n * - >= 1m: shows decomposed format with whole seconds (e.g., \"1m 13s\", \"2m 13s\")\n */\nexport function formatDuration(ms: number, compact = false): string {\n if (ms === 0) {\n return '0s';\n }\n\n if (ms < MS_IN_SECOND) {\n const roundedMs = Math.round(ms);\n return roundedMs < MS_IN_SECOND ? `${roundedMs}ms` : '1s';\n }\n\n const roundedMs = Math.round(ms / MS_IN_SECOND) * MS_IN_SECOND;\n\n if (roundedMs < MS_IN_MINUTE) {\n return `${Math.floor(roundedMs / MS_IN_SECOND)}s`;\n }\n\n // Compact format: multi-unit without decimals (e.g. \"8m 20s\", \"2h 30m\")\n if (compact) {\n if (roundedMs < MS_IN_HOUR) {\n const m = Math.floor(roundedMs / MS_IN_MINUTE);\n const s = Math.floor((roundedMs % MS_IN_MINUTE) / MS_IN_SECOND);\n return s > 0 ? `${m}m ${s}s` : `${m}m`;\n }\n const h = Math.floor(roundedMs / MS_IN_HOUR);\n const m = Math.floor((roundedMs % MS_IN_HOUR) / MS_IN_MINUTE);\n return m > 0 ? `${h}h ${m}m` : `${h}h`;\n }\n\n // Full format: decompose into larger units + whole seconds.\n const days = Math.floor(roundedMs / MS_IN_DAY);\n const hours = Math.floor((roundedMs % MS_IN_DAY) / MS_IN_HOUR);\n const minutes = Math.floor((roundedMs % MS_IN_HOUR) / MS_IN_MINUTE);\n const seconds = Math.floor((roundedMs % MS_IN_MINUTE) / MS_IN_SECOND);\n\n const parts: string[] = [];\n\n if (days > 0) {\n parts.push(`${days}d`);\n }\n if (hours > 0) {\n parts.push(`${hours}h`);\n }\n if (minutes > 0) {\n parts.push(`${minutes}m`);\n }\n parts.push(`${seconds}s`);\n\n return parts.join(' ');\n}\n\n/**\n * Returns a formatted pagination display string\n * @param currentPage - The current page number\n * @param totalPages - The total number of pages visited so far\n * @param hasMore - Whether there are more pages available\n * @returns Formatted string like \"Page 1 of 3+\" or \"Page 2 of 2\"\n */\nexport function getPaginationDisplay(\n currentPage: number,\n totalPages: number,\n hasMore: boolean\n): string {\n if (hasMore) {\n return `Page ${currentPage} of ${totalPages}+`;\n }\n return `Page ${currentPage} of ${totalPages}`;\n}\n\n// ============================================================================\n// Durable Agent Utilities\n// ============================================================================\n\n/**\n * Check if a step is a doStreamStep (LLM call with conversation input)\n */\nexport function isDoStreamStep(stepName: string): boolean {\n return stepName.endsWith('//doStreamStep');\n}\n\n/**\n * Extract the conversation from a hydrated doStreamStep input.\n * doStreamStep signature: (conversationPrompt, model, writable, tools, options)\n * So input[0] is the conversation.\n */\nexport function extractConversation(stepInput: unknown): ModelMessage[] | null {\n if (!Array.isArray(stepInput) || stepInput.length === 0) {\n return null;\n }\n\n const firstArg = stepInput[0];\n\n if (!Array.isArray(firstArg)) {\n return null;\n }\n\n // Validate it looks like ModelMessage[]\n if (\n !firstArg.every((msg) => msg && typeof msg === 'object' && 'role' in msg)\n ) {\n return null;\n }\n\n return firstArg as ModelMessage[];\n}\n\n/**\n * A doStreamStep with its conversation input extracted\n */\nexport interface StreamStep {\n stepId: string;\n stepName: string;\n displayName: string;\n conversation: ModelMessage[];\n}\n\n/**\n * Identifies all stream steps (doStreamStep) in a run and extracts their conversations.\n */\nexport function identifyStreamSteps(steps: Step[]): StreamStep[] {\n return steps\n .filter((step) => isDoStreamStep(step.stepName))\n .map((step) => {\n const functionName = step.stepName.split('//').pop() ?? 'unknown';\n const conversation = extractConversation(step.input) ?? [];\n\n return {\n stepId: step.stepId,\n stepName: step.stepName,\n displayName: functionName,\n conversation,\n };\n });\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@workflow/web-shared",
3
3
  "description": "Shared components for Workflow Observability UI",
4
- "version": "5.0.0-beta.3",
4
+ "version": "5.0.0-beta.4",
5
5
  "private": false,
6
6
  "files": [
7
7
  "dist",
@@ -53,7 +53,7 @@
53
53
  "streamdown": "2.3.0",
54
54
  "tailwind-merge": "3.5.0",
55
55
  "tailwindcss": "4",
56
- "@workflow/core": "5.0.0-beta.3",
56
+ "@workflow/core": "5.0.0-beta.4",
57
57
  "@workflow/utils": "5.0.0-beta.1",
58
58
  "@workflow/world": "5.0.0-beta.1"
59
59
  },
@@ -1,13 +1,15 @@
1
1
  import { Circle } from 'lucide-react';
2
2
  import { cn } from '../../../lib/utils';
3
3
  import type { Span } from '../../trace-viewer/types';
4
- import { formatDuration, getHighResInMs } from '../../trace-viewer/util/timing';
4
+ import { formatDuration } from '../../trace-viewer/util/timing';
5
5
  import {
6
6
  WorkflowIcon,
7
7
  WebhookIcon,
8
8
  SleepIcon,
9
9
  StepForwardIcon,
10
10
  } from '../icons';
11
+ import { getSpanDurationMs } from '../utils';
12
+ import { MiddleTruncate } from './middle-truncate/middle-truncate';
11
13
 
12
14
  interface EventStyle {
13
15
  icon: React.ComponentType<{ className?: string }>;
@@ -26,6 +28,8 @@ const defaultStyle: EventStyle = {
26
28
  className: 'text-gray-900',
27
29
  };
28
30
 
31
+ const ROW_HEIGHT_CLASS = 'h-10';
32
+
29
33
  function getEventStyle(resource: string, isErrored: boolean): EventStyle {
30
34
  const style = eventStyles[resource] ?? defaultStyle;
31
35
  return {
@@ -43,7 +47,7 @@ const EventRow = ({
43
47
  isSelected: boolean;
44
48
  onSelectSpan: (spanId: string) => void;
45
49
  }) => {
46
- const durationMs = getHighResInMs(span.duration);
50
+ const durationMs = getSpanDurationMs(span);
47
51
  const isErrored =
48
52
  (span.attributes.data as Record<string, unknown>).status === 'failed';
49
53
  const { icon: Icon, className: tagClassName } = getEventStyle(
@@ -53,24 +57,31 @@ const EventRow = ({
53
57
 
54
58
  return (
55
59
  <li
56
- className="overflow-clip group"
57
- role="treeitem"
60
+ className={cn(
61
+ 'relative overflow-clip group after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-gray-alpha-400',
62
+ ROW_HEIGHT_CLASS
63
+ )}
64
+ role='treeitem'
58
65
  aria-selected={isSelected}
59
66
  aria-expanded={isSelected}
60
67
  aria-level={1}
61
68
  onClick={() => onSelectSpan(span.spanId)}
62
69
  >
63
- <div className="hover:bg-gray-100 group-aria-selected:bg-gray-100 group-aria-selected:hover:bg-gray-200 hover:aria-selected:bg-gray-100 rounded-sm px-2 h-[34px] py-1.5 flex">
64
- <div className="flex items-center gap-2">
65
- <span className={tagClassName}>
66
- <Icon className="w-4 h-4" />
67
- </span>
68
- <span className="text-label-14">{span.name}</span>
69
- </div>
70
- <div className="ml-auto">
71
- <span className="text-label-14 text-gray-900 tabular-nums">
72
- {formatDuration(durationMs)}
73
- </span>
70
+ <div className='h-full hover:bg-gray-100 group-aria-selected:bg-gray-100 group-aria-selected:hover:bg-gray-200'>
71
+ <div className='flex h-full min-w-0 items-center px-2'>
72
+ <div className='flex min-w-0 flex-1 items-center gap-2'>
73
+ <span className={cn('shrink-0', tagClassName)}>
74
+ <Icon className='w-4 h-4' />
75
+ </span>
76
+ <span className='min-w-0 text-label-14'>
77
+ <MiddleTruncate value={span.name} />
78
+ </span>
79
+ </div>
80
+ <div className='ml-2 shrink-0'>
81
+ <span className='text-label-14 text-gray-900 tabular-nums'>
82
+ {formatDuration(durationMs)}
83
+ </span>
84
+ </div>
74
85
  </div>
75
86
  </div>
76
87
  </li>
@@ -87,11 +98,7 @@ const EventList = ({
87
98
  onSelectSpan: (spanId: string) => void;
88
99
  }) => {
89
100
  return (
90
- <ul
91
- id="event-list"
92
- role="tree"
93
- className="block min-h-0 overflow-visible px-2 py-2"
94
- >
101
+ <ul id='event-list' role='tree' className='block min-h-0 overflow-visible'>
95
102
  {spans.map((span) => {
96
103
  return (
97
104
  <EventRow