@hilbras/omninode 0.10.0 → 1.0.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 CHANGED
@@ -18,9 +18,26 @@ existing intelligence; it does not try to become another model.
18
18
 
19
19
  ## Status
20
20
 
21
- **v0.10.0 — Phase 9: End-to-End Workflow** (see [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md)
22
- for the full plan and roadmap). The full loop from §28 works in one command:
23
- `User → project context → memory → research agents → reports → aggregation → planner → plan → execution → result`.
21
+ **v1.0.0 — Phases 10 + 11: Production Release.** The plan is complete: the
22
+ full loop from §28 runs in one command —
23
+ `User → project context → memory → research agents → reports → aggregation →
24
+ planner → plan → execution → result` — with audit logging, error recovery and
25
+ production hardening (see [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md)
26
+ for the plan and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the design).
27
+
28
+ ### What's in v1.0
29
+
30
+ | Capability | Where |
31
+ | --- | --- |
32
+ | Provider abstraction, custom providers, model discovery | `src/providers/`, `src/registry/` |
33
+ | OmniHilbras integration (optional) | `src/providers/omnihilbras/` |
34
+ | CLI agents (stdin/arg/protocol), agent registry | `src/agents/` |
35
+ | Roles, projects, tasks (lifecycle + retry) | `src/roles/`, `src/tasks/` |
36
+ | Pipelines (DAG, parallel, retries, conditions) | `src/pipelines/` |
37
+ | Multi-AI research + report aggregation | `src/reports/` |
38
+ | Planner layer (model + heuristic) | `src/planner/` |
39
+ | Remembera / local memory | `src/memory/` |
40
+ | CLI, configuration, security foundations, docs | `src/cli/`, `src/config/`, `docs/` |
24
41
 
25
42
  What exists today:
26
43
 
