@ryanfw/prompt-orchestration-pipeline 0.10.0 → 0.11.0

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.
@@ -75,6 +75,8 @@ function normalizeTasks(rawTasks) {
75
75
  : undefined,
76
76
  // Preserve tokenUsage if present
77
77
  ...(t && t.tokenUsage ? { tokenUsage: t.tokenUsage } : {}),
78
+ // Preserve error object if present
79
+ ...(t && t.error ? { error: t.error } : {}),
78
80
  };
79
81
  tasks[name] = taskObj;
80
82
  });
@@ -106,11 +108,28 @@ function normalizeTasks(rawTasks) {
106
108
  ...(typeof t?.failedStage === "string" && t.failedStage.length > 0
107
109
  ? { failedStage: t.failedStage }
108
110
  : {}),
111
+ // Prefer new files.* schema, fallback to legacy artifacts
112
+ files:
113
+ t && t.files
114
+ ? {
115
+ artifacts: Array.isArray(t.files.artifacts)
116
+ ? t.files.artifacts.slice()
117
+ : [],
118
+ logs: Array.isArray(t.files.logs) ? t.files.logs.slice() : [],
119
+ tmp: Array.isArray(t.files.tmp) ? t.files.tmp.slice() : [],
120
+ }
121
+ : {
122
+ artifacts: [],
123
+ logs: [],
124
+ tmp: [],
125
+ },
109
126
  artifacts: Array.isArray(t && t.artifacts)
110
127
  ? t.artifacts.slice()
111
128
  : undefined,
112
129
  // Preserve tokenUsage if present
113
130
  ...(t && t.tokenUsage ? { tokenUsage: t.tokenUsage } : {}),
131
+ // Preserve error object if present
132
+ ...(t && t.error ? { error: t.error } : {}),
114
133
  };
115
134
  });
116
135
  return { tasks, warnings };
