@hilbras/omninode 0.5.1 → 0.6.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 +38 -3
- package/dist/cli/index.js +587 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +466 -61
- package/dist/index.js +503 -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 {
|
|
@@ -1101,13 +1157,445 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
1101
1157
|
});
|
|
1102
1158
|
}
|
|
1103
1159
|
|
|
1160
|
+
// src/pipelines/engine.ts
|
|
1161
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1162
|
+
var PipelineEngine = class {
|
|
1163
|
+
constructor(options) {
|
|
1164
|
+
this.options = options;
|
|
1165
|
+
this.log = (options.log ?? logger).child({ component: "pipelines" });
|
|
1166
|
+
}
|
|
1167
|
+
options;
|
|
1168
|
+
log;
|
|
1169
|
+
get store() {
|
|
1170
|
+
return this.options.store;
|
|
1171
|
+
}
|
|
1172
|
+
/** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
|
|
1173
|
+
validate(def) {
|
|
1174
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1175
|
+
for (const step of def.steps) {
|
|
1176
|
+
if (ids.has(step.id)) {
|
|
1177
|
+
throw new PipelineError("PIPELINE_INVALID", `Duplicate step id "${step.id}".`);
|
|
1178
|
+
}
|
|
1179
|
+
ids.add(step.id);
|
|
1180
|
+
}
|
|
1181
|
+
for (const step of def.steps) {
|
|
1182
|
+
for (const dep of step.dependsOn ?? []) {
|
|
1183
|
+
if (!ids.has(dep)) {
|
|
1184
|
+
throw new PipelineError(
|
|
1185
|
+
"PIPELINE_INVALID",
|
|
1186
|
+
`Step "${step.id}" depends on unknown step "${dep}".`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (step.kind === "research" && (step.agents ?? []).length === 0) {
|
|
1191
|
+
throw new PipelineError(
|
|
1192
|
+
"PIPELINE_INVALID",
|
|
1193
|
+
`Research step "${step.id}" needs at least one agent.`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
if ((step.kind === "execute" || step.kind === "custom") && !step.agent) {
|
|
1197
|
+
throw new PipelineError("PIPELINE_INVALID", `Step "${step.id}" (${step.kind}) needs an agent.`);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
this.assertAcyclic(def);
|
|
1201
|
+
}
|
|
1202
|
+
assertAcyclic(def) {
|
|
1203
|
+
const deps = this.effectiveDependencies(def);
|
|
1204
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
1205
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const [id, list] of deps) {
|
|
1207
|
+
remaining.set(id, list.length);
|
|
1208
|
+
for (const d of list) {
|
|
1209
|
+
dependents.set(d, [...dependents.get(d) ?? [], id]);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const queue = [...deps.keys()].filter((id) => (remaining.get(id) ?? 0) === 0);
|
|
1213
|
+
let processed = 0;
|
|
1214
|
+
while (queue.length > 0) {
|
|
1215
|
+
const id = queue.shift();
|
|
1216
|
+
processed += 1;
|
|
1217
|
+
for (const next of dependents.get(id) ?? []) {
|
|
1218
|
+
const left = (remaining.get(next) ?? 0) - 1;
|
|
1219
|
+
remaining.set(next, left);
|
|
1220
|
+
if (left === 0) queue.push(next);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (processed < deps.size) {
|
|
1224
|
+
const stuck = [...deps.keys()].filter((id) => (remaining.get(id) ?? 0) > 0);
|
|
1225
|
+
throw new PipelineError(
|
|
1226
|
+
"PIPELINE_INVALID",
|
|
1227
|
+
`Pipeline "${def.id}" has a dependency cycle involving: ${stuck.join(", ")}.`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Steps run sequentially in definition order unless they declare explicit
|
|
1233
|
+
* dependencies, which enable parallel branches.
|
|
1234
|
+
*/
|
|
1235
|
+
effectiveDependencies(def) {
|
|
1236
|
+
const deps = /* @__PURE__ */ new Map();
|
|
1237
|
+
def.steps.forEach((step, index) => {
|
|
1238
|
+
const explicit = step.dependsOn ?? [];
|
|
1239
|
+
deps.set(
|
|
1240
|
+
step.id,
|
|
1241
|
+
explicit.length > 0 ? explicit : index > 0 ? [def.steps[index - 1].id] : []
|
|
1242
|
+
);
|
|
1243
|
+
});
|
|
1244
|
+
return deps;
|
|
1245
|
+
}
|
|
1246
|
+
async run(def, options = {}) {
|
|
1247
|
+
this.validate(def);
|
|
1248
|
+
const objective = (options.objective ?? def.objective ?? "").trim();
|
|
1249
|
+
if (objective.length === 0) {
|
|
1250
|
+
throw new PipelineError(
|
|
1251
|
+
"PIPELINE_INVALID",
|
|
1252
|
+
`Pipeline "${def.id}" needs an objective \u2014 pass one or set it in the definition.`
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
const role = def.role ? this.options.roles.get(def.role) : void 0;
|
|
1256
|
+
if (def.role && !role) {
|
|
1257
|
+
throw new PipelineError(
|
|
1258
|
+
"PIPELINE_INVALID",
|
|
1259
|
+
`Role "${def.role}" (pipeline "${def.id}") is not defined in project.roles.`
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
for (const step of def.steps) {
|
|
1263
|
+
for (const name of [...step.agents ?? [], ...step.agent ? [step.agent] : []]) {
|
|
1264
|
+
if (!this.options.agents.get(name)) {
|
|
1265
|
+
throw new PipelineError(
|
|
1266
|
+
"PIPELINE_INVALID",
|
|
1267
|
+
`Step "${step.id}" references unregistered agent "${name}".`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1273
|
+
const run = {
|
|
1274
|
+
id: `run-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`,
|
|
1275
|
+
pipelineId: def.id,
|
|
1276
|
+
status: "running",
|
|
1277
|
+
startedAt: now,
|
|
1278
|
+
stepRuns: []
|
|
1279
|
+
};
|
|
1280
|
+
await this.options.store.save(run);
|
|
1281
|
+
this.log.info(`Running pipeline ${def.id} as ${run.id}.`);
|
|
1282
|
+
const deps = this.effectiveDependencies(def);
|
|
1283
|
+
const outcomes = /* @__PURE__ */ new Map();
|
|
1284
|
+
const pending = new Set(def.steps.map((s) => s.id));
|
|
1285
|
+
const contextParts = [];
|
|
1286
|
+
const allReports = [];
|
|
1287
|
+
while (pending.size > 0) {
|
|
1288
|
+
const ready = def.steps.filter(
|
|
1289
|
+
(step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
|
|
1290
|
+
);
|
|
1291
|
+
if (ready.length === 0) {
|
|
1292
|
+
throw new PipelineError("PIPELINE_FAILED", `Pipeline "${def.id}" scheduling stalled.`);
|
|
1293
|
+
}
|
|
1294
|
+
const settled = await Promise.all(
|
|
1295
|
+
ready.map(async (step) => {
|
|
1296
|
+
if (!this.shouldRun(step, deps.get(step.id) ?? [], outcomes)) {
|
|
1297
|
+
return { status: "skipped", summary: "", taskIds: [], reports: [] };
|
|
1298
|
+
}
|
|
1299
|
+
const ctx = {
|
|
1300
|
+
objective,
|
|
1301
|
+
...role ? { role } : {},
|
|
1302
|
+
combinedContext: contextParts.join("\n\n")
|
|
1303
|
+
};
|
|
1304
|
+
return this.executeStep(step, ctx);
|
|
1305
|
+
})
|
|
1306
|
+
);
|
|
1307
|
+
ready.forEach((step, index) => {
|
|
1308
|
+
const outcome = settled[index];
|
|
1309
|
+
outcomes.set(step.id, outcome);
|
|
1310
|
+
pending.delete(step.id);
|
|
1311
|
+
allReports.push(...outcome.reports);
|
|
1312
|
+
if (outcome.status !== "skipped" && outcome.summary.length > 0) {
|
|
1313
|
+
const contribution = outcome.contextContribution ?? outcome.summary;
|
|
1314
|
+
contextParts.push(`## Step "${step.id}" (${step.kind})
|
|
1315
|
+
${contribution}`);
|
|
1316
|
+
}
|
|
1317
|
+
const stepRun = {
|
|
1318
|
+
stepId: step.id,
|
|
1319
|
+
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : "completed",
|
|
1320
|
+
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
1321
|
+
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
1322
|
+
};
|
|
1323
|
+
run.stepRuns.push(stepRun);
|
|
1324
|
+
options.onStep?.(stepRun);
|
|
1325
|
+
});
|
|
1326
|
+
run.reports = [...allReports];
|
|
1327
|
+
await this.options.store.save(run);
|
|
1328
|
+
}
|
|
1329
|
+
const failed = [...outcomes.values()].some((o) => o.status === "failed");
|
|
1330
|
+
const finalStatus = failed ? "failed" : "completed";
|
|
1331
|
+
const finished = {
|
|
1332
|
+
...run,
|
|
1333
|
+
status: finalStatus,
|
|
1334
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1335
|
+
...allReports.length > 0 ? { reports: allReports } : {}
|
|
1336
|
+
};
|
|
1337
|
+
await this.options.store.save(finished);
|
|
1338
|
+
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
1339
|
+
return finished;
|
|
1340
|
+
}
|
|
1341
|
+
async get(runId) {
|
|
1342
|
+
return this.options.store.get(runId);
|
|
1343
|
+
}
|
|
1344
|
+
async listRuns(filter) {
|
|
1345
|
+
return this.options.store.list(filter);
|
|
1346
|
+
}
|
|
1347
|
+
shouldRun(step, deps, outcomes) {
|
|
1348
|
+
const condition = step.condition ?? "on-success";
|
|
1349
|
+
const depStatuses = deps.map((d) => outcomes.get(d)?.status);
|
|
1350
|
+
const anyFailed = depStatuses.includes("failed");
|
|
1351
|
+
const anySkipped = depStatuses.includes("skipped");
|
|
1352
|
+
if (condition === "always") return true;
|
|
1353
|
+
if (condition === "on-failure") return anyFailed;
|
|
1354
|
+
return !anyFailed && !anySkipped;
|
|
1355
|
+
}
|
|
1356
|
+
async executeStep(step, ctx) {
|
|
1357
|
+
const retries = step.retries ?? 0;
|
|
1358
|
+
let last;
|
|
1359
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
1360
|
+
if (attempt > 0) {
|
|
1361
|
+
this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
|
|
1362
|
+
}
|
|
1363
|
+
last = await this.executeStepOnce(step, ctx);
|
|
1364
|
+
if (last.status !== "failed") return last;
|
|
1365
|
+
}
|
|
1366
|
+
return last;
|
|
1367
|
+
}
|
|
1368
|
+
async executeStepOnce(step, ctx) {
|
|
1369
|
+
switch (step.kind) {
|
|
1370
|
+
case "research":
|
|
1371
|
+
return this.runFanOut(step, ctx);
|
|
1372
|
+
case "collect":
|
|
1373
|
+
return { status: "completed", summary: "(collect: outputs gathered from previous steps)", taskIds: [], reports: [] };
|
|
1374
|
+
case "analyze":
|
|
1375
|
+
if (!step.agent) {
|
|
1376
|
+
return { status: "completed", summary: "(analyze: pass-through, no agent assigned)", taskIds: [], reports: [] };
|
|
1377
|
+
}
|
|
1378
|
+
return this.runSingle(step, ctx, step.agent);
|
|
1379
|
+
case "plan":
|
|
1380
|
+
return this.runPlan(step, ctx);
|
|
1381
|
+
case "execute":
|
|
1382
|
+
case "custom":
|
|
1383
|
+
return this.runSingle(step, ctx, step.agent);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
/** Research fan-out: one task per agent, in parallel (§15). */
|
|
1387
|
+
async runFanOut(step, ctx) {
|
|
1388
|
+
const agentNames = step.agents ?? [];
|
|
1389
|
+
const created = await Promise.all(
|
|
1390
|
+
agentNames.map(
|
|
1391
|
+
(name) => this.options.tasks.create({
|
|
1392
|
+
objective: ctx.objective,
|
|
1393
|
+
agent: name,
|
|
1394
|
+
...ctx.role ? { role: ctx.role.id } : {},
|
|
1395
|
+
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
1396
|
+
})
|
|
1397
|
+
)
|
|
1398
|
+
);
|
|
1399
|
+
const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
|
|
1400
|
+
const failedTasks = results.filter((r) => r.status !== "completed");
|
|
1401
|
+
const lines = results.map((result, index) => {
|
|
1402
|
+
const agentName = agentNames[index] ?? "unknown";
|
|
1403
|
+
const outcome = result.result?.summary ?? result.result?.error ?? "(no output)";
|
|
1404
|
+
return `- ${agentName}: ${outcome}`;
|
|
1405
|
+
});
|
|
1406
|
+
const reports = results.flatMap((r) => r.result?.reports ?? []);
|
|
1407
|
+
const summary = lines.join("\n");
|
|
1408
|
+
if (failedTasks.length > 0) {
|
|
1409
|
+
const firstError = failedTasks[0]?.result?.error ?? "unknown error";
|
|
1410
|
+
return {
|
|
1411
|
+
status: "failed",
|
|
1412
|
+
summary,
|
|
1413
|
+
taskIds: created.map((t) => t.id),
|
|
1414
|
+
error: `${failedTasks.length}/${results.length} task(s) failed \u2014 ${firstError}`,
|
|
1415
|
+
reports
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
return { status: "completed", summary, taskIds: created.map((t) => t.id), reports };
|
|
1419
|
+
}
|
|
1420
|
+
async runSingle(step, ctx, agentName) {
|
|
1421
|
+
const task = await this.options.tasks.create({
|
|
1422
|
+
objective: ctx.objective,
|
|
1423
|
+
agent: agentName,
|
|
1424
|
+
...ctx.role ? { role: ctx.role.id } : {},
|
|
1425
|
+
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
1426
|
+
});
|
|
1427
|
+
const result = await this.options.tasks.run(task.id);
|
|
1428
|
+
if (result.status !== "completed") {
|
|
1429
|
+
return {
|
|
1430
|
+
status: "failed",
|
|
1431
|
+
summary: result.result?.summary ?? "",
|
|
1432
|
+
taskIds: [task.id],
|
|
1433
|
+
error: result.result?.error ?? `task ${task.id} did not complete`,
|
|
1434
|
+
reports: result.result?.reports ?? []
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
return {
|
|
1438
|
+
status: "completed",
|
|
1439
|
+
summary: result.result?.summary ?? "(no output)",
|
|
1440
|
+
taskIds: [task.id],
|
|
1441
|
+
reports: result.result?.reports ?? []
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
async runPlan(step, ctx) {
|
|
1445
|
+
if (!step.model) {
|
|
1446
|
+
return { status: "completed", summary: "(plan: no model configured \u2014 skipped)", taskIds: [], reports: [] };
|
|
1447
|
+
}
|
|
1448
|
+
const messages = [
|
|
1449
|
+
{
|
|
1450
|
+
role: "system",
|
|
1451
|
+
content: "You are a planning assistant inside OmniNode. Given the objective and the combined research context, produce a prioritized, actionable plan."
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
role: "user",
|
|
1455
|
+
content: `# Objective
|
|
1456
|
+
${ctx.objective}
|
|
1457
|
+
|
|
1458
|
+
${ctx.combinedContext}
|
|
1459
|
+
|
|
1460
|
+
Produce the final plan.`
|
|
1461
|
+
}
|
|
1462
|
+
];
|
|
1463
|
+
try {
|
|
1464
|
+
const planText = await this.getChat()(step.model, messages);
|
|
1465
|
+
return {
|
|
1466
|
+
status: "completed",
|
|
1467
|
+
summary: planText.split("\n", 1)[0] ?? planText,
|
|
1468
|
+
contextContribution: planText,
|
|
1469
|
+
taskIds: [],
|
|
1470
|
+
reports: []
|
|
1471
|
+
};
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
return {
|
|
1474
|
+
status: "failed",
|
|
1475
|
+
summary: "",
|
|
1476
|
+
taskIds: [],
|
|
1477
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1478
|
+
reports: []
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
getChat() {
|
|
1483
|
+
if (this.options.chat) return this.options.chat;
|
|
1484
|
+
throw new PipelineError(
|
|
1485
|
+
"PIPELINE_FAILED",
|
|
1486
|
+
"No chat function available \u2014 configure a provider for plan steps."
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
function createDefaultChatFn(providers) {
|
|
1491
|
+
return async (modelRef, messages) => {
|
|
1492
|
+
const separator = modelRef.indexOf(":");
|
|
1493
|
+
if (separator <= 0 || separator === modelRef.length - 1) {
|
|
1494
|
+
throw new PipelineError(
|
|
1495
|
+
"PIPELINE_INVALID",
|
|
1496
|
+
`Invalid model reference "${modelRef}". Use "provider:model-id".`
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
const providerName = modelRef.slice(0, separator);
|
|
1500
|
+
const modelId = modelRef.slice(separator + 1);
|
|
1501
|
+
const providerConfig = providers.find((p) => p.name === providerName);
|
|
1502
|
+
if (!providerConfig) {
|
|
1503
|
+
throw new PipelineError(
|
|
1504
|
+
"PIPELINE_INVALID",
|
|
1505
|
+
`Provider "${providerName}" (model "${modelRef}") is not configured.`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
const provider = createProvider(providerConfig);
|
|
1509
|
+
const response = await provider.chat({ model: modelId, messages });
|
|
1510
|
+
return response.content;
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// src/pipelines/store.ts
|
|
1515
|
+
import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
1516
|
+
import { dirname as dirname2 } from "path";
|
|
1517
|
+
var FilePipelineRunStore = class {
|
|
1518
|
+
filePath;
|
|
1519
|
+
queue = Promise.resolve();
|
|
1520
|
+
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
1521
|
+
this.filePath = `${directory}/pipelines.json`;
|
|
1522
|
+
}
|
|
1523
|
+
async save(run) {
|
|
1524
|
+
return this.synchronized(async () => {
|
|
1525
|
+
const runs = await this.load();
|
|
1526
|
+
const index = runs.findIndex((r) => r.id === run.id);
|
|
1527
|
+
if (index >= 0) runs[index] = run;
|
|
1528
|
+
else runs.push(run);
|
|
1529
|
+
await this.write(runs);
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
async get(id) {
|
|
1533
|
+
return this.synchronized(async () => {
|
|
1534
|
+
const runs = await this.load();
|
|
1535
|
+
return runs.find((r) => r.id === id);
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
async list(filter = {}) {
|
|
1539
|
+
return this.synchronized(async () => {
|
|
1540
|
+
const runs = await this.load();
|
|
1541
|
+
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 ?? ""));
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
synchronized(operation) {
|
|
1545
|
+
const result = this.queue.then(operation, operation);
|
|
1546
|
+
this.queue = result.then(
|
|
1547
|
+
() => void 0,
|
|
1548
|
+
() => void 0
|
|
1549
|
+
);
|
|
1550
|
+
return result;
|
|
1551
|
+
}
|
|
1552
|
+
async load() {
|
|
1553
|
+
try {
|
|
1554
|
+
const raw = await readFile2(this.filePath, "utf8");
|
|
1555
|
+
const parsed = JSON.parse(raw);
|
|
1556
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1557
|
+
} catch {
|
|
1558
|
+
return [];
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
async write(runs) {
|
|
1562
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1563
|
+
await mkdir2(dirname2(this.filePath), { recursive: true });
|
|
1564
|
+
await writeFile2(tmp, `${JSON.stringify(runs, null, 2)}
|
|
1565
|
+
`, "utf8");
|
|
1566
|
+
await rename2(tmp, this.filePath);
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
// src/pipelines/engine-factory.ts
|
|
1571
|
+
function buildPipelineEngine(config, log = logger) {
|
|
1572
|
+
const agents = buildAgentRegistry(config, log);
|
|
1573
|
+
const roles = buildRoleRegistry(config);
|
|
1574
|
+
const tasks = new TaskEngine({
|
|
1575
|
+
agents,
|
|
1576
|
+
roles,
|
|
1577
|
+
store: new FileTaskStore(),
|
|
1578
|
+
log
|
|
1579
|
+
});
|
|
1580
|
+
return new PipelineEngine({
|
|
1581
|
+
tasks,
|
|
1582
|
+
agents,
|
|
1583
|
+
roles,
|
|
1584
|
+
providers: config.project.providers,
|
|
1585
|
+
store: new FilePipelineRunStore(),
|
|
1586
|
+
chat: createDefaultChatFn(config.project.providers),
|
|
1587
|
+
log
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1104
1591
|
// src/version.ts
|
|
1105
|
-
var OMNINODE_VERSION = "0.
|
|
1592
|
+
var OMNINODE_VERSION = "0.6.0";
|
|
1106
1593
|
export {
|
|
1107
1594
|
AgentError,
|
|
1108
1595
|
AgentRegistry,
|
|
1109
1596
|
ConfigError,
|
|
1110
1597
|
ConsoleLogSink,
|
|
1598
|
+
FilePipelineRunStore,
|
|
1111
1599
|
FileTaskStore,
|
|
1112
1600
|
Logger,
|
|
1113
1601
|
MemoryError,
|
|
@@ -1117,6 +1605,7 @@ export {
|
|
|
1117
1605
|
OmniHilbrasProvider,
|
|
1118
1606
|
OmniNodeError,
|
|
1119
1607
|
OpenAICompatibleProvider,
|
|
1608
|
+
PipelineEngine,
|
|
1120
1609
|
PipelineError,
|
|
1121
1610
|
ProcessAgent,
|
|
1122
1611
|
ProtocolError,
|
|
@@ -1128,9 +1617,11 @@ export {
|
|
|
1128
1617
|
appConfigSchema,
|
|
1129
1618
|
authHeaders,
|
|
1130
1619
|
buildAgentRegistry,
|
|
1620
|
+
buildPipelineEngine,
|
|
1131
1621
|
buildRoleRegistry,
|
|
1132
1622
|
composeTaskText,
|
|
1133
1623
|
createAgent,
|
|
1624
|
+
createDefaultChatFn,
|
|
1134
1625
|
createProvider,
|
|
1135
1626
|
createTaskEngine,
|
|
1136
1627
|
defaultProjectConfigYaml,
|
|
@@ -1139,6 +1630,8 @@ export {
|
|
|
1139
1630
|
isOmniNodeError,
|
|
1140
1631
|
loadConfig,
|
|
1141
1632
|
logger,
|
|
1633
|
+
pipelineConfigSchema,
|
|
1634
|
+
pipelineStepConfigSchema,
|
|
1142
1635
|
projectConfigSchema,
|
|
1143
1636
|
providerConfigSchema,
|
|
1144
1637
|
registryKey,
|