@ryanfw/prompt-orchestration-pipeline 0.9.1 → 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.
- package/package.json +2 -1
- package/src/api/index.js +38 -1
- package/src/components/DAGGrid.jsx +24 -7
- package/src/components/JobDetail.jsx +11 -0
- package/src/components/TaskDetailSidebar.jsx +27 -3
- package/src/components/UploadSeed.jsx +2 -2
- package/src/config/log-events.js +77 -0
- package/src/core/file-io.js +202 -7
- package/src/core/orchestrator.js +140 -4
- package/src/core/pipeline-runner.js +84 -6
- package/src/core/status-initializer.js +155 -0
- package/src/core/status-writer.js +151 -13
- package/src/core/symlink-utils.js +196 -0
- package/src/core/task-runner.js +37 -7
- package/src/ui/client/adapters/job-adapter.js +21 -2
- package/src/ui/client/hooks/useJobDetailWithUpdates.js +92 -0
- package/src/ui/dist/assets/{index-DqkbzXZ1.js → index-DeDzq-Kk.js} +129 -14
- package/src/ui/dist/assets/style-aBtD_Yrs.css +62 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/server.js +201 -109
- package/src/ui/zip-utils.js +103 -0
- package/src/ui/dist/assets/style-DBF9NQGk.css +0 -62
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createLogger } from "./logger.js";
|
|
4
|
+
import { ensureTaskSymlinkBridge } from "./symlink-bridge.js";
|
|
4
5
|
|
|
5
6
|
const logger = createLogger("SymlinkUtils");
|
|
6
7
|
|
|
@@ -49,6 +50,201 @@ export async function ensureSymlink(linkPath, targetPath, type) {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Validates that required task symlinks exist and point to accessible targets.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} taskDir - The task directory containing symlinks
|
|
57
|
+
* @param {Object} expectedTargets - Expected symlink targets
|
|
58
|
+
* @param {string} expectedTargets.nodeModules - Expected target for node_modules symlink
|
|
59
|
+
* @param {string} expectedTargets.taskRoot - Expected target for _task_root symlink
|
|
60
|
+
* @returns {Object} Validation result with isValid flag and details
|
|
61
|
+
*/
|
|
62
|
+
export async function validateTaskSymlinks(taskDir, expectedTargets) {
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
const validationErrors = [];
|
|
65
|
+
const validationDetails = {};
|
|
66
|
+
|
|
67
|
+
const symlinksToValidate = [
|
|
68
|
+
{ name: "node_modules", expectedTarget: expectedTargets.nodeModules },
|
|
69
|
+
{ name: "_task_root", expectedTarget: expectedTargets.taskRoot },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const { name, expectedTarget } of symlinksToValidate) {
|
|
73
|
+
const linkPath = path.join(taskDir, name);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Check if symlink exists
|
|
77
|
+
const stats = await fs.lstat(linkPath);
|
|
78
|
+
|
|
79
|
+
if (!stats.isSymbolicLink()) {
|
|
80
|
+
validationErrors.push(
|
|
81
|
+
`${name} exists but is not a symlink (type: ${stats.isFile() ? "file" : "directory"})`
|
|
82
|
+
);
|
|
83
|
+
validationDetails[name] = {
|
|
84
|
+
exists: true,
|
|
85
|
+
isSymlink: false,
|
|
86
|
+
targetAccessible: false,
|
|
87
|
+
};
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Read the symlink target
|
|
92
|
+
const actualTarget = await fs.readlink(linkPath);
|
|
93
|
+
|
|
94
|
+
// Check if target matches expected (normalize paths for comparison)
|
|
95
|
+
const normalizedActual = path.resolve(taskDir, actualTarget);
|
|
96
|
+
const normalizedExpected = path.resolve(expectedTarget);
|
|
97
|
+
|
|
98
|
+
if (normalizedActual !== normalizedExpected) {
|
|
99
|
+
validationErrors.push(
|
|
100
|
+
`${name} points to wrong target: expected ${expectedTarget}, got ${actualTarget}`
|
|
101
|
+
);
|
|
102
|
+
validationDetails[name] = {
|
|
103
|
+
exists: true,
|
|
104
|
+
isSymlink: true,
|
|
105
|
+
targetAccessible: false,
|
|
106
|
+
actualTarget,
|
|
107
|
+
expectedTarget,
|
|
108
|
+
};
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check if target is accessible
|
|
113
|
+
const targetStats = await fs.stat(normalizedActual).catch(() => null);
|
|
114
|
+
if (!targetStats) {
|
|
115
|
+
validationErrors.push(
|
|
116
|
+
`${name} target is not accessible: ${actualTarget}`
|
|
117
|
+
);
|
|
118
|
+
validationDetails[name] = {
|
|
119
|
+
exists: true,
|
|
120
|
+
isSymlink: true,
|
|
121
|
+
targetAccessible: false,
|
|
122
|
+
actualTarget,
|
|
123
|
+
};
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!targetStats.isDirectory()) {
|
|
128
|
+
validationErrors.push(
|
|
129
|
+
`${name} target is not a directory: ${actualTarget}`
|
|
130
|
+
);
|
|
131
|
+
validationDetails[name] = {
|
|
132
|
+
exists: true,
|
|
133
|
+
isSymlink: true,
|
|
134
|
+
targetAccessible: false,
|
|
135
|
+
actualTarget,
|
|
136
|
+
targetType: "file",
|
|
137
|
+
};
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Symlink is valid
|
|
142
|
+
validationDetails[name] = {
|
|
143
|
+
exists: true,
|
|
144
|
+
isSymlink: true,
|
|
145
|
+
targetAccessible: true,
|
|
146
|
+
actualTarget,
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error.code === "ENOENT") {
|
|
150
|
+
validationErrors.push(`${name} symlink does not exist`);
|
|
151
|
+
validationDetails[name] = {
|
|
152
|
+
exists: false,
|
|
153
|
+
isSymlink: false,
|
|
154
|
+
targetAccessible: false,
|
|
155
|
+
};
|
|
156
|
+
} else {
|
|
157
|
+
validationErrors.push(`${name} validation failed: ${error.message}`);
|
|
158
|
+
validationDetails[name] = {
|
|
159
|
+
exists: false,
|
|
160
|
+
isSymlink: false,
|
|
161
|
+
targetAccessible: false,
|
|
162
|
+
error: error.message,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const isValid = validationErrors.length === 0;
|
|
169
|
+
const duration = Date.now() - startTime;
|
|
170
|
+
|
|
171
|
+
logger.debug("Symlink validation completed", {
|
|
172
|
+
taskDir,
|
|
173
|
+
isValid,
|
|
174
|
+
errorsCount: validationErrors.length,
|
|
175
|
+
duration,
|
|
176
|
+
details: validationDetails,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
isValid,
|
|
181
|
+
errors: validationErrors,
|
|
182
|
+
details: validationDetails,
|
|
183
|
+
duration,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Repairs task symlinks by recreating them using the existing symlink bridge.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} taskDir - The task directory where symlinks should be created
|
|
191
|
+
* @param {string} poRoot - The repository root directory
|
|
192
|
+
* @param {string} taskModulePath - Absolute path to the original task module
|
|
193
|
+
* @returns {Object} Repair result with success flag and details
|
|
194
|
+
*/
|
|
195
|
+
export async function repairTaskSymlinks(taskDir, poRoot, taskModulePath) {
|
|
196
|
+
const startTime = Date.now();
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
logger.log("Repairing task symlinks", {
|
|
200
|
+
taskDir,
|
|
201
|
+
poRoot,
|
|
202
|
+
taskModulePath,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Use existing ensureTaskSymlinkBridge for repairs
|
|
206
|
+
const relocatedEntry = await ensureTaskSymlinkBridge({
|
|
207
|
+
taskDir,
|
|
208
|
+
poRoot,
|
|
209
|
+
taskModulePath,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const duration = Date.now() - startTime;
|
|
213
|
+
|
|
214
|
+
logger.log("Task symlinks repaired successfully", {
|
|
215
|
+
taskDir,
|
|
216
|
+
duration,
|
|
217
|
+
relocatedEntry,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
relocatedEntry,
|
|
223
|
+
duration,
|
|
224
|
+
errors: [],
|
|
225
|
+
};
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const duration = Date.now() - startTime;
|
|
228
|
+
const errorMessage = `Failed to repair task symlinks: ${error.message}`;
|
|
229
|
+
|
|
230
|
+
logger.error("Task symlink repair failed", {
|
|
231
|
+
taskDir,
|
|
232
|
+
poRoot,
|
|
233
|
+
taskModulePath,
|
|
234
|
+
duration,
|
|
235
|
+
error: error.message,
|
|
236
|
+
stack: error.stack,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
relocatedEntry: null,
|
|
242
|
+
duration,
|
|
243
|
+
errors: [errorMessage],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
52
248
|
/**
|
|
53
249
|
* Removes task symlinks from a completed job directory to avoid dangling links.
|
|
54
250
|
*
|
package/src/core/task-runner.js
CHANGED
|
@@ -4,12 +4,13 @@ import fs from "fs";
|
|
|
4
4
|
import { createLLM, getLLMEvents } from "../llm/index.js";
|
|
5
5
|
import { loadFreshModule } from "./module-loader.js";
|
|
6
6
|
import { loadEnvironment } from "./environment.js";
|
|
7
|
-
import { createTaskFileIO } from "./file-io.js";
|
|
7
|
+
import { createTaskFileIO, generateLogName } from "./file-io.js";
|
|
8
8
|
import { writeJobStatus } from "./status-writer.js";
|
|
9
9
|
import { computeDeterministicProgress } from "./progress.js";
|
|
10
10
|
import { TaskState } from "../config/statuses.js";
|
|
11
11
|
import { validateWithSchema } from "../api/validators/json.js";
|
|
12
12
|
import { createJobLogger } from "./logger.js";
|
|
13
|
+
import { LogEvent, LogFileExtension } from "../config/log-events.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Derives model key and token counts from LLM metric event.
|
|
@@ -67,13 +68,13 @@ function assertStageResult(stageName, result) {
|
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
if (!
|
|
71
|
+
if (!Object.prototype.hasOwnProperty.call(result, "output")) {
|
|
71
72
|
throw new Error(
|
|
72
73
|
`Stage "${stageName}" result missing required property: output`
|
|
73
74
|
);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
if (!
|
|
77
|
+
if (!Object.prototype.hasOwnProperty.call(result, "flags")) {
|
|
77
78
|
throw new Error(
|
|
78
79
|
`Stage "${stageName}" result missing required property: flags`
|
|
79
80
|
);
|
|
@@ -507,7 +508,11 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
507
508
|
}
|
|
508
509
|
|
|
509
510
|
// Add console output capture before stage execution using IO
|
|
510
|
-
const logName =
|
|
511
|
+
const logName = generateLogName(
|
|
512
|
+
context.meta.taskName,
|
|
513
|
+
stageName,
|
|
514
|
+
LogEvent.START
|
|
515
|
+
);
|
|
511
516
|
const logPath = path.join(context.meta.workDir, "files", "logs", logName);
|
|
512
517
|
console.debug("[task-runner] stage log path resolution via IO", {
|
|
513
518
|
stage: stageName,
|
|
@@ -593,7 +598,12 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
593
598
|
},
|
|
594
599
|
};
|
|
595
600
|
await context.io.writeLog(
|
|
596
|
-
|
|
601
|
+
generateLogName(
|
|
602
|
+
context.meta.taskName,
|
|
603
|
+
stageName,
|
|
604
|
+
LogEvent.CONTEXT,
|
|
605
|
+
LogFileExtension.JSON
|
|
606
|
+
),
|
|
597
607
|
JSON.stringify(snapshot, null, 2),
|
|
598
608
|
{ mode: "replace" }
|
|
599
609
|
);
|
|
@@ -696,6 +706,18 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
696
706
|
}
|
|
697
707
|
}
|
|
698
708
|
|
|
709
|
+
// Add explicit completion log after stage completion
|
|
710
|
+
const completeLogName = generateLogName(
|
|
711
|
+
context.meta.taskName,
|
|
712
|
+
stageName,
|
|
713
|
+
LogEvent.COMPLETE
|
|
714
|
+
);
|
|
715
|
+
await context.io.writeLog(
|
|
716
|
+
completeLogName,
|
|
717
|
+
`Stage ${stageName} completed at ${new Date().toISOString()}\n`,
|
|
718
|
+
{ mode: "replace" }
|
|
719
|
+
);
|
|
720
|
+
|
|
699
721
|
const ms = +(performance.now() - start).toFixed(2);
|
|
700
722
|
logger.log("Stage completed successfully", {
|
|
701
723
|
stage: stageName,
|
|
@@ -729,9 +751,17 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
729
751
|
context.meta.workDir,
|
|
730
752
|
"files",
|
|
731
753
|
"logs",
|
|
732
|
-
|
|
754
|
+
generateLogName(context.meta.taskName, stageName, LogEvent.START)
|
|
755
|
+
),
|
|
756
|
+
snapshotPath: path.join(
|
|
757
|
+
logsDir,
|
|
758
|
+
generateLogName(
|
|
759
|
+
context.meta.taskName,
|
|
760
|
+
stageName,
|
|
761
|
+
LogEvent.CONTEXT,
|
|
762
|
+
LogFileExtension.JSON
|
|
763
|
+
)
|
|
733
764
|
),
|
|
734
|
-
snapshotPath: path.join(logsDir, `stage-${stageName}-context.json`),
|
|
735
765
|
dataHasSeed: !!context.data?.seed,
|
|
736
766
|
seedHasData: context.data?.seed?.data !== undefined,
|
|
737
767
|
flagsKeys: Object.keys(context.flags || {}),
|
|
@@ -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) {
|