@hilbras/omninode 0.5.2 → 0.7.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 +48 -4
- package/dist/cli/index.js +990 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +546 -61
- package/dist/index.js +815 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -137,11 +137,29 @@ var roleConfigSchema = z.object({
|
|
|
137
137
|
system_prompt: z.string().optional(),
|
|
138
138
|
metadata: z.record(z.unknown()).optional()
|
|
139
139
|
}).strict();
|
|
140
|
+
var pipelineStepConfigSchema = z.object({
|
|
141
|
+
id: z.string().min(1),
|
|
142
|
+
kind: z.enum(["research", "collect", "analyze", "plan", "execute", "custom"]),
|
|
143
|
+
agents: z.array(z.string()).optional(),
|
|
144
|
+
agent: z.string().optional(),
|
|
145
|
+
model: z.string().optional(),
|
|
146
|
+
depends_on: z.array(z.string()).optional(),
|
|
147
|
+
retries: z.number().int().min(0).max(10).optional(),
|
|
148
|
+
condition: z.enum(["always", "on-success", "on-failure"]).optional()
|
|
149
|
+
}).strict();
|
|
150
|
+
var pipelineConfigSchema = z.object({
|
|
151
|
+
id: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "pipeline id must be kebab-case"),
|
|
152
|
+
name: z.string().optional(),
|
|
153
|
+
objective: z.string().optional(),
|
|
154
|
+
role: z.string().optional(),
|
|
155
|
+
steps: z.array(pipelineStepConfigSchema).min(1)
|
|
156
|
+
}).strict();
|
|
140
157
|
var projectConfigSchema = z.object({
|
|
141
158
|
name: z.string().min(1),
|
|
142
159
|
providers: z.array(providerConfigSchema).default([]),
|
|
143
160
|
agents: z.array(agentConfigSchema).default([]),
|
|
144
161
|
roles: z.array(roleConfigSchema).default([]),
|
|
162
|
+
pipelines: z.array(pipelineConfigSchema).default([]),
|
|
145
163
|
memory: z.object({ provider: z.string().min(1) }).strict().optional()
|
|
146
164
|
}).strict();
|
|
147
165
|
var appConfigSchema = z.object({
|
|
@@ -211,11 +229,30 @@ function toAppConfig(yaml) {
|
|
|
211
229
|
providers: yaml.project.providers.map(toProviderConfig),
|
|
212
230
|
agents: yaml.project.agents.map(toAgentConfig),
|
|
213
231
|
roles: yaml.project.roles.map(toRoleDefinition),
|
|
232
|
+
pipelines: yaml.project.pipelines.map(toPipelineDefinition),
|
|
214
233
|
...yaml.project.memory !== void 0 ? { memory: yaml.project.memory } : {}
|
|
215
234
|
},
|
|
216
235
|
...yaml.logging !== void 0 ? { logging: yaml.logging } : {}
|
|
217
236
|
};
|
|
218
237
|
}
|
|
238
|
+
function toPipelineDefinition(raw) {
|
|
239
|
+
return {
|
|
240
|
+
id: raw.id,
|
|
241
|
+
name: raw.name,
|
|
242
|
+
objective: raw.objective,
|
|
243
|
+
role: raw.role,
|
|
244
|
+
steps: raw.steps.map((step) => ({
|
|
245
|
+
id: step.id,
|
|
246
|
+
kind: step.kind,
|
|
247
|
+
agents: step.agents,
|
|
248
|
+
agent: step.agent,
|
|
249
|
+
model: step.model,
|
|
250
|
+
dependsOn: step.depends_on,
|
|
251
|
+
retries: step.retries,
|
|
252
|
+
condition: step.condition
|
|
253
|
+
}))
|
|
254
|
+
};
|
|
255
|
+
}
|
|
219
256
|
function toProviderConfig(raw) {
|
|
220
257
|
return {
|
|
221
258
|
name: raw.name,
|
|
@@ -914,23 +951,42 @@ import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
|
914
951
|
import { dirname } from "path";
|
|
915
952
|
var FileTaskStore = class {
|
|
916
953
|
filePath;
|
|
954
|
+
/**
|
|
955
|
+
* Read-modify-write operations are serialized: parallel pipeline tasks
|
|
956
|
+
* would otherwise race on the JSON file (last writer wins).
|
|
957
|
+
*/
|
|
958
|
+
queue = Promise.resolve();
|
|
917
959
|
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
918
960
|
this.filePath = `${directory}/tasks.json`;
|
|
919
961
|
}
|
|
920
962
|
async save(task) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
963
|
+
return this.synchronized(async () => {
|
|
964
|
+
const tasks = await this.load();
|
|
965
|
+
const index = tasks.findIndex((t) => t.id === task.id);
|
|
966
|
+
if (index >= 0) tasks[index] = task;
|
|
967
|
+
else tasks.push(task);
|
|
968
|
+
await this.write(tasks);
|
|
969
|
+
});
|
|
926
970
|
}
|
|
927
971
|
async get(id) {
|
|
928
|
-
|
|
929
|
-
|
|
972
|
+
return this.synchronized(async () => {
|
|
973
|
+
const tasks = await this.load();
|
|
974
|
+
return tasks.find((t) => t.id === id);
|
|
975
|
+
});
|
|
930
976
|
}
|
|
931
977
|
async list(filter = {}) {
|
|
932
|
-
|
|
933
|
-
|
|
978
|
+
return this.synchronized(async () => {
|
|
979
|
+
const tasks = await this.load();
|
|
980
|
+
return tasks.filter((task) => filter.status ? task.status === filter.status : true).filter((task) => filter.project ? task.project === filter.project : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
synchronized(operation) {
|
|
984
|
+
const result = this.queue.then(operation, operation);
|
|
985
|
+
this.queue = result.then(
|
|
986
|
+
() => void 0,
|
|
987
|
+
() => void 0
|
|
988
|
+
);
|
|
989
|
+
return result;
|
|
934
990
|
}
|
|
935
991
|
async load() {
|
|
936
992
|
try {
|
|
@@ -1041,6 +1097,8 @@ var TaskEngine = class {
|
|
|
1041
1097
|
const result = {
|
|
1042
1098
|
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1043
1099
|
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
1100
|
+
// Keep the verbatim output (bounded) for report extraction and audit.
|
|
1101
|
+
...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
|
|
1044
1102
|
...output.error !== void 0 ? { error: output.error } : {},
|
|
1045
1103
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1046
1104
|
};
|
|
@@ -1101,13 +1159,755 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
1101
1159
|
});
|
|
1102
1160
|
}
|
|
1103
1161
|
|
|
1162
|
+
// src/pipelines/engine.ts
|
|
1163
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1164
|
+
var PipelineEngine = class {
|
|
1165
|
+
constructor(options) {
|
|
1166
|
+
this.options = options;
|
|
1167
|
+
this.log = (options.log ?? logger).child({ component: "pipelines" });
|
|
1168
|
+
}
|
|
1169
|
+
options;
|
|
1170
|
+
log;
|
|
1171
|
+
get store() {
|
|
1172
|
+
return this.options.store;
|
|
1173
|
+
}
|
|
1174
|
+
/** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
|
|
1175
|
+
validate(def) {
|
|
1176
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1177
|
+
for (const step of def.steps) {
|
|
1178
|
+
if (ids.has(step.id)) {
|
|
1179
|
+
throw new PipelineError("PIPELINE_INVALID", `Duplicate step id "${step.id}".`);
|
|
1180
|
+
}
|
|
1181
|
+
ids.add(step.id);
|
|
1182
|
+
}
|
|
1183
|
+
for (const step of def.steps) {
|
|
1184
|
+
for (const dep of step.dependsOn ?? []) {
|
|
1185
|
+
if (!ids.has(dep)) {
|
|
1186
|
+
throw new PipelineError(
|
|
1187
|
+
"PIPELINE_INVALID",
|
|
1188
|
+
`Step "${step.id}" depends on unknown step "${dep}".`
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (step.kind === "research" && (step.agents ?? []).length === 0) {
|
|
1193
|
+
throw new PipelineError(
|
|
1194
|
+
"PIPELINE_INVALID",
|
|
1195
|
+
`Research step "${step.id}" needs at least one agent.`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
if ((step.kind === "execute" || step.kind === "custom") && !step.agent) {
|
|
1199
|
+
throw new PipelineError("PIPELINE_INVALID", `Step "${step.id}" (${step.kind}) needs an agent.`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
this.assertAcyclic(def);
|
|
1203
|
+
}
|
|
1204
|
+
assertAcyclic(def) {
|
|
1205
|
+
const deps = this.effectiveDependencies(def);
|
|
1206
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
1207
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
1208
|
+
for (const [id, list] of deps) {
|
|
1209
|
+
remaining.set(id, list.length);
|
|
1210
|
+
for (const d of list) {
|
|
1211
|
+
dependents.set(d, [...dependents.get(d) ?? [], id]);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
const queue = [...deps.keys()].filter((id) => (remaining.get(id) ?? 0) === 0);
|
|
1215
|
+
let processed = 0;
|
|
1216
|
+
while (queue.length > 0) {
|
|
1217
|
+
const id = queue.shift();
|
|
1218
|
+
processed += 1;
|
|
1219
|
+
for (const next of dependents.get(id) ?? []) {
|
|
1220
|
+
const left = (remaining.get(next) ?? 0) - 1;
|
|
1221
|
+
remaining.set(next, left);
|
|
1222
|
+
if (left === 0) queue.push(next);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
if (processed < deps.size) {
|
|
1226
|
+
const stuck = [...deps.keys()].filter((id) => (remaining.get(id) ?? 0) > 0);
|
|
1227
|
+
throw new PipelineError(
|
|
1228
|
+
"PIPELINE_INVALID",
|
|
1229
|
+
`Pipeline "${def.id}" has a dependency cycle involving: ${stuck.join(", ")}.`
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Steps run sequentially in definition order unless they declare explicit
|
|
1235
|
+
* dependencies, which enable parallel branches.
|
|
1236
|
+
*/
|
|
1237
|
+
effectiveDependencies(def) {
|
|
1238
|
+
const deps = /* @__PURE__ */ new Map();
|
|
1239
|
+
def.steps.forEach((step, index) => {
|
|
1240
|
+
const explicit = step.dependsOn ?? [];
|
|
1241
|
+
deps.set(
|
|
1242
|
+
step.id,
|
|
1243
|
+
explicit.length > 0 ? explicit : index > 0 ? [def.steps[index - 1].id] : []
|
|
1244
|
+
);
|
|
1245
|
+
});
|
|
1246
|
+
return deps;
|
|
1247
|
+
}
|
|
1248
|
+
async run(def, options = {}) {
|
|
1249
|
+
this.validate(def);
|
|
1250
|
+
const objective = (options.objective ?? def.objective ?? "").trim();
|
|
1251
|
+
if (objective.length === 0) {
|
|
1252
|
+
throw new PipelineError(
|
|
1253
|
+
"PIPELINE_INVALID",
|
|
1254
|
+
`Pipeline "${def.id}" needs an objective \u2014 pass one or set it in the definition.`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
const role = def.role ? this.options.roles.get(def.role) : void 0;
|
|
1258
|
+
if (def.role && !role) {
|
|
1259
|
+
throw new PipelineError(
|
|
1260
|
+
"PIPELINE_INVALID",
|
|
1261
|
+
`Role "${def.role}" (pipeline "${def.id}") is not defined in project.roles.`
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
for (const step of def.steps) {
|
|
1265
|
+
for (const name of [...step.agents ?? [], ...step.agent ? [step.agent] : []]) {
|
|
1266
|
+
if (!this.options.agents.get(name)) {
|
|
1267
|
+
throw new PipelineError(
|
|
1268
|
+
"PIPELINE_INVALID",
|
|
1269
|
+
`Step "${step.id}" references unregistered agent "${name}".`
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1275
|
+
const run = {
|
|
1276
|
+
id: `run-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`,
|
|
1277
|
+
pipelineId: def.id,
|
|
1278
|
+
status: "running",
|
|
1279
|
+
startedAt: now,
|
|
1280
|
+
stepRuns: []
|
|
1281
|
+
};
|
|
1282
|
+
await this.options.store.save(run);
|
|
1283
|
+
this.log.info(`Running pipeline ${def.id} as ${run.id}.`);
|
|
1284
|
+
const deps = this.effectiveDependencies(def);
|
|
1285
|
+
const outcomes = /* @__PURE__ */ new Map();
|
|
1286
|
+
const pending = new Set(def.steps.map((s) => s.id));
|
|
1287
|
+
const contextParts = [];
|
|
1288
|
+
const allReports = [];
|
|
1289
|
+
while (pending.size > 0) {
|
|
1290
|
+
const ready = def.steps.filter(
|
|
1291
|
+
(step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
|
|
1292
|
+
);
|
|
1293
|
+
if (ready.length === 0) {
|
|
1294
|
+
throw new PipelineError("PIPELINE_FAILED", `Pipeline "${def.id}" scheduling stalled.`);
|
|
1295
|
+
}
|
|
1296
|
+
const settled = await Promise.all(
|
|
1297
|
+
ready.map(async (step) => {
|
|
1298
|
+
if (!this.shouldRun(step, deps.get(step.id) ?? [], outcomes)) {
|
|
1299
|
+
return { status: "skipped", summary: "", taskIds: [], reports: [] };
|
|
1300
|
+
}
|
|
1301
|
+
const ctx = {
|
|
1302
|
+
objective,
|
|
1303
|
+
...role ? { role } : {},
|
|
1304
|
+
combinedContext: contextParts.join("\n\n")
|
|
1305
|
+
};
|
|
1306
|
+
return this.executeStep(step, ctx);
|
|
1307
|
+
})
|
|
1308
|
+
);
|
|
1309
|
+
ready.forEach((step, index) => {
|
|
1310
|
+
const outcome = settled[index];
|
|
1311
|
+
outcomes.set(step.id, outcome);
|
|
1312
|
+
pending.delete(step.id);
|
|
1313
|
+
allReports.push(...outcome.reports);
|
|
1314
|
+
if (outcome.status !== "skipped" && outcome.summary.length > 0) {
|
|
1315
|
+
const contribution = outcome.contextContribution ?? outcome.summary;
|
|
1316
|
+
contextParts.push(`## Step "${step.id}" (${step.kind})
|
|
1317
|
+
${contribution}`);
|
|
1318
|
+
}
|
|
1319
|
+
const stepRun = {
|
|
1320
|
+
stepId: step.id,
|
|
1321
|
+
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : "completed",
|
|
1322
|
+
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
1323
|
+
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
1324
|
+
};
|
|
1325
|
+
run.stepRuns.push(stepRun);
|
|
1326
|
+
options.onStep?.(stepRun);
|
|
1327
|
+
});
|
|
1328
|
+
run.reports = [...allReports];
|
|
1329
|
+
await this.options.store.save(run);
|
|
1330
|
+
}
|
|
1331
|
+
const failed = [...outcomes.values()].some((o) => o.status === "failed");
|
|
1332
|
+
const finalStatus = failed ? "failed" : "completed";
|
|
1333
|
+
let combinedReportId;
|
|
1334
|
+
if (this.options.reports) {
|
|
1335
|
+
const taskIds = [...outcomes.values()].flatMap((o) => o.taskIds);
|
|
1336
|
+
const tasks = (await Promise.all(taskIds.map((id) => this.options.tasks.get(id)))).filter((task) => task !== void 0);
|
|
1337
|
+
const collected = await this.options.reports.collectFromTasks(tasks);
|
|
1338
|
+
await this.options.reports.saveReports(collected);
|
|
1339
|
+
const combined = await this.options.reports.generateCombined(collected, {
|
|
1340
|
+
objective,
|
|
1341
|
+
pipelineRunId: run.id,
|
|
1342
|
+
taskIds
|
|
1343
|
+
});
|
|
1344
|
+
combinedReportId = combined?.id;
|
|
1345
|
+
if (collected.length > 0) {
|
|
1346
|
+
run.reports = collected;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const finished = {
|
|
1350
|
+
...run,
|
|
1351
|
+
status: finalStatus,
|
|
1352
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1353
|
+
...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
|
|
1354
|
+
...combinedReportId !== void 0 ? { combinedReportId } : {}
|
|
1355
|
+
};
|
|
1356
|
+
await this.options.store.save(finished);
|
|
1357
|
+
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
1358
|
+
return finished;
|
|
1359
|
+
}
|
|
1360
|
+
async get(runId) {
|
|
1361
|
+
return this.options.store.get(runId);
|
|
1362
|
+
}
|
|
1363
|
+
async listRuns(filter) {
|
|
1364
|
+
return this.options.store.list(filter);
|
|
1365
|
+
}
|
|
1366
|
+
shouldRun(step, deps, outcomes) {
|
|
1367
|
+
const condition = step.condition ?? "on-success";
|
|
1368
|
+
const depStatuses = deps.map((d) => outcomes.get(d)?.status);
|
|
1369
|
+
const anyFailed = depStatuses.includes("failed");
|
|
1370
|
+
const anySkipped = depStatuses.includes("skipped");
|
|
1371
|
+
if (condition === "always") return true;
|
|
1372
|
+
if (condition === "on-failure") return anyFailed;
|
|
1373
|
+
return !anyFailed && !anySkipped;
|
|
1374
|
+
}
|
|
1375
|
+
async executeStep(step, ctx) {
|
|
1376
|
+
const retries = step.retries ?? 0;
|
|
1377
|
+
let last;
|
|
1378
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
1379
|
+
if (attempt > 0) {
|
|
1380
|
+
this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
|
|
1381
|
+
}
|
|
1382
|
+
last = await this.executeStepOnce(step, ctx);
|
|
1383
|
+
if (last.status !== "failed") return last;
|
|
1384
|
+
}
|
|
1385
|
+
return last;
|
|
1386
|
+
}
|
|
1387
|
+
async executeStepOnce(step, ctx) {
|
|
1388
|
+
switch (step.kind) {
|
|
1389
|
+
case "research":
|
|
1390
|
+
return this.runFanOut(step, ctx);
|
|
1391
|
+
case "collect":
|
|
1392
|
+
return { status: "completed", summary: "(collect: outputs gathered from previous steps)", taskIds: [], reports: [] };
|
|
1393
|
+
case "analyze":
|
|
1394
|
+
if (!step.agent) {
|
|
1395
|
+
return { status: "completed", summary: "(analyze: pass-through, no agent assigned)", taskIds: [], reports: [] };
|
|
1396
|
+
}
|
|
1397
|
+
return this.runSingle(step, ctx, step.agent);
|
|
1398
|
+
case "plan":
|
|
1399
|
+
return this.runPlan(step, ctx);
|
|
1400
|
+
case "execute":
|
|
1401
|
+
case "custom":
|
|
1402
|
+
return this.runSingle(step, ctx, step.agent);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
/** Research fan-out: one task per agent, in parallel (§15). */
|
|
1406
|
+
async runFanOut(step, ctx) {
|
|
1407
|
+
const agentNames = step.agents ?? [];
|
|
1408
|
+
const created = await Promise.all(
|
|
1409
|
+
agentNames.map(
|
|
1410
|
+
(name) => this.options.tasks.create({
|
|
1411
|
+
objective: ctx.objective,
|
|
1412
|
+
agent: name,
|
|
1413
|
+
...ctx.role ? { role: ctx.role.id } : {},
|
|
1414
|
+
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
1415
|
+
})
|
|
1416
|
+
)
|
|
1417
|
+
);
|
|
1418
|
+
const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
|
|
1419
|
+
const failedTasks = results.filter((r) => r.status !== "completed");
|
|
1420
|
+
const lines = results.map((result, index) => {
|
|
1421
|
+
const agentName = agentNames[index] ?? "unknown";
|
|
1422
|
+
const outcome = result.result?.summary ?? result.result?.error ?? "(no output)";
|
|
1423
|
+
return `- ${agentName}: ${outcome}`;
|
|
1424
|
+
});
|
|
1425
|
+
const reports = results.flatMap((r) => r.result?.reports ?? []);
|
|
1426
|
+
const summary = lines.join("\n");
|
|
1427
|
+
if (failedTasks.length > 0) {
|
|
1428
|
+
const firstError = failedTasks[0]?.result?.error ?? "unknown error";
|
|
1429
|
+
return {
|
|
1430
|
+
status: "failed",
|
|
1431
|
+
summary,
|
|
1432
|
+
taskIds: created.map((t) => t.id),
|
|
1433
|
+
error: `${failedTasks.length}/${results.length} task(s) failed \u2014 ${firstError}`,
|
|
1434
|
+
reports
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
return { status: "completed", summary, taskIds: created.map((t) => t.id), reports };
|
|
1438
|
+
}
|
|
1439
|
+
async runSingle(step, ctx, agentName) {
|
|
1440
|
+
const task = await this.options.tasks.create({
|
|
1441
|
+
objective: ctx.objective,
|
|
1442
|
+
agent: agentName,
|
|
1443
|
+
...ctx.role ? { role: ctx.role.id } : {},
|
|
1444
|
+
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
1445
|
+
});
|
|
1446
|
+
const result = await this.options.tasks.run(task.id);
|
|
1447
|
+
if (result.status !== "completed") {
|
|
1448
|
+
return {
|
|
1449
|
+
status: "failed",
|
|
1450
|
+
summary: result.result?.summary ?? "",
|
|
1451
|
+
taskIds: [task.id],
|
|
1452
|
+
error: result.result?.error ?? `task ${task.id} did not complete`,
|
|
1453
|
+
reports: result.result?.reports ?? []
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
return {
|
|
1457
|
+
status: "completed",
|
|
1458
|
+
summary: result.result?.summary ?? "(no output)",
|
|
1459
|
+
taskIds: [task.id],
|
|
1460
|
+
reports: result.result?.reports ?? []
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
async runPlan(step, ctx) {
|
|
1464
|
+
if (!step.model) {
|
|
1465
|
+
return { status: "completed", summary: "(plan: no model configured \u2014 skipped)", taskIds: [], reports: [] };
|
|
1466
|
+
}
|
|
1467
|
+
const messages = [
|
|
1468
|
+
{
|
|
1469
|
+
role: "system",
|
|
1470
|
+
content: "You are a planning assistant inside OmniNode. Given the objective and the combined research context, produce a prioritized, actionable plan."
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
role: "user",
|
|
1474
|
+
content: `# Objective
|
|
1475
|
+
${ctx.objective}
|
|
1476
|
+
|
|
1477
|
+
${ctx.combinedContext}
|
|
1478
|
+
|
|
1479
|
+
Produce the final plan.`
|
|
1480
|
+
}
|
|
1481
|
+
];
|
|
1482
|
+
try {
|
|
1483
|
+
const planText = await this.getChat()(step.model, messages);
|
|
1484
|
+
return {
|
|
1485
|
+
status: "completed",
|
|
1486
|
+
summary: planText.split("\n", 1)[0] ?? planText,
|
|
1487
|
+
contextContribution: planText,
|
|
1488
|
+
taskIds: [],
|
|
1489
|
+
reports: []
|
|
1490
|
+
};
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
return {
|
|
1493
|
+
status: "failed",
|
|
1494
|
+
summary: "",
|
|
1495
|
+
taskIds: [],
|
|
1496
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1497
|
+
reports: []
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
getChat() {
|
|
1502
|
+
if (this.options.chat) return this.options.chat;
|
|
1503
|
+
throw new PipelineError(
|
|
1504
|
+
"PIPELINE_FAILED",
|
|
1505
|
+
"No chat function available \u2014 configure a provider for plan steps."
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
function createDefaultChatFn(providers) {
|
|
1510
|
+
return async (modelRef, messages) => {
|
|
1511
|
+
const separator = modelRef.indexOf(":");
|
|
1512
|
+
if (separator <= 0 || separator === modelRef.length - 1) {
|
|
1513
|
+
throw new PipelineError(
|
|
1514
|
+
"PIPELINE_INVALID",
|
|
1515
|
+
`Invalid model reference "${modelRef}". Use "provider:model-id".`
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
const providerName = modelRef.slice(0, separator);
|
|
1519
|
+
const modelId = modelRef.slice(separator + 1);
|
|
1520
|
+
const providerConfig = providers.find((p) => p.name === providerName);
|
|
1521
|
+
if (!providerConfig) {
|
|
1522
|
+
throw new PipelineError(
|
|
1523
|
+
"PIPELINE_INVALID",
|
|
1524
|
+
`Provider "${providerName}" (model "${modelRef}") is not configured.`
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
const provider = createProvider(providerConfig);
|
|
1528
|
+
const response = await provider.chat({ model: modelId, messages });
|
|
1529
|
+
return response.content;
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// src/pipelines/store.ts
|
|
1534
|
+
import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
1535
|
+
import { dirname as dirname2 } from "path";
|
|
1536
|
+
var FilePipelineRunStore = class {
|
|
1537
|
+
filePath;
|
|
1538
|
+
queue = Promise.resolve();
|
|
1539
|
+
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
1540
|
+
this.filePath = `${directory}/pipelines.json`;
|
|
1541
|
+
}
|
|
1542
|
+
async save(run) {
|
|
1543
|
+
return this.synchronized(async () => {
|
|
1544
|
+
const runs = await this.load();
|
|
1545
|
+
const index = runs.findIndex((r) => r.id === run.id);
|
|
1546
|
+
if (index >= 0) runs[index] = run;
|
|
1547
|
+
else runs.push(run);
|
|
1548
|
+
await this.write(runs);
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
async get(id) {
|
|
1552
|
+
return this.synchronized(async () => {
|
|
1553
|
+
const runs = await this.load();
|
|
1554
|
+
return runs.find((r) => r.id === id);
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
async list(filter = {}) {
|
|
1558
|
+
return this.synchronized(async () => {
|
|
1559
|
+
const runs = await this.load();
|
|
1560
|
+
return runs.filter((run) => filter.pipelineId ? run.pipelineId === filter.pipelineId : true).filter((run) => filter.status ? run.status === filter.status : true).sort((a, b) => (a.startedAt ?? "").localeCompare(b.startedAt ?? ""));
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
synchronized(operation) {
|
|
1564
|
+
const result = this.queue.then(operation, operation);
|
|
1565
|
+
this.queue = result.then(
|
|
1566
|
+
() => void 0,
|
|
1567
|
+
() => void 0
|
|
1568
|
+
);
|
|
1569
|
+
return result;
|
|
1570
|
+
}
|
|
1571
|
+
async load() {
|
|
1572
|
+
try {
|
|
1573
|
+
const raw = await readFile2(this.filePath, "utf8");
|
|
1574
|
+
const parsed = JSON.parse(raw);
|
|
1575
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1576
|
+
} catch {
|
|
1577
|
+
return [];
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
async write(runs) {
|
|
1581
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1582
|
+
await mkdir2(dirname2(this.filePath), { recursive: true });
|
|
1583
|
+
await writeFile2(tmp, `${JSON.stringify(runs, null, 2)}
|
|
1584
|
+
`, "utf8");
|
|
1585
|
+
await rename2(tmp, this.filePath);
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
// src/reports/aggregate.ts
|
|
1590
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1591
|
+
var SEVERITY_RANK = {
|
|
1592
|
+
info: 0,
|
|
1593
|
+
low: 1,
|
|
1594
|
+
medium: 2,
|
|
1595
|
+
high: 3,
|
|
1596
|
+
critical: 4
|
|
1597
|
+
};
|
|
1598
|
+
function aggregateReports(reports, options = {}) {
|
|
1599
|
+
const threshold = options.similarityThreshold ?? 0.7;
|
|
1600
|
+
const findingGroups = [];
|
|
1601
|
+
const recommendationGroups = [];
|
|
1602
|
+
for (const report of reports) {
|
|
1603
|
+
for (const finding of report.findings) {
|
|
1604
|
+
mergeFinding(findingGroups, finding, report, threshold);
|
|
1605
|
+
}
|
|
1606
|
+
for (const text of report.recommendations) {
|
|
1607
|
+
mergeRecommendation(recommendationGroups, text, report, threshold);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
const sources = [...new Set(reports.map((r) => r.agent))].sort();
|
|
1611
|
+
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
1612
|
+
const summary = `${reports.length} report(s) from ${sources.length > 0 ? sources.join(", ") : "no agents"}: ${findingGroups.length} unique finding group(s) (${totalRawFindings} raw), ${recommendationGroups.length} recommendation group(s).`;
|
|
1613
|
+
return {
|
|
1614
|
+
id: `combined-${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`,
|
|
1615
|
+
title: options.objective !== void 0 ? truncate(options.objective, 120) : "Combined report",
|
|
1616
|
+
...options.objective !== void 0 ? { objective: options.objective } : {},
|
|
1617
|
+
...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
|
|
1618
|
+
taskIds: options.taskIds ?? [],
|
|
1619
|
+
summary,
|
|
1620
|
+
findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
|
|
1621
|
+
recommendations: recommendationGroups,
|
|
1622
|
+
sources,
|
|
1623
|
+
reportIds: reports.map((r) => r.id),
|
|
1624
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
function rank(finding) {
|
|
1628
|
+
return finding.severity ? SEVERITY_RANK[finding.severity] : -1;
|
|
1629
|
+
}
|
|
1630
|
+
function mergeFinding(groups, finding, report, threshold) {
|
|
1631
|
+
const source = { agent: report.agent, reportId: report.id };
|
|
1632
|
+
const group = groups.find((g) => similar(g.title, finding.title, threshold));
|
|
1633
|
+
if (group) {
|
|
1634
|
+
group.occurrences += 1;
|
|
1635
|
+
group.details.push(finding.detail);
|
|
1636
|
+
group.sources.push(source);
|
|
1637
|
+
group.evidence.push(...finding.evidence ?? []);
|
|
1638
|
+
if (finding.severity && (!group.severity || SEVERITY_RANK[finding.severity] > SEVERITY_RANK[group.severity])) {
|
|
1639
|
+
group.severity = finding.severity;
|
|
1640
|
+
}
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
groups.push({
|
|
1644
|
+
title: finding.title,
|
|
1645
|
+
details: [finding.detail],
|
|
1646
|
+
sources: [source],
|
|
1647
|
+
occurrences: 1,
|
|
1648
|
+
...finding.severity ? { severity: finding.severity } : {},
|
|
1649
|
+
evidence: [...finding.evidence ?? []]
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function mergeRecommendation(groups, text, report, threshold) {
|
|
1653
|
+
const source = { agent: report.agent, reportId: report.id };
|
|
1654
|
+
const group = groups.find((g) => similar(g.text, text, threshold));
|
|
1655
|
+
if (group) {
|
|
1656
|
+
group.occurrences += 1;
|
|
1657
|
+
group.sources.push(source);
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
groups.push({ text, sources: [source], occurrences: 1 });
|
|
1661
|
+
}
|
|
1662
|
+
function similar(a, b, threshold) {
|
|
1663
|
+
const left = tokens(a);
|
|
1664
|
+
const right = tokens(b);
|
|
1665
|
+
if (left.size === 0 || right.size === 0) return false;
|
|
1666
|
+
let intersection = 0;
|
|
1667
|
+
for (const token of left) {
|
|
1668
|
+
if (right.has(token)) intersection += 1;
|
|
1669
|
+
}
|
|
1670
|
+
const union = left.size + right.size - intersection;
|
|
1671
|
+
return intersection / union >= threshold;
|
|
1672
|
+
}
|
|
1673
|
+
function tokens(text) {
|
|
1674
|
+
return new Set(
|
|
1675
|
+
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
function truncate(text, max) {
|
|
1679
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// src/reports/extract.ts
|
|
1683
|
+
var BULLET = /^(?:[-*•]|\d+[.)])\s+(.*)$/;
|
|
1684
|
+
function parseSections(raw) {
|
|
1685
|
+
const sections = [];
|
|
1686
|
+
let current = { heading: "", bullets: [] };
|
|
1687
|
+
for (const line of raw.split("\n")) {
|
|
1688
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line.trim());
|
|
1689
|
+
if (heading) {
|
|
1690
|
+
if (current.heading.length > 0 || current.bullets.length > 0) {
|
|
1691
|
+
sections.push(current);
|
|
1692
|
+
}
|
|
1693
|
+
current = { heading: heading[2]?.toLowerCase() ?? "", bullets: [] };
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
const bullet = BULLET.exec(line.trim());
|
|
1697
|
+
if (bullet && current.heading.length > 0) {
|
|
1698
|
+
current.bullets.push(bullet[1]?.trim() ?? "");
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
if (current.heading.length > 0 || current.bullets.length > 0) {
|
|
1702
|
+
sections.push(current);
|
|
1703
|
+
}
|
|
1704
|
+
return sections;
|
|
1705
|
+
}
|
|
1706
|
+
function collectBullets(sections, patterns) {
|
|
1707
|
+
const matches = sections.filter((section) => patterns.some((p) => p.test(section.heading)));
|
|
1708
|
+
return matches.flatMap((section) => section.bullets).filter((b) => b.length > 0);
|
|
1709
|
+
}
|
|
1710
|
+
function guessSeverity(text) {
|
|
1711
|
+
if (/critical|severe/i.test(text)) return "critical";
|
|
1712
|
+
if (/\bhigh\b|major/i.test(text)) return "high";
|
|
1713
|
+
if (/medium|moderate/i.test(text)) return "medium";
|
|
1714
|
+
if (/\blow\b|minor/i.test(text)) return "low";
|
|
1715
|
+
return void 0;
|
|
1716
|
+
}
|
|
1717
|
+
function extractReportFromText(raw, meta) {
|
|
1718
|
+
const trimmed = raw.trim();
|
|
1719
|
+
const nonEmpty = trimmed.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
1720
|
+
const proseLine = nonEmpty.find((line) => !line.startsWith("#") && !BULLET.test(line)) ?? nonEmpty[0] ?? "";
|
|
1721
|
+
const cleaned = proseLine.replace(/^#{1,6}\s+/, "").replace(BULLET, "$1");
|
|
1722
|
+
const summary = cleaned.length > 0 ? truncate2(cleaned, 200) : "(no output)";
|
|
1723
|
+
const sections = parseSections(trimmed);
|
|
1724
|
+
const findingTexts = collectBullets(sections, [/finding/, /issue/, /problem/, /vulnerab/]);
|
|
1725
|
+
const recommendationTexts = collectBullets(sections, [/recommend/, /suggestion/, /next step/, /action/]);
|
|
1726
|
+
const evidenceTexts = collectBullets(sections, [/evidence/, /proof/, /log/]);
|
|
1727
|
+
const findings = findingTexts.map((text, index) => ({
|
|
1728
|
+
id: `${meta.taskId}-f-${index + 1}`,
|
|
1729
|
+
title: truncate2(text, 120),
|
|
1730
|
+
detail: text,
|
|
1731
|
+
severity: guessSeverity(text)
|
|
1732
|
+
}));
|
|
1733
|
+
return {
|
|
1734
|
+
id: `${meta.taskId}-report`,
|
|
1735
|
+
taskId: meta.taskId,
|
|
1736
|
+
agent: meta.agent,
|
|
1737
|
+
...meta.model !== void 0 ? { model: meta.model } : {},
|
|
1738
|
+
summary,
|
|
1739
|
+
findings,
|
|
1740
|
+
recommendations: recommendationTexts.map((t) => truncate2(t, 200)),
|
|
1741
|
+
metadata: evidenceTexts.length > 0 ? { evidence: evidenceTexts } : void 0,
|
|
1742
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
function truncate2(text, max) {
|
|
1746
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// src/reports/service.ts
|
|
1750
|
+
var ReportService = class {
|
|
1751
|
+
constructor(store, log = logger) {
|
|
1752
|
+
this.store = store;
|
|
1753
|
+
this.log = log.child({ component: "reports" });
|
|
1754
|
+
}
|
|
1755
|
+
store;
|
|
1756
|
+
log;
|
|
1757
|
+
get storeRef() {
|
|
1758
|
+
return this.store;
|
|
1759
|
+
}
|
|
1760
|
+
/** Normalizes finished tasks into reports (§17 — normalizer). */
|
|
1761
|
+
async collectFromTasks(tasks) {
|
|
1762
|
+
const reports = [];
|
|
1763
|
+
for (const task of tasks) {
|
|
1764
|
+
const structured = task.result?.reports ?? [];
|
|
1765
|
+
if (structured.length > 0) {
|
|
1766
|
+
reports.push(...structured);
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
const text = task.result?.rawOutput ?? task.result?.summary ?? "";
|
|
1770
|
+
if (text.trim().length === 0) continue;
|
|
1771
|
+
reports.push(
|
|
1772
|
+
extractReportFromText(text, {
|
|
1773
|
+
taskId: task.id,
|
|
1774
|
+
agent: task.agent ?? "unknown"
|
|
1775
|
+
})
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
return reports;
|
|
1779
|
+
}
|
|
1780
|
+
async saveReports(reports) {
|
|
1781
|
+
if (reports.length === 0) return;
|
|
1782
|
+
await this.store.saveReports(reports);
|
|
1783
|
+
this.log.info(`Stored ${reports.length} report(s).`);
|
|
1784
|
+
}
|
|
1785
|
+
async generateCombined(reports, meta = {}) {
|
|
1786
|
+
if (reports.length === 0) return void 0;
|
|
1787
|
+
const options = {
|
|
1788
|
+
...meta.objective !== void 0 ? { objective: meta.objective } : {},
|
|
1789
|
+
...meta.pipelineRunId !== void 0 ? { pipelineRunId: meta.pipelineRunId } : {},
|
|
1790
|
+
...meta.taskIds !== void 0 ? { taskIds: meta.taskIds } : {}
|
|
1791
|
+
};
|
|
1792
|
+
const combined = aggregateReports(reports, options);
|
|
1793
|
+
await this.store.saveCombined(combined);
|
|
1794
|
+
this.log.info(`Generated combined report ${combined.id}.`);
|
|
1795
|
+
return combined;
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
// src/reports/store.ts
|
|
1800
|
+
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
|
|
1801
|
+
import { dirname as dirname3 } from "path";
|
|
1802
|
+
var FileReportStore = class {
|
|
1803
|
+
filePath;
|
|
1804
|
+
queue = Promise.resolve();
|
|
1805
|
+
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
1806
|
+
this.filePath = `${directory}/reports.json`;
|
|
1807
|
+
}
|
|
1808
|
+
async saveReports(reports) {
|
|
1809
|
+
return this.synchronized(async () => {
|
|
1810
|
+
const entries = await this.load();
|
|
1811
|
+
for (const report of reports) {
|
|
1812
|
+
const index = entries.findIndex(
|
|
1813
|
+
(e) => e.kind === "report" && e.report.id === report.id
|
|
1814
|
+
);
|
|
1815
|
+
const entry = { kind: "report", report };
|
|
1816
|
+
if (index >= 0) entries[index] = entry;
|
|
1817
|
+
else entries.push(entry);
|
|
1818
|
+
}
|
|
1819
|
+
await this.write(entries);
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
async listReports(filter = {}) {
|
|
1823
|
+
return this.synchronized(async () => {
|
|
1824
|
+
const entries = await this.load();
|
|
1825
|
+
return entries.filter((e) => e.kind === "report").map((e) => e.report).filter((report) => filter.taskId ? report.taskId === filter.taskId : true).filter((report) => filter.agent ? report.agent === filter.agent : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
async saveCombined(combined) {
|
|
1829
|
+
return this.synchronized(async () => {
|
|
1830
|
+
const entries = await this.load();
|
|
1831
|
+
const index = entries.findIndex(
|
|
1832
|
+
(e) => e.kind === "combined" && e.combined.id === combined.id
|
|
1833
|
+
);
|
|
1834
|
+
const entry = { kind: "combined", combined };
|
|
1835
|
+
if (index >= 0) entries[index] = entry;
|
|
1836
|
+
else entries.push(entry);
|
|
1837
|
+
await this.write(entries);
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
async getCombined(id) {
|
|
1841
|
+
return this.synchronized(async () => {
|
|
1842
|
+
const entries = await this.load();
|
|
1843
|
+
const found = entries.find((e) => e.kind === "combined" && e.combined.id === id);
|
|
1844
|
+
return found?.kind === "combined" ? found.combined : void 0;
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
async listCombined(filter = {}) {
|
|
1848
|
+
return this.synchronized(async () => {
|
|
1849
|
+
const entries = await this.load();
|
|
1850
|
+
return entries.filter((e) => e.kind === "combined").map((e) => e.combined).filter(
|
|
1851
|
+
(combined) => filter.pipelineRunId ? combined.pipelineRunId === filter.pipelineRunId : true
|
|
1852
|
+
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
synchronized(operation) {
|
|
1856
|
+
const result = this.queue.then(operation, operation);
|
|
1857
|
+
this.queue = result.then(
|
|
1858
|
+
() => void 0,
|
|
1859
|
+
() => void 0
|
|
1860
|
+
);
|
|
1861
|
+
return result;
|
|
1862
|
+
}
|
|
1863
|
+
async load() {
|
|
1864
|
+
try {
|
|
1865
|
+
const raw = await readFile3(this.filePath, "utf8");
|
|
1866
|
+
const parsed = JSON.parse(raw);
|
|
1867
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1868
|
+
} catch {
|
|
1869
|
+
return [];
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
async write(entries) {
|
|
1873
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1874
|
+
await mkdir3(dirname3(this.filePath), { recursive: true });
|
|
1875
|
+
await writeFile3(tmp, `${JSON.stringify(entries, null, 2)}
|
|
1876
|
+
`, "utf8");
|
|
1877
|
+
await rename3(tmp, this.filePath);
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
// src/pipelines/engine-factory.ts
|
|
1882
|
+
function buildPipelineEngine(config, log = logger) {
|
|
1883
|
+
const agents = buildAgentRegistry(config, log);
|
|
1884
|
+
const roles = buildRoleRegistry(config);
|
|
1885
|
+
const tasks = new TaskEngine({
|
|
1886
|
+
agents,
|
|
1887
|
+
roles,
|
|
1888
|
+
store: new FileTaskStore(),
|
|
1889
|
+
log
|
|
1890
|
+
});
|
|
1891
|
+
return new PipelineEngine({
|
|
1892
|
+
tasks,
|
|
1893
|
+
agents,
|
|
1894
|
+
roles,
|
|
1895
|
+
providers: config.project.providers,
|
|
1896
|
+
store: new FilePipelineRunStore(),
|
|
1897
|
+
chat: createDefaultChatFn(config.project.providers),
|
|
1898
|
+
reports: new ReportService(new FileReportStore(), log),
|
|
1899
|
+
log
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1104
1903
|
// src/version.ts
|
|
1105
|
-
var OMNINODE_VERSION = "0.
|
|
1904
|
+
var OMNINODE_VERSION = "0.7.0";
|
|
1106
1905
|
export {
|
|
1107
1906
|
AgentError,
|
|
1108
1907
|
AgentRegistry,
|
|
1109
1908
|
ConfigError,
|
|
1110
1909
|
ConsoleLogSink,
|
|
1910
|
+
FilePipelineRunStore,
|
|
1111
1911
|
FileTaskStore,
|
|
1112
1912
|
Logger,
|
|
1113
1913
|
MemoryError,
|
|
@@ -1117,6 +1917,7 @@ export {
|
|
|
1117
1917
|
OmniHilbrasProvider,
|
|
1118
1918
|
OmniNodeError,
|
|
1119
1919
|
OpenAICompatibleProvider,
|
|
1920
|
+
PipelineEngine,
|
|
1120
1921
|
PipelineError,
|
|
1121
1922
|
ProcessAgent,
|
|
1122
1923
|
ProtocolError,
|
|
@@ -1128,9 +1929,11 @@ export {
|
|
|
1128
1929
|
appConfigSchema,
|
|
1129
1930
|
authHeaders,
|
|
1130
1931
|
buildAgentRegistry,
|
|
1932
|
+
buildPipelineEngine,
|
|
1131
1933
|
buildRoleRegistry,
|
|
1132
1934
|
composeTaskText,
|
|
1133
1935
|
createAgent,
|
|
1936
|
+
createDefaultChatFn,
|
|
1134
1937
|
createProvider,
|
|
1135
1938
|
createTaskEngine,
|
|
1136
1939
|
defaultProjectConfigYaml,
|
|
@@ -1139,6 +1942,8 @@ export {
|
|
|
1139
1942
|
isOmniNodeError,
|
|
1140
1943
|
loadConfig,
|
|
1141
1944
|
logger,
|
|
1945
|
+
pipelineConfigSchema,
|
|
1946
|
+
pipelineStepConfigSchema,
|
|
1142
1947
|
projectConfigSchema,
|
|
1143
1948
|
providerConfigSchema,
|
|
1144
1949
|
registryKey,
|