@@ -251,18 +268,27 @@ Phases 0–11 are laid out in [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.m
251
268
  | v0.3.0 | OmniHilbras Integration (optional, like every provider) |
252
269
  | v0.4.0 | Agent System |
253
270
  | v0.5.0 | Roles & Tasks |
254
- | v0.7.0 | Multi-AI Reports |
271
+ | v0.6.0 | Pipeline Engine |
255
272
  | v0.7.0 | Multi-AI Reports |
256
273
  | v0.8.0 | Remembera / Memory |
257
- | v0.8.0 | Remembera / Memory |
258
274
  | v0.9.0 | Planner |
259
- | **v0.10.0** | **End-to-End Workflow (current)** |
260
- | v1.0.0 | Production Release |
275
+ | v0.10.0 | End-to-End Workflow (current) |
276
+ | **v1.0.0** | **Production Release — Phases 10 + 11 (current, final)** |
261
277
 
262
278
  Core architectural rule: OmniNode must run successfully with **zero Hilbras
263
279
  dependencies**. OmniHilbras and Remembera make it more powerful, but neither is
264
280
  required for the core engine.
265
281
 
282
+ ## Documentation
283
+
284
+ - [Development plan](docs/DEVELOPMENT_PLAN.md) — the full 12-phase roadmap
285
+ - [Architecture](docs/ARCHITECTURE.md) — module map, workflow, local state
286
+ - [Security model](docs/SECURITY.md) — secrets, process execution, env isolation, audit log
287
+ - [Example project](examples/demo/) — a runnable end-to-end demo (`./demo.sh`)
288
+
289
+ Prefer a container? `docker build -t omninode .` then
290
+ `docker run --rm -v "$PWD:/work" -w /work omninode run my-pipeline "audit the repo"`.
291
+
266
292
  ## Install
267
293
 
268
294
  ```bash
package/dist/cli/index.js CHANGED
@@ -6,7 +6,7 @@ import { realpathSync } from "fs";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/version.ts
9
- var OMNINODE_VERSION = "0.10.0";
9
+ var OMNINODE_VERSION = "1.0.0";
10
10
 
11
11
  // src/errors/index.ts
12
12
  var OmniNodeError = class extends Error {
@@ -87,6 +87,7 @@ var agentConfigSchema = z.object({
87
87
  env: z.record(z.string()).optional(),
88
88
  input_mode: z.enum(["stdin", "arg", "protocol"]).default("stdin"),
89
89
  timeout_ms: z.number().int().positive().optional(),
90
+ inherit_env: z.boolean().optional(),
90
91
  metadata: z.record(z.unknown()).optional()
91
92
  }).strict();
92
93
  var roleConfigSchema = z.object({
@@ -260,6 +261,7 @@ function toAgentConfig(raw) {
260
261
  env: raw.env,
261
262
  inputMode: raw.input_mode,
262
263
  timeoutMs: raw.timeout_ms,
264
+ inheritEnv: raw.inherit_env,
263
265
  metadata: raw.metadata
264
266
  };
265
267
  }
@@ -897,6 +899,17 @@ function registerModelsCommand(program) {
897
899
  import { spawn } from "child_process";
898
900
  var DEFAULT_TIMEOUT_MS2 = 6e5;
899
901
  var KILL_GRACE_MS = 5e3;
902
+ function buildChildEnv(config) {
903
+ const configured = config.env ?? {};
904
+ if (config.inheritEnv !== false) {
905
+ return { ...process.env, ...configured };
906
+ }
907
+ return {
908
+ PATH: process.env.PATH ?? "",
909
+ HOME: process.env.HOME ?? "",
910
+ ...configured
911
+ };
912
+ }
900
913
  function composeTaskText(input) {
901
914
  const sections = [`# Objective
902
915
  ${input.objective}`];
@@ -963,7 +976,7 @@ var ProcessAgent = class {
963
976
  [...this.config.args ?? [], ...mode === "arg" ? [composeTaskText(input)] : []],
964
977
  {
965
978
  cwd: this.config.cwd,
966
- env: { ...process.env, ...this.config.env },
979
+ env: buildChildEnv(this.config),
967
980
  stdio: ["pipe", "pipe", "pipe"]
968
981
  }
969
982
  );
@@ -1400,6 +1413,7 @@ var TaskEngine = class {
1400
1413
  };
1401
1414
  await this.options.store.save(task);
1402
1415
  this.log.info(`Created task ${task.id}.`);
1416
+ await this.audit("task.created", task.id, { objective: task.objective, project: task.project });
1403
1417
  return task;
1404
1418
  }
1405
1419
  async queue(id) {
@@ -1491,6 +1505,7 @@ var TaskEngine = class {
1491
1505
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1492
1506
  };
1493
1507
  await this.options.store.save(reset);
1508
+ await this.audit("task.retry", id, { from: task.status });
1494
1509
  this.log.info(`Task ${id} reset for retry.`);
1495
1510
  return reset;
1496
1511
  }
@@ -1519,16 +1534,68 @@ var TaskEngine = class {
1519
1534
  ...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
1520
1535
  };
1521
1536
  await this.options.store.save(updated);
1537
+ await this.audit(`task.${to === "queued" ? "queued" : to}`, updated.id, {
1538
+ from: task.status
1539
+ });
1522
1540
  return updated;
1523
1541
  }
1542
+ async audit(action, id, detail) {
1543
+ await this.options.audit?.record({
1544
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1545
+ action,
1546
+ id,
1547
+ ...detail !== void 0 ? { detail } : {}
1548
+ });
1549
+ }
1524
1550
  };
1525
1551
  function generateTaskId() {
1526
1552
  return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
1527
1553
  }
1528
1554
 
1529
- // src/memory/local.ts
1530
- import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
1555
+ // src/audit/index.ts
1556
+ import { appendFile, mkdir as mkdir2 } from "fs/promises";
1531
1557
  import { dirname as dirname2 } from "path";
1558
+ var FileAuditLog = class {
1559
+ filePath;
1560
+ log;
1561
+ constructor(directory = `${process.cwd()}/.omninode`, log = logger) {
1562
+ this.filePath = `${directory}/audit.jsonl`;
1563
+ this.log = log.child({ component: "audit" });
1564
+ }
1565
+ async record(event) {
1566
+ try {
1567
+ await mkdir2(dirname2(this.filePath), { recursive: true });
1568
+ await appendFile(this.filePath, `${JSON.stringify(event)}
1569
+ `, "utf8");
1570
+ } catch (error) {
1571
+ this.log.warn(
1572
+ `Audit write failed: ${error instanceof Error ? error.message : String(error)}`
1573
+ );
1574
+ }
1575
+ }
1576
+ /** Recent events, oldest first; used by the CLI. */
1577
+ async recent(limit = 20) {
1578
+ const { readFile: readFile6 } = await import("fs/promises");
1579
+ let raw;
1580
+ try {
1581
+ raw = await readFile6(this.filePath, "utf8");
1582
+ } catch {
1583
+ return [];
1584
+ }
1585
+ const events = raw.split("\n").filter((line) => line.length > 0).map((line) => {
1586
+ try {
1587
+ return JSON.parse(line);
1588
+ } catch {
1589
+ return void 0;
1590
+ }
1591
+ }).filter((event) => event !== void 0);
1592
+ return events.slice(-limit);
1593
+ }
1594
+ };
1595
+
1596
+ // src/memory/local.ts
1597
+ import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
1598
+ import { dirname as dirname3 } from "path";
1532
1599
  var STOPWORDS = /* @__PURE__ */ new Set([
1533
1600
  "the",
1534
1601
  "and",
@@ -1641,7 +1708,7 @@ var LocalMemoryProvider = class {
1641
1708
  }
1642
1709
  async saveAll(entries) {
1643
1710
  const tmp = `${this.filePath}.tmp`;
1644
- await mkdir2(dirname2(this.filePath), { recursive: true });
1711
+ await mkdir3(dirname3(this.filePath), { recursive: true });
1645
1712
  await writeFile2(tmp, `${JSON.stringify(entries, null, 2)}
1646
1713
  `, "utf8");
1647
1714
  await rename2(tmp, this.filePath);
@@ -1848,6 +1915,7 @@ function createTaskEngine(config, log = logger, directory) {
1848
1915
  roles,
1849
1916
  store: new FileTaskStore(directory),
1850
1917
  ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
1918
+ audit: new FileAuditLog(),
1851
1919
  log
1852
1920
  });
1853
1921
  }
@@ -2160,8 +2228,8 @@ var ReportService = class {
2160
2228
  };
2161
2229
 
2162
2230
  // src/reports/store.ts
2163
- import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
2164
- import { dirname as dirname3 } from "path";
2231
+ import { mkdir as mkdir4, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
2232
+ import { dirname as dirname4 } from "path";
2165
2233
  var FileReportStore = class {
2166
2234
  filePath;
2167
2235
  queue = Promise.resolve();
@@ -2234,7 +2302,7 @@ var FileReportStore = class {
2234
2302
  }
2235
2303
  async write(entries) {
2236
2304
  const tmp = `${this.filePath}.tmp`;
2237
- await mkdir3(dirname3(this.filePath), { recursive: true });
2305
+ await mkdir4(dirname4(this.filePath), { recursive: true });
2238
2306
  await writeFile3(tmp, `${JSON.stringify(entries, null, 2)}
2239
2307
  `, "utf8");
2240
2308
  await rename3(tmp, this.filePath);
@@ -2676,6 +2744,12 @@ var PipelineEngine = class {
2676
2744
  };
2677
2745
  await this.options.store.save(run2);
2678
2746
  this.log.info(`Running pipeline ${def.id} as ${run2.id}.`);
2747
+ await this.options.audit?.record({
2748
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2749
+ action: "pipeline.run.started",
2750
+ id: run2.id,
2751
+ detail: { pipeline: def.id, objective }
2752
+ });
2679
2753
  const deps = this.effectiveDependencies(def);
2680
2754
  const outcomes = /* @__PURE__ */ new Map();
2681
2755
  const pending = new Set(def.steps.map((s) => s.id));
@@ -2766,6 +2840,12 @@ ${contribution}`);
2766
2840
  };
2767
2841
  await this.options.store.save(finished);
2768
2842
  this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
2843
+ await this.options.audit?.record({
2844
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2845
+ action: "pipeline.run.finished",
2846
+ id: run2.id,
2847
+ detail: { pipeline: def.id, status: finalStatus }
2848
+ });
2769
2849
  return finished;
2770
2850
  }
2771
2851
  async get(runId) {
@@ -2910,6 +2990,12 @@ ${contribution}`);
2910
2990
  };
2911
2991
  }
2912
2992
  await this.options.plans?.save(plan);
2993
+ await this.options.audit?.record({
2994
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2995
+ action: "plan.generated",
2996
+ id: plan.id,
2997
+ detail: { generatedBy: plan.generatedBy, steps: plan.steps.length }
2998
+ });
2913
2999
  return {
2914
3000
  status: "completed",
2915
3001
  summary: plan.summary,
@@ -2967,8 +3053,8 @@ function createDefaultChatFn(providers) {
2967
3053
  }
2968
3054
 
2969
3055
  // src/pipelines/store.ts
2970
- import { mkdir as mkdir4, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "fs/promises";
2971
- import { dirname as dirname4 } from "path";
3056
+ import { mkdir as mkdir5, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "fs/promises";
3057
+ import { dirname as dirname5 } from "path";
2972
3058
  var FilePipelineRunStore = class {
2973
3059
  filePath;
2974
3060
  queue = Promise.resolve();
@@ -3015,7 +3101,7 @@ var FilePipelineRunStore = class {
3015
3101
  }
3016
3102
  async write(runs) {
3017
3103
  const tmp = `${this.filePath}.tmp`;
3018
- await mkdir4(dirname4(this.filePath), { recursive: true });
3104
+ await mkdir5(dirname5(this.filePath), { recursive: true });
3019
3105
  await writeFile4(tmp, `${JSON.stringify(runs, null, 2)}
3020
3106
  `, "utf8");
3021
3107
  await rename4(tmp, this.filePath);
@@ -3023,8 +3109,8 @@ var FilePipelineRunStore = class {
3023
3109
  };
3024
3110
 
3025
3111
  // src/planner/store.ts
3026
- import { mkdir as mkdir5, readFile as readFile5, rename as rename5, writeFile as writeFile5 } from "fs/promises";
3027
- import { dirname as dirname5 } from "path";
3112
+ import { mkdir as mkdir6, readFile as readFile5, rename as rename5, writeFile as writeFile5 } from "fs/promises";
3113
+ import { dirname as dirname6 } from "path";
3028
3114
  var FilePlanStore = class {
3029
3115
  filePath;
3030
3116
  queue = Promise.resolve();
@@ -3071,7 +3157,7 @@ var FilePlanStore = class {
3071
3157
  }
3072
3158
  async write(plans) {
3073
3159
  const tmp = `${this.filePath}.tmp`;
3074
- await mkdir5(dirname5(this.filePath), { recursive: true });
3160
+ await mkdir6(dirname6(this.filePath), { recursive: true });
3075
3161
  await writeFile5(tmp, `${JSON.stringify(plans, null, 2)}
3076
3162
  `, "utf8");
3077
3163
  await rename5(tmp, this.filePath);
@@ -3109,6 +3195,7 @@ function buildPipelineEngine(config, log = logger) {
3109
3195
  roles,
3110
3196
  store: new FileTaskStore(),
3111
3197
  ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
3198
+ audit: new FileAuditLog(),
3112
3199
  log
3113
3200
  });
3114
3201
  return new PipelineEngine({
@@ -3121,6 +3208,7 @@ function buildPipelineEngine(config, log = logger) {
3121
3208
  ...config.project.planner !== void 0 ? { planner: buildPlanner(config.project.planner, chat, log) } : {},
3122
3209
  plans: new FilePlanStore(),
3123
3210
  reports: new ReportService(new FileReportStore(), log),
3211
+ audit: new FileAuditLog(),
3124
3212
  log
3125
3213
  });
3126
3214
  }
@@ -3344,6 +3432,14 @@ function registerPlanCommands(program) {
3344
3432
  }
3345
3433
 
3346
3434
  // src/cli/commands/status.ts
3435
+ function groupBy(items, key) {
3436
+ const counts = {};
3437
+ for (const item of items) {
3438
+ const k = key(item);
3439
+ counts[k] = (counts[k] ?? 0) + 1;
3440
+ }
3441
+ return counts;
3442
+ }
3347
3443
  function countBy(items, key) {
3348
3444
  const counts = /* @__PURE__ */ new Map();
3349
3445
  for (const item of items) {
@@ -3354,41 +3450,77 @@ function countBy(items, key) {
3354
3450
  return `${items.length} (${[...counts.entries()].map(([k, v]) => `${k} ${v}`).join(", ")})`;
3355
3451
  }
3356
3452
  function registerStatusCommand(program) {
3357
- program.command("status").description("Show the project's OmniNode state overview (config, tasks, runs, reports, plans, memory).").action(async () => {
3453
+ program.command("status").description("Show the project's OmniNode state overview (config, tasks, runs, reports, plans, memory).").option("--json", "Emit machine-readable JSON (for scripts and CI).").action(async (options) => {
3358
3454
  const config = loadConfig();
3359
- console.log(`OmniNode v${OMNINODE_VERSION} \u2014 project "${config.project.name}"`);
3360
- console.log(
3361
- `providers: ${config.project.providers.length > 0 ? config.project.providers.map((p) => p.name).join(", ") : "none"}`
3362
- );
3363
- console.log(
3364
- `agents: ${config.project.agents.length > 0 ? config.project.agents.map((a) => a.name).join(", ") : "none"}`
3365
- );
3366
- console.log(
3367
- `roles: ${config.project.roles.length > 0 ? config.project.roles.map((r) => r.id).join(", ") : "none"}`
3368
- );
3369
- console.log(
3370
- `pipelines: ${config.project.pipelines.length > 0 ? config.project.pipelines.map((p) => p.id).join(", ") : "none"}`
3371
- );
3372
3455
  const tasks = await new FileTaskStore().list();
3373
- console.log(`tasks: ${countBy(tasks, (t) => t.status)}`);
3374
3456
  const runs = await new FilePipelineRunStore().list();
3375
- console.log(`runs: ${countBy(runs, (r) => r.status)}`);
3376
3457
  const reportStore = new FileReportStore();
3377
3458
  const reports = await reportStore.listReports();
3378
3459
  const combined = await reportStore.listCombined();
3379
- console.log(`reports: ${reports.length} raw, ${combined.length} combined`);
3380
3460
  const plans = await new FilePlanStore().list();
3381
- console.log(`plans: ${plans.length}`);
3461
+ let memory;
3382
3462
  if (config.project.memory !== void 0) {
3383
- const memory = new MemoryService(createMemoryProvider(config.project.memory));
3384
- const entries = await memory.providerRef.query({ limit: 1e4 });
3385
- console.log(`memory: ${memory.providerName}, ${entries.length} entr(ies)`);
3463
+ const service2 = new MemoryService(createMemoryProvider(config.project.memory));
3464
+ memory = {
3465
+ provider: service2.providerName,
3466
+ entries: (await service2.providerRef.query({ limit: 1e4 })).length
3467
+ };
3468
+ }
3469
+ if (options.json) {
3470
+ console.log(
3471
+ JSON.stringify(
3472
+ {
3473
+ version: OMNINODE_VERSION,
3474
+ project: config.project.name,
3475
+ providers: config.project.providers.map((p) => p.name),
3476
+ agents: config.project.agents.map((a) => a.name),
3477
+ roles: config.project.roles.map((r) => r.id),
3478
+ pipelines: config.project.pipelines.map((p) => p.id),
3479
+ tasks: { total: tasks.length, byStatus: groupBy(tasks, (t) => t.status) },
3480
+ runs: { total: runs.length, byStatus: groupBy(runs, (r) => r.status) },
3481
+ reports: { raw: reports.length, combined: combined.length },
3482
+ plans: plans.length,
3483
+ ...memory !== void 0 ? { memory } : {}
3484
+ },
3485
+ null,
3486
+ 2
3487
+ )
3488
+ );
3489
+ return;
3490
+ }
3491
+ console.log(`OmniNode v${OMNINODE_VERSION} \u2014 project "${config.project.name}"`);
3492
+ console.log(`providers: ${config.project.providers.map((p) => p.name).join(", ") || "none"}`);
3493
+ console.log(`agents: ${config.project.agents.map((a) => a.name).join(", ") || "none"}`);
3494
+ console.log(`roles: ${config.project.roles.map((r) => r.id).join(", ") || "none"}`);
3495
+ console.log(`pipelines: ${config.project.pipelines.map((p) => p.id).join(", ") || "none"}`);
3496
+ console.log(`tasks: ${countBy(tasks, (t) => t.status)}`);
3497
+ console.log(`runs: ${countBy(runs, (r) => r.status)}`);
3498
+ console.log(`reports: ${reports.length} raw, ${combined.length} combined`);
3499
+ console.log(`plans: ${plans.length}`);
3500
+ if (memory) {
3501
+ console.log(`memory: ${memory.provider}, ${memory.entries} entr(ies)`);
3386
3502
  } else {
3387
3503
  console.log("memory: not configured (local provider available \u2014 add a `memory:` section)");
3388
3504
  }
3389
3505
  });
3390
3506
  }
3391
3507
 
3508
+ // src/cli/commands/audit.ts
3509
+ function registerAuditCommand(program) {
3510
+ program.command("audit").description("Show the recent audit log \u2014 task and pipeline lifecycle events (\xA724).").option("-n, --limit <count>", "Number of events to show (default 20).", (value) => Number(value), 20).action(async (options) => {
3511
+ const events = await new FileAuditLog().recent(options.limit);
3512
+ if (events.length === 0) {
3513
+ console.log("No audit events recorded yet.");
3514
+ return;
3515
+ }
3516
+ for (const event of events) {
3517
+ const id = event.id ? ` ${event.id}` : "";
3518
+ const detail = event.detail && Object.keys(event.detail).length > 0 ? ` ${JSON.stringify(event.detail)}` : "";
3519
+ console.log(`${event.at} ${event.action}${id}${detail}`);
3520
+ }
3521
+ });
3522
+ }
3523
+
3392
3524
  // src/cli/index.ts
3393
3525
  function createProgram() {
3394
3526
  const program = new Command();
@@ -3407,6 +3539,7 @@ function createProgram() {
3407
3539
  registerMemoryCommands(program);
3408
3540
  registerPlanCommands(program);
3409
3541
  registerStatusCommand(program);
3542
+ registerAuditCommand(program);
3410
3543
  registerRunAlias(program);
3411
3544
  return program;
3412
3545
  }