@@ -152,7 +171,7 @@ export function adaptJobSummary(apiJob) {
152
171
  // Demo-only: read canonical fields strictly
153
172
  const id = apiJob.jobId;
154
173
  const name = apiJob.title || "";
155
- const rawTasks = apiJob.tasks;
174
+ const rawTasks = apiJob.tasks || apiJob.tasksStatus; // Handle both formats for backward compatibility
156
175
  const location = apiJob.location;
157
176
 
158
177
  // Job-level stage metadata
@@ -221,7 +240,7 @@ export function adaptJobDetail(apiDetail) {
221
240
  // Demo-only: read canonical fields strictly
222
241
  const id = apiDetail.jobId;
223
242
  const name = apiDetail.title || "";
224
- const rawTasks = apiDetail.tasks;
243
+ const rawTasks = apiDetail.tasks || apiDetail.tasksStatus; // Handle both formats for backward compatibility
225
244
  const location = apiDetail.location;
226
245
 
227
246
  // Job-level stage metadata
@@ -347,6 +347,7 @@ export function useJobDetailWithUpdates(jobId) {
347
347
  newEs.addEventListener("job:removed", onJobRemoved);
348
348
  newEs.addEventListener("status:changed", onStatusChanged);
349
349
  newEs.addEventListener("state:change", onStateChange);
350
+ newEs.addEventListener("task:updated", onTaskUpdated);
350
351
  newEs.addEventListener("error", onError);
351
352
 
352
353
  esRef.current = newEs;
@@ -373,6 +374,94 @@ export function useJobDetailWithUpdates(jobId) {
373
374
  return;
374
375
  }
375
376
 
377
+ // Handle task:updated events with task-level merge logic
378
+ if (type === "task:updated") {
379
+ const p = payload || {};
380
+ const { jobId: eventJobId, taskId, task } = p;
381
+
382
+ // Filter by jobId
383
+ if (eventJobId && eventJobId !== jobId) {
384
+ return;
385
+ }
386
+
387
+ // Validate required fields
388
+ if (!taskId || !task) {
389
+ return;
390
+ }
391
+
392
+ startTransition(() => {
393
+ setData((prev) => {
394
+ // If no previous data or tasks, return unchanged
395
+ if (!prev || !prev.tasks) {
396
+ return prev;
397
+ }
398
+
399
+ const prevTask = prev.tasks[taskId];
400
+
401
+ // Compare observable fields to determine if update is needed
402
+ const fieldsToCompare = [
403
+ "state",
404
+ "currentStage",
405
+ "failedStage",
406
+ "startedAt",
407
+ "endedAt",
408
+ "attempts",
409
+ "executionTimeMs",
410
+ "error",
411
+ ];
412
+
413
+ let hasChanged = false;
414
+
415
+ // If task doesn't exist yet, always treat as changed
416
+ if (!prevTask) {
417
+ hasChanged = true;
418
+ } else {
419
+ // Compare observable fields to determine if update is needed
420
+ for (const field of fieldsToCompare) {
421
+ if (prevTask[field] !== task[field]) {
422
+ hasChanged = true;
423
+ break;
424
+ }
425
+ }
426
+
427
+ // Also compare tokenUsage and files arrays by reference
428
+ if (
429
+ prevTask.tokenUsage !== task.tokenUsage ||
430
+ prevTask.files !== task.files
431
+ ) {
432
+ hasChanged = true;
433
+ }
434
+ }
435
+
436
+ if (!hasChanged) {
437
+ return prev; // No change, preserve identity
438
+ }
439
+
440
+ // Create new tasks map with updated task
441
+ const nextTasks = { ...prev.tasks, [taskId]: task };
442
+
443
+ // Recompute job-level summary fields
444
+ const taskCount = Object.keys(nextTasks).length;
445
+ const doneCount = Object.values(nextTasks).filter(
446
+ (t) => t.state === "done"
447
+ ).length;
448
+ const progress =
449
+ taskCount > 0 ? (doneCount / taskCount) * 100 : 0;
450
+
451
+ // Return new job object with updated tasks and summary
452
+ return {
453
+ ...prev,
454
+ tasks: nextTasks,
455
+ doneCount,
456
+ taskCount,
457
+ progress,
458
+ lastUpdated: new Date().toISOString(),
459
+ };
460
+ });
461
+ });
462
+ return; // Skip generic handling for task:updated
463
+ }
464
+
376
465
  // Path-matching state:change → schedule debounced refetch
377
466
  if (type === "state:change") {
378
467
  const d = (payload && (payload.data || payload)) || {};
@@ -415,6 +504,7 @@ export function useJobDetailWithUpdates(jobId) {
415
504
  const onStatusChanged = (evt) =>
416
505
  handleIncomingEvent("status:changed", evt);
417
506
  const onStateChange = (evt) => handleIncomingEvent("state:change", evt);
507
+ const onTaskUpdated = (evt) => handleIncomingEvent("task:updated", evt);
418
508
 
419
509
  es.addEventListener("open", onOpen);
420
510
  es.addEventListener("job:updated", onJobUpdated);
@@ -422,6 +512,7 @@ export function useJobDetailWithUpdates(jobId) {
422
512
  es.addEventListener("job:removed", onJobRemoved);
423
513
  es.addEventListener("status:changed", onStatusChanged);
424
514
  es.addEventListener("state:change", onStateChange);
515
+ es.addEventListener("task:updated", onTaskUpdated);
425
516
  es.addEventListener("error", onError);
426
517
 
427
518
  // Set connection status from readyState when possible
@@ -439,6 +530,7 @@ export function useJobDetailWithUpdates(jobId) {
439
530
  es.removeEventListener("job:removed", onJobRemoved);
440
531
  es.removeEventListener("status:changed", onStatusChanged);
441
532
  es.removeEventListener("state:change", onStateChange);
533
+ es.removeEventListener("task:updated", onTaskUpdated);
442
534
  es.removeEventListener("error", onError);
443
535
  es.close();
444
536
  } catch (err) {
@@ -20023,7 +20023,9 @@ function normalizeTasks(rawTasks) {
20023
20023
  },
20024
20024
  artifacts: Array.isArray(t2 && t2.artifacts) ? t2.artifacts.slice() : void 0,
20025
20025
  // Preserve tokenUsage if present
20026
- ...t2 && t2.tokenUsage ? { tokenUsage: t2.tokenUsage } : {}
20026
+ ...t2 && t2.tokenUsage ? { tokenUsage: t2.tokenUsage } : {},
20027
+ // Preserve error object if present
20028
+ ...t2 && t2.error ? { error: t2.error } : {}
20027
20029
  };
20028
20030
  tasks[name] = taskObj;
20029
20031
  });
@@ -20045,9 +20047,21 @@ function normalizeTasks(rawTasks) {
20045
20047
  // Preserve stage metadata for DAG visualization
20046
20048
  ...typeof t2?.currentStage === "string" && t2.currentStage.length > 0 ? { currentStage: t2.currentStage } : {},
20047
20049
  ...typeof t2?.failedStage === "string" && t2.failedStage.length > 0 ? { failedStage: t2.failedStage } : {},
20050
+ // Prefer new files.* schema, fallback to legacy artifacts
20051
+ files: t2 && t2.files ? {
20052
+ artifacts: Array.isArray(t2.files.artifacts) ? t2.files.artifacts.slice() : [],
20053
+ logs: Array.isArray(t2.files.logs) ? t2.files.logs.slice() : [],
20054
+ tmp: Array.isArray(t2.files.tmp) ? t2.files.tmp.slice() : []
20055
+ } : {
20056
+ artifacts: [],
20057
+ logs: [],
20058
+ tmp: []
20059
+ },
20048
20060
  artifacts: Array.isArray(t2 && t2.artifacts) ? t2.artifacts.slice() : void 0,
20049
20061
  // Preserve tokenUsage if present
20050
- ...t2 && t2.tokenUsage ? { tokenUsage: t2.tokenUsage } : {}
20062
+ ...t2 && t2.tokenUsage ? { tokenUsage: t2.tokenUsage } : {},
20063
+ // Preserve error object if present
20064
+ ...t2 && t2.error ? { error: t2.error } : {}
20051
20065
  };
20052
20066
  });
20053
20067
  return { tasks, warnings };
@@ -20068,7 +20082,7 @@ function computeJobSummaryStats(tasks) {
20068
20082
  function adaptJobSummary(apiJob) {
20069
20083
  const id = apiJob.jobId;
20070
20084
  const name = apiJob.title || "";
20071
- const rawTasks = apiJob.tasks;
20085
+ const rawTasks = apiJob.tasks || apiJob.tasksStatus;
20072
20086
  const location = apiJob.location;
20073
20087
  const current = apiJob.current;
20074
20088
  const currentStage = apiJob.currentStage;
@@ -20112,7 +20126,7 @@ function adaptJobSummary(apiJob) {
20112
20126
  function adaptJobDetail(apiDetail) {
20113
20127
  const id = apiDetail.jobId;
20114
20128
  const name = apiDetail.title || "";
20115
- const rawTasks = apiDetail.tasks;
20129
+ const rawTasks = apiDetail.tasks || apiDetail.tasksStatus;
20116
20130
  const location = apiDetail.location;
20117
20131
  const current = apiDetail.current;
20118
20132
  const currentStage = apiDetail.currentStage;
@@ -20930,7 +20944,7 @@ function UploadSeed({ onUploadSuccess }) {
20930
20944
  " ",
20931
20945
  "or drag and drop"
20932
20946
  ] }),
20933
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500", children: "JSON files only" })
20947
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500", children: "JSON or zip files only" })
20934
20948
  ] })
20935
20949
  }
