@hilbras/omninode 2.0.0-alpha.10 → 2.0.0-alpha.11
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 +8 -1
- package/dist/cli/index.js +162 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +143 -10
- package/dist/index.js +423 -317
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -166,7 +166,9 @@ var pipelineStepConfigSchema = z.object({
|
|
|
166
166
|
model: z.string().optional(),
|
|
167
167
|
depends_on: z.array(z.string()).optional(),
|
|
168
168
|
retries: z.number().int().min(0).max(10).optional(),
|
|
169
|
-
condition: z.enum(["always", "on-success", "on-failure"]).optional()
|
|
169
|
+
condition: z.enum(["always", "on-success", "on-failure"]).optional(),
|
|
170
|
+
/** Constraints passed to the planner for plan steps (§15). */
|
|
171
|
+
constraints: z.array(z.string()).optional()
|
|
170
172
|
}).strict();
|
|
171
173
|
var pipelineConfigSchema = z.object({
|
|
172
174
|
id: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "pipeline id must be kebab-case"),
|
|
@@ -296,7 +298,8 @@ function toPipelineDefinition(raw) {
|
|
|
296
298
|
model: step.model,
|
|
297
299
|
dependsOn: step.depends_on,
|
|
298
300
|
retries: step.retries,
|
|
299
|
-
condition: step.condition
|
|
301
|
+
condition: step.condition,
|
|
302
|
+
constraints: step.constraints
|
|
300
303
|
}))
|
|
301
304
|
};
|
|
302
305
|
}
|
|
@@ -2580,10 +2583,98 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
2580
2583
|
}
|
|
2581
2584
|
|
|
2582
2585
|
// src/pipelines/engine.ts
|
|
2583
|
-
import { randomBytes as
|
|
2586
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
2584
2587
|
|
|
2585
|
-
// src/
|
|
2588
|
+
// src/reports/aggregate.ts
|
|
2586
2589
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2590
|
+
|
|
2591
|
+
// src/reports/schema.ts
|
|
2592
|
+
import { z as z2 } from "zod";
|
|
2593
|
+
var evidenceSchema = z2.union([
|
|
2594
|
+
z2.string().transform((excerpt) => ({ kind: "observation", excerpt })),
|
|
2595
|
+
z2.object({
|
|
2596
|
+
kind: z2.enum(["file", "line", "url", "command", "observation", "artifact"]),
|
|
2597
|
+
ref: z2.string().optional(),
|
|
2598
|
+
line: z2.number().int().optional(),
|
|
2599
|
+
excerpt: z2.string().optional(),
|
|
2600
|
+
agent: z2.string().optional()
|
|
2601
|
+
})
|
|
2602
|
+
]);
|
|
2603
|
+
var findingSchema = z2.object({
|
|
2604
|
+
id: z2.string().min(1),
|
|
2605
|
+
title: z2.string().min(1),
|
|
2606
|
+
detail: z2.string().default(""),
|
|
2607
|
+
description: z2.string().optional(),
|
|
2608
|
+
severity: z2.enum(["info", "low", "medium", "high", "critical"]).optional(),
|
|
2609
|
+
confidence: z2.enum(["low", "medium", "high"]).optional(),
|
|
2610
|
+
severityBySource: z2.record(z2.enum(["info", "low", "medium", "high", "critical"])).optional(),
|
|
2611
|
+
confidenceBySource: z2.record(z2.enum(["low", "medium", "high"])).optional(),
|
|
2612
|
+
recommendations: z2.array(z2.string()).optional(),
|
|
2613
|
+
evidence: z2.array(evidenceSchema).optional(),
|
|
2614
|
+
location: z2.string().optional(),
|
|
2615
|
+
tags: z2.array(z2.string()).optional(),
|
|
2616
|
+
source: z2.object({ agent: z2.string().optional(), reportId: z2.string().optional(), pipelineRunId: z2.string().optional() }).optional(),
|
|
2617
|
+
recommendation: z2.string().optional()
|
|
2618
|
+
});
|
|
2619
|
+
var artifactSchema = z2.object({
|
|
2620
|
+
kind: z2.enum(["file", "patch", "log", "screenshot", "document", "data"]),
|
|
2621
|
+
ref: z2.string().min(1),
|
|
2622
|
+
description: z2.string().optional(),
|
|
2623
|
+
mediaType: z2.string().optional(),
|
|
2624
|
+
bytes: z2.number().int().optional()
|
|
2625
|
+
});
|
|
2626
|
+
var reportSchema = z2.object({
|
|
2627
|
+
id: z2.string().min(1),
|
|
2628
|
+
taskId: z2.string().min(1),
|
|
2629
|
+
agent: z2.string().min(1),
|
|
2630
|
+
agentId: z2.string().optional(),
|
|
2631
|
+
pipelineRunId: z2.string().optional(),
|
|
2632
|
+
model: z2.string().optional(),
|
|
2633
|
+
summary: z2.string(),
|
|
2634
|
+
findings: z2.array(findingSchema),
|
|
2635
|
+
recommendations: z2.array(z2.string()),
|
|
2636
|
+
confidence: z2.enum(["low", "medium", "high"]).optional(),
|
|
2637
|
+
evidence: z2.array(evidenceSchema).optional(),
|
|
2638
|
+
artifacts: z2.array(artifactSchema).optional(),
|
|
2639
|
+
createdAt: z2.string().min(1),
|
|
2640
|
+
metadata: z2.record(z2.unknown()).optional()
|
|
2641
|
+
});
|
|
2642
|
+
function validateReport(value) {
|
|
2643
|
+
const parsed = reportSchema.safeParse(value);
|
|
2644
|
+
if (!parsed.success) {
|
|
2645
|
+
const issues = parsed.error.issues.map((issue) => ({
|
|
2646
|
+
path: issue.path.join(".") || "(root)",
|
|
2647
|
+
message: issue.message
|
|
2648
|
+
}));
|
|
2649
|
+
return {
|
|
2650
|
+
valid: false,
|
|
2651
|
+
issues,
|
|
2652
|
+
reason: `report failed schema validation (${issues.length} issue(s))`
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
const report = parsed.data;
|
|
2656
|
+
report.findings = report.findings.map((finding) => ({
|
|
2657
|
+
...finding,
|
|
2658
|
+
description: finding.description ?? finding.detail,
|
|
2659
|
+
...finding.source === void 0 ? { source: { agent: report.agent, reportId: report.id, ...report.pipelineRunId !== void 0 ? { pipelineRunId: report.pipelineRunId } : {} } } : {}
|
|
2660
|
+
}));
|
|
2661
|
+
return { valid: true, report };
|
|
2662
|
+
}
|
|
2663
|
+
function normalizeEvidence(input) {
|
|
2664
|
+
if (input === void 0) return void 0;
|
|
2665
|
+
return input.map(
|
|
2666
|
+
(item) => typeof item === "string" ? { kind: "observation", excerpt: item } : item
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
function normalizeFinding(finding) {
|
|
2670
|
+
return {
|
|
2671
|
+
...finding,
|
|
2672
|
+
description: finding.description ?? finding.detail,
|
|
2673
|
+
evidence: normalizeEvidence(finding.evidence)
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// src/reports/aggregate.ts
|
|
2587
2678
|
var SEVERITY_RANK = {
|
|
2588
2679
|
info: 0,
|
|
2589
2680
|
low: 1,
|
|
@@ -2591,23 +2682,197 @@ var SEVERITY_RANK = {
|
|
|
2591
2682
|
high: 3,
|
|
2592
2683
|
critical: 4
|
|
2593
2684
|
};
|
|
2685
|
+
function aggregateReports(reports, options = {}) {
|
|
2686
|
+
const threshold = options.similarityThreshold ?? 0.7;
|
|
2687
|
+
const findingGroups = [];
|
|
2688
|
+
const recommendationGroups = [];
|
|
2689
|
+
for (const report of reports) {
|
|
2690
|
+
for (const finding of report.findings) {
|
|
2691
|
+
mergeFinding(findingGroups, finding, report, threshold);
|
|
2692
|
+
}
|
|
2693
|
+
for (const text of report.recommendations) {
|
|
2694
|
+
mergeRecommendation(recommendationGroups, text, report, threshold);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
const sources = [...new Set(reports.map((r) => r.agent))].sort();
|
|
2698
|
+
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
2699
|
+
const conflicts = detectConflicts(findingGroups);
|
|
2700
|
+
const agreements = findingGroups.filter(
|
|
2701
|
+
(group) => group.sources.length > 1 && group.severity !== void 0 && new Set(Object.values(group.severityBySource ?? {})).size === 1
|
|
2702
|
+
);
|
|
2703
|
+
const disputed = conflicts.length;
|
|
2704
|
+
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).`;
|
|
2705
|
+
return {
|
|
2706
|
+
id: `combined-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`,
|
|
2707
|
+
title: options.objective !== void 0 ? truncate(options.objective, 120) : "Combined report",
|
|
2708
|
+
...options.objective !== void 0 ? { objective: options.objective } : {},
|
|
2709
|
+
...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
|
|
2710
|
+
taskIds: options.taskIds ?? [],
|
|
2711
|
+
summary,
|
|
2712
|
+
findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
|
|
2713
|
+
agreements,
|
|
2714
|
+
conflicts,
|
|
2715
|
+
recommendations: recommendationGroups,
|
|
2716
|
+
sources,
|
|
2717
|
+
reportIds: reports.map((r) => r.id),
|
|
2718
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2719
|
+
metadata: {
|
|
2720
|
+
agents: sources.length,
|
|
2721
|
+
reports: reports.length,
|
|
2722
|
+
rawFindings: totalRawFindings,
|
|
2723
|
+
findingGroups: findingGroups.length,
|
|
2724
|
+
agreements: agreements.length,
|
|
2725
|
+
conflicts: conflicts.length
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
function rankConfidence(confidence) {
|
|
2730
|
+
return confidence ? { low: 1, medium: 2, high: 3 }[confidence] : 0;
|
|
2731
|
+
}
|
|
2732
|
+
function detectConflicts(groups) {
|
|
2733
|
+
const conflicts = [];
|
|
2734
|
+
for (const group of groups) {
|
|
2735
|
+
if (group.sources.length < 2) continue;
|
|
2736
|
+
const severities = new Set(Object.values(group.severityBySource ?? {}));
|
|
2737
|
+
if (severities.size > 1) {
|
|
2738
|
+
conflicts.push({
|
|
2739
|
+
type: "severity",
|
|
2740
|
+
findingTitle: group.title,
|
|
2741
|
+
positions: group.sources.map((source) => position(source, group)),
|
|
2742
|
+
note: `Agents disagree on severity (${[...severities].sort((a, b) => SEVERITY_RANK[a] - SEVERITY_RANK[b]).join(", ")}); the highest severity is shown, every position is preserved.`
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
const recommendations = (group.recommendations ?? []).filter((r) => r.length > 0);
|
|
2746
|
+
if (recommendations.length > 1) {
|
|
2747
|
+
conflicts.push({
|
|
2748
|
+
type: "recommendation",
|
|
2749
|
+
findingTitle: group.title,
|
|
2750
|
+
positions: group.sources.map((source) => position(source, group)),
|
|
2751
|
+
note: `Agents proposed ${recommendations.length} different fixes; none was discarded.`
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
return conflicts;
|
|
2756
|
+
}
|
|
2757
|
+
function position(source, group) {
|
|
2758
|
+
return {
|
|
2759
|
+
agent: source.agent,
|
|
2760
|
+
reportId: source.reportId,
|
|
2761
|
+
...group.severityBySource?.[source.agent] !== void 0 ? { severity: group.severityBySource[source.agent] } : {},
|
|
2762
|
+
...group.confidenceBySource?.[source.agent] !== void 0 ? { confidence: group.confidenceBySource[source.agent] } : {},
|
|
2763
|
+
...group.recommendations?.[0] !== void 0 ? { recommendation: group.recommendations[0] } : {}
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
function rank(finding) {
|
|
2767
|
+
return finding.severity ? SEVERITY_RANK[finding.severity] : -1;
|
|
2768
|
+
}
|
|
2769
|
+
function mergeFinding(groups, finding, report, threshold) {
|
|
2770
|
+
const source = { agent: report.agent, reportId: report.id };
|
|
2771
|
+
const group = groups.find((g) => similar(g.title, finding.title, threshold));
|
|
2772
|
+
if (group) {
|
|
2773
|
+
group.occurrences += 1;
|
|
2774
|
+
group.details.push(finding.detail);
|
|
2775
|
+
group.sources.push(source);
|
|
2776
|
+
group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
|
|
2777
|
+
if (finding.severity && (!group.severity || SEVERITY_RANK[finding.severity] > SEVERITY_RANK[group.severity])) {
|
|
2778
|
+
group.severity = finding.severity;
|
|
2779
|
+
}
|
|
2780
|
+
if (finding.confidence && rankConfidence(finding.confidence) > rankConfidence(group.confidence)) {
|
|
2781
|
+
group.confidence = finding.confidence;
|
|
2782
|
+
}
|
|
2783
|
+
if (finding.severity) {
|
|
2784
|
+
group.severityBySource = { ...group.severityBySource, [report.agent]: finding.severity };
|
|
2785
|
+
}
|
|
2786
|
+
if (finding.confidence) {
|
|
2787
|
+
group.confidenceBySource = { ...group.confidenceBySource, [report.agent]: finding.confidence };
|
|
2788
|
+
}
|
|
2789
|
+
if (finding.recommendation && !group.recommendations?.includes(finding.recommendation)) {
|
|
2790
|
+
group.recommendations = [...group.recommendations ?? [], finding.recommendation];
|
|
2791
|
+
}
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
groups.push({
|
|
2795
|
+
title: finding.title,
|
|
2796
|
+
details: [finding.detail],
|
|
2797
|
+
sources: [source],
|
|
2798
|
+
occurrences: 1,
|
|
2799
|
+
...finding.severity ? { severity: finding.severity } : {},
|
|
2800
|
+
...finding.confidence ? { confidence: finding.confidence } : {},
|
|
2801
|
+
...finding.severity ? { severityBySource: { [report.agent]: finding.severity } } : {},
|
|
2802
|
+
...finding.confidence ? { confidenceBySource: { [report.agent]: finding.confidence } } : {},
|
|
2803
|
+
...finding.recommendation ? { recommendations: [finding.recommendation] } : {},
|
|
2804
|
+
evidence: [...normalizeEvidence(finding.evidence) ?? []]
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
function mergeRecommendation(groups, text, report, threshold) {
|
|
2808
|
+
const source = { agent: report.agent, reportId: report.id };
|
|
2809
|
+
const group = groups.find((g) => similar(g.text, text, threshold));
|
|
2810
|
+
if (group) {
|
|
2811
|
+
group.occurrences += 1;
|
|
2812
|
+
group.sources.push(source);
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
groups.push({ text, sources: [source], occurrences: 1 });
|
|
2816
|
+
}
|
|
2817
|
+
function similar(a, b, threshold) {
|
|
2818
|
+
const left = tokens(a);
|
|
2819
|
+
const right = tokens(b);
|
|
2820
|
+
if (left.size === 0 || right.size === 0) return false;
|
|
2821
|
+
let intersection = 0;
|
|
2822
|
+
for (const token of left) {
|
|
2823
|
+
if (right.has(token)) intersection += 1;
|
|
2824
|
+
}
|
|
2825
|
+
const union = left.size + right.size - intersection;
|
|
2826
|
+
return intersection / union >= threshold;
|
|
2827
|
+
}
|
|
2828
|
+
function tokens(text) {
|
|
2829
|
+
return new Set(
|
|
2830
|
+
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
function truncate(text, max) {
|
|
2834
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
// src/planner/heuristic.ts
|
|
2838
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2839
|
+
var SEVERITY_RANK2 = {
|
|
2840
|
+
info: 0,
|
|
2841
|
+
low: 1,
|
|
2842
|
+
medium: 2,
|
|
2843
|
+
high: 3,
|
|
2844
|
+
critical: 4
|
|
2845
|
+
};
|
|
2594
2846
|
var HeuristicPlanner = class {
|
|
2595
2847
|
name = "heuristic";
|
|
2596
2848
|
async plan(request) {
|
|
2597
2849
|
const steps = [];
|
|
2598
|
-
const findings = request.reports.flatMap(
|
|
2599
|
-
(report) => report.findings.map((finding) => ({ finding, agent: report.agent }))
|
|
2600
|
-
);
|
|
2601
2850
|
const recommendations = dedupe(request.reports.flatMap((r) => r.recommendations));
|
|
2602
|
-
for (const
|
|
2851
|
+
for (const text of recommendations) {
|
|
2603
2852
|
steps.push({
|
|
2604
2853
|
id: `step-${steps.length + 1}`,
|
|
2605
|
-
title:
|
|
2854
|
+
title: truncate2(text, 120),
|
|
2606
2855
|
order: steps.length + 1,
|
|
2607
|
-
...
|
|
2856
|
+
...steps.length > 0 ? { dependsOn: [`step-${steps.length}`] } : {}
|
|
2608
2857
|
});
|
|
2609
2858
|
}
|
|
2610
|
-
const
|
|
2859
|
+
const contributed = request.aggregated ? request.aggregated.findings.map((finding) => ({
|
|
2860
|
+
finding: {
|
|
2861
|
+
id: `agg-${slug(finding.title)}`,
|
|
2862
|
+
title: finding.title,
|
|
2863
|
+
detail: finding.details[0] ?? finding.title,
|
|
2864
|
+
...finding.severity ? { severity: finding.severity } : {},
|
|
2865
|
+
...finding.recommendations && finding.recommendations.length > 0 ? { recommendation: finding.recommendations[0] } : {}
|
|
2866
|
+
},
|
|
2867
|
+
agent: finding.sources.map((s) => s.agent).join("+")
|
|
2868
|
+
})) : request.reports.flatMap(
|
|
2869
|
+
(report) => report.findings.map((finding) => ({ finding, agent: report.agent }))
|
|
2870
|
+
);
|
|
2871
|
+
const significant = contributed.filter(
|
|
2872
|
+
({ finding }) => finding.severity ? SEVERITY_RANK2[finding.severity] >= SEVERITY_RANK2.high : false
|
|
2873
|
+
).sort(
|
|
2874
|
+
(a, b) => SEVERITY_RANK2[b.finding.severity ?? "info"] - SEVERITY_RANK2[a.finding.severity ?? "info"]
|
|
2875
|
+
);
|
|
2611
2876
|
for (const { finding } of significant) {
|
|
2612
2877
|
steps.push(remediationStep(finding, steps.length + 1));
|
|
2613
2878
|
}
|
|
@@ -2620,12 +2885,20 @@ var HeuristicPlanner = class {
|
|
|
2620
2885
|
});
|
|
2621
2886
|
}
|
|
2622
2887
|
const risks = significant.filter(({ finding }) => finding.severity === "critical").map(({ finding }) => `Critical finding unaddressed until its step completes: ${finding.title}`);
|
|
2888
|
+
const conflicts = request.aggregated?.conflicts ?? [];
|
|
2889
|
+
if (conflicts.length > 0) {
|
|
2890
|
+
risks.push(
|
|
2891
|
+
`${conflicts.length} unresolved agent disagreement(s): ${conflicts.map((c) => c.findingTitle).join("; ")}`
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2623
2894
|
return {
|
|
2624
|
-
id: `plan-${Date.now().toString(36)}-${
|
|
2895
|
+
id: `plan-${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`,
|
|
2625
2896
|
objective: request.objective,
|
|
2897
|
+
goal: request.aggregated?.title ?? truncate2(request.objective, 120),
|
|
2626
2898
|
summary: `Derived from ${request.reports.length} report(s): ${recommendations.length} recommendation step(s), ${significant.length} remediation step(s).`,
|
|
2627
2899
|
steps,
|
|
2628
2900
|
...risks.length > 0 ? { risks } : {},
|
|
2901
|
+
...request.constraints !== void 0 && request.constraints.length > 0 ? { constraints: request.constraints } : {},
|
|
2629
2902
|
generatedBy: "heuristic",
|
|
2630
2903
|
...request.taskId !== void 0 ? { taskId: request.taskId } : {},
|
|
2631
2904
|
...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
|
|
@@ -2636,28 +2909,80 @@ var HeuristicPlanner = class {
|
|
|
2636
2909
|
function remediationStep(finding, order) {
|
|
2637
2910
|
return {
|
|
2638
2911
|
id: `step-${order}`,
|
|
2639
|
-
title: `Fix: ${
|
|
2912
|
+
title: `Fix: ${truncate2(finding.title, 100)}`,
|
|
2640
2913
|
description: finding.detail,
|
|
2641
2914
|
order,
|
|
2642
2915
|
dependsOn: order > 1 ? [`step-${order - 1}`] : [],
|
|
2643
|
-
sourceFindings: [finding.id]
|
|
2916
|
+
sourceFindings: [finding.id],
|
|
2917
|
+
...finding.recommendation ? { recommendation: finding.recommendation } : {}
|
|
2644
2918
|
};
|
|
2645
2919
|
}
|
|
2646
2920
|
function dedupe(texts) {
|
|
2647
2921
|
return [...new Set(texts.map((t) => t.trim()).filter((t) => t.length > 0))];
|
|
2648
2922
|
}
|
|
2649
|
-
function
|
|
2923
|
+
function slug(text) {
|
|
2924
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
2925
|
+
}
|
|
2926
|
+
function truncate2(text, max) {
|
|
2650
2927
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
2651
2928
|
}
|
|
2652
2929
|
|
|
2653
2930
|
// src/planner/model.ts
|
|
2654
|
-
import { randomBytes as
|
|
2655
|
-
|
|
2931
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
2932
|
+
|
|
2933
|
+
// src/planner/schema.ts
|
|
2934
|
+
import { z as z3 } from "zod";
|
|
2935
|
+
var planStepSchema = z3.object({
|
|
2936
|
+
id: z3.string().min(1).optional(),
|
|
2937
|
+
title: z3.string().min(1),
|
|
2938
|
+
description: z3.string().optional(),
|
|
2939
|
+
order: z3.number().int().optional(),
|
|
2940
|
+
targets: z3.array(z3.string()).optional(),
|
|
2941
|
+
acceptance_criteria: z3.array(z3.string()).optional(),
|
|
2942
|
+
depends_on: z3.array(z3.string()).optional(),
|
|
2943
|
+
source_findings: z3.array(z3.string()).optional()
|
|
2944
|
+
});
|
|
2945
|
+
var planJsonSchema = z3.object({
|
|
2946
|
+
goal: z3.string().min(1),
|
|
2947
|
+
summary: z3.string().min(1),
|
|
2948
|
+
steps: z3.array(planStepSchema).min(1),
|
|
2949
|
+
risks: z3.array(z3.string()).optional(),
|
|
2950
|
+
notes: z3.array(z3.string()).optional()
|
|
2951
|
+
});
|
|
2952
|
+
function validatePlan(plan) {
|
|
2953
|
+
const issues = [];
|
|
2954
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
2955
|
+
for (const [index, step] of plan.steps.entries()) {
|
|
2956
|
+
if (stepIds.has(step.id)) {
|
|
2957
|
+
issues.push({ path: `steps.${index}.id`, message: `duplicate step id "${step.id}"` });
|
|
2958
|
+
}
|
|
2959
|
+
stepIds.add(step.id);
|
|
2960
|
+
if (!step.title || step.title.trim().length === 0) {
|
|
2961
|
+
issues.push({ path: `steps.${index}.title`, message: "title must not be empty" });
|
|
2962
|
+
}
|
|
2963
|
+
for (const dep of step.dependsOn ?? []) {
|
|
2964
|
+
if (!stepIds.has(dep) && !plan.steps.some((s) => s.id === dep)) {
|
|
2965
|
+
issues.push({ path: `steps.${index}.dependsOn`, message: `unknown step dependency "${dep}"` });
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
if (plan.steps.length === 0) {
|
|
2970
|
+
issues.push({ path: "steps", message: "a plan needs at least one step" });
|
|
2971
|
+
}
|
|
2972
|
+
if (!plan.summary || plan.summary.trim().length === 0) {
|
|
2973
|
+
issues.push({ path: "summary", message: "summary must not be empty" });
|
|
2974
|
+
}
|
|
2975
|
+
return issues.length > 0 ? { valid: false, issues, reason: `plan failed validation (${issues.length} issue(s))` } : { valid: true, plan };
|
|
2976
|
+
}
|
|
2656
2977
|
|
|
2657
2978
|
// src/planner/context.ts
|
|
2658
2979
|
function buildPlannerContext(request) {
|
|
2659
|
-
const parts = [`#
|
|
2980
|
+
const parts = [`# Task
|
|
2660
2981
|
${request.objective}`];
|
|
2982
|
+
if (request.projectContext) {
|
|
2983
|
+
parts.push(`# Project context
|
|
2984
|
+
${request.projectContext}`);
|
|
2985
|
+
}
|
|
2661
2986
|
if (request.role) {
|
|
2662
2987
|
const roleLines = [`# Role
|
|
2663
2988
|
${request.role.name} (${request.role.id})`];
|
|
@@ -2677,7 +3002,7 @@ ${request.role.name} (${request.role.id})`];
|
|
|
2677
3002
|
)
|
|
2678
3003
|
);
|
|
2679
3004
|
parts.push(
|
|
2680
|
-
`#
|
|
3005
|
+
`# Agent reports (${request.reports.length} by ${agents})
|
|
2681
3006
|
` + (findingLines.length > 0 ? findingLines.join("\n") : "(no findings reported)")
|
|
2682
3007
|
);
|
|
2683
3008
|
const recommendationLines = request.reports.flatMap(
|
|
@@ -2687,9 +3012,20 @@ ${request.role.name} (${request.role.id})`];
|
|
|
2687
3012
|
parts.push(`# Recommendations
|
|
2688
3013
|
${recommendationLines.join("\n")}`);
|
|
2689
3014
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
3015
|
+
}
|
|
3016
|
+
if (request.aggregated) {
|
|
3017
|
+
const aggregatedLines = [
|
|
3018
|
+
`- findings: ${request.aggregated.findings.length} (agreements: ${request.aggregated.agreements.length}, conflicts: ${request.aggregated.conflicts.length})`,
|
|
3019
|
+
...request.aggregated.findings.map(
|
|
3020
|
+
(finding) => `- [${finding.severity ?? "info"}] ${finding.title} (x${finding.occurrences})`
|
|
3021
|
+
),
|
|
3022
|
+
// Conflicts stay visible to the planner — it must plan around disagreement.
|
|
3023
|
+
...request.aggregated.conflicts.map(
|
|
3024
|
+
(conflict) => `- CONFLICT (${conflict.type}) on "${conflict.findingTitle}": ${conflict.note}`
|
|
3025
|
+
)
|
|
3026
|
+
];
|
|
3027
|
+
parts.push(`# Aggregated findings
|
|
3028
|
+
${aggregatedLines.join("\n")}`);
|
|
2693
3029
|
}
|
|
2694
3030
|
if (request.memory && request.memory.length > 0) {
|
|
2695
3031
|
parts.push(
|
|
@@ -2697,7 +3033,11 @@ ${summaryLines.join("\n")}`);
|
|
|
2697
3033
|
` + request.memory.map((entry) => `- [${entry.scope}] ${entry.content}`).join("\n")
|
|
2698
3034
|
);
|
|
2699
3035
|
}
|
|
2700
|
-
if (request.
|
|
3036
|
+
if (request.constraints && request.constraints.length > 0) {
|
|
3037
|
+
parts.push(`# Constraints
|
|
3038
|
+
${request.constraints.map((c) => `- ${c}`).join("\n")}`);
|
|
3039
|
+
}
|
|
3040
|
+
if (request.context) {
|
|
2701
3041
|
parts.push(`# Research context
|
|
2702
3042
|
${request.context}`);
|
|
2703
3043
|
}
|
|
@@ -2705,22 +3045,6 @@ ${request.context}`);
|
|
|
2705
3045
|
}
|
|
2706
3046
|
|
|
2707
3047
|
// src/planner/model.ts
|
|
2708
|
-
var planStepJsonSchema = z2.object({
|
|
2709
|
-
id: z2.string().optional(),
|
|
2710
|
-
title: z2.string().min(1),
|
|
2711
|
-
description: z2.string().optional(),
|
|
2712
|
-
order: z2.number().int().optional(),
|
|
2713
|
-
targets: z2.array(z2.string()).optional(),
|
|
2714
|
-
acceptance_criteria: z2.array(z2.string()).optional(),
|
|
2715
|
-
depends_on: z2.array(z2.string()).optional(),
|
|
2716
|
-
source_findings: z2.array(z2.string()).optional()
|
|
2717
|
-
}).strict();
|
|
2718
|
-
var planJsonSchema = z2.object({
|
|
2719
|
-
summary: z2.string().min(1),
|
|
2720
|
-
steps: z2.array(planStepJsonSchema).min(1),
|
|
2721
|
-
risks: z2.array(z2.string()).optional(),
|
|
2722
|
-
notes: z2.array(z2.string()).optional()
|
|
2723
|
-
}).strict();
|
|
2724
3048
|
var ModelPlanner = class {
|
|
2725
3049
|
constructor(options) {
|
|
2726
3050
|
this.options = options;
|
|
@@ -2734,7 +3058,7 @@ var ModelPlanner = class {
|
|
|
2734
3058
|
const messages = [
|
|
2735
3059
|
{
|
|
2736
3060
|
role: "system",
|
|
2737
|
-
content: 'You are the OmniNode planner. Given the objective and the combined research context, produce a final analysis and a prioritized, actionable implementation plan. Respond with JSON ONLY (no prose, no code fences) matching exactly this shape: {"summary": string, "steps": [{"id": string, "title": string, "description": string, "order": number, "targets": string[], "acceptance_criteria": string[], "source_findings": string[]}], "risks": string[], "notes": string[]}. ' + (this.options.instruction ?? "")
|
|
3061
|
+
content: 'You are the OmniNode planner. Given the objective and the combined research context, produce a final analysis and a prioritized, actionable implementation plan. Respond with JSON ONLY (no prose, no code fences) matching exactly this shape: {"goal": string, "summary": string, "steps": [{"id": string, "title": string, "description": string, "order": number, "targets": string[], "acceptance_criteria": string[], "source_findings": string[]}], "risks": string[], "notes": string[]}. ' + (this.options.instruction ?? "")
|
|
2738
3062
|
},
|
|
2739
3063
|
{ role: "user", content: context }
|
|
2740
3064
|
];
|
|
@@ -2745,60 +3069,86 @@ var ModelPlanner = class {
|
|
|
2745
3069
|
return this.degrade(request, `planner model call failed: ${describe(error)}`);
|
|
2746
3070
|
}
|
|
2747
3071
|
const parsed = parsePlanJson(responseText);
|
|
2748
|
-
if (!parsed) {
|
|
2749
|
-
return this.degrade(request,
|
|
3072
|
+
if (!parsed.ok) {
|
|
3073
|
+
return this.degrade(request, `planner model output rejected: ${parsed.reason}`, parsed.issues);
|
|
2750
3074
|
}
|
|
2751
3075
|
const plan = {
|
|
2752
|
-
id: `plan-${Date.now().toString(36)}-${
|
|
3076
|
+
id: `plan-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
|
|
2753
3077
|
objective: request.objective,
|
|
3078
|
+
goal: parsed.goal,
|
|
2754
3079
|
summary: parsed.summary,
|
|
2755
3080
|
steps: parsed.steps,
|
|
2756
3081
|
...parsed.risks !== void 0 ? { risks: parsed.risks } : {},
|
|
2757
3082
|
...parsed.notes !== void 0 ? { notes: parsed.notes } : {},
|
|
3083
|
+
...request.constraints !== void 0 && request.constraints.length > 0 ? { constraints: request.constraints } : {},
|
|
2758
3084
|
generatedBy: `model:${this.options.model}`,
|
|
2759
3085
|
model: this.options.model,
|
|
2760
3086
|
...request.taskId !== void 0 ? { taskId: request.taskId } : {},
|
|
2761
3087
|
...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
|
|
2762
3088
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2763
3089
|
};
|
|
2764
|
-
|
|
3090
|
+
const validation = validatePlan(plan);
|
|
3091
|
+
if (!validation.valid) {
|
|
3092
|
+
return this.degrade(request, `generated plan rejected: ${validation.reason}`, validation.issues);
|
|
3093
|
+
}
|
|
3094
|
+
return validation.plan;
|
|
2765
3095
|
}
|
|
2766
|
-
async degrade(request, reason) {
|
|
3096
|
+
async degrade(request, reason, issues = []) {
|
|
2767
3097
|
if (this.options.fallback) {
|
|
2768
3098
|
this.log.warn(`${reason} \u2014 using the heuristic fallback planner.`);
|
|
2769
3099
|
return this.options.fallback.plan(request);
|
|
2770
3100
|
}
|
|
2771
|
-
throw new PlannerError("
|
|
3101
|
+
throw new PlannerError("PLAN_INVALID", `Planner failed: ${reason}.`, {
|
|
3102
|
+
details: { reason, issues }
|
|
3103
|
+
});
|
|
2772
3104
|
}
|
|
2773
3105
|
};
|
|
2774
3106
|
function parsePlanJson(text) {
|
|
2775
3107
|
const unfenced = text.replace(/```(?:json)?/gi, "");
|
|
2776
3108
|
const start = unfenced.indexOf("{");
|
|
2777
3109
|
const end = unfenced.lastIndexOf("}");
|
|
2778
|
-
if (start < 0 || end <= start)
|
|
3110
|
+
if (start < 0 || end <= start) {
|
|
3111
|
+
return { ok: false, reason: "planner output contained no JSON object", issues: [] };
|
|
3112
|
+
}
|
|
3113
|
+
let json;
|
|
2779
3114
|
try {
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
if (!parsed.success) return void 0;
|
|
2783
|
-
const steps = parsed.data.steps.map((step, index) => ({
|
|
2784
|
-
id: step.id ?? `step-${index + 1}`,
|
|
2785
|
-
title: step.title,
|
|
2786
|
-
description: step.description,
|
|
2787
|
-
order: step.order ?? index + 1,
|
|
2788
|
-
targets: step.targets,
|
|
2789
|
-
acceptanceCriteria: step.acceptance_criteria,
|
|
2790
|
-
dependsOn: step.depends_on,
|
|
2791
|
-
sourceFindings: step.source_findings
|
|
2792
|
-
}));
|
|
3115
|
+
json = JSON.parse(unfenced.slice(start, end + 1));
|
|
3116
|
+
} catch (error) {
|
|
2793
3117
|
return {
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3118
|
+
ok: false,
|
|
3119
|
+
reason: `planner output was not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
3120
|
+
issues: []
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
const parsed = planJsonSchema.safeParse(json);
|
|
3124
|
+
if (!parsed.success) {
|
|
3125
|
+
return {
|
|
3126
|
+
ok: false,
|
|
3127
|
+
reason: "planner output did not match the plan schema",
|
|
3128
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
3129
|
+
path: issue.path.join(".") || "(root)",
|
|
3130
|
+
message: issue.message
|
|
3131
|
+
}))
|
|
2798
3132
|
};
|
|
2799
|
-
} catch {
|
|
2800
|
-
return void 0;
|
|
2801
3133
|
}
|
|
3134
|
+
const steps = parsed.data.steps.map((step, index) => ({
|
|
3135
|
+
id: step.id ?? `step-${index + 1}`,
|
|
3136
|
+
title: step.title,
|
|
3137
|
+
description: step.description,
|
|
3138
|
+
order: step.order ?? index + 1,
|
|
3139
|
+
targets: step.targets,
|
|
3140
|
+
acceptanceCriteria: step.acceptance_criteria,
|
|
3141
|
+
dependsOn: step.depends_on,
|
|
3142
|
+
sourceFindings: step.source_findings
|
|
3143
|
+
})).sort((a, b) => a.order - b.order);
|
|
3144
|
+
return {
|
|
3145
|
+
ok: true,
|
|
3146
|
+
goal: parsed.data.goal,
|
|
3147
|
+
summary: parsed.data.summary,
|
|
3148
|
+
steps,
|
|
3149
|
+
risks: parsed.data.risks,
|
|
3150
|
+
notes: parsed.data.notes
|
|
3151
|
+
};
|
|
2802
3152
|
}
|
|
2803
3153
|
function describe(error) {
|
|
2804
3154
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -2947,7 +3297,7 @@ var PipelineEngine = class {
|
|
|
2947
3297
|
}
|
|
2948
3298
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2949
3299
|
const run = {
|
|
2950
|
-
id: `run-${Date.now().toString(36)}-${
|
|
3300
|
+
id: `run-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
|
|
2951
3301
|
pipelineId: def.id,
|
|
2952
3302
|
attempt: 1,
|
|
2953
3303
|
objective,
|
|
@@ -3236,11 +3586,14 @@ ${contribution}`);
|
|
|
3236
3586
|
};
|
|
3237
3587
|
}
|
|
3238
3588
|
async runPlan(step, ctx) {
|
|
3589
|
+
const aggregated = ctx.reports.length > 0 ? aggregateReports(ctx.reports) : void 0;
|
|
3239
3590
|
const request = {
|
|
3240
3591
|
objective: ctx.objective,
|
|
3241
3592
|
reports: ctx.reports,
|
|
3593
|
+
...aggregated !== void 0 ? { aggregated } : {},
|
|
3242
3594
|
...ctx.combinedContext.length > 0 ? { context: ctx.combinedContext } : {},
|
|
3243
3595
|
...ctx.role ? { role: ctx.role } : {},
|
|
3596
|
+
...step.constraints !== void 0 && step.constraints.length > 0 ? { constraints: step.constraints } : {},
|
|
3244
3597
|
...ctx.pipelineRunId !== void 0 ? { pipelineRunId: ctx.pipelineRunId } : {}
|
|
3245
3598
|
};
|
|
3246
3599
|
let planner;
|
|
@@ -3399,255 +3752,6 @@ function buildPlanner(plannerConfig, chat, log = logger) {
|
|
|
3399
3752
|
});
|
|
3400
3753
|
}
|
|
3401
3754
|
|
|
3402
|
-
// src/reports/aggregate.ts
|
|
3403
|
-
import { randomBytes as randomBytes5 } from "crypto";
|
|
3404
|
-
|
|
3405
|
-
// src/reports/schema.ts
|
|
3406
|
-
import { z as z3 } from "zod";
|
|
3407
|
-
var evidenceSchema = z3.union([
|
|
3408
|
-
z3.string().transform((excerpt) => ({ kind: "observation", excerpt })),
|
|
3409
|
-
z3.object({
|
|
3410
|
-
kind: z3.enum(["file", "line", "url", "command", "observation", "artifact"]),
|
|
3411
|
-
ref: z3.string().optional(),
|
|
3412
|
-
line: z3.number().int().optional(),
|
|
3413
|
-
excerpt: z3.string().optional(),
|
|
3414
|
-
agent: z3.string().optional()
|
|
3415
|
-
})
|
|
3416
|
-
]);
|
|
3417
|
-
var findingSchema = z3.object({
|
|
3418
|
-
id: z3.string().min(1),
|
|
3419
|
-
title: z3.string().min(1),
|
|
3420
|
-
detail: z3.string().default(""),
|
|
3421
|
-
description: z3.string().optional(),
|
|
3422
|
-
severity: z3.enum(["info", "low", "medium", "high", "critical"]).optional(),
|
|
3423
|
-
confidence: z3.enum(["low", "medium", "high"]).optional(),
|
|
3424
|
-
severityBySource: z3.record(z3.enum(["info", "low", "medium", "high", "critical"])).optional(),
|
|
3425
|
-
confidenceBySource: z3.record(z3.enum(["low", "medium", "high"])).optional(),
|
|
3426
|
-
recommendations: z3.array(z3.string()).optional(),
|
|
3427
|
-
evidence: z3.array(evidenceSchema).optional(),
|
|
3428
|
-
location: z3.string().optional(),
|
|
3429
|
-
tags: z3.array(z3.string()).optional(),
|
|
3430
|
-
source: z3.object({ agent: z3.string().optional(), reportId: z3.string().optional(), pipelineRunId: z3.string().optional() }).optional(),
|
|
3431
|
-
recommendation: z3.string().optional()
|
|
3432
|
-
});
|
|
3433
|
-
var artifactSchema = z3.object({
|
|
3434
|
-
kind: z3.enum(["file", "patch", "log", "screenshot", "document", "data"]),
|
|
3435
|
-
ref: z3.string().min(1),
|
|
3436
|
-
description: z3.string().optional(),
|
|
3437
|
-
mediaType: z3.string().optional(),
|
|
3438
|
-
bytes: z3.number().int().optional()
|
|
3439
|
-
});
|
|
3440
|
-
var reportSchema = z3.object({
|
|
3441
|
-
id: z3.string().min(1),
|
|
3442
|
-
taskId: z3.string().min(1),
|
|
3443
|
-
agent: z3.string().min(1),
|
|
3444
|
-
agentId: z3.string().optional(),
|
|
3445
|
-
pipelineRunId: z3.string().optional(),
|
|
3446
|
-
model: z3.string().optional(),
|
|
3447
|
-
summary: z3.string(),
|
|
3448
|
-
findings: z3.array(findingSchema),
|
|
3449
|
-
recommendations: z3.array(z3.string()),
|
|
3450
|
-
confidence: z3.enum(["low", "medium", "high"]).optional(),
|
|
3451
|
-
evidence: z3.array(evidenceSchema).optional(),
|
|
3452
|
-
artifacts: z3.array(artifactSchema).optional(),
|
|
3453
|
-
createdAt: z3.string().min(1),
|
|
3454
|
-
metadata: z3.record(z3.unknown()).optional()
|
|
3455
|
-
});
|
|
3456
|
-
function validateReport(value) {
|
|
3457
|
-
const parsed = reportSchema.safeParse(value);
|
|
3458
|
-
if (!parsed.success) {
|
|
3459
|
-
const issues = parsed.error.issues.map((issue) => ({
|
|
3460
|
-
path: issue.path.join(".") || "(root)",
|
|
3461
|
-
message: issue.message
|
|
3462
|
-
}));
|
|
3463
|
-
return {
|
|
3464
|
-
valid: false,
|
|
3465
|
-
issues,
|
|
3466
|
-
reason: `report failed schema validation (${issues.length} issue(s))`
|
|
3467
|
-
};
|
|
3468
|
-
}
|
|
3469
|
-
const report = parsed.data;
|
|
3470
|
-
report.findings = report.findings.map((finding) => ({
|
|
3471
|
-
...finding,
|
|
3472
|
-
description: finding.description ?? finding.detail,
|
|
3473
|
-
...finding.source === void 0 ? { source: { agent: report.agent, reportId: report.id, ...report.pipelineRunId !== void 0 ? { pipelineRunId: report.pipelineRunId } : {} } } : {}
|
|
3474
|
-
}));
|
|
3475
|
-
return { valid: true, report };
|
|
3476
|
-
}
|
|
3477
|
-
function normalizeEvidence(input) {
|
|
3478
|
-
if (input === void 0) return void 0;
|
|
3479
|
-
return input.map(
|
|
3480
|
-
(item) => typeof item === "string" ? { kind: "observation", excerpt: item } : item
|
|
3481
|
-
);
|
|
3482
|
-
}
|
|
3483
|
-
function normalizeFinding(finding) {
|
|
3484
|
-
return {
|
|
3485
|
-
...finding,
|
|
3486
|
-
description: finding.description ?? finding.detail,
|
|
3487
|
-
evidence: normalizeEvidence(finding.evidence)
|
|
3488
|
-
};
|
|
3489
|
-
}
|
|
3490
|
-
|
|
3491
|
-
// src/reports/aggregate.ts
|
|
3492
|
-
var SEVERITY_RANK2 = {
|
|
3493
|
-
info: 0,
|
|
3494
|
-
low: 1,
|
|
3495
|
-
medium: 2,
|
|
3496
|
-
high: 3,
|
|
3497
|
-
critical: 4
|
|
3498
|
-
};
|
|
3499
|
-
function aggregateReports(reports, options = {}) {
|
|
3500
|
-
const threshold = options.similarityThreshold ?? 0.7;
|
|
3501
|
-
const findingGroups = [];
|
|
3502
|
-
const recommendationGroups = [];
|
|
3503
|
-
for (const report of reports) {
|
|
3504
|
-
for (const finding of report.findings) {
|
|
3505
|
-
mergeFinding(findingGroups, finding, report, threshold);
|
|
3506
|
-
}
|
|
3507
|
-
for (const text of report.recommendations) {
|
|
3508
|
-
mergeRecommendation(recommendationGroups, text, report, threshold);
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
const sources = [...new Set(reports.map((r) => r.agent))].sort();
|
|
3512
|
-
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
3513
|
-
const conflicts = detectConflicts(findingGroups);
|
|
3514
|
-
const agreements = findingGroups.filter(
|
|
3515
|
-
(group) => group.sources.length > 1 && group.severity !== void 0 && new Set(Object.values(group.severityBySource ?? {})).size === 1
|
|
3516
|
-
);
|
|
3517
|
-
const disputed = conflicts.length;
|
|
3518
|
-
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).`;
|
|
3519
|
-
return {
|
|
3520
|
-
id: `combined-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
|
|
3521
|
-
title: options.objective !== void 0 ? truncate2(options.objective, 120) : "Combined report",
|
|
3522
|
-
...options.objective !== void 0 ? { objective: options.objective } : {},
|
|
3523
|
-
...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
|
|
3524
|
-
taskIds: options.taskIds ?? [],
|
|
3525
|
-
summary,
|
|
3526
|
-
findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
|
|
3527
|
-
agreements,
|
|
3528
|
-
conflicts,
|
|
3529
|
-
recommendations: recommendationGroups,
|
|
3530
|
-
sources,
|
|
3531
|
-
reportIds: reports.map((r) => r.id),
|
|
3532
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3533
|
-
metadata: {
|
|
3534
|
-
agents: sources.length,
|
|
3535
|
-
reports: reports.length,
|
|
3536
|
-
rawFindings: totalRawFindings,
|
|
3537
|
-
findingGroups: findingGroups.length,
|
|
3538
|
-
agreements: agreements.length,
|
|
3539
|
-
conflicts: conflicts.length
|
|
3540
|
-
}
|
|
3541
|
-
};
|
|
3542
|
-
}
|
|
3543
|
-
function rankConfidence(confidence) {
|
|
3544
|
-
return confidence ? { low: 1, medium: 2, high: 3 }[confidence] : 0;
|
|
3545
|
-
}
|
|
3546
|
-
function detectConflicts(groups) {
|
|
3547
|
-
const conflicts = [];
|
|
3548
|
-
for (const group of groups) {
|
|
3549
|
-
if (group.sources.length < 2) continue;
|
|
3550
|
-
const severities = new Set(Object.values(group.severityBySource ?? {}));
|
|
3551
|
-
if (severities.size > 1) {
|
|
3552
|
-
conflicts.push({
|
|
3553
|
-
type: "severity",
|
|
3554
|
-
findingTitle: group.title,
|
|
3555
|
-
positions: group.sources.map((source) => position(source, group)),
|
|
3556
|
-
note: `Agents disagree on severity (${[...severities].sort((a, b) => SEVERITY_RANK2[a] - SEVERITY_RANK2[b]).join(", ")}); the highest severity is shown, every position is preserved.`
|
|
3557
|
-
});
|
|
3558
|
-
}
|
|
3559
|
-
const recommendations = (group.recommendations ?? []).filter((r) => r.length > 0);
|
|
3560
|
-
if (recommendations.length > 1) {
|
|
3561
|
-
conflicts.push({
|
|
3562
|
-
type: "recommendation",
|
|
3563
|
-
findingTitle: group.title,
|
|
3564
|
-
positions: group.sources.map((source) => position(source, group)),
|
|
3565
|
-
note: `Agents proposed ${recommendations.length} different fixes; none was discarded.`
|
|
3566
|
-
});
|
|
3567
|
-
}
|
|
3568
|
-
}
|
|
3569
|
-
return conflicts;
|
|
3570
|
-
}
|
|
3571
|
-
function position(source, group) {
|
|
3572
|
-
return {
|
|
3573
|
-
agent: source.agent,
|
|
3574
|
-
reportId: source.reportId,
|
|
3575
|
-
...group.severityBySource?.[source.agent] !== void 0 ? { severity: group.severityBySource[source.agent] } : {},
|
|
3576
|
-
...group.confidenceBySource?.[source.agent] !== void 0 ? { confidence: group.confidenceBySource[source.agent] } : {},
|
|
3577
|
-
...group.recommendations?.[0] !== void 0 ? { recommendation: group.recommendations[0] } : {}
|
|
3578
|
-
};
|
|
3579
|
-
}
|
|
3580
|
-
function rank(finding) {
|
|
3581
|
-
return finding.severity ? SEVERITY_RANK2[finding.severity] : -1;
|
|
3582
|
-
}
|
|
3583
|
-
function mergeFinding(groups, finding, report, threshold) {
|
|
3584
|
-
const source = { agent: report.agent, reportId: report.id };
|
|
3585
|
-
const group = groups.find((g) => similar(g.title, finding.title, threshold));
|
|
3586
|
-
if (group) {
|
|
3587
|
-
group.occurrences += 1;
|
|
3588
|
-
group.details.push(finding.detail);
|
|
3589
|
-
group.sources.push(source);
|
|
3590
|
-
group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
|
|
3591
|
-
if (finding.severity && (!group.severity || SEVERITY_RANK2[finding.severity] > SEVERITY_RANK2[group.severity])) {
|
|
3592
|
-
group.severity = finding.severity;
|
|
3593
|
-
}
|
|
3594
|
-
if (finding.confidence && rankConfidence(finding.confidence) > rankConfidence(group.confidence)) {
|
|
3595
|
-
group.confidence = finding.confidence;
|
|
3596
|
-
}
|
|
3597
|
-
if (finding.severity) {
|
|
3598
|
-
group.severityBySource = { ...group.severityBySource, [report.agent]: finding.severity };
|
|
3599
|
-
}
|
|
3600
|
-
if (finding.confidence) {
|
|
3601
|
-
group.confidenceBySource = { ...group.confidenceBySource, [report.agent]: finding.confidence };
|
|
3602
|
-
}
|
|
3603
|
-
if (finding.recommendation && !group.recommendations?.includes(finding.recommendation)) {
|
|
3604
|
-
group.recommendations = [...group.recommendations ?? [], finding.recommendation];
|
|
3605
|
-
}
|
|
3606
|
-
return;
|
|
3607
|
-
}
|
|
3608
|
-
groups.push({
|
|
3609
|
-
title: finding.title,
|
|
3610
|
-
details: [finding.detail],
|
|
3611
|
-
sources: [source],
|
|
3612
|
-
occurrences: 1,
|
|
3613
|
-
...finding.severity ? { severity: finding.severity } : {},
|
|
3614
|
-
...finding.confidence ? { confidence: finding.confidence } : {},
|
|
3615
|
-
...finding.severity ? { severityBySource: { [report.agent]: finding.severity } } : {},
|
|
3616
|
-
...finding.confidence ? { confidenceBySource: { [report.agent]: finding.confidence } } : {},
|
|
3617
|
-
...finding.recommendation ? { recommendations: [finding.recommendation] } : {},
|
|
3618
|
-
evidence: [...normalizeEvidence(finding.evidence) ?? []]
|
|
3619
|
-
});
|
|
3620
|
-
}
|
|
3621
|
-
function mergeRecommendation(groups, text, report, threshold) {
|
|
3622
|
-
const source = { agent: report.agent, reportId: report.id };
|
|
3623
|
-
const group = groups.find((g) => similar(g.text, text, threshold));
|
|
3624
|
-
if (group) {
|
|
3625
|
-
group.occurrences += 1;
|
|
3626
|
-
group.sources.push(source);
|
|
3627
|
-
return;
|
|
3628
|
-
}
|
|
3629
|
-
groups.push({ text, sources: [source], occurrences: 1 });
|
|
3630
|
-
}
|
|
3631
|
-
function similar(a, b, threshold) {
|
|
3632
|
-
const left = tokens(a);
|
|
3633
|
-
const right = tokens(b);
|
|
3634
|
-
if (left.size === 0 || right.size === 0) return false;
|
|
3635
|
-
let intersection = 0;
|
|
3636
|
-
for (const token of left) {
|
|
3637
|
-
if (right.has(token)) intersection += 1;
|
|
3638
|
-
}
|
|
3639
|
-
const union = left.size + right.size - intersection;
|
|
3640
|
-
return intersection / union >= threshold;
|
|
3641
|
-
}
|
|
3642
|
-
function tokens(text) {
|
|
3643
|
-
return new Set(
|
|
3644
|
-
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
|
|
3645
|
-
);
|
|
3646
|
-
}
|
|
3647
|
-
function truncate2(text, max) {
|
|
3648
|
-
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
3755
|
// src/reports/extract.ts
|
|
3652
3756
|
var BULLET = /^(?:[-*•]|\d+[.)])\s+(.*)$/;
|
|
3653
3757
|
function parseSections(raw) {
|
|
@@ -3874,7 +3978,7 @@ var ProjectStores = class {
|
|
|
3874
3978
|
};
|
|
3875
3979
|
|
|
3876
3980
|
// src/version.ts
|
|
3877
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
3981
|
+
var OMNINODE_VERSION = "2.0.0-alpha.11";
|
|
3878
3982
|
export {
|
|
3879
3983
|
AgentAdapterRegistry,
|
|
3880
3984
|
AgentError,
|
|
@@ -3969,6 +4073,7 @@ export {
|
|
|
3969
4073
|
parseRetryAfter,
|
|
3970
4074
|
pipelineConfigSchema,
|
|
3971
4075
|
pipelineStepConfigSchema,
|
|
4076
|
+
planJsonSchema,
|
|
3972
4077
|
plannerConfigSchema,
|
|
3973
4078
|
projectConfigSchema,
|
|
3974
4079
|
providerConfigSchema,
|
|
@@ -3983,6 +4088,7 @@ export {
|
|
|
3983
4088
|
sanitizeChunk,
|
|
3984
4089
|
similar,
|
|
3985
4090
|
validateEnvelope,
|
|
4091
|
+
validatePlan,
|
|
3986
4092
|
validateReport,
|
|
3987
4093
|
validateWorkingDirectory
|
|
3988
4094
|
};
|