@ryanfw/prompt-orchestration-pipeline 0.0.1 → 0.3.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/README.md +415 -24
- package/package.json +45 -8
- package/src/api/files.js +48 -0
- package/src/api/index.js +149 -53
- package/src/api/validators/seed.js +141 -0
- package/src/cli/index.js +456 -29
- package/src/cli/run-orchestrator.js +39 -0
- package/src/cli/update-pipeline-json.js +47 -0
- package/src/components/DAGGrid.jsx +649 -0
- package/src/components/JobCard.jsx +96 -0
- package/src/components/JobDetail.jsx +159 -0
- package/src/components/JobTable.jsx +202 -0
- package/src/components/Layout.jsx +134 -0
- package/src/components/TaskFilePane.jsx +570 -0
- package/src/components/UploadSeed.jsx +239 -0
- package/src/components/ui/badge.jsx +20 -0
- package/src/components/ui/button.jsx +43 -0
- package/src/components/ui/card.jsx +20 -0
- package/src/components/ui/focus-styles.css +60 -0
- package/src/components/ui/progress.jsx +26 -0
- package/src/components/ui/select.jsx +27 -0
- package/src/components/ui/separator.jsx +6 -0
- package/src/config/paths.js +99 -0
- package/src/core/config.js +270 -9
- package/src/core/file-io.js +202 -0
- package/src/core/module-loader.js +157 -0
- package/src/core/orchestrator.js +275 -294
- package/src/core/pipeline-runner.js +95 -41
- package/src/core/progress.js +66 -0
- package/src/core/status-writer.js +331 -0
- package/src/core/task-runner.js +719 -73
- package/src/core/validation.js +120 -1
- package/src/lib/utils.js +6 -0
- package/src/llm/README.md +139 -30
- package/src/llm/index.js +222 -72
- package/src/pages/PipelineDetail.jsx +111 -0
- package/src/pages/PromptPipelineDashboard.jsx +223 -0
- package/src/providers/deepseek.js +3 -15
- package/src/ui/client/adapters/job-adapter.js +258 -0
- package/src/ui/client/bootstrap.js +120 -0
- package/src/ui/client/hooks/useJobDetailWithUpdates.js +619 -0
- package/src/ui/client/hooks/useJobList.js +50 -0
- package/src/ui/client/hooks/useJobListWithUpdates.js +335 -0
- package/src/ui/client/hooks/useTicker.js +26 -0
- package/src/ui/client/index.css +31 -0
- package/src/ui/client/index.html +18 -0
- package/src/ui/client/main.jsx +38 -0
- package/src/ui/config-bridge.browser.js +149 -0
- package/src/ui/config-bridge.js +149 -0
- package/src/ui/config-bridge.node.js +310 -0
- package/src/ui/dist/assets/index-BDABnI-4.js +33399 -0
- package/src/ui/dist/assets/style-Ks8LY8gB.css +28496 -0
- package/src/ui/dist/index.html +19 -0
- package/src/ui/endpoints/job-endpoints.js +300 -0
- package/src/ui/file-reader.js +216 -0
- package/src/ui/job-change-detector.js +83 -0
- package/src/ui/job-index.js +231 -0
- package/src/ui/job-reader.js +274 -0
- package/src/ui/job-scanner.js +188 -0
- package/src/ui/public/app.js +3 -1
- package/src/ui/server.js +1636 -59
- package/src/ui/sse-enhancer.js +149 -0
- package/src/ui/sse.js +204 -0
- package/src/ui/state-snapshot.js +252 -0
- package/src/ui/transformers/list-transformer.js +347 -0
- package/src/ui/transformers/status-transformer.js +307 -0
- package/src/ui/watcher.js +61 -7
- package/src/utils/dag.js +101 -0
- package/src/utils/duration.js +126 -0
- package/src/utils/id-generator.js +30 -0
- package/src/utils/jobs.js +7 -0
- package/src/utils/pipelines.js +44 -0
- package/src/utils/task-files.js +271 -0
- package/src/utils/ui.jsx +76 -0
- package/src/ui/public/index.html +0 -53
- package/src/ui/public/style.css +0 -341
package/src/core/task-runner.js
CHANGED
|
@@ -1,26 +1,358 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
|
+
import fs from "fs";
|
|
3
4
|
import { createLLM, getLLMEvents } from "../llm/index.js";
|
|
5
|
+
import { loadFreshModule } from "./module-loader.js";
|
|
4
6
|
import { loadEnvironment } from "./environment.js";
|
|
5
7
|
import { getConfig } from "./config.js";
|
|
8
|
+
import { createTaskFileIO } from "./file-io.js";
|
|
9
|
+
import { writeJobStatus } from "./status-writer.js";
|
|
10
|
+
import { computeDeterministicProgress } from "./progress.js";
|
|
6
11
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Validates that a value is a plain object (not array, null, or class instance).
|
|
14
|
+
* @param {*} value - The value to check
|
|
15
|
+
* @returns {boolean} True if the value is a plain object, false otherwise
|
|
16
|
+
*/
|
|
17
|
+
function isPlainObject(value) {
|
|
18
|
+
if (typeof value !== "object") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (value === null) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (Object.getPrototypeOf(value) === Object.prototype) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validates stage handler return values conform to { output, flags } contract.
|
|
35
|
+
* @param {string} stageName - The name of the stage for error reporting
|
|
36
|
+
* @param {*} result - The result returned by the stage handler
|
|
37
|
+
* @throws {Error} If the result doesn't conform to the expected contract
|
|
38
|
+
*/
|
|
39
|
+
function assertStageResult(stageName, result) {
|
|
40
|
+
if (result === null || result === undefined) {
|
|
41
|
+
throw new Error(`Stage "${stageName}" returned null or undefined`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof result !== "object") {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Stage "${stageName}" must return an object, got ${typeof result}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!result.hasOwnProperty("output")) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Stage "${stageName}" result missing required property: output`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!result.hasOwnProperty("flags")) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Stage "${stageName}" result missing required property: flags`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!isPlainObject(result.flags)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Stage "${stageName}" flags must be a plain object, got ${typeof result.flags}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validates flag values match declared types in schema.
|
|
71
|
+
* @param {string} stageName - The name of the stage for error reporting
|
|
72
|
+
* @param {object} flags - The flags object to validate
|
|
73
|
+
* @param {object} schema - The schema defining expected types for each flag
|
|
74
|
+
* @throws {Error} If flag types don't match the schema
|
|
75
|
+
*/
|
|
76
|
+
function validateFlagTypes(stageName, flags, schema) {
|
|
77
|
+
if (schema === undefined || schema === null) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const key in schema) {
|
|
82
|
+
const expectedTypes = schema[key];
|
|
83
|
+
const actualType = typeof flags[key];
|
|
84
|
+
|
|
85
|
+
// Allow undefined flags (they may be optional)
|
|
86
|
+
if (flags[key] === undefined) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof expectedTypes === "string") {
|
|
91
|
+
// Single expected type
|
|
92
|
+
if (actualType !== expectedTypes) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Stage "${stageName}" flag "${key}" has type ${actualType}, expected ${expectedTypes}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
} else if (Array.isArray(expectedTypes)) {
|
|
98
|
+
// Multiple allowed types
|
|
99
|
+
if (!expectedTypes.includes(actualType)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Stage "${stageName}" flag "${key}" has type ${actualType}, expected one of: ${expectedTypes.join(", ")}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Detects type conflicts when merging new flags into existing flags.
|
|
110
|
+
* @param {object} currentFlags - The existing flags object
|
|
111
|
+
* @param {object} newFlags - The new flags to merge
|
|
112
|
+
* @param {string} stageName - The name of the stage for error reporting
|
|
113
|
+
* @throws {Error} If any flag would change type when merged
|
|
114
|
+
*/
|
|
115
|
+
function checkFlagTypeConflicts(currentFlags, newFlags, stageName) {
|
|
116
|
+
for (const key of Object.keys(newFlags)) {
|
|
117
|
+
if (key in currentFlags) {
|
|
118
|
+
const currentType = typeof currentFlags[key];
|
|
119
|
+
const newType = typeof newFlags[key];
|
|
120
|
+
if (currentType !== newType) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Stage "${stageName}" attempted to change flag "${key}" type from ${currentType} to ${newType}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Ensures log directory exists before creating log files.
|
|
131
|
+
* @param {string} workDir - The working directory path
|
|
132
|
+
* @param {string} jobId - The job ID
|
|
133
|
+
* @returns {string} The full path to the logs directory
|
|
134
|
+
*/
|
|
135
|
+
function ensureLogDirectory(workDir, jobId) {
|
|
136
|
+
const logsPath = path.join(workDir, "files", "logs");
|
|
137
|
+
fs.mkdirSync(logsPath, { recursive: true });
|
|
138
|
+
return logsPath;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Writes a compact pre-execution snapshot for debugging stage inputs.
|
|
143
|
+
* Safe: does not throw on write failure; logs warnings instead.
|
|
144
|
+
* @param {string} stageName - Name of the stage
|
|
145
|
+
* @param {object} snapshot - Summary data to persist
|
|
146
|
+
* @param {string} logsDir - Directory to write the snapshot into
|
|
147
|
+
*/
|
|
148
|
+
function writePreExecutionSnapshot(stageName, snapshot, logsDir) {
|
|
149
|
+
const snapshotPath = path.join(logsDir, `stage-${stageName}-context.json`);
|
|
150
|
+
try {
|
|
151
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.warn(
|
|
154
|
+
`[task-runner] Failed to write pre-execution snapshot for ${stageName}: ${error.message}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Redirects console output to a log file for a stage.
|
|
161
|
+
* @param {string} logPath - The path to the log file
|
|
162
|
+
* @returns {() => void} A function that restores console output and closes the log stream
|
|
163
|
+
*/
|
|
164
|
+
function captureConsoleOutput(logPath) {
|
|
165
|
+
// Ensure the directory for the log file exists
|
|
166
|
+
const logDir = path.dirname(logPath);
|
|
167
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
168
|
+
|
|
169
|
+
const logStream = fs.createWriteStream(logPath, { flags: "w" });
|
|
170
|
+
|
|
171
|
+
// Store original console methods
|
|
172
|
+
const originalLog = console.log;
|
|
173
|
+
const originalError = console.error;
|
|
174
|
+
const originalWarn = console.warn;
|
|
175
|
+
const originalInfo = console.info;
|
|
176
|
+
const originalDebug = console.debug;
|
|
177
|
+
|
|
178
|
+
// Override console methods to write to stream
|
|
179
|
+
console.log = (...args) => logStream.write(args.join(" ") + "\n");
|
|
180
|
+
console.error = (...args) =>
|
|
181
|
+
logStream.write("[ERROR] " + args.join(" ") + "\n");
|
|
182
|
+
console.warn = (...args) =>
|
|
183
|
+
logStream.write("[WARN] " + args.join(" ") + "\n");
|
|
184
|
+
console.info = (...args) =>
|
|
185
|
+
logStream.write("[INFO] " + args.join(" ") + "\n");
|
|
186
|
+
console.debug = (...args) =>
|
|
187
|
+
logStream.write("[DEBUG] " + args.join(" ") + "\n");
|
|
188
|
+
|
|
189
|
+
// Return restoration function
|
|
190
|
+
return () => {
|
|
191
|
+
logStream.end();
|
|
192
|
+
console.log = originalLog;
|
|
193
|
+
console.error = originalError;
|
|
194
|
+
console.warn = originalWarn;
|
|
195
|
+
console.info = originalInfo;
|
|
196
|
+
console.debug = originalDebug;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function readStatusSnapshot(statusPath) {
|
|
201
|
+
try {
|
|
202
|
+
if (!statusPath || !fs.existsSync(statusPath)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const raw = fs.readFileSync(statusPath, "utf8");
|
|
206
|
+
if (!raw) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
return JSON.parse(raw);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.warn(
|
|
212
|
+
`[task-runner] Failed to read existing status file at ${statusPath}: ${error.message}`
|
|
213
|
+
);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function mergeStatusSnapshot(existing, updates) {
|
|
219
|
+
const base =
|
|
220
|
+
existing && typeof existing === "object" && !Array.isArray(existing)
|
|
221
|
+
? { ...existing }
|
|
222
|
+
: {};
|
|
223
|
+
|
|
224
|
+
if (updates?.data) {
|
|
225
|
+
base.data = { ...(existing?.data || {}), ...updates.data };
|
|
226
|
+
}
|
|
227
|
+
if (updates?.flags) {
|
|
228
|
+
base.flags = { ...(existing?.flags || {}), ...updates.flags };
|
|
229
|
+
}
|
|
230
|
+
if (Object.prototype.hasOwnProperty.call(updates || {}, "logs")) {
|
|
231
|
+
base.logs = updates.logs;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const [key, value] of Object.entries(updates || {})) {
|
|
235
|
+
if (key === "data" || key === "flags" || key === "logs") continue;
|
|
236
|
+
base[key] = value;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return base;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function persistStatusSnapshot(statusPath, updates) {
|
|
243
|
+
if (!statusPath || !updates) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const existing = readStatusSnapshot(statusPath);
|
|
247
|
+
const merged = mergeStatusSnapshot(existing, updates);
|
|
248
|
+
fs.writeFileSync(statusPath, JSON.stringify(merged, null, 2));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Flag schemas for each pipeline stage.
|
|
253
|
+
* Defines required flags (prerequisites) and produced flags (outputs) with their types.
|
|
254
|
+
*/
|
|
255
|
+
const FLAG_SCHEMAS = {
|
|
256
|
+
validateStructure: {
|
|
257
|
+
requires: {},
|
|
258
|
+
produces: {
|
|
259
|
+
validationFailed: "boolean",
|
|
260
|
+
lastValidationError: ["string", "object", "undefined"],
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
critique: {
|
|
264
|
+
requires: {},
|
|
265
|
+
produces: {
|
|
266
|
+
critiqueComplete: "boolean",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
refine: {
|
|
270
|
+
requires: {
|
|
271
|
+
validationFailed: "boolean",
|
|
272
|
+
},
|
|
273
|
+
produces: {
|
|
274
|
+
refined: "boolean",
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Canonical pipeline stage execution order for the modern pipeline.
|
|
281
|
+
* Each stage defines its handler, skip predicate, and iteration limits.
|
|
282
|
+
* Stages with missing handlers are automatically skipped during execution.
|
|
283
|
+
* This is the single, unified pipeline with no legacy execution paths.
|
|
284
|
+
*/
|
|
285
|
+
const PIPELINE_STAGES = [
|
|
286
|
+
{
|
|
287
|
+
name: "ingestion",
|
|
288
|
+
handler: null, // Will be populated from dynamic module import
|
|
289
|
+
skipIf: null,
|
|
290
|
+
maxIterations: null,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: "preProcessing",
|
|
294
|
+
handler: null, // Will be populated from dynamic module import
|
|
295
|
+
skipIf: null,
|
|
296
|
+
maxIterations: null,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "promptTemplating",
|
|
300
|
+
handler: null, // Will be populated from dynamic module import
|
|
301
|
+
skipIf: null,
|
|
302
|
+
maxIterations: null,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "inference",
|
|
306
|
+
handler: null, // Will be populated from dynamic module import
|
|
307
|
+
skipIf: null,
|
|
308
|
+
maxIterations: null,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "parsing",
|
|
312
|
+
handler: null, // Will be populated from dynamic module import
|
|
313
|
+
skipIf: null,
|
|
314
|
+
maxIterations: null,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "validateStructure",
|
|
318
|
+
handler: null, // Will be populated from dynamic module import
|
|
319
|
+
skipIf: null,
|
|
320
|
+
maxIterations: null,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: "validateQuality",
|
|
324
|
+
handler: null, // Will be populated from dynamic module import
|
|
325
|
+
skipIf: null,
|
|
326
|
+
maxIterations: null,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "critique",
|
|
330
|
+
handler: null, // Will be populated from dynamic module import
|
|
331
|
+
skipIf: (flags) => flags.validationFailed === false,
|
|
332
|
+
maxIterations: null,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "refine",
|
|
336
|
+
handler: null, // Will be populated from dynamic module import
|
|
337
|
+
skipIf: (flags) => flags.validationFailed === false,
|
|
338
|
+
maxIterations: (seed) => seed.maxRefinements || 1,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: "finalValidation",
|
|
342
|
+
handler: null, // Will be populated from dynamic module import
|
|
343
|
+
skipIf: null,
|
|
344
|
+
maxIterations: null,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "integration",
|
|
348
|
+
handler: null, // Will be populated from dynamic module import
|
|
349
|
+
skipIf: null,
|
|
350
|
+
maxIterations: null,
|
|
351
|
+
},
|
|
20
352
|
];
|
|
21
353
|
|
|
22
354
|
/**
|
|
23
|
-
* Runs a pipeline by loading a module that exports functions keyed by
|
|
355
|
+
* Runs a pipeline by loading a module that exports functions keyed by stage name.
|
|
24
356
|
*/
|
|
25
357
|
export async function runPipeline(modulePath, initialContext = {}) {
|
|
26
358
|
if (!initialContext.envLoaded) {
|
|
@@ -28,20 +360,15 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
28
360
|
initialContext.envLoaded = true;
|
|
29
361
|
}
|
|
30
362
|
|
|
31
|
-
if (!initialContext.llm)
|
|
32
|
-
initialContext.llm = createLLM({
|
|
33
|
-
defaultProvider: initialContext.modelConfig?.defaultProvider || "openai",
|
|
34
|
-
});
|
|
35
|
-
}
|
|
363
|
+
if (!initialContext.llm) initialContext.llm = createLLM();
|
|
36
364
|
|
|
37
|
-
const config = getConfig();
|
|
38
365
|
const llmMetrics = [];
|
|
39
366
|
const llmEvents = getLLMEvents();
|
|
40
367
|
|
|
41
368
|
const onLLMComplete = (metric) => {
|
|
42
369
|
llmMetrics.push({
|
|
43
370
|
...metric,
|
|
44
|
-
task: context.taskName,
|
|
371
|
+
task: context.meta.taskName,
|
|
45
372
|
stage: context.currentStage,
|
|
46
373
|
});
|
|
47
374
|
};
|
|
@@ -52,35 +379,111 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
52
379
|
);
|
|
53
380
|
|
|
54
381
|
const abs = toAbsFileURL(modulePath);
|
|
55
|
-
|
|
56
|
-
const modUrl = `${abs.href}?t=${Date.now()}`;
|
|
57
|
-
const mod = await import(modUrl);
|
|
382
|
+
const mod = await loadFreshModule(abs);
|
|
58
383
|
const tasks = mod.default ?? mod;
|
|
59
384
|
|
|
60
|
-
|
|
385
|
+
// Populate PIPELINE_STAGES handlers from dynamically loaded tasks or test override
|
|
386
|
+
const handlersSource = initialContext.tasksOverride || tasks;
|
|
387
|
+
PIPELINE_STAGES.forEach((stageConfig) => {
|
|
388
|
+
if (
|
|
389
|
+
handlersSource[stageConfig.name] &&
|
|
390
|
+
typeof handlersSource[stageConfig.name] === "function"
|
|
391
|
+
) {
|
|
392
|
+
stageConfig.handler = handlersSource[stageConfig.name];
|
|
393
|
+
} else {
|
|
394
|
+
// Set handler to null when not available - will be skipped
|
|
395
|
+
stageConfig.handler = null;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Create fileIO singleton if we have the required context
|
|
400
|
+
let fileIO = null;
|
|
401
|
+
if (
|
|
402
|
+
initialContext.workDir &&
|
|
403
|
+
initialContext.taskName &&
|
|
404
|
+
initialContext.statusPath
|
|
405
|
+
) {
|
|
406
|
+
fileIO = createTaskFileIO({
|
|
407
|
+
workDir: initialContext.workDir,
|
|
408
|
+
taskName: initialContext.taskName,
|
|
409
|
+
getStage: () => context.currentStage,
|
|
410
|
+
statusPath: initialContext.statusPath,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Extract seed and maxRefinements for new context structure
|
|
415
|
+
const seed = initialContext.seed || initialContext;
|
|
416
|
+
const maxRefinements = seed.maxRefinements ?? 1; // Default to 1 unless explicitly set
|
|
417
|
+
|
|
418
|
+
// Create new context structure with io, llm, meta, data, flags, logs, currentStage
|
|
419
|
+
const context = {
|
|
420
|
+
io: fileIO,
|
|
421
|
+
llm: initialContext.llm,
|
|
422
|
+
meta: {
|
|
423
|
+
taskName: initialContext.taskName,
|
|
424
|
+
workDir: initialContext.workDir,
|
|
425
|
+
statusPath: initialContext.statusPath,
|
|
426
|
+
jobId: initialContext.jobId,
|
|
427
|
+
envLoaded: initialContext.envLoaded,
|
|
428
|
+
modelConfig: initialContext.modelConfig,
|
|
429
|
+
pipelineTasks:
|
|
430
|
+
initialContext.meta?.pipelineTasks ||
|
|
431
|
+
initialContext.pipelineTasks ||
|
|
432
|
+
[],
|
|
433
|
+
},
|
|
434
|
+
data: {
|
|
435
|
+
seed: seed,
|
|
436
|
+
},
|
|
437
|
+
flags: {},
|
|
438
|
+
logs: [],
|
|
439
|
+
currentStage: null,
|
|
440
|
+
};
|
|
61
441
|
const logs = [];
|
|
62
442
|
let needsRefinement = false;
|
|
63
443
|
let refinementCount = 0;
|
|
64
|
-
|
|
444
|
+
let lastStageOutput = context.data.seed;
|
|
445
|
+
let lastStageName = "seed";
|
|
446
|
+
let lastExecutedStageName = "seed";
|
|
447
|
+
|
|
448
|
+
// Ensure log directory exists before stage execution
|
|
449
|
+
const logsDir = ensureLogDirectory(context.meta.workDir, context.meta.jobId);
|
|
65
450
|
|
|
66
451
|
do {
|
|
67
452
|
needsRefinement = false;
|
|
68
453
|
let preRefinedThisCycle = false;
|
|
69
454
|
|
|
70
|
-
for (const
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
455
|
+
for (const stageConfig of PIPELINE_STAGES) {
|
|
456
|
+
const stageName = stageConfig.name;
|
|
457
|
+
const stageHandler = stageConfig.handler;
|
|
458
|
+
|
|
459
|
+
// Skip stages when skipIf predicate returns true
|
|
460
|
+
if (stageConfig.skipIf && stageConfig.skipIf(context.flags)) {
|
|
461
|
+
context.logs.push({
|
|
462
|
+
stage: stageName,
|
|
463
|
+
action: "skipped",
|
|
464
|
+
reason: "skipIf predicate returned true",
|
|
465
|
+
timestamp: new Date().toISOString(),
|
|
466
|
+
});
|
|
75
467
|
continue;
|
|
76
468
|
}
|
|
77
469
|
|
|
470
|
+
// Skip if handler is not available (not implemented)
|
|
471
|
+
if (typeof stageHandler !== "function") {
|
|
472
|
+
logs.push({
|
|
473
|
+
stage: stageName,
|
|
474
|
+
skipped: true,
|
|
475
|
+
refinementCycle: refinementCount,
|
|
476
|
+
});
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Skip ingestion and preProcessing during refinement cycles
|
|
78
481
|
if (
|
|
79
482
|
refinementCount > 0 &&
|
|
80
|
-
["ingestion", "preProcessing"].includes(
|
|
483
|
+
["ingestion", "preProcessing"].includes(stageName)
|
|
81
484
|
) {
|
|
82
485
|
logs.push({
|
|
83
|
-
stage,
|
|
486
|
+
stage: stageName,
|
|
84
487
|
skipped: true,
|
|
85
488
|
reason: "refinement-cycle",
|
|
86
489
|
refinementCycle: refinementCount,
|
|
@@ -88,15 +491,17 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
88
491
|
continue;
|
|
89
492
|
}
|
|
90
493
|
|
|
494
|
+
// Handle pre-refinement logic for validation stages
|
|
91
495
|
if (
|
|
92
496
|
refinementCount > 0 &&
|
|
93
497
|
!preRefinedThisCycle &&
|
|
94
|
-
!context.refined &&
|
|
95
|
-
(
|
|
498
|
+
!context.flags.refined &&
|
|
499
|
+
(stageName === "validateStructure" || stageName === "validateQuality")
|
|
96
500
|
) {
|
|
97
501
|
for (const s of ["critique", "refine"]) {
|
|
98
|
-
const
|
|
99
|
-
|
|
502
|
+
const sConfig = PIPELINE_STAGES.find((config) => config.name === s);
|
|
503
|
+
const sHandler = sConfig?.handler;
|
|
504
|
+
if (typeof sHandler !== "function") {
|
|
100
505
|
logs.push({
|
|
101
506
|
stage: s,
|
|
102
507
|
skipped: true,
|
|
@@ -107,8 +512,7 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
107
512
|
}
|
|
108
513
|
const sStart = performance.now();
|
|
109
514
|
try {
|
|
110
|
-
const r = await
|
|
111
|
-
if (r && typeof r === "object") Object.assign(context, r);
|
|
515
|
+
const r = await sHandler(context);
|
|
112
516
|
const sMs = +(performance.now() - sStart).toFixed(2);
|
|
113
517
|
logs.push({
|
|
114
518
|
stage: s,
|
|
@@ -140,9 +544,13 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
140
544
|
preRefinedThisCycle = true;
|
|
141
545
|
}
|
|
142
546
|
|
|
143
|
-
|
|
547
|
+
// Skip critique and refine if already pre-refined
|
|
548
|
+
if (
|
|
549
|
+
preRefinedThisCycle &&
|
|
550
|
+
(stageName === "critique" || stageName === "refine")
|
|
551
|
+
) {
|
|
144
552
|
logs.push({
|
|
145
|
-
stage,
|
|
553
|
+
stage: stageName,
|
|
146
554
|
skipped: true,
|
|
147
555
|
reason: "already-pre-refined",
|
|
148
556
|
refinementCycle: refinementCount,
|
|
@@ -150,52 +558,286 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
150
558
|
continue;
|
|
151
559
|
}
|
|
152
560
|
|
|
561
|
+
// Add console output capture before stage execution
|
|
562
|
+
const logPath = path.join(
|
|
563
|
+
context.meta.workDir,
|
|
564
|
+
"files",
|
|
565
|
+
"logs",
|
|
566
|
+
`stage-${stageName}.log`
|
|
567
|
+
);
|
|
568
|
+
console.debug("[task-runner] stage log path resolution", {
|
|
569
|
+
stage: stageName,
|
|
570
|
+
workDir: context.meta.workDir,
|
|
571
|
+
jobId: context.meta.jobId,
|
|
572
|
+
logPath,
|
|
573
|
+
});
|
|
574
|
+
const restoreConsole = captureConsoleOutput(logPath);
|
|
575
|
+
|
|
576
|
+
// Set current stage before execution
|
|
577
|
+
context.currentStage = stageName;
|
|
578
|
+
|
|
579
|
+
// Write stage start status using writeJobStatus
|
|
580
|
+
if (context.meta.workDir && context.meta.taskName) {
|
|
581
|
+
try {
|
|
582
|
+
await writeJobStatus(context.meta.workDir, (snapshot) => {
|
|
583
|
+
snapshot.current = context.meta.taskName;
|
|
584
|
+
snapshot.currentStage = stageName;
|
|
585
|
+
snapshot.lastUpdated = new Date().toISOString();
|
|
586
|
+
|
|
587
|
+
// Ensure task exists and update task-specific fields
|
|
588
|
+
if (!snapshot.tasks[context.meta.taskName]) {
|
|
589
|
+
snapshot.tasks[context.meta.taskName] = {};
|
|
590
|
+
}
|
|
591
|
+
snapshot.tasks[context.meta.taskName].currentStage = stageName;
|
|
592
|
+
snapshot.tasks[context.meta.taskName].state = "running";
|
|
593
|
+
});
|
|
594
|
+
} catch (error) {
|
|
595
|
+
// Don't fail the pipeline if status write fails
|
|
596
|
+
console.warn(`Failed to write stage start status: ${error.message}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Clone data and flags before stage execution
|
|
601
|
+
const stageData = JSON.parse(JSON.stringify(context.data));
|
|
602
|
+
const stageFlags = JSON.parse(JSON.stringify(context.flags));
|
|
603
|
+
const stageContext = {
|
|
604
|
+
io: context.io,
|
|
605
|
+
llm: context.llm,
|
|
606
|
+
meta: context.meta,
|
|
607
|
+
data: stageData,
|
|
608
|
+
flags: stageFlags,
|
|
609
|
+
currentStage: stageName,
|
|
610
|
+
output: JSON.parse(
|
|
611
|
+
JSON.stringify(
|
|
612
|
+
lastStageOutput !== undefined
|
|
613
|
+
? lastStageOutput
|
|
614
|
+
: (context.data.seed ?? null)
|
|
615
|
+
)
|
|
616
|
+
),
|
|
617
|
+
previousStage: lastExecutedStageName,
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// Write pre-execution snapshot for debugging inputs
|
|
621
|
+
const snapshot = {
|
|
622
|
+
meta: { taskName: context.meta.taskName, jobId: context.meta.jobId },
|
|
623
|
+
previousStage: lastExecutedStageName,
|
|
624
|
+
refinementCycle: refinementCount,
|
|
625
|
+
dataSummary: {
|
|
626
|
+
keys: Object.keys(context.data),
|
|
627
|
+
hasSeed: !!context.data?.seed,
|
|
628
|
+
seedKeys: Object.keys(context.data?.seed || {}),
|
|
629
|
+
seedHasData: context.data?.seed?.data !== undefined,
|
|
630
|
+
},
|
|
631
|
+
flagsSummary: {
|
|
632
|
+
keys: Object.keys(context.flags),
|
|
633
|
+
},
|
|
634
|
+
outputSummary: {
|
|
635
|
+
type: typeof stageContext.output,
|
|
636
|
+
keys:
|
|
637
|
+
stageContext.output && typeof stageContext.output === "object"
|
|
638
|
+
? Object.keys(stageContext.output).slice(0, 20)
|
|
639
|
+
: [],
|
|
640
|
+
},
|
|
641
|
+
};
|
|
642
|
+
writePreExecutionSnapshot(stageName, snapshot, logsDir);
|
|
643
|
+
|
|
644
|
+
// Validate prerequisite flags before stage execution
|
|
645
|
+
const requiredFlags = FLAG_SCHEMAS[stageName]?.requires;
|
|
646
|
+
if (requiredFlags && Object.keys(requiredFlags).length > 0) {
|
|
647
|
+
validateFlagTypes(stageName, context.flags, requiredFlags);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Execute the stage
|
|
153
651
|
const start = performance.now();
|
|
652
|
+
let stageResult;
|
|
154
653
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
654
|
+
context.logs.push({
|
|
655
|
+
stage: stageName,
|
|
656
|
+
action: "debugging",
|
|
657
|
+
data: stageContext,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
console.log("STAGE CONTEXT", JSON.stringify(stageContext, null, 2));
|
|
661
|
+
stageResult = await stageHandler(stageContext);
|
|
662
|
+
|
|
663
|
+
// Validate stage result shape after execution
|
|
664
|
+
assertStageResult(stageName, stageResult);
|
|
665
|
+
|
|
666
|
+
// Validate produced flags against schema
|
|
667
|
+
const producedFlagsSchema = FLAG_SCHEMAS[stageName]?.produces;
|
|
668
|
+
if (producedFlagsSchema) {
|
|
669
|
+
validateFlagTypes(stageName, stageResult.flags, producedFlagsSchema);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Check for flag type conflicts before merging
|
|
673
|
+
checkFlagTypeConflicts(context.flags, stageResult.flags, stageName);
|
|
674
|
+
|
|
675
|
+
// Store stage output in context.data
|
|
676
|
+
context.data[stageName] = stageResult.output;
|
|
677
|
+
lastStageName = stageName;
|
|
678
|
+
|
|
679
|
+
// Only update lastStageOutput and lastExecutedStageName for non-validation stages
|
|
680
|
+
// This ensures previousStage and context.output skip validation stages
|
|
681
|
+
const validationStages = [
|
|
682
|
+
"validateStructure",
|
|
683
|
+
"validateQuality",
|
|
684
|
+
"validateFinal",
|
|
685
|
+
"finalValidation",
|
|
686
|
+
];
|
|
687
|
+
if (!validationStages.includes(stageName)) {
|
|
688
|
+
lastStageOutput = stageResult.output;
|
|
689
|
+
lastExecutedStageName = stageName;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Merge stage flags into context.flags
|
|
693
|
+
context.flags = { ...context.flags, ...stageResult.flags };
|
|
694
|
+
|
|
695
|
+
// Add audit log entry after stage completes
|
|
696
|
+
context.logs.push({
|
|
697
|
+
stage: stageName,
|
|
698
|
+
action: "completed",
|
|
699
|
+
outputType: typeof stageResult.output,
|
|
700
|
+
flagKeys: Object.keys(stageResult.flags),
|
|
701
|
+
timestamp: new Date().toISOString(),
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Write stage completion status
|
|
705
|
+
if (context.meta.workDir && context.meta.taskName) {
|
|
706
|
+
try {
|
|
707
|
+
await writeJobStatus(context.meta.workDir, (snapshot) => {
|
|
708
|
+
// Keep current task and stage as-is since we're still within the same task
|
|
709
|
+
snapshot.current = context.meta.taskName;
|
|
710
|
+
snapshot.currentStage = stageName;
|
|
711
|
+
snapshot.lastUpdated = new Date().toISOString();
|
|
712
|
+
|
|
713
|
+
// Compute deterministic progress after stage completion
|
|
714
|
+
const pct = computeDeterministicProgress(
|
|
715
|
+
context.meta.pipelineTasks || [],
|
|
716
|
+
context.meta.taskName,
|
|
717
|
+
stageName
|
|
718
|
+
);
|
|
719
|
+
snapshot.progress = pct;
|
|
720
|
+
|
|
721
|
+
// Debug log for progress computation
|
|
722
|
+
console.debug("[task-runner] stage completion progress", {
|
|
723
|
+
task: context.meta.taskName,
|
|
724
|
+
stage: stageName,
|
|
725
|
+
progress: pct,
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// Ensure task exists and update task-specific fields
|
|
729
|
+
if (!snapshot.tasks[context.meta.taskName]) {
|
|
730
|
+
snapshot.tasks[context.meta.taskName] = {};
|
|
731
|
+
}
|
|
732
|
+
snapshot.tasks[context.meta.taskName].currentStage = stageName;
|
|
733
|
+
snapshot.tasks[context.meta.taskName].state = "running";
|
|
734
|
+
});
|
|
735
|
+
} catch (error) {
|
|
736
|
+
// Don't fail the pipeline if status write fails
|
|
737
|
+
console.warn(
|
|
738
|
+
`Failed to write stage completion status: ${error.message}`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
158
742
|
|
|
159
743
|
const ms = +(performance.now() - start).toFixed(2);
|
|
160
|
-
logs.push({
|
|
744
|
+
logs.push({
|
|
745
|
+
stage: stageName,
|
|
746
|
+
ok: true,
|
|
747
|
+
ms,
|
|
748
|
+
refinementCycle: refinementCount,
|
|
749
|
+
});
|
|
161
750
|
|
|
162
751
|
if (
|
|
163
|
-
(
|
|
164
|
-
|
|
752
|
+
(stageName === "validateStructure" ||
|
|
753
|
+
stageName === "validateQuality") &&
|
|
754
|
+
context.flags.validationFailed &&
|
|
165
755
|
refinementCount < maxRefinements
|
|
166
756
|
) {
|
|
167
757
|
needsRefinement = true;
|
|
168
|
-
|
|
758
|
+
// Don't reset validationFailed here - let the refinement cycle handle it
|
|
169
759
|
break;
|
|
170
760
|
}
|
|
171
761
|
} catch (error) {
|
|
762
|
+
console.error(`Stage ${stageName} failed:`, error);
|
|
172
763
|
const ms = +(performance.now() - start).toFixed(2);
|
|
173
764
|
const errInfo = normalizeError(error);
|
|
765
|
+
|
|
766
|
+
// Attach debug metadata to the error envelope for richer diagnostics
|
|
767
|
+
errInfo.debug = {
|
|
768
|
+
stage: stageName,
|
|
769
|
+
previousStage: lastExecutedStageName,
|
|
770
|
+
refinementCycle: refinementCount,
|
|
771
|
+
logPath: path.join(
|
|
772
|
+
context.meta.workDir,
|
|
773
|
+
"files",
|
|
774
|
+
"logs",
|
|
775
|
+
`stage-${stageName}.log`
|
|
776
|
+
),
|
|
777
|
+
snapshotPath: path.join(logsDir, `stage-${stageName}-context.json`),
|
|
778
|
+
dataHasSeed: !!context.data?.seed,
|
|
779
|
+
seedHasData: context.data?.seed?.data !== undefined,
|
|
780
|
+
flagsKeys: Object.keys(context.flags || {}),
|
|
781
|
+
};
|
|
782
|
+
|
|
174
783
|
logs.push({
|
|
175
|
-
stage,
|
|
784
|
+
stage: stageName,
|
|
176
785
|
ok: false,
|
|
177
786
|
ms,
|
|
178
787
|
error: errInfo,
|
|
179
788
|
refinementCycle: refinementCount,
|
|
180
789
|
});
|
|
181
790
|
|
|
791
|
+
// For validation stages, trigger refinement if we haven't exceeded max refinements AND maxRefinements > 0
|
|
182
792
|
if (
|
|
183
|
-
(
|
|
793
|
+
(stageName === "validateStructure" ||
|
|
794
|
+
stageName === "validateQuality") &&
|
|
795
|
+
maxRefinements > 0 &&
|
|
184
796
|
refinementCount < maxRefinements
|
|
185
797
|
) {
|
|
186
|
-
context.lastValidationError = errInfo;
|
|
798
|
+
context.flags.lastValidationError = errInfo;
|
|
799
|
+
context.flags.validationFailed = true; // Set the flag to trigger refinement
|
|
187
800
|
needsRefinement = true;
|
|
188
801
|
break;
|
|
189
802
|
}
|
|
190
803
|
|
|
804
|
+
// Write failure status using writeJobStatus
|
|
805
|
+
if (context.meta.workDir && context.meta.taskName) {
|
|
806
|
+
try {
|
|
807
|
+
await writeJobStatus(context.meta.workDir, (snapshot) => {
|
|
808
|
+
snapshot.current = context.meta.taskName;
|
|
809
|
+
snapshot.currentStage = stageName;
|
|
810
|
+
snapshot.state = "failed";
|
|
811
|
+
snapshot.lastUpdated = new Date().toISOString();
|
|
812
|
+
|
|
813
|
+
// Ensure task exists and update task-specific fields
|
|
814
|
+
if (!snapshot.tasks[context.meta.taskName]) {
|
|
815
|
+
snapshot.tasks[context.meta.taskName] = {};
|
|
816
|
+
}
|
|
817
|
+
snapshot.tasks[context.meta.taskName].state = "failed";
|
|
818
|
+
snapshot.tasks[context.meta.taskName].failedStage = stageName;
|
|
819
|
+
snapshot.tasks[context.meta.taskName].currentStage = stageName;
|
|
820
|
+
});
|
|
821
|
+
} catch (error) {
|
|
822
|
+
// Don't fail the pipeline if status write fails
|
|
823
|
+
console.warn(`Failed to write failure status: ${error.message}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// For non-validation stages or when refinements are exhausted, fail immediately
|
|
191
828
|
return {
|
|
192
829
|
ok: false,
|
|
193
|
-
failedStage:
|
|
830
|
+
failedStage: stageName,
|
|
194
831
|
error: errInfo,
|
|
195
832
|
logs,
|
|
196
833
|
context,
|
|
197
834
|
refinementAttempts: refinementCount,
|
|
198
835
|
};
|
|
836
|
+
} finally {
|
|
837
|
+
// Add console output restoration after stage execution
|
|
838
|
+
if (restoreConsole) {
|
|
839
|
+
restoreConsole();
|
|
840
|
+
}
|
|
199
841
|
}
|
|
200
842
|
}
|
|
201
843
|
|
|
@@ -204,7 +846,7 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
204
846
|
logs.push({
|
|
205
847
|
stage: "refinement-trigger",
|
|
206
848
|
refinementCycle: refinementCount,
|
|
207
|
-
reason: context.lastValidationError
|
|
849
|
+
reason: context.flags.lastValidationError
|
|
208
850
|
? "validation-error"
|
|
209
851
|
: "validation-failed-flag",
|
|
210
852
|
});
|
|
@@ -216,7 +858,7 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
216
858
|
typeof tasks.validateStructure === "function" ||
|
|
217
859
|
typeof tasks.validateQuality === "function";
|
|
218
860
|
|
|
219
|
-
if (context.validationFailed && hasValidation) {
|
|
861
|
+
if (context.flags.validationFailed && hasValidation) {
|
|
220
862
|
return {
|
|
221
863
|
ok: false,
|
|
222
864
|
failedStage: "final-validation",
|
|
@@ -229,6 +871,29 @@ export async function runPipeline(modulePath, initialContext = {}) {
|
|
|
229
871
|
|
|
230
872
|
llmEvents.off("llm:request:complete", onLLMComplete);
|
|
231
873
|
|
|
874
|
+
// Write final status with currentStage: null to indicate completion
|
|
875
|
+
if (context.meta.workDir && context.meta.taskName) {
|
|
876
|
+
try {
|
|
877
|
+
await writeJobStatus(context.meta.workDir, (snapshot) => {
|
|
878
|
+
snapshot.current = null;
|
|
879
|
+
snapshot.currentStage = null;
|
|
880
|
+
snapshot.state = "done";
|
|
881
|
+
snapshot.progress = 100;
|
|
882
|
+
snapshot.lastUpdated = new Date().toISOString();
|
|
883
|
+
|
|
884
|
+
// Update task state to done
|
|
885
|
+
if (!snapshot.tasks[context.meta.taskName]) {
|
|
886
|
+
snapshot.tasks[context.meta.taskName] = {};
|
|
887
|
+
}
|
|
888
|
+
snapshot.tasks[context.meta.taskName].state = "done";
|
|
889
|
+
snapshot.tasks[context.meta.taskName].currentStage = null;
|
|
890
|
+
});
|
|
891
|
+
} catch (error) {
|
|
892
|
+
// Don't fail the pipeline if final status write fails
|
|
893
|
+
console.warn(`Failed to write final status: ${error.message}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
232
897
|
return {
|
|
233
898
|
ok: true,
|
|
234
899
|
logs,
|
|
@@ -252,25 +917,6 @@ export async function runPipelineWithModelRouting(
|
|
|
252
917
|
return runPipeline(modulePath, context);
|
|
253
918
|
}
|
|
254
919
|
|
|
255
|
-
export function selectModel(taskType, complexity, speed = "normal") {
|
|
256
|
-
const modelMap = {
|
|
257
|
-
"simple-fast": "gpt-3.5-turbo",
|
|
258
|
-
"simple-accurate": "gpt-4",
|
|
259
|
-
"complex-fast": "gpt-4",
|
|
260
|
-
"complex-accurate": "gpt-4-turbo",
|
|
261
|
-
specialized: "claude-3-opus",
|
|
262
|
-
};
|
|
263
|
-
const key =
|
|
264
|
-
complexity === "high"
|
|
265
|
-
? speed === "fast"
|
|
266
|
-
? "complex-fast"
|
|
267
|
-
: "complex-accurate"
|
|
268
|
-
: speed === "fast"
|
|
269
|
-
? "simple-fast"
|
|
270
|
-
: "simple-accurate";
|
|
271
|
-
return modelMap[key] || "gpt-4";
|
|
272
|
-
}
|
|
273
|
-
|
|
274
920
|
function toAbsFileURL(p) {
|
|
275
921
|
if (!path.isAbsolute(p)) {
|
|
276
922
|
throw new Error(
|