20936
20950
  ),
@@ -20939,7 +20953,7 @@ function UploadSeed({ onUploadSuccess }) {
20939
20953
  {
20940
20954
  ref: fileInputRef,
20941
20955
  type: "file",
20942
- accept: ".json",
20956
+ accept: ".json,.zip",
20943
20957
  className: "hidden",
20944
20958
  onChange: handleFileChange,
20945
20959
  "data-testid": "file-input"
@@ -21706,6 +21720,7 @@ function TaskDetailSidebar({
21706
21720
  jobId,
21707
21721
  taskId,
21708
21722
  taskBody,
21723
+ taskError,
21709
21724
  filesByTypeForItem = () => ({ artifacts: [], logs: [], tmp: [] }),
21710
21725
  task,
21711
21726
  onClose,
@@ -21715,6 +21730,7 @@ function TaskDetailSidebar({
21715
21730
  const [filePaneType, setFilePaneType] = reactExports.useState("artifacts");
21716
21731
  const [filePaneOpen, setFilePaneOpen] = reactExports.useState(false);
21717
21732
  const [filePaneFilename, setFilePaneFilename] = reactExports.useState(null);
21733
+ const [showStack, setShowStack] = reactExports.useState(false);
21718
21734
  const closeButtonRef = reactExports.useRef(null);
21719
21735
  const getHeaderClasses2 = (status2) => {
21720
21736
  switch (status2) {
@@ -21794,7 +21810,29 @@ function TaskDetailSidebar({
21794
21810
  }
21795
21811
  ),
21796
21812
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 space-y-8 overflow-y-auto h-full", children: [
21797
- status === TaskState.FAILED && taskBody && /* @__PURE__ */ jsxRuntimeExports.jsx("section", { "aria-label": "Error", children: /* @__PURE__ */ jsxRuntimeExports.jsx(n$2, { role: "alert", "aria-live": "assertive", children: /* @__PURE__ */ jsxRuntimeExports.jsx(u$1, { className: "whitespace-pre-wrap break-words", children: taskBody }) }) }),
21813
+ status === TaskState.FAILED && (taskError?.message || taskBody) && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { "aria-label": "Error", children: [
21814
+ /* @__PURE__ */ jsxRuntimeExports.jsx(n$2, { role: "alert", "aria-live": "assertive", children: /* @__PURE__ */ jsxRuntimeExports.jsx(u$1, { className: "whitespace-pre-wrap break-words", children: taskError?.message || taskBody }) }),
21815
+ taskError?.stack && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3", children: [
21816
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
21817
+ "button",
21818
+ {
21819
+ onClick: () => setShowStack(!showStack),
21820
+ className: "text-sm text-blue-600 hover:text-blue-800 underline",
21821
+ "aria-expanded": showStack,
21822
+ "aria-controls": "error-stack",
21823
+ children: showStack ? "Hide stack" : "Show stack"
21824
+ }
21825
+ ),
21826
+ showStack && /* @__PURE__ */ jsxRuntimeExports.jsx(
21827
+ "pre",
21828
+ {
21829
+ id: "error-stack",
21830
+ className: "mt-2 p-2 bg-gray-50 border rounded text-xs font-mono max-h-64 overflow-auto whitespace-pre-wrap",
21831
+ children: taskError.stack
21832
+ }
21833
+ )
21834
+ ] })
21835
+ ] }),
21798
21836
  /* @__PURE__ */ jsxRuntimeExports.jsx("section", { className: "mt-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-4", children: [
21799
21837
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-base font-semibold text-gray-900", children: "Files" }),
21800
21838
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center space-x-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex rounded-lg border border-gray-200 bg-gray-50 p-1", children: [
@@ -22187,6 +22225,9 @@ const getHeaderClasses = (status) => {
22187
22225
  const canShowRestart = (status) => {
22188
22226
  return status === TaskState.FAILED || status === TaskState.DONE;
22189
22227
  };
22228
+ const areEqualTaskCardProps = (prevProps, nextProps) => {
22229
+ return prevProps.item === nextProps.item && prevProps.idx === nextProps.idx && prevProps.status === nextProps.status && prevProps.isActive === nextProps.isActive && prevProps.canRestart === nextProps.canRestart && prevProps.isSubmitting === nextProps.isSubmitting && prevProps.disabledReason === nextProps.disabledReason && prevProps.onClick === nextProps.onClick && prevProps.onKeyDown === nextProps.onKeyDown && prevProps.handleRestartClick === nextProps.handleRestartClick;
22230
+ };
22190
22231
  const TaskCard = reactExports.memo(function TaskCard2({
22191
22232
  item,
22192
22233
  idx,
@@ -22195,7 +22236,7 @@ const TaskCard = reactExports.memo(function TaskCard2({
22195
22236
  isActive,
22196
22237
  canRestart,
22197
22238
  isSubmitting,
22198
- getRestartDisabledReason,
22239
+ disabledReason,
22199
22240
  onClick,
22200
22241
  onKeyDown,
22201
22242
  handleRestartClick
@@ -22293,7 +22334,7 @@ const TaskCard = reactExports.memo(function TaskCard2({
22293
22334
  onClick: (e2) => handleRestartClick(e2, item.id),
22294
22335
  disabled: !canRestart || isSubmitting,
22295
22336
  className: "text-xs cursor-pointer disabled:cursor-not-allowed",
22296
- title: !canRestart ? getRestartDisabledReason() : `Restart job from ${item.id}`,
22337
+ title: !canRestart ? disabledReason : `Restart job from ${item.id}`,
22297
22338
  children: "Restart"
22298
22339
  }
22299
22340
  ) })
@@ -22301,14 +22342,15 @@ const TaskCard = reactExports.memo(function TaskCard2({
22301
22342
  ]
22302
22343
  }
22303
22344
  );
22304
- });
22345
+ }, areEqualTaskCardProps);
22305
22346
  function DAGGrid({
22306
22347
  items,
22307
22348
  cols = 3,
22308
22349
  cardClass = "",
22309
22350
  activeIndex = 0,
22310
22351
  jobId,
22311
- filesByTypeForItem = () => createEmptyTaskFiles()
22352
+ filesByTypeForItem = () => createEmptyTaskFiles(),
22353
+ taskById = {}
22312
22354
  }) {
22313
22355
  const overlayRef = reactExports.useRef(null);
22314
22356
  const gridRef = reactExports.useRef(null);
@@ -22458,7 +22500,7 @@ function DAGGrid({
22458
22500
  cancelAnimationFrame(rafRef.current);
22459
22501
  }
22460
22502
  };
22461
- }, [items, effectiveCols, visualOrder]);
22503
+ }, [items.length, effectiveCols, visualOrder]);
22462
22504
  const getStatus = (index2) => {
22463
22505
  const item = items[index2];
22464
22506
  const s2 = item?.status;
@@ -22653,6 +22695,7 @@ function DAGGrid({
22653
22695
  const status = getStatus(idx);
22654
22696
  const isActive = idx === activeIndex;
22655
22697
  const canRestart = isRestartEnabled();
22698
+ const restartDisabledReason = getRestartDisabledReason();
22656
22699
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
22657
22700
  TaskCard,
22658
22701
  {
@@ -22662,7 +22705,7 @@ function DAGGrid({
22662
22705
  isActive,
22663
22706
  canRestart,
22664
22707
  isSubmitting,
22665
- getRestartDisabledReason,
22708
+ disabledReason: restartDisabledReason,
22666
22709
  onClick: () => {
22667
22710
  setOpenIdx(idx);
22668
22711
  },
@@ -22689,6 +22732,7 @@ function DAGGrid({
22689
22732
  jobId,
22690
22733
  taskId: items[openIdx]?.id || `task-${openIdx}`,
22691
22734
  taskBody: items[openIdx]?.body || null,
22735
+ taskError: taskById[items[openIdx]?.id]?.error || null,
22692
22736
  filesByTypeForItem,
22693
22737
  task: items[openIdx],
22694
22738
  taskIndex: openIdx,
@@ -22874,6 +22918,12 @@ function JobDetail({ job, pipeline }) {
22874
22918
  }
22875
22919
  return item;
22876
22920
  });
22921
+ const allReused = newItems.every(
22922
+ (item, index2) => item === prevItems[index2]
22923
+ );
22924
+ if (allReused && prevItems.length === newItems.length) {
22925
+ return prevItems;
22926
+ }
22877
22927
  prevDagItemsRef.current = newItems;
22878
22928
  return newItems;
22879
22929
  }, [stableDagItems]);
@@ -22894,7 +22944,8 @@ function JobDetail({ job, pipeline }) {
22894
22944
  items: dagItems,
22895
22945
  activeIndex,
22896
22946
  jobId: job.id,
22897
- filesByTypeForItem
22947
+ filesByTypeForItem,
22948
+ taskById
22898
22949
  }
22899
22950
  ) });
22900
22951
  }
@@ -23153,6 +23204,7 @@ function useJobDetailWithUpdates(jobId) {
23153
23204
  newEs.addEventListener("job:removed", onJobRemoved);
23154
23205
  newEs.addEventListener("status:changed", onStatusChanged);
23155
23206
  newEs.addEventListener("state:change", onStateChange);
23207
+ newEs.addEventListener("task:updated", onTaskUpdated);
23156
23208
  newEs.addEventListener("error", onError);
23157
23209
  esRef.current = newEs;
23158
23210
  } catch (err) {
@@ -23172,6 +23224,66 @@ function useJobDetailWithUpdates(jobId) {
23172
23224
  eventQueue.current = (eventQueue.current || []).concat(eventObj);
23173
23225
  return;
23174
23226
  }
23227
+ if (type === "task:updated") {
23228
+ const p2 = payload || {};
23229
+ const { jobId: eventJobId, taskId, task } = p2;
23230
+ if (eventJobId && eventJobId !== jobId) {
23231
+ return;
23232
+ }
23233
+ if (!taskId || !task) {
23234
+ return;
23235
+ }
23236
+ startTransition(() => {
23237
+ setData((prev) => {
23238
+ if (!prev || !prev.tasks) {
23239
+ return prev;
23240
+ }
23241
+ const prevTask = prev.tasks[taskId];
23242
+ const fieldsToCompare = [
23243
+ "state",
23244
+ "currentStage",
23245
+ "failedStage",
23246
+ "startedAt",
23247
+ "endedAt",
23248
+ "attempts",
23249
+ "executionTimeMs",
23250
+ "error"
23251
+ ];
23252
+ let hasChanged = false;
23253
+ if (!prevTask) {
23254
+ hasChanged = true;
23255
+ } else {
23256
+ for (const field of fieldsToCompare) {
23257
+ if (prevTask[field] !== task[field]) {
23258
+ hasChanged = true;
23259
+ break;
23260
+ }
23261
+ }
23262
+ if (prevTask.tokenUsage !== task.tokenUsage || prevTask.files !== task.files) {
23263
+ hasChanged = true;
23264
+ }
23265
+ }
23266
+ if (!hasChanged) {
23267
+ return prev;
23268
+ }
23269
+ const nextTasks = { ...prev.tasks, [taskId]: task };
23270
+ const taskCount = Object.keys(nextTasks).length;
23271
+ const doneCount = Object.values(nextTasks).filter(
23272
+ (t2) => t2.state === "done"
23273
+ ).length;
23274
+ const progress = taskCount > 0 ? doneCount / taskCount * 100 : 0;
23275
+ return {
23276
+ ...prev,
23277
+ tasks: nextTasks,
23278
+ doneCount,
23279
+ taskCount,
23280
+ progress,
23281
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
23282
+ };
23283
+ });
23284
+ });
23285
+ return;
23286
+ }
23175
23287
  if (type === "state:change") {
23176
23288
  const d2 = payload && (payload.data || payload) || {};
23177
23289
  if (typeof d2.path === "string" && matchesJobTasksStatusPath(d2.path, jobId)) {
@@ -23204,12 +23316,14 @@ function useJobDetailWithUpdates(jobId) {
23204
23316
  const onJobRemoved = (evt) => handleIncomingEvent("job:removed", evt);
23205
23317
  const onStatusChanged = (evt) => handleIncomingEvent("status:changed", evt);
23206
23318
  const onStateChange = (evt) => handleIncomingEvent("state:change", evt);
23319
+ const onTaskUpdated = (evt) => handleIncomingEvent("task:updated", evt);
23207
23320
  es.addEventListener("open", onOpen);
23208
23321
  es.addEventListener("job:updated", onJobUpdated);
23209
23322
  es.addEventListener("job:created", onJobCreated);
23210
23323
  es.addEventListener("job:removed", onJobRemoved);
23211
23324
  es.addEventListener("status:changed", onStatusChanged);
23212
23325
  es.addEventListener("state:change", onStateChange);
23326
+ es.addEventListener("task:updated", onTaskUpdated);
23213
23327
  es.addEventListener("error", onError);
23214
23328
  if (es.readyState === 1 && mountedRef.current) {
23215
23329
  setConnectionStatus("connected");
@@ -23224,6 +23338,7 @@ function useJobDetailWithUpdates(jobId) {
23224
23338
  es.removeEventListener("job:removed", onJobRemoved);
23225
23339
  es.removeEventListener("status:changed", onStatusChanged);
23226
23340
  es.removeEventListener("state:change", onStateChange);
23341
+ es.removeEventListener("task:updated", onTaskUpdated);
23227
23342
  es.removeEventListener("error", onError);
23228
23343
  es.close();
23229
23344
  } catch (err) {