@hilbras/omninode 0.6.0 → 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/dist/index.d.ts CHANGED
@@ -27,6 +27,43 @@ interface Report {
27
27
  createdAt: string;
28
28
  metadata?: Record<string, unknown>;
29
29
  }
30
+ /** Source attribution for an aggregated finding (§17). */
31
+ interface FindingSource {
32
+ agent: string;
33
+ reportId: string;
34
+ }
35
+ interface AggregatedFinding {
36
+ title: string;
37
+ /** Distinct details contributed across reports. */
38
+ details: string[];
39
+ sources: FindingSource[];
40
+ /** How many reports reported this (or a similar) finding. */
41
+ occurrences: number;
42
+ /** Highest severity seen across merged duplicates. */
43
+ severity?: FindingSeverity;
44
+ evidence: string[];
45
+ }
46
+ interface AggregatedRecommendation {
47
+ text: string;
48
+ sources: FindingSource[];
49
+ occurrences: number;
50
+ }
51
+ /** Combined intelligence produced from several independent reports (§17). */
52
+ interface CombinedReport {
53
+ id: string;
54
+ title: string;
55
+ objective?: string;
56
+ pipelineRunId?: string;
57
+ taskIds: string[];
58
+ summary: string;
59
+ findings: AggregatedFinding[];
60
+ recommendations: AggregatedRecommendation[];
61
+ /** Distinct agent names that contributed. */
62
+ sources: string[];
63
+ reportIds: string[];
64
+ createdAt: string;
65
+ metadata?: Record<string, unknown>;
66
+ }
30
67
 
31
68
  /** Role system: what an AI is supposed to do, independent of any provider (§11). */
