@hilbras/omninode 2.0.0-alpha.7 → 2.0.0-alpha.9
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 +16 -3
- package/dist/cli/index.js +267 -61
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +214 -46
- package/dist/index.js +265 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,9 +18,9 @@ existing intelligence; it does not try to become another model.
|
|
|
18
18
|
|
|
19
19
|
## Status
|
|
20
20
|
|
|
21
|
-
**v2.0.0-alpha.
|
|
21
|
+
**v2.0.0-alpha.9 — v2 Phase 9: Report System v2**
|
|
22
22
|
(the reliability & interoperability line: architecture → execution →
|
|
23
|
-
protocol → adapters → pipelines → providers → OmniHilbras) (the v2
|
|
23
|
+
protocol → adapters → pipelines → providers → OmniHilbras → memory → reports) (the v2
|
|
24
24
|
roadmap is [docs/ROADMAP_V2.md](docs/ROADMAP_V2.md); the v1 plan is complete:
|
|
25
25
|
the
|
|
26
26
|
full loop from §28 runs in one command —
|
|
@@ -31,6 +31,19 @@ for the plan and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the design).
|
|
|
31
31
|
|
|
32
32
|
### What's new in v2 (so far)
|
|
33
33
|
|
|
34
|
+
- **Phase 9 — Report System v2**: reports are validated machine-readable
|
|
35
|
+
objects — `validateReport()` runs on everything entering the system and
|
|
36
|
+
malformed reports are rejected with diagnostics instead of poisoning
|
|
37
|
+
aggregation. Findings gain description/confidence/source/recommendation,
|
|
38
|
+
evidence is typed (`file`, `line`, `url`, `command`, `observation`,
|
|
39
|
+
`artifact`), and reports carry artifacts and run provenance.
|
|
40
|
+
Details: [REPORTS.md](docs/REPORTS.md).
|
|
41
|
+
- **Phase 8 — Remembera / Memory v2**: the memory contract is formalized
|
|
42
|
+
(`retrieve` / `store` / `search` / `metadata` with v1 `query` / `write`
|
|
43
|
+
aliases kept working), memory gains structured **categories** (decisions,
|
|
44
|
+
problems, conventions, tasks…), retrieval is **task-oriented and budgeted**
|
|
45
|
+
(never "load all memory"), and `memory.required: true` makes backend
|
|
46
|
+
failures fatal instead of tolerated. Details: [MEMORY.md](docs/MEMORY.md).
|
|
34
47
|
- **Phase 7 — OmniHilbras Integration v2**: the dedicated adapter now
|
|
35
48
|
captures gateway-level metadata (version, tier, region) and attaches it
|
|
36
49
|
to discovered models, honors provider-level `timeout_ms`, and supports
|
|
@@ -333,7 +346,7 @@ required for the core engine.
|
|
|
333
346
|
- [Architecture](docs/ARCHITECTURE.md) — module map, workflow, local state
|
|
334
347
|
- [Security model](docs/SECURITY.md) — secrets, process execution, env isolation, audit log
|
|
335
348
|
- [API stability policy](docs/API_STABILITY.md) and [public API inventory](docs/API.md)
|
|
336
|
-
- [Agent Protocol v2 spec](docs/PROTOCOL.md) · [Providers](docs/PROVIDERS.md) · [Deprecations](docs/DEPRECATIONS.md) · [Changelog](CHANGELOG.md) · [v2 roadmap](docs/ROADMAP_V2.md)
|
|
349
|
+
- [Agent Protocol v2 spec](docs/PROTOCOL.md) · [Providers](docs/PROVIDERS.md) · [Memory](docs/MEMORY.md) · [Reports](docs/REPORTS.md) · [Deprecations](docs/DEPRECATIONS.md) · [Changelog](CHANGELOG.md) · [v2 roadmap](docs/ROADMAP_V2.md)
|
|
337
350
|
- [Example project](examples/demo/) — a runnable end-to-end demo (`./demo.sh`)
|
|
338
351
|
|
|
339
352
|
Prefer a container? `docker build -t omninode .` then
|
package/dist/cli/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { realpathSync } from "fs";
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/version.ts
|
|
9
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
9
|
+
var OMNINODE_VERSION = "2.0.0-alpha.9";
|
|
10
10
|
|
|
11
11
|
// src/errors/index.ts
|
|
12
12
|
var OmniNodeError = class extends Error {
|
|
@@ -134,7 +134,8 @@ var projectConfigSchema = z.object({
|
|
|
134
134
|
provider: z.string().min(1).default("local"),
|
|
135
135
|
base_url: z.string().url().optional(),
|
|
136
136
|
api_key_env_var: z.string().min(1).optional(),
|
|
137
|
-
|
|
137
|
+
/** When true, memory failures fail tasks instead of being tolerated (§12). */
|
|
138
|
+
required: z.boolean().optional()
|
|
138
139
|
}).strict().optional()
|
|
139
140
|
}).strict();
|
|
140
141
|
var appConfigSchema = z.object({
|
|
@@ -216,7 +217,8 @@ function toAppConfig(yaml) {
|
|
|
216
217
|
memory: {
|
|
217
218
|
provider: yaml.project.memory.provider,
|
|
218
219
|
baseUrl: yaml.project.memory.base_url,
|
|
219
|
-
apiKeyEnvVar: yaml.project.memory.api_key_env_var
|
|
220
|
+
apiKeyEnvVar: yaml.project.memory.api_key_env_var,
|
|
221
|
+
required: yaml.project.memory.required
|
|
220
222
|
}
|
|
221
223
|
} : {}
|
|
222
224
|
},
|
|
@@ -2490,6 +2492,23 @@ var FileAuditLog = class {
|
|
|
2490
2492
|
}
|
|
2491
2493
|
};
|
|
2492
2494
|
|
|
2495
|
+
// src/memory/base.ts
|
|
2496
|
+
function defaultSearch(provider, text, options = {}) {
|
|
2497
|
+
return provider.retrieve({
|
|
2498
|
+
text,
|
|
2499
|
+
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
2500
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
function defaultMetadata(name, options = {}) {
|
|
2504
|
+
return {
|
|
2505
|
+
name,
|
|
2506
|
+
capabilities: options.capabilities ?? ["retrieve", "store", "search"],
|
|
2507
|
+
relevanceRanking: options.relevanceRanking ?? false,
|
|
2508
|
+
...options.backend !== void 0 ? { backend: options.backend } : {}
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2493
2512
|
// src/memory/local.ts
|
|
2494
2513
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
2495
2514
|
"the",
|
|
@@ -2552,9 +2571,27 @@ var LocalMemoryProvider = class extends JsonFileStore {
|
|
|
2552
2571
|
constructor(directory) {
|
|
2553
2572
|
super({ ...directory !== void 0 ? { directory } : {}, fileName: "memory.json" });
|
|
2554
2573
|
}
|
|
2555
|
-
|
|
2574
|
+
metadata() {
|
|
2575
|
+
return defaultMetadata(this.name, {
|
|
2576
|
+
capabilities: ["retrieve", "store", "search", "categories", "tags"],
|
|
2577
|
+
relevanceRanking: true,
|
|
2578
|
+
backend: { kind: "json-file", deterministicScoring: true }
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
search(text, options = {}) {
|
|
2582
|
+
return defaultSearch(this, text, options);
|
|
2583
|
+
}
|
|
2584
|
+
/** @deprecated v1 alias of {@link retrieve}. */
|
|
2585
|
+
query(query) {
|
|
2586
|
+
return this.retrieve(query);
|
|
2587
|
+
}
|
|
2588
|
+
/** @deprecated v1 alias of {@link store}. */
|
|
2589
|
+
write(entry2) {
|
|
2590
|
+
return this.store(entry2);
|
|
2591
|
+
}
|
|
2592
|
+
async retrieve(query) {
|
|
2556
2593
|
const entries = await this.readAll();
|
|
2557
|
-
const filtered = entries.filter((entry2) => query.scope ? entry2.scope === query.scope : true).filter(
|
|
2594
|
+
const filtered = entries.filter((entry2) => query.scope ? entry2.scope === query.scope : true).filter((entry2) => query.category ? entry2.category === query.category : true).filter(
|
|
2558
2595
|
(entry2) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry2.tags ?? []).includes(tag)) : true
|
|
2559
2596
|
);
|
|
2560
2597
|
if (!query.text || query.text.trim().length === 0) {
|
|
@@ -2563,7 +2600,7 @@ var LocalMemoryProvider = class extends JsonFileStore {
|
|
|
2563
2600
|
const queryKeywords = keywords(query.text);
|
|
2564
2601
|
return filtered.map((entry2) => ({ entry: entry2, score: relevanceScore(queryKeywords, entry2) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry: entry2 }) => entry2);
|
|
2565
2602
|
}
|
|
2566
|
-
|
|
2603
|
+
store(entry2) {
|
|
2567
2604
|
return this.mutate((entries) => {
|
|
2568
2605
|
const index = entries.findIndex((e) => e.key === entry2.key);
|
|
2569
2606
|
const stamped = {
|
|
@@ -2594,11 +2631,40 @@ var RememberaMemoryProvider = class {
|
|
|
2594
2631
|
this.env = config.env ?? process.env;
|
|
2595
2632
|
this.log = log.child({ component: "remembera" });
|
|
2596
2633
|
}
|
|
2634
|
+
get authenticationConfigured() {
|
|
2635
|
+
return this.apiKeyEnvVar === void 0 || Boolean(this.env[this.apiKeyEnvVar]);
|
|
2636
|
+
}
|
|
2597
2637
|
headers() {
|
|
2598
2638
|
const key = this.apiKeyEnvVar ? resolveApiKey({ name: "remembera", apiKeyEnvVar: this.apiKeyEnvVar }, this.env) : void 0;
|
|
2599
2639
|
return key ? { authorization: `Bearer ${key}` } : {};
|
|
2600
2640
|
}
|
|
2601
|
-
|
|
2641
|
+
/** Search delegates to the gateway's relevance query. */
|
|
2642
|
+
search(text, options = {}) {
|
|
2643
|
+
return defaultSearch(
|
|
2644
|
+
this,
|
|
2645
|
+
text,
|
|
2646
|
+
{
|
|
2647
|
+
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
2648
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
2649
|
+
}
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
metadata() {
|
|
2653
|
+
return defaultMetadata(this.name, {
|
|
2654
|
+
capabilities: ["retrieve", "store", "search", "categories", "tags"],
|
|
2655
|
+
relevanceRanking: true,
|
|
2656
|
+
backend: { kind: "http", baseUrl: this.baseUrl, authenticated: this.authenticationConfigured }
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
/** @deprecated v1 alias of {@link retrieve}. */
|
|
2660
|
+
query(query) {
|
|
2661
|
+
return this.retrieve(query);
|
|
2662
|
+
}
|
|
2663
|
+
/** @deprecated v1 alias of {@link store}. */
|
|
2664
|
+
write(entry2) {
|
|
2665
|
+
return this.store(entry2);
|
|
2666
|
+
}
|
|
2667
|
+
async retrieve(query) {
|
|
2602
2668
|
const { status, body } = await this.request(`${this.baseUrl}/api/memory/query`, query);
|
|
2603
2669
|
if (status !== 200) {
|
|
2604
2670
|
throw new MemoryError(`Remembera query returned HTTP ${status}.`);
|
|
@@ -2609,7 +2675,7 @@ var RememberaMemoryProvider = class {
|
|
|
2609
2675
|
}
|
|
2610
2676
|
return entries.map((entry2) => this.toEntry(entry2)).filter((entry2) => entry2 !== void 0);
|
|
2611
2677
|
}
|
|
2612
|
-
async
|
|
2678
|
+
async store(entry2) {
|
|
2613
2679
|
const { status } = await this.request(`${this.baseUrl}/api/memory`, entry2);
|
|
2614
2680
|
if (status !== 200 && status !== 201) {
|
|
2615
2681
|
throw new MemoryError(`Remembera write returned HTTP ${status}.`);
|
|
@@ -2644,6 +2710,7 @@ var RememberaMemoryProvider = class {
|
|
|
2644
2710
|
content: entry2.content,
|
|
2645
2711
|
scope: scope === "project" || scope === "role" || scope === "task" || scope === "knowledge" ? scope : "knowledge",
|
|
2646
2712
|
...Array.isArray(entry2.tags) ? { tags: entry2.tags.filter((t) => typeof t === "string") } : {},
|
|
2713
|
+
...typeof entry2.category === "string" ? { category: entry2.category } : {},
|
|
2647
2714
|
...typeof entry2.createdAt === "string" ? { createdAt: entry2.createdAt } : {},
|
|
2648
2715
|
...typeof entry2.updatedAt === "string" ? { updatedAt: entry2.updatedAt } : {}
|
|
2649
2716
|
};
|
|
@@ -2674,57 +2741,54 @@ function createMemoryProvider(config) {
|
|
|
2674
2741
|
}
|
|
2675
2742
|
|
|
2676
2743
|
// src/memory/service.ts
|
|
2744
|
+
var DEFAULT_ENTRY_LIMIT = 8;
|
|
2677
2745
|
var MAX_CONTEXT_CHARS = 4e3;
|
|
2678
2746
|
var MemoryService = class {
|
|
2679
|
-
constructor(provider, log = logger) {
|
|
2747
|
+
constructor(provider, log = logger, options = {}) {
|
|
2680
2748
|
this.provider = provider;
|
|
2681
2749
|
this.log = log.child({ component: "memory", provider: provider.name });
|
|
2750
|
+
this.required = options.required ?? false;
|
|
2751
|
+
this.maxContextChars = options.maxContextChars ?? MAX_CONTEXT_CHARS;
|
|
2682
2752
|
}
|
|
2683
2753
|
provider;
|
|
2684
2754
|
log;
|
|
2755
|
+
required;
|
|
2756
|
+
maxContextChars;
|
|
2685
2757
|
get providerRef() {
|
|
2686
2758
|
return this.provider;
|
|
2687
2759
|
}
|
|
2688
2760
|
get providerName() {
|
|
2689
2761
|
return this.provider.name;
|
|
2690
2762
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2763
|
+
get isRequired() {
|
|
2764
|
+
return this.required;
|
|
2765
|
+
}
|
|
2766
|
+
/** Structured, budgeted context for a task (§12 — avoid loading all memory). */
|
|
2767
|
+
async contextFor(query) {
|
|
2768
|
+
let entries;
|
|
2693
2769
|
try {
|
|
2694
|
-
|
|
2770
|
+
entries = await this.provider.retrieve({
|
|
2695
2771
|
text: query.objective,
|
|
2696
2772
|
...query.role ? { tags: [query.role] } : {},
|
|
2697
|
-
limit:
|
|
2773
|
+
limit: query.limit ?? DEFAULT_ENTRY_LIMIT
|
|
2698
2774
|
});
|
|
2699
|
-
if (entries.length === 0) return "";
|
|
2700
|
-
const byScope = /* @__PURE__ */ new Map();
|
|
2701
|
-
for (const entry2 of entries) {
|
|
2702
|
-
byScope.set(entry2.scope, [...byScope.get(entry2.scope) ?? [], entry2]);
|
|
2703
|
-
}
|
|
2704
|
-
const parts = [];
|
|
2705
|
-
for (const scope of ["project", "task", "role", "knowledge"]) {
|
|
2706
|
-
const scopeEntries = byScope.get(scope) ?? [];
|
|
2707
|
-
if (scopeEntries.length === 0) continue;
|
|
2708
|
-
const lines = scopeEntries.map((entry2) => `- ${entry2.content}`).join("\n");
|
|
2709
|
-
parts.push(`### Relevant memory (${scope})
|
|
2710
|
-
${lines}`);
|
|
2711
|
-
}
|
|
2712
|
-
const context = parts.join("\n\n");
|
|
2713
|
-
return context.length > MAX_CONTEXT_CHARS ? `${context.slice(0, MAX_CONTEXT_CHARS)}\u2026` : context;
|
|
2714
2775
|
} catch (error) {
|
|
2715
|
-
this.
|
|
2716
|
-
`Memory query failed (continuing without memory): ${error instanceof Error ? error.message : String(error)}`
|
|
2717
|
-
);
|
|
2718
|
-
return "";
|
|
2776
|
+
return this.handleFailure("retrieve", error, { text: "", entries: [], truncated: false });
|
|
2719
2777
|
}
|
|
2778
|
+
return this.format(entries);
|
|
2720
2779
|
}
|
|
2721
|
-
/**
|
|
2780
|
+
/** Prompt-ready context text (v1 convenience wrapper). */
|
|
2781
|
+
async gatherContext(query) {
|
|
2782
|
+
return (await this.contextFor(query)).text;
|
|
2783
|
+
}
|
|
2784
|
+
/** Records a finished task's outcome — task history plus "known problems" (§12). */
|
|
2722
2785
|
async recordTaskOutcome(task) {
|
|
2723
2786
|
const outcome = task.result?.summary ?? task.result?.error ?? "(no output)";
|
|
2724
2787
|
try {
|
|
2725
|
-
await this.provider.
|
|
2788
|
+
await this.provider.store({
|
|
2726
2789
|
key: `task:${task.id}`,
|
|
2727
2790
|
scope: "task",
|
|
2791
|
+
category: "tasks",
|
|
2728
2792
|
content: `Objective: ${task.objective}
|
|
2729
2793
|
Status: ${task.status}
|
|
2730
2794
|
Outcome: ${outcome}`,
|
|
@@ -2735,18 +2799,53 @@ Outcome: ${outcome}`,
|
|
|
2735
2799
|
(report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
|
|
2736
2800
|
);
|
|
2737
2801
|
for (const { report, finding } of significant) {
|
|
2738
|
-
await this.provider.
|
|
2802
|
+
await this.provider.store({
|
|
2739
2803
|
key: `finding:${report.id}:${finding.id}`,
|
|
2740
2804
|
scope: "project",
|
|
2805
|
+
category: "problems",
|
|
2741
2806
|
content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
|
|
2742
2807
|
tags: ["known-problem", report.agent]
|
|
2743
2808
|
});
|
|
2744
2809
|
}
|
|
2745
2810
|
} catch (error) {
|
|
2746
|
-
this.
|
|
2747
|
-
|
|
2811
|
+
this.handleFailure("store", error, void 0);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
format(entries) {
|
|
2815
|
+
if (entries.length === 0) return { text: "", entries, truncated: false };
|
|
2816
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
2817
|
+
for (const entry2 of entries) {
|
|
2818
|
+
byScope.set(entry2.scope, [...byScope.get(entry2.scope) ?? [], entry2]);
|
|
2819
|
+
}
|
|
2820
|
+
const parts = [];
|
|
2821
|
+
for (const scope of ["project", "task", "role", "knowledge"]) {
|
|
2822
|
+
const scopeEntries = byScope.get(scope) ?? [];
|
|
2823
|
+
if (scopeEntries.length === 0) continue;
|
|
2824
|
+
const lines = scopeEntries.map((entry2) => {
|
|
2825
|
+
const category = entry2.category ? `[${entry2.category}] ` : "";
|
|
2826
|
+
return `- ${category}${entry2.content}`;
|
|
2827
|
+
}).join("\n");
|
|
2828
|
+
parts.push(`### Relevant memory (${scope})
|
|
2829
|
+
${lines}`);
|
|
2830
|
+
}
|
|
2831
|
+
let text = parts.join("\n\n");
|
|
2832
|
+
const truncated = text.length > this.maxContextChars;
|
|
2833
|
+
if (truncated) text = `${text.slice(0, this.maxContextChars - 1)}\u2026`;
|
|
2834
|
+
return { text, entries, truncated };
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Failure policy (§12): continue unless memory is explicitly required.
|
|
2838
|
+
*/
|
|
2839
|
+
handleFailure(operation, error, fallback) {
|
|
2840
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2841
|
+
if (this.required) {
|
|
2842
|
+
throw new MemoryError(
|
|
2843
|
+
`Memory is marked required, but ${operation} failed: ${message}`,
|
|
2844
|
+
{ cause: error }
|
|
2748
2845
|
);
|
|
2749
2846
|
}
|
|
2847
|
+
this.log.warn(`Memory ${operation} failed (continuing without memory): ${message}`);
|
|
2848
|
+
return fallback ?? { text: "", entries: [], truncated: false };
|
|
2750
2849
|
}
|
|
2751
2850
|
};
|
|
2752
2851
|
|
|
@@ -2780,7 +2879,11 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
2780
2879
|
agents,
|
|
2781
2880
|
roles,
|
|
2782
2881
|
store: new FileTaskStore(directory),
|
|
2783
|
-
...config.project.memory !== void 0 ? {
|
|
2882
|
+
...config.project.memory !== void 0 ? {
|
|
2883
|
+
memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
|
|
2884
|
+
required: config.project.memory.required
|
|
2885
|
+
})
|
|
2886
|
+
} : {},
|
|
2784
2887
|
audit: new FileAuditLog(),
|
|
2785
2888
|
log
|
|
2786
2889
|
});
|
|
@@ -2894,6 +2997,84 @@ function truncate2(text, max) {
|
|
|
2894
2997
|
|
|
2895
2998
|
// src/reports/aggregate.ts
|
|
2896
2999
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
3000
|
+
|
|
3001
|
+
// src/reports/schema.ts
|
|
3002
|
+
import { z as z2 } from "zod";
|
|
3003
|
+
var evidenceSchema = z2.union([
|
|
3004
|
+
z2.string().transform((excerpt) => ({ kind: "observation", excerpt })),
|
|
3005
|
+
z2.object({
|
|
3006
|
+
kind: z2.enum(["file", "line", "url", "command", "observation", "artifact"]),
|
|
3007
|
+
ref: z2.string().optional(),
|
|
3008
|
+
line: z2.number().int().optional(),
|
|
3009
|
+
excerpt: z2.string().optional(),
|
|
3010
|
+
agent: z2.string().optional()
|
|
3011
|
+
})
|
|
3012
|
+
]);
|
|
3013
|
+
var findingSchema = z2.object({
|
|
3014
|
+
id: z2.string().min(1),
|
|
3015
|
+
title: z2.string().min(1),
|
|
3016
|
+
detail: z2.string().default(""),
|
|
3017
|
+
description: z2.string().optional(),
|
|
3018
|
+
severity: z2.enum(["info", "low", "medium", "high", "critical"]).optional(),
|
|
3019
|
+
confidence: z2.enum(["low", "medium", "high"]).optional(),
|
|
3020
|
+
evidence: z2.array(evidenceSchema).optional(),
|
|
3021
|
+
location: z2.string().optional(),
|
|
3022
|
+
tags: z2.array(z2.string()).optional(),
|
|
3023
|
+
source: z2.object({ agent: z2.string().optional(), reportId: z2.string().optional(), pipelineRunId: z2.string().optional() }).optional(),
|
|
3024
|
+
recommendation: z2.string().optional()
|
|
3025
|
+
});
|
|
3026
|
+
var artifactSchema = z2.object({
|
|
3027
|
+
kind: z2.enum(["file", "patch", "log", "screenshot", "document", "data"]),
|
|
3028
|
+
ref: z2.string().min(1),
|
|
3029
|
+
description: z2.string().optional(),
|
|
3030
|
+
mediaType: z2.string().optional(),
|
|
3031
|
+
bytes: z2.number().int().optional()
|
|
3032
|
+
});
|
|
3033
|
+
var reportSchema = z2.object({
|
|
3034
|
+
id: z2.string().min(1),
|
|
3035
|
+
taskId: z2.string().min(1),
|
|
3036
|
+
agent: z2.string().min(1),
|
|
3037
|
+
agentId: z2.string().optional(),
|
|
3038
|
+
pipelineRunId: z2.string().optional(),
|
|
3039
|
+
model: z2.string().optional(),
|
|
3040
|
+
summary: z2.string(),
|
|
3041
|
+
findings: z2.array(findingSchema),
|
|
3042
|
+
recommendations: z2.array(z2.string()),
|
|
3043
|
+
confidence: z2.enum(["low", "medium", "high"]).optional(),
|
|
3044
|
+
evidence: z2.array(evidenceSchema).optional(),
|
|
3045
|
+
artifacts: z2.array(artifactSchema).optional(),
|
|
3046
|
+
createdAt: z2.string().min(1),
|
|
3047
|
+
metadata: z2.record(z2.unknown()).optional()
|
|
3048
|
+
});
|
|
3049
|
+
function validateReport(value) {
|
|
3050
|
+
const parsed = reportSchema.safeParse(value);
|
|
3051
|
+
if (!parsed.success) {
|
|
3052
|
+
const issues = parsed.error.issues.map((issue) => ({
|
|
3053
|
+
path: issue.path.join(".") || "(root)",
|
|
3054
|
+
message: issue.message
|
|
3055
|
+
}));
|
|
3056
|
+
return {
|
|
3057
|
+
valid: false,
|
|
3058
|
+
issues,
|
|
3059
|
+
reason: `report failed schema validation (${issues.length} issue(s))`
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
const report = parsed.data;
|
|
3063
|
+
report.findings = report.findings.map((finding) => ({
|
|
3064
|
+
...finding,
|
|
3065
|
+
description: finding.description ?? finding.detail,
|
|
3066
|
+
...finding.source === void 0 ? { source: { agent: report.agent, reportId: report.id, ...report.pipelineRunId !== void 0 ? { pipelineRunId: report.pipelineRunId } : {} } } : {}
|
|
3067
|
+
}));
|
|
3068
|
+
return { valid: true, report };
|
|
3069
|
+
}
|
|
3070
|
+
function normalizeEvidence(input) {
|
|
3071
|
+
if (input === void 0) return void 0;
|
|
3072
|
+
return input.map(
|
|
3073
|
+
(item) => typeof item === "string" ? { kind: "observation", excerpt: item } : item
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
// src/reports/aggregate.ts
|
|
2897
3078
|
var SEVERITY_RANK = {
|
|
2898
3079
|
info: 0,
|
|
2899
3080
|
low: 1,
|
|
@@ -2940,7 +3121,7 @@ function mergeFinding(groups, finding, report, threshold) {
|
|
|
2940
3121
|
group.occurrences += 1;
|
|
2941
3122
|
group.details.push(finding.detail);
|
|
2942
3123
|
group.sources.push(source);
|
|
2943
|
-
group.evidence.push(...finding.evidence ?? []);
|
|
3124
|
+
group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
|
|
2944
3125
|
if (finding.severity && (!group.severity || SEVERITY_RANK[finding.severity] > SEVERITY_RANK[group.severity])) {
|
|
2945
3126
|
group.severity = finding.severity;
|
|
2946
3127
|
}
|
|
@@ -2952,7 +3133,7 @@ function mergeFinding(groups, finding, report, threshold) {
|
|
|
2952
3133
|
sources: [source],
|
|
2953
3134
|
occurrences: 1,
|
|
2954
3135
|
...finding.severity ? { severity: finding.severity } : {},
|
|
2955
|
-
evidence: [...finding.evidence ?? []]
|
|
3136
|
+
evidence: [...normalizeEvidence(finding.evidence) ?? []]
|
|
2956
3137
|
});
|
|
2957
3138
|
}
|
|
2958
3139
|
function mergeRecommendation(groups, text, report, threshold) {
|
|
@@ -3034,6 +3215,7 @@ function extractReportFromText(raw, meta) {
|
|
|
3034
3215
|
id: `${meta.taskId}-f-${index + 1}`,
|
|
3035
3216
|
title: truncate4(text, 120),
|
|
3036
3217
|
detail: text,
|
|
3218
|
+
description: text,
|
|
3037
3219
|
severity: guessSeverity(text)
|
|
3038
3220
|
}));
|
|
3039
3221
|
return {
|
|
@@ -3044,7 +3226,8 @@ function extractReportFromText(raw, meta) {
|
|
|
3044
3226
|
summary,
|
|
3045
3227
|
findings,
|
|
3046
3228
|
recommendations: recommendationTexts.map((t) => truncate4(t, 200)),
|
|
3047
|
-
|
|
3229
|
+
...evidenceTexts.length > 0 ? { evidence: evidenceTexts.map((excerpt) => ({ kind: "observation", excerpt })) } : {},
|
|
3230
|
+
metadata: evidenceTexts.length > 0 ? { evidenceCount: evidenceTexts.length } : void 0,
|
|
3048
3231
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3049
3232
|
};
|
|
3050
3233
|
}
|
|
@@ -3060,27 +3243,46 @@ var ReportService = class {
|
|
|
3060
3243
|
}
|
|
3061
3244
|
store;
|
|
3062
3245
|
log;
|
|
3246
|
+
/** Rejected reports from the last collection (§13 — malformed never enters aggregation). */
|
|
3247
|
+
lastDiagnostics = { collected: 0, accepted: 0, rejected: [] };
|
|
3063
3248
|
get storeRef() {
|
|
3064
3249
|
return this.store;
|
|
3065
3250
|
}
|
|
3066
|
-
/** Normalizes finished tasks into reports (§17 — normalizer)
|
|
3251
|
+
/** Normalizes finished tasks into reports (§17 — normalizer), validating each
|
|
3252
|
+
* one so malformed reports never reach aggregation (§13). */
|
|
3067
3253
|
async collectFromTasks(tasks) {
|
|
3068
|
-
const
|
|
3254
|
+
const candidates = [];
|
|
3069
3255
|
for (const task of tasks) {
|
|
3070
3256
|
const structured = task.result?.reports ?? [];
|
|
3071
3257
|
if (structured.length > 0) {
|
|
3072
|
-
|
|
3258
|
+
candidates.push(...structured);
|
|
3073
3259
|
continue;
|
|
3074
3260
|
}
|
|
3075
3261
|
const text = task.result?.rawOutput ?? task.result?.summary ?? "";
|
|
3076
3262
|
if (text.trim().length === 0) continue;
|
|
3077
|
-
|
|
3263
|
+
candidates.push(
|
|
3078
3264
|
extractReportFromText(text, {
|
|
3079
3265
|
taskId: task.id,
|
|
3080
3266
|
agent: task.agent ?? "unknown"
|
|
3081
3267
|
})
|
|
3082
3268
|
);
|
|
3083
3269
|
}
|
|
3270
|
+
const reports = [];
|
|
3271
|
+
const rejected = [];
|
|
3272
|
+
for (const candidate of candidates) {
|
|
3273
|
+
const result = validateReport(candidate);
|
|
3274
|
+
if (result.valid) {
|
|
3275
|
+
reports.push(result.report);
|
|
3276
|
+
} else {
|
|
3277
|
+
rejected.push({
|
|
3278
|
+
key: candidate?.id ?? "(unknown)",
|
|
3279
|
+
reason: result.reason,
|
|
3280
|
+
issues: result.issues.map((issue) => `${issue.path}: ${issue.message}`)
|
|
3281
|
+
});
|
|
3282
|
+
this.log.warn(`Rejected malformed report: ${result.reason}`);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
this.lastDiagnostics = { collected: candidates.length, accepted: reports.length, rejected };
|
|
3084
3286
|
return reports;
|
|
3085
3287
|
}
|
|
3086
3288
|
async saveReports(reports) {
|
|
@@ -3300,7 +3502,7 @@ function truncate5(text, max) {
|
|
|
3300
3502
|
|
|
3301
3503
|
// src/planner/model.ts
|
|
3302
3504
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
3303
|
-
import { z as
|
|
3505
|
+
import { z as z3 } from "zod";
|
|
3304
3506
|
|
|
3305
3507
|
// src/planner/context.ts
|
|
3306
3508
|
function buildPlannerContext(request) {
|
|
@@ -3353,21 +3555,21 @@ ${request.context}`);
|
|
|
3353
3555
|
}
|
|
3354
3556
|
|
|
3355
3557
|
// src/planner/model.ts
|
|
3356
|
-
var planStepJsonSchema =
|
|
3357
|
-
id:
|
|
3358
|
-
title:
|
|
3359
|
-
description:
|
|
3360
|
-
order:
|
|
3361
|
-
targets:
|
|
3362
|
-
acceptance_criteria:
|
|
3363
|
-
depends_on:
|
|
3364
|
-
source_findings:
|
|
3558
|
+
var planStepJsonSchema = z3.object({
|
|
3559
|
+
id: z3.string().optional(),
|
|
3560
|
+
title: z3.string().min(1),
|
|
3561
|
+
description: z3.string().optional(),
|
|
3562
|
+
order: z3.number().int().optional(),
|
|
3563
|
+
targets: z3.array(z3.string()).optional(),
|
|
3564
|
+
acceptance_criteria: z3.array(z3.string()).optional(),
|
|
3565
|
+
depends_on: z3.array(z3.string()).optional(),
|
|
3566
|
+
source_findings: z3.array(z3.string()).optional()
|
|
3365
3567
|
}).strict();
|
|
3366
|
-
var planJsonSchema =
|
|
3367
|
-
summary:
|
|
3368
|
-
steps:
|
|
3369
|
-
risks:
|
|
3370
|
-
notes:
|
|
3568
|
+
var planJsonSchema = z3.object({
|
|
3569
|
+
summary: z3.string().min(1),
|
|
3570
|
+
steps: z3.array(planStepJsonSchema).min(1),
|
|
3571
|
+
risks: z3.array(z3.string()).optional(),
|
|
3572
|
+
notes: z3.array(z3.string()).optional()
|
|
3371
3573
|
}).strict();
|
|
3372
3574
|
var ModelPlanner = class {
|
|
3373
3575
|
constructor(options) {
|
|
@@ -4056,7 +4258,11 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
4056
4258
|
agents,
|
|
4057
4259
|
roles,
|
|
4058
4260
|
store: new FileTaskStore(),
|
|
4059
|
-
...config.project.memory !== void 0 ? {
|
|
4261
|
+
...config.project.memory !== void 0 ? {
|
|
4262
|
+
memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
|
|
4263
|
+
required: config.project.memory.required
|
|
4264
|
+
})
|
|
4265
|
+
} : {},
|
|
4060
4266
|
audit: new FileAuditLog(),
|
|
4061
4267
|
log
|
|
4062
4268
|
});
|