@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/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
- const tasks = await this.load();
922
- const index = tasks.findIndex((t) => t.id === task.id);
923
- if (index >= 0) tasks[index] = task;
924
- else tasks.push(task);
925
- await this.write(tasks);
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
- const tasks = await this.load();
929
- return tasks.find((t) => t.id === id);
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
- const tasks = await this.load();
933
- 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));
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.4.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,