32
69
  interface RoleDefinition {
@@ -201,6 +238,8 @@ interface TaskResult {
201
238
  reportId?: string;
202
239
  /** Structured reports produced by protocol-mode agents (Phase 6 builds on this). */
203
240
  reports?: Report[];
241
+ /** Truncated verbatim agent output, kept for report extraction and audit. */
242
+ rawOutput?: string;
204
243
  error?: string;
205
244
  finishedAt?: string;
206
245
  }
@@ -263,6 +302,8 @@ interface PipelineRun {
263
302
  stepRuns: PipelineStepRun[];
264
303
  /** Structured reports gathered across all steps (Phase 6 builds on this). */
265
304
  reports?: Report[];
305
+ /** Set when the report system generated a combined report for this run. */
306
+ combinedReportId?: string;
266
307
  }
267
308
 
268
309
  /** Agent ↔ OmniNode communication protocol (§21). */
@@ -411,7 +452,7 @@ interface IChatProvider extends IProvider {
411
452
  }
412
453
 
413
454
  /** Typed error hierarchy shared across the whole codebase (§4.1 — error model). */
414
- type OmniNodeErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "PROVIDER_NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "PROVIDER_AUTH_FAILED" | "MODEL_NOT_FOUND" | "AGENT_NOT_FOUND" | "AGENT_FAILED" | "AGENT_TIMEOUT" | "TASK_INVALID" | "TASK_NOT_FOUND" | "TASK_FAILED" | "PIPELINE_NOT_FOUND" | "PIPELINE_INVALID" | "PIPELINE_FAILED" | "PROTOCOL_VIOLATION" | "MEMORY_UNAVAILABLE" | "NOT_IMPLEMENTED" | "CLI_USAGE" | "INTERNAL";
455
+ type OmniNodeErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "PROVIDER_NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "PROVIDER_AUTH_FAILED" | "MODEL_NOT_FOUND" | "AGENT_NOT_FOUND" | "AGENT_FAILED" | "AGENT_TIMEOUT" | "TASK_INVALID" | "TASK_NOT_FOUND" | "TASK_FAILED" | "PIPELINE_NOT_FOUND" | "PIPELINE_INVALID" | "PIPELINE_FAILED" | "PROTOCOL_VIOLATION" | "MEMORY_UNAVAILABLE" | "REPORT_NOT_FOUND" | "NOT_IMPLEMENTED" | "CLI_USAGE" | "INTERNAL";
415
456
  interface OmniNodeErrorOptions {
416
457
  cause?: unknown;
417
458
  details?: Record<string, unknown>;
@@ -1520,6 +1561,43 @@ declare class TaskEngine {
1520
1561
 
1521
1562
  declare function createTaskEngine(config: AppConfig, log?: Logger, directory?: string): TaskEngine;
1522
1563
 
1564
+ interface ReportFilter {
1565
+ taskId?: string;
1566
+ agent?: string;
1567
+ }
1568
+ interface CombinedReportFilter {
1569
+ pipelineRunId?: string;
1570
+ }
1571
+ interface ReportStore {
1572
+ saveReports(reports: Report[]): Promise<void>;
1573
+ listReports(filter?: ReportFilter): Promise<Report[]>;
1574
+ saveCombined(combined: CombinedReport): Promise<void>;
1575
+ getCombined(id: string): Promise<CombinedReport | undefined>;
1576
+ listCombined(filter?: CombinedReportFilter): Promise<CombinedReport[]>;
1577
+ }
1578
+
1579
+ /**
1580
+ * Report service (§16–§17): collects reports from finished tasks (structured
1581
+ * protocol reports when present, extraction from text otherwise), persists
1582
+ * them with source attribution and generates combined reports.
1583
+ */
1584
+
1585
+ interface CombinedMeta {
1586
+ objective?: string;
1587
+ pipelineRunId?: string;
1588
+ taskIds?: string[];
1589
+ }
1590
+ declare class ReportService {
1591
+ private readonly store;
1592
+ private readonly log;
1593
+ constructor(store: ReportStore, log?: Logger);
1594
+ get storeRef(): ReportStore;
1595
+ /** Normalizes finished tasks into reports (§17 — normalizer). */
1596
+ collectFromTasks(tasks: Task[]): Promise<Report[]>;
1597
+ saveReports(reports: Report[]): Promise<void>;
1598
+ generateCombined(reports: Report[], meta?: CombinedMeta): Promise<CombinedReport | undefined>;
1599
+ }
1600
+
1523
1601
  interface PipelineRunFilter {
1524
1602
  pipelineId?: string;
1525
1603
  status?: PipelineRunStatus;
@@ -1549,6 +1627,8 @@ interface PipelineEngineOptions {
1549
1627
  providers: AppConfig["project"]["providers"];
1550
1628
  store: PipelineRunStore;
1551
1629
  chat?: ChatFn;
1630
+ /** When present, finished runs have their reports collected, stored and combined (§16–§17). */
1631
+ reports?: ReportService;
1552
1632
  log?: Logger;
1553
1633
  }
1554
1634
  interface RunPipelineOptions {
@@ -1589,6 +1669,6 @@ declare function createDefaultChatFn(providers: AppConfig["project"]["providers"
1589
1669
 
1590
1670
  declare function buildPipelineEngine(config: AppConfig, log?: Logger): PipelineEngine;
1591
1671
 
1592
- declare const OMNINODE_VERSION = "0.6.0";
1672
+ declare const OMNINODE_VERSION = "0.7.0";
1593
1673
 
1594
- export { type AgentConfig, type AgentConfigYaml, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AppConfig, type AppConfigYaml, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type Confidence, ConfigError, ConsoleLogSink, type CreateTaskInput, type DiscoveryResult, type EnvelopeMeta, FilePipelineRunStore, FileTaskStore, type Finding, type FindingSeverity, type HttpRequestOptions, type HttpResponse, type IAgent, type IChatProvider, type IMemoryProvider, type IProvider, type LoadConfigOptions, type LogLevel, type LogSink, Logger, type LoggerOptions, type MemoryEntry, MemoryError, type MemoryQuery, type MemoryScope, type MessageEnvelope, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunFilter, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, ProcessAgent, type Project, type ProjectConfigYaml, ProtocolError, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type Report, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, appConfigSchema, authHeaders, buildAgentRegistry, buildPipelineEngine, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createProvider, createTaskEngine, defaultProjectConfigYaml, expandEnvRefs, findConfigFile, isOmniNodeError, loadConfig, logger, pipelineConfigSchema, pipelineStepConfigSchema, projectConfigSchema, providerConfigSchema, registryKey, requestJson, resolveApiKey, roleConfigSchema };
1674
+ export { type AgentConfig, type AgentConfigYaml, AgentError, type AgentInfo, type AgentInputMode, type AgentIntegrationType, AgentRegistry, type AgentStatus, type AgentTaskInput, type AgentTaskOutput, type AgentToNodeMessage, type AggregatedFinding, type AggregatedRecommendation, type AppConfig, type AppConfigYaml, type ChatFn, type ChatMessage, type ChatRequest, type ChatResponse, type ChatRole, type ChatUsage, type CombinedReport, type Confidence, ConfigError, ConsoleLogSink, type CreateTaskInput, type DiscoveryResult, type EnvelopeMeta, FilePipelineRunStore, FileTaskStore, type Finding, type FindingSeverity, type FindingSource, type HttpRequestOptions, type HttpResponse, type IAgent, type IChatProvider, type IMemoryProvider, type IProvider, type LoadConfigOptions, type LogLevel, type LogSink, Logger, type LoggerOptions, type MemoryEntry, MemoryError, type MemoryQuery, type MemoryScope, type MessageEnvelope, type ModelCapabilities, type ModelFilter, type ModelInfo, ModelRegistry, type ModelStatus, type NodeToAgentMessage, OMNINODE_VERSION, OPENAI_COMPATIBLE_TYPES, type OmniHilbrasConnectionResult, OmniHilbrasProvider, OmniNodeError, type OmniNodeErrorCode, type OmniNodeErrorOptions, OpenAICompatibleProvider, type PipelineConfigYaml, type PipelineDefinition, PipelineEngine, type PipelineEngineOptions, PipelineError, type PipelineRun, type PipelineRunFilter, type PipelineRunStatus, type PipelineRunStore, type PipelineStep, type PipelineStepCondition, type PipelineStepConfigYaml, type PipelineStepKind, type PipelineStepRun, ProcessAgent, type Project, type ProjectConfigYaml, ProtocolError, type ProviderConfig, type ProviderConfigYaml, ProviderError, type ProviderHealth, type ProviderStatus, type ProviderType, type Report, type RoleConfigYaml, type RoleDefinition, RoleRegistry, type RunPipelineOptions, type Task, type TaskContext, TaskEngine, type TaskEngineOptions, TaskError, type TaskResult, type TaskStatus, type TaskStore, type TaskStoreFilter, type WritableLike, agentConfigSchema, appConfigSchema, authHeaders, buildAgentRegistry, buildPipelineEngine, buildRoleRegistry, composeTaskText, createAgent, createDefaultChatFn, createProvider, createTaskEngine, defaultProjectConfigYaml, expandEnvRefs, findConfigFile, isOmniNodeError, loadConfig, logger, pipelineConfigSchema, pipelineStepConfigSchema, projectConfigSchema, providerConfigSchema, registryKey, requestJson, resolveApiKey, roleConfigSchema };
package/dist/index.js CHANGED
@@ -1097,6 +1097,8 @@ var TaskEngine = class {
1097
1097
  const result = {
1098
1098
  ...output.summary !== void 0 ? { summary: output.summary } : {},
1099
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) } : {},
1100
1102
  ...output.error !== void 0 ? { error: output.error } : {},
1101
1103
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
1102
1104
  };
@@ -1328,11 +1330,28 @@ ${contribution}`);
1328
1330
  }
1329
1331
  const failed = [...outcomes.values()].some((o) => o.status === "failed");
1330
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
+ }
1331
1349
  const finished = {
1332
1350
  ...run,
1333
1351
  status: finalStatus,
1334
1352
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1335
- ...allReports.length > 0 ? { reports: allReports } : {}
1353
+ ...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
1354
+ ...combinedReportId !== void 0 ? { combinedReportId } : {}
1336
1355
  };
1337
1356
  await this.options.store.save(finished);
1338
1357
  this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
@@ -1567,6 +1586,298 @@ var FilePipelineRunStore = class {
1567
1586
  }
1568
1587
  };
1569
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
+
1570
1881
  // src/pipelines/engine-factory.ts
1571
1882
  function buildPipelineEngine(config, log = logger) {
1572
1883
  const agents = buildAgentRegistry(config, log);
@@ -1584,12 +1895,13 @@ function buildPipelineEngine(config, log = logger) {
1584
1895
  providers: config.project.providers,
1585
1896
  store: new FilePipelineRunStore(),
1586
1897
  chat: createDefaultChatFn(config.project.providers),
1898
+ reports: new ReportService(new FileReportStore(), log),
1587
1899
  log
1588
1900
  });
1589
1901
  }
1590
1902
 
1591
1903
  // src/version.ts
1592
- var OMNINODE_VERSION = "0.6.0";
1904
+ var OMNINODE_VERSION = "0.7.0";
1593
1905
  export {
1594
1906
  AgentError,
1595
1907
  AgentRegistry,