@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/README.md +12 -3
- package/dist/cli/index.js +413 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +83 -3
- package/dist/index.js +314 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
...
|
|
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.
|
|
1904
|
+
var OMNINODE_VERSION = "0.7.0";
|
|
1593
1905
|
export {
|
|
1594
1906
|
AgentError,
|
|
1595
1907
|
AgentRegistry,
|