@hilbras/omninode 2.0.0-alpha.19 → 2.0.0-alpha.20

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
@@ -5,7 +5,7 @@
5
5
  [![npm downloads](https://img.shields.io/npm/dm/@hilbras/omninode)](https://www.npmjs.com/package/@hilbras/omninode)
6
6
  [![Node.js support](https://img.shields.io/node/v/@hilbras/omninode)](https://www.npmjs.com/package/@hilbras/omninode)
7
7
  [![License: MIT](https://img.shields.io/github/license/Hilbras/OmniNode)](LICENSE)
8
- [![v2 progress](https://img.shields.io/badge/v2-19%20%2F%2024%20phases-8A2BE2)](docs/ROADMAP_V2.md)
8
+ [![v2 progress](https://img.shields.io/badge/v2-20%20%2F%2024%20phases-8A2BE2)](docs/ROADMAP_V2.md)
9
9
 
10
10
  **OmniNode** is a provider-agnostic multi-AI orchestration platform. It coordinates
11
11
  CLI-based AI agents, AI providers, persistent memory, roles, reports and planning
@@ -36,7 +36,7 @@ release — is in progress**, shipping as tagged prereleases (`v2.0.0-alpha.N`).
36
36
  | End-to-end workflow in one command | `omninode run <pipeline> "<objective>"` |
37
37
  | Audit log, security model, examples, Docker runtime | [`src/audit/`](src/audit), [docs/SECURITY.md](docs/SECURITY.md) |
38
38
 
39
- ### v2.0.0 progress (19 of 24 phases)
39
+ ### v2.0.0 progress (20 of 24 phases)
40
40
 
41
41
  | # | Phase | Outcome |
42
42
  | --- | --- | --- |
@@ -59,7 +59,8 @@ release — is in progress**, shipping as tagged prereleases (`v2.0.0-alpha.N`).
59
59
  | 17 | Testing expansion | explicit failure matrix + regression tests |
60
60
  | 18 | Documentation overhaul | complete document set, rewritten README |
61
61
  | 19 | API & package quality | curated public surface with CI guards, boundary validation, verified package contents |
62
- | 20–24 | Performance · developer experience · CI/CD · migration tooling · final hardening | next: performance |
62
+ | 20 | Performance & resources | bounded reports/context/audit log, concurrency limits, released handles |
63
+ | 21–24 | Developer experience · CI/CD · migration tooling · final hardening | next: developer experience |
63
64
 
64
65
  The roadmap is [docs/ROADMAP_V2.md](docs/ROADMAP_V2.md); the completed v1 plan is
65
66
  [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md).
package/dist/cli/index.js CHANGED
@@ -23,7 +23,7 @@ import { realpathSync } from "fs";
23
23
  import { Command } from "commander";
24
24
 
25
25
  // src/version.ts
26
- var OMNINODE_VERSION = "2.0.0-alpha.19";
26
+ var OMNINODE_VERSION = "2.0.0-alpha.20";
27
27
 
28
28
  // src/cli/exit-codes.ts
29
29
  var EXIT = {
@@ -1183,6 +1183,9 @@ var ProcessAgent = class {
1183
1183
  clearTimeout(timer);
1184
1184
  this.running.delete(input.taskId);
1185
1185
  GLOBAL_RUNNING.delete(child);
1186
+ child.stdin?.destroy();
1187
+ child.stdout?.destroy();
1188
+ child.stderr?.destroy();
1186
1189
  finish();
1187
1190
  };
1188
1191
  const collect = (into, chunk) => {
@@ -2627,18 +2630,31 @@ function sleep(ms) {
2627
2630
  }
2628
2631
 
2629
2632
  // src/audit/index.ts
2630
- import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
2633
+ import { appendFile, mkdir as mkdir2, readFile as readFile2, rename as rename2, stat as stat2 } from "fs/promises";
2631
2634
  import { dirname as dirname2 } from "path";
2635
+ var DEFAULT_AUDIT_MAX_BYTES = 5 * 1024 * 1024;
2632
2636
  var FileAuditLog = class {
2633
2637
  filePath;
2638
+ maxBytes;
2634
2639
  log;
2635
- constructor(directory = `${process.cwd()}/.omninode`, log = logger) {
2640
+ constructor(directory = `${process.cwd()}/.omninode`, log = logger, maxBytes = DEFAULT_AUDIT_MAX_BYTES) {
2636
2641
  this.filePath = `${directory}/audit.jsonl`;
2642
+ this.maxBytes = maxBytes;
2637
2643
  this.log = log.child({ component: "audit" });
2638
2644
  }
2645
+ /** Rotates the log once it passes the size budget, keeping one backup. */
2646
+ async rotateIfNeeded() {
2647
+ try {
2648
+ const stats = await stat2(this.filePath);
2649
+ if (stats.size <= this.maxBytes) return;
2650
+ await rename2(this.filePath, `${this.filePath}.1`);
2651
+ } catch {
2652
+ }
2653
+ }
2639
2654
  async record(event) {
2640
2655
  try {
2641
2656
  await mkdir2(dirname2(this.filePath), { recursive: true });
2657
+ await this.rotateIfNeeded();
2642
2658
  await appendFile(this.filePath, `${JSON.stringify(event)}
2643
2659
  `, "utf8");
2644
2660
  } catch (error) {
@@ -3018,6 +3034,7 @@ var SEVERITY_RANK = {
3018
3034
  high: 3,
3019
3035
  critical: 4
3020
3036
  };
3037
+ var MAX_COMBINED_FINDINGS = 200;
3021
3038
  function aggregateReports(reports, options = {}) {
3022
3039
  const threshold = options.similarityThreshold ?? 0.7;
3023
3040
  const findingGroups = [];
@@ -3036,6 +3053,10 @@ function aggregateReports(reports, options = {}) {
3036
3053
  const agreements = findingGroups.filter(
3037
3054
  (group) => group.sources.length > 1 && group.severity !== void 0 && new Set(Object.values(group.severityBySource ?? {})).size === 1
3038
3055
  );
3056
+ const maxFindings = options.maxFindings ?? MAX_COMBINED_FINDINGS;
3057
+ const orderedFindings = findingGroups.sort((a, b) => rank(b) - rank(a));
3058
+ const retainedFindings = orderedFindings.slice(0, maxFindings);
3059
+ const droppedFindings = orderedFindings.length - retainedFindings.length;
3039
3060
  const disputed = conflicts.length;
3040
3061
  const summary = `${reports.length} report(s) from ${sources.length > 0 ? sources.join(", ") : "no agents"}: ${findingGroups.length} unique finding group(s) (${totalRawFindings} raw), ${agreements.length} agreement(s), ${disputed} conflict(s), ${recommendationGroups.length} recommendation group(s).`;
3041
3062
  return {
@@ -3045,7 +3066,7 @@ function aggregateReports(reports, options = {}) {
3045
3066
  ...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
3046
3067
  taskIds: options.taskIds ?? [],
3047
3068
  summary,
3048
- findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
3069
+ findings: retainedFindings,
3049
3070
  agreements,
3050
3071
  conflicts,
3051
3072
  recommendations: recommendationGroups,
@@ -3058,7 +3079,8 @@ function aggregateReports(reports, options = {}) {
3058
3079
  rawFindings: totalRawFindings,
3059
3080
  findingGroups: findingGroups.length,
3060
3081
  agreements: agreements.length,
3061
- conflicts: conflicts.length
3082
+ conflicts: conflicts.length,
3083
+ findingsTruncated: droppedFindings
3062
3084
  }
3063
3085
  };
3064
3086
  }
@@ -3796,6 +3818,13 @@ function describe(error) {
3796
3818
  }
3797
3819
 
3798
3820
  // src/pipelines/engine.ts
3821
+ var DEFAULT_MAX_PARALLEL_STEPS = 8;
3822
+ var MAX_COMBINED_CONTEXT_CHARS = 2e4;
3823
+ function budgetContext(context, maxChars = MAX_COMBINED_CONTEXT_CHARS) {
3824
+ if (context.length <= maxChars) return context;
3825
+ const marker = "\n[earlier context trimmed]\n";
3826
+ return marker + context.slice(context.length - (maxChars - marker.length));
3827
+ }
3799
3828
  var PipelineEngine = class {
3800
3829
  constructor(options) {
3801
3830
  this.options = options;
@@ -3991,24 +4020,35 @@ var PipelineEngine = class {
3991
4020
  if (ready.length === 0) {
3992
4021
  throw new PipelineError("PIPELINE_FAILED", `Pipeline "${def.id}" scheduling stalled.`);
3993
4022
  }
3994
- const settled = await Promise.all(
3995
- ready.map(async (step) => {
3996
- if (!this.shouldRun(step, deps.get(step.id) ?? [], outcomes)) {
3997
- return { status: "skipped", summary: "", taskIds: [], reports: [] };
3998
- }
3999
- const ctx = {
4000
- objective,
4001
- ...role ? { role } : {},
4002
- combinedContext: contextParts.join("\n\n"),
4003
- reports: [...allReports],
4004
- pipelineRunId: run2.id,
4005
- runId: run2.id,
4006
- pipelineDefId: def.id,
4007
- taskId: run2.id
4008
- };
4009
- return this.executeStep(step, ctx);
4010
- })
4011
- );
4023
+ const settled = [];
4024
+ const limit = Math.max(1, this.options.maxParallelSteps ?? DEFAULT_MAX_PARALLEL_STEPS);
4025
+ for (let offset = 0; offset < ready.length; offset += limit) {
4026
+ const batch = ready.slice(offset, offset + limit);
4027
+ settled.push(
4028
+ ...await Promise.all(
4029
+ batch.map(async (step) => {
4030
+ if (!this.shouldRun(step, deps.get(step.id) ?? [], outcomes)) {
4031
+ return { status: "skipped", summary: "", taskIds: [], reports: [] };
4032
+ }
4033
+ const ctx = {
4034
+ maxContextChars: this.options.maxContextChars,
4035
+ objective,
4036
+ ...role ? { role } : {},
4037
+ combinedContext: budgetContext(
4038
+ contextParts.join("\n\n"),
4039
+ this.options.maxContextChars ?? MAX_COMBINED_CONTEXT_CHARS
4040
+ ),
4041
+ reports: [...allReports],
4042
+ pipelineRunId: run2.id,
4043
+ runId: run2.id,
4044
+ pipelineDefId: def.id,
4045
+ taskId: run2.id
4046
+ };
4047
+ return this.executeStep(step, ctx);
4048
+ })
4049
+ )
4050
+ );
4051
+ }
4012
4052
  ready.forEach((step, index) => {
4013
4053
  const outcome = settled[index];
4014
4054
  outcomes.set(step.id, outcome);
@@ -4233,7 +4273,9 @@ ${contribution}`);
4233
4273
  };
4234
4274
  }
4235
4275
  async runPlan(step, ctx) {
4236
- const aggregated = ctx.reports.length > 0 ? aggregateReports(ctx.reports) : void 0;
4276
+ const aggregated = ctx.reports.length > 0 ? aggregateReports(ctx.reports, {
4277
+ ...step.constraints !== void 0 && step.constraints.length > 0 ? { maxFindings: MAX_COMBINED_FINDINGS } : {}
4278
+ }) : void 0;
4237
4279
  const request = {
4238
4280
  objective: ctx.objective,
4239
4281
  reports: ctx.reports,