@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/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 randomBytes4 } from "crypto";
2586
+ import { randomBytes as randomBytes5 } from "crypto";
2584
2587
 
2585
- // src/planner/heuristic.ts
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 [index, text] of recommendations.entries()) {
2851
+ for (const text of recommendations) {
2603
2852
  steps.push({
2604
2853
  id: `step-${steps.length + 1}`,
2605
- title: truncate(text, 120),
2854
+ title: truncate2(text, 120),
2606
2855
  order: steps.length + 1,
2607
- ...index > 0 ? { dependsOn: [`step-${steps.length}`] } : {}
2856
+ ...steps.length > 0 ? { dependsOn: [`step-${steps.length}`] } : {}
2608
2857
  });
2609
2858
  }
2610
- const significant = findings.filter(({ finding }) => finding.severity ? SEVERITY_RANK[finding.severity] >= SEVERITY_RANK.high : false).sort((a, b) => SEVERITY_RANK[b.finding.severity ?? "info"] - SEVERITY_RANK[a.finding.severity ?? "info"]);
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)}-${randomBytes2(3).toString("hex")}`,
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: ${truncate(finding.title, 100)}`,
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 truncate(text, max) {
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 randomBytes3 } from "crypto";
2655
- import { z as z2 } from "zod";
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 = [`# Objective
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
- `# Findings (${request.reports.length} report(s) by ${agents})
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
- const summaryLines = request.reports.map((report) => `- [${report.agent}] ${report.summary}`);
2691
- parts.push(`# Report summaries
2692
- ${summaryLines.join("\n")}`);
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.context && request.context.length > 0) {
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, "planner model output was not valid plan JSON");
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)}-${randomBytes3(3).toString("hex")}`,
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
- return plan;
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("PLANNER_FAILED", `Planner failed: ${reason}.`);
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) return void 0;
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
- const json = JSON.parse(unfenced.slice(start, end + 1));
2781
- const parsed = planJsonSchema.safeParse(json);
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
- summary: parsed.data.summary,
2795
- steps: steps.sort((a, b) => a.order - b.order),
2796
- risks: parsed.data.risks,
2797
- notes: parsed.data.notes
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)}-${randomBytes4(3).toString("hex")}`,
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.10";
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
  };