open-multi-agent-kit 0.78.0 → 0.78.1
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/CHANGELOG.md +44 -15
- package/MATURITY.md +2 -2
- package/README.md +56 -26
- package/ROADMAP.md +36 -28
- package/dist/cli/register-basic-commands.js +3 -2
- package/dist/cli/register-mcp-dag-cron-screenshot-commands.js +2 -0
- package/dist/cli/register-tool-commands.js +11 -0
- package/dist/cli/register-workflow-commands.js +1 -0
- package/dist/cli/registry/tooling.js +3 -2
- package/dist/commands/chat/core.js +5 -0
- package/dist/commands/chat/native-root-loop.js +60 -0
- package/dist/commands/dag-from-spec.d.ts +1 -0
- package/dist/commands/dag-from-spec.js +61 -1
- package/dist/commands/graph.d.ts +62 -0
- package/dist/commands/graph.js +182 -0
- package/dist/commands/merge.d.ts +1 -0
- package/dist/commands/merge.js +88 -0
- package/dist/commands/parallel/core.js +3 -3
- package/dist/commands/provider.js +5 -3
- package/dist/commands/star.js +6 -1
- package/dist/commands/summary.d.ts +4 -1
- package/dist/commands/summary.js +103 -1
- package/dist/commands/team.d.ts +1 -0
- package/dist/commands/team.js +38 -0
- package/dist/contracts/provider-health.d.ts +42 -0
- package/dist/contracts/provider-health.js +9 -0
- package/dist/goal/intent-frame.d.ts +24 -0
- package/dist/goal/intent-frame.js +18 -0
- package/dist/memory/local-graph-memory-store.d.ts +15 -0
- package/dist/memory/local-graph-memory-store.js +176 -0
- package/dist/memory/memory-store.d.ts +18 -0
- package/dist/memory/memory-store.js +18 -0
- package/dist/orchestration/adaptorch-topology.d.ts +59 -0
- package/dist/orchestration/adaptorch-topology.js +194 -0
- package/dist/orchestration/capability-routing.d.ts +23 -0
- package/dist/orchestration/capability-routing.js +56 -0
- package/dist/orchestration/dag-compiler-types.d.ts +3 -0
- package/dist/orchestration/dag-compiler.js +14 -1
- package/dist/orchestration/parallel-orchestrator.d.ts +6 -0
- package/dist/orchestration/parallel-orchestrator.js +31 -0
- package/dist/providers/provider-health.d.ts +39 -0
- package/dist/providers/provider-health.js +161 -0
- package/dist/runtime/context-broker.d.ts +13 -4
- package/dist/runtime/context-broker.js +14 -1
- package/dist/runtime/headroom-policy.d.ts +37 -0
- package/dist/runtime/headroom-policy.js +122 -0
- package/dist/runtime/ouroboros-policy.d.ts +57 -0
- package/dist/runtime/ouroboros-policy.js +134 -0
- package/dist/runtime/runtime-backed-task-runner.js +9 -1
- package/dist/runtime/tool-dispatch-contracts.d.ts +57 -1
- package/dist/runtime/tool-dispatch-contracts.js +79 -3
- package/dist/safety/tool-authority-gate.d.ts +62 -0
- package/dist/safety/tool-authority-gate.js +108 -0
- package/dist/schema/provider.schema.d.ts +4 -4
- package/dist/util/first-run-star.d.ts +9 -0
- package/dist/util/first-run-star.js +42 -1
- package/dist/util/terminal-input.d.ts +20 -0
- package/dist/util/terminal-input.js +32 -0
- package/dist/util/update-check.d.ts +6 -1
- package/dist/util/update-check.js +35 -1
- package/docs/2026-06-08/critical-issues.md +20 -0
- package/docs/2026-06-08/improvements.md +14 -0
- package/docs/2026-06-08/init-checklist.md +25 -0
- package/docs/2026-06-08/plan.md +20 -0
- package/docs/getting-started.md +31 -3
- package/docs/integrations/ouroboros.md +96 -0
- package/docs/provider-maturity.md +1 -1
- package/docs/versioning.md +3 -3
- package/package.json +1 -1
- package/dist/native/linux-x64/omk-safety +0 -0
|
@@ -3,7 +3,9 @@ import { join } from "path";
|
|
|
3
3
|
import { writeFile, mkdir } from "fs/promises";
|
|
4
4
|
import { getProjectRoot, pathExists, getRunPath, getRunsDir } from "../util/fs.js";
|
|
5
5
|
import { header, label } from "../util/theme.js";
|
|
6
|
-
import { createDag } from "../orchestration/dag.js";
|
|
6
|
+
import { createDag, getExecutionWaves } from "../orchestration/dag.js";
|
|
7
|
+
import { createOmkJsonEnvelope } from "../util/json-envelope.js";
|
|
8
|
+
import { emitJson } from "../util/cli-contract.js";
|
|
7
9
|
function inferRole(description, explicitRole) {
|
|
8
10
|
if (explicitRole)
|
|
9
11
|
return explicitRole;
|
|
@@ -243,6 +245,61 @@ export async function loadSpecDag(specDir, options = {}) {
|
|
|
243
245
|
}
|
|
244
246
|
return tasksToDag(tasks, { parallel: options.parallel });
|
|
245
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Project the in-memory DAG artifact into the envelope `data` shape. The inner
|
|
250
|
+
* dag artifact (nodes) is preserved verbatim; edges/batches/stats are derived
|
|
251
|
+
* from the existing dependsOn graph (no new data sources).
|
|
252
|
+
*/
|
|
253
|
+
function buildDagJsonData(inputId, dag) {
|
|
254
|
+
const edges = dag.nodes.flatMap((node) => node.dependsOn.map((from) => ({ from, to: node.id })));
|
|
255
|
+
const batches = getExecutionWaves(dag).map((wave) => wave.map((node) => node.id));
|
|
256
|
+
return {
|
|
257
|
+
inputId,
|
|
258
|
+
nodes: dag.nodes,
|
|
259
|
+
edges,
|
|
260
|
+
batches,
|
|
261
|
+
stats: { nodes: dag.nodes.length, edges: edges.length, batches: batches.length },
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* JSON path for `omk dag from-spec --json`.
|
|
266
|
+
* Emits EXACTLY ONE omk.contract.v1 envelope to stdout (no banner, no ANSI) and
|
|
267
|
+
* never throws on a missing/empty tasks.md, so JSON stdout stays one envelope.
|
|
268
|
+
*/
|
|
269
|
+
async function emitDagFromSpecJson(targetDir, options) {
|
|
270
|
+
const started = Date.now();
|
|
271
|
+
let dag;
|
|
272
|
+
try {
|
|
273
|
+
dag = await loadSpecDag(targetDir, { parallel: options.parallel });
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
277
|
+
const empty = { nodes: [] };
|
|
278
|
+
emitJson(createOmkJsonEnvelope({
|
|
279
|
+
command: "dag",
|
|
280
|
+
status: "not-applicable",
|
|
281
|
+
ok: false,
|
|
282
|
+
data: buildDagJsonData(targetDir, empty),
|
|
283
|
+
warnings: [
|
|
284
|
+
{ code: "RUN_ARTIFACT_MISSING", message, recoverable: true, severity: "warning" },
|
|
285
|
+
],
|
|
286
|
+
durationMs: Date.now() - started,
|
|
287
|
+
}));
|
|
288
|
+
return empty;
|
|
289
|
+
}
|
|
290
|
+
if (options.output) {
|
|
291
|
+
await mkdir(getRunsDir(getProjectRoot()), { recursive: true });
|
|
292
|
+
await writeFile(options.output, JSON.stringify(dag, null, 2));
|
|
293
|
+
}
|
|
294
|
+
emitJson(createOmkJsonEnvelope({
|
|
295
|
+
command: "dag",
|
|
296
|
+
status: "passed",
|
|
297
|
+
ok: true,
|
|
298
|
+
data: buildDagJsonData(targetDir, dag),
|
|
299
|
+
durationMs: Date.now() - started,
|
|
300
|
+
}));
|
|
301
|
+
return dag;
|
|
302
|
+
}
|
|
246
303
|
export async function dagFromSpecCommand(specDir, options = {}) {
|
|
247
304
|
const root = getProjectRoot();
|
|
248
305
|
let targetDir = specDir;
|
|
@@ -266,6 +323,9 @@ export async function dagFromSpecCommand(specDir, options = {}) {
|
|
|
266
323
|
targetDir = getRunPath(options.run, undefined, root);
|
|
267
324
|
}
|
|
268
325
|
}
|
|
326
|
+
if (options.json === true || process.argv.includes("--json")) {
|
|
327
|
+
return emitDagFromSpecJson(targetDir, { output: options.output, parallel: options.parallel });
|
|
328
|
+
}
|
|
269
329
|
const dag = await loadSpecDag(targetDir, { parallel: options.parallel });
|
|
270
330
|
const dagJson = JSON.stringify(dag, null, 2);
|
|
271
331
|
if (options.output) {
|
package/dist/commands/graph.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { RunManifest } from "../contracts/run.js";
|
|
2
|
+
import type { GraphState } from "../memory/local-graph-memory-store.js";
|
|
1
3
|
export interface GraphViewCommandOptions {
|
|
2
4
|
input?: string;
|
|
3
5
|
output?: string;
|
|
@@ -7,3 +9,63 @@ export interface GraphViewCommandOptions {
|
|
|
7
9
|
open?: boolean;
|
|
8
10
|
}
|
|
9
11
|
export declare function graphViewCommand(options?: GraphViewCommandOptions): Promise<void>;
|
|
12
|
+
export type GraphAuditVerdict = "passed" | "partial" | "failed";
|
|
13
|
+
export interface GraphAuditCounts {
|
|
14
|
+
providerRoute: number;
|
|
15
|
+
provider: number;
|
|
16
|
+
evidence: number;
|
|
17
|
+
decision: number;
|
|
18
|
+
artifact: number;
|
|
19
|
+
}
|
|
20
|
+
export interface GraphAuditMismatch {
|
|
21
|
+
field: string;
|
|
22
|
+
graph: number | string | null;
|
|
23
|
+
manifest: number | string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface GraphAuditDangler {
|
|
26
|
+
edgeId: string;
|
|
27
|
+
type: string;
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
missing: "from" | "to";
|
|
31
|
+
}
|
|
32
|
+
export interface GraphAuditRunResult {
|
|
33
|
+
runId: string;
|
|
34
|
+
runNodeFound: boolean;
|
|
35
|
+
orphan: boolean;
|
|
36
|
+
manifestPresent: boolean;
|
|
37
|
+
counts: GraphAuditCounts;
|
|
38
|
+
mismatches: GraphAuditMismatch[];
|
|
39
|
+
danglers: GraphAuditDangler[];
|
|
40
|
+
verdict: GraphAuditVerdict;
|
|
41
|
+
notes: string[];
|
|
42
|
+
}
|
|
43
|
+
export interface GraphAuditReport {
|
|
44
|
+
schemaVersion: "omk.graph-audit.v1";
|
|
45
|
+
verdict: GraphAuditVerdict;
|
|
46
|
+
statePath?: string;
|
|
47
|
+
generatedAt: string;
|
|
48
|
+
summary: {
|
|
49
|
+
total: number;
|
|
50
|
+
passed: number;
|
|
51
|
+
partial: number;
|
|
52
|
+
failed: number;
|
|
53
|
+
};
|
|
54
|
+
runs: GraphAuditRunResult[];
|
|
55
|
+
}
|
|
56
|
+
export interface GraphAuditCommandOptions {
|
|
57
|
+
run?: string;
|
|
58
|
+
input?: string;
|
|
59
|
+
json?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Pure auditor: cross-checks linked run subgraphs against their manifests.
|
|
63
|
+
* For each run it asserts the Run node exists with >=1 provider-route, provider,
|
|
64
|
+
* evidence, decision, and artifact edge, cross-checks evidence counts/artifact
|
|
65
|
+
* counts/status against the manifest, and flags orphan runs and dangling edges.
|
|
66
|
+
*/
|
|
67
|
+
export declare function auditGraphRuns(state: GraphState, entries: ReadonlyArray<{
|
|
68
|
+
runId: string;
|
|
69
|
+
manifest?: RunManifest | null;
|
|
70
|
+
}>, statePath?: string): GraphAuditReport;
|
|
71
|
+
export declare function graphAuditCommand(options?: GraphAuditCommandOptions): Promise<void>;
|
package/dist/commands/graph.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
1
2
|
import { join, resolve } from "path";
|
|
2
3
|
import { getProjectRoot } from "../util/fs.js";
|
|
3
4
|
import { createGraphView } from "../memory/graph-viewer.js";
|
|
4
5
|
import { header, label, status } from "../util/theme.js";
|
|
6
|
+
import { createOmkJsonEnvelope } from "../util/json-envelope.js";
|
|
7
|
+
import { getRunArtifactPath, listValidRunIds, validateRunId } from "../util/run-store.js";
|
|
8
|
+
import { RunManifestSchema } from "../schema/run-manifest.schema.js";
|
|
5
9
|
export async function graphViewCommand(options = {}) {
|
|
6
10
|
const root = getProjectRoot();
|
|
7
11
|
const inputPath = options.input ? resolve(root, options.input) : join(root, ".omk", "memory", "graph-state.json");
|
|
@@ -24,3 +28,181 @@ export async function graphViewCommand(options = {}) {
|
|
|
24
28
|
console.log(label("Edges", String(result.edgeCount)));
|
|
25
29
|
console.log(status.ok("Graph HTML generated"));
|
|
26
30
|
}
|
|
31
|
+
const EVIDENCE_SUMMARY_FIELDS = ["required", "passed", "failed", "missing"];
|
|
32
|
+
/**
|
|
33
|
+
* Pure auditor: cross-checks linked run subgraphs against their manifests.
|
|
34
|
+
* For each run it asserts the Run node exists with >=1 provider-route, provider,
|
|
35
|
+
* evidence, decision, and artifact edge, cross-checks evidence counts/artifact
|
|
36
|
+
* counts/status against the manifest, and flags orphan runs and dangling edges.
|
|
37
|
+
*/
|
|
38
|
+
export function auditGraphRuns(state, entries, statePath) {
|
|
39
|
+
const nodeIds = new Set(state.nodes.map((node) => node.id));
|
|
40
|
+
const runs = entries.map((entry) => auditSingleRun(state, nodeIds, entry.runId, entry.manifest ?? null));
|
|
41
|
+
const summary = { total: runs.length, passed: 0, partial: 0, failed: 0 };
|
|
42
|
+
for (const run of runs)
|
|
43
|
+
summary[run.verdict] += 1;
|
|
44
|
+
const verdict = summary.failed > 0 ? "failed" : summary.partial > 0 ? "partial" : "passed";
|
|
45
|
+
return {
|
|
46
|
+
schemaVersion: "omk.graph-audit.v1",
|
|
47
|
+
verdict,
|
|
48
|
+
statePath,
|
|
49
|
+
generatedAt: new Date().toISOString(),
|
|
50
|
+
summary,
|
|
51
|
+
runs,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function auditSingleRun(state, nodeIds, runId, manifest) {
|
|
55
|
+
const notes = [];
|
|
56
|
+
const mismatches = [];
|
|
57
|
+
const danglers = [];
|
|
58
|
+
const counts = { providerRoute: 0, provider: 0, evidence: 0, decision: 0, artifact: 0 };
|
|
59
|
+
const manifestPresent = manifest !== null;
|
|
60
|
+
const runNode = state.nodes.find((node) => node.type === "Run" && node.properties.runId === runId);
|
|
61
|
+
if (!runNode) {
|
|
62
|
+
notes.push("run node not found in graph");
|
|
63
|
+
if (!manifestPresent)
|
|
64
|
+
notes.push("run-manifest.json not found");
|
|
65
|
+
return { runId, runNodeFound: false, orphan: false, manifestPresent, counts, mismatches, danglers, verdict: "failed", notes };
|
|
66
|
+
}
|
|
67
|
+
const outgoing = state.edges.filter((edge) => edge.from === runNode.id);
|
|
68
|
+
const routeEdges = outgoing.filter((edge) => edge.type === "HAS_PROVIDER_ROUTE");
|
|
69
|
+
counts.providerRoute = routeEdges.length;
|
|
70
|
+
counts.evidence = outgoing.filter((edge) => edge.type === "HAS_EVIDENCE").length;
|
|
71
|
+
counts.decision = outgoing.filter((edge) => edge.type === "HAS_DECISION").length;
|
|
72
|
+
counts.artifact = outgoing.filter((edge) => edge.type === "TOUCHES_FILE").length;
|
|
73
|
+
const routeToEdges = routeEdges.flatMap((routeEdge) => state.edges.filter((edge) => edge.from === routeEdge.to && edge.type === "ROUTES_TO"));
|
|
74
|
+
counts.provider = routeToEdges.length;
|
|
75
|
+
const orphan = outgoing.length === 0;
|
|
76
|
+
// Dangler detection across the run subgraph (run edges + route ROUTES_TO).
|
|
77
|
+
for (const edge of [...outgoing, ...routeToEdges]) {
|
|
78
|
+
if (!nodeIds.has(edge.from))
|
|
79
|
+
danglers.push({ edgeId: edge.id, type: edge.type, from: edge.from, to: edge.to, missing: "from" });
|
|
80
|
+
if (!nodeIds.has(edge.to))
|
|
81
|
+
danglers.push({ edgeId: edge.id, type: edge.type, from: edge.from, to: edge.to, missing: "to" });
|
|
82
|
+
}
|
|
83
|
+
if (manifest) {
|
|
84
|
+
const evidenceNode = outgoing
|
|
85
|
+
.filter((edge) => edge.type === "HAS_EVIDENCE")
|
|
86
|
+
.map((edge) => state.nodes.find((node) => node.id === edge.to))
|
|
87
|
+
.find((node) => node !== undefined);
|
|
88
|
+
if (evidenceNode) {
|
|
89
|
+
for (const field of EVIDENCE_SUMMARY_FIELDS) {
|
|
90
|
+
const graphValue = Number(evidenceNode.properties[field] ?? Number.NaN);
|
|
91
|
+
const manifestValue = manifest.evidenceSummary[field];
|
|
92
|
+
if (graphValue !== manifestValue) {
|
|
93
|
+
mismatches.push({ field: `evidence.${field}`, graph: Number.isNaN(graphValue) ? null : graphValue, manifest: manifestValue });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
notes.push("evidence node missing for cross-check");
|
|
99
|
+
}
|
|
100
|
+
if (counts.artifact !== manifest.artifacts.length) {
|
|
101
|
+
mismatches.push({ field: "artifact.count", graph: counts.artifact, manifest: manifest.artifacts.length });
|
|
102
|
+
}
|
|
103
|
+
const graphStatus = String(runNode.properties.status ?? "");
|
|
104
|
+
if (graphStatus !== manifest.status) {
|
|
105
|
+
mismatches.push({ field: "run.status", graph: graphStatus, manifest: manifest.status });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
notes.push("run-manifest.json not found; structural audit only");
|
|
110
|
+
}
|
|
111
|
+
const hasAllEdges = counts.providerRoute >= 1 &&
|
|
112
|
+
counts.provider >= 1 &&
|
|
113
|
+
counts.evidence >= 1 &&
|
|
114
|
+
counts.decision >= 1 &&
|
|
115
|
+
counts.artifact >= 1;
|
|
116
|
+
let verdict;
|
|
117
|
+
if (orphan || !hasAllEdges)
|
|
118
|
+
verdict = "failed";
|
|
119
|
+
else if (danglers.length > 0 || mismatches.length > 0 || !manifestPresent)
|
|
120
|
+
verdict = "partial";
|
|
121
|
+
else
|
|
122
|
+
verdict = "passed";
|
|
123
|
+
return { runId, runNodeFound: true, orphan, manifestPresent, counts, mismatches, danglers, verdict, notes };
|
|
124
|
+
}
|
|
125
|
+
async function loadGraphStateForAudit(statePath) {
|
|
126
|
+
try {
|
|
127
|
+
const raw = await readFile(statePath, "utf-8");
|
|
128
|
+
const parsed = JSON.parse(raw);
|
|
129
|
+
if (Array.isArray(parsed.nodes) && Array.isArray(parsed.edges)) {
|
|
130
|
+
return { ...parsed, nodes: parsed.nodes, edges: parsed.edges };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// fall through to an empty state
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
version: 1,
|
|
138
|
+
ontology: { version: "", classes: [], relationTypes: [], description: "" },
|
|
139
|
+
project: { key: "", name: "", root: "" },
|
|
140
|
+
updatedAt: new Date().toISOString(),
|
|
141
|
+
nodes: [],
|
|
142
|
+
edges: [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function loadRunManifestForAudit(runId, root) {
|
|
146
|
+
try {
|
|
147
|
+
const raw = await readFile(getRunArtifactPath(runId, "run-manifest.json", root), "utf-8");
|
|
148
|
+
const parsed = RunManifestSchema.safeParse(JSON.parse(raw));
|
|
149
|
+
return parsed.success ? parsed.data : null;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export async function graphAuditCommand(options = {}) {
|
|
156
|
+
const startedAt = Date.now();
|
|
157
|
+
const root = getProjectRoot();
|
|
158
|
+
const statePath = options.input ? resolve(root, options.input) : join(root, ".omk", "memory", "graph-state.json");
|
|
159
|
+
const state = await loadGraphStateForAudit(statePath);
|
|
160
|
+
const runIds = options.run ? [validateRunId(options.run)] : await listValidRunIds(root);
|
|
161
|
+
const entries = [];
|
|
162
|
+
for (const runId of runIds) {
|
|
163
|
+
entries.push({ runId, manifest: await loadRunManifestForAudit(runId, root) });
|
|
164
|
+
}
|
|
165
|
+
const report = auditGraphRuns(state, entries, statePath);
|
|
166
|
+
if (options.json) {
|
|
167
|
+
console.log(JSON.stringify(createOmkJsonEnvelope({
|
|
168
|
+
command: "graph",
|
|
169
|
+
status: report.verdict,
|
|
170
|
+
data: report,
|
|
171
|
+
durationMs: Date.now() - startedAt,
|
|
172
|
+
})));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
printGraphAuditReport(report);
|
|
176
|
+
if (report.verdict === "failed")
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
}
|
|
179
|
+
function printGraphAuditReport(report) {
|
|
180
|
+
console.log(header("OMK Graph Audit"));
|
|
181
|
+
if (report.statePath)
|
|
182
|
+
console.log(label("State", report.statePath));
|
|
183
|
+
console.log(label("Runs", `${report.summary.total} (passed ${report.summary.passed}, partial ${report.summary.partial}, failed ${report.summary.failed})`));
|
|
184
|
+
for (const run of report.runs) {
|
|
185
|
+
const line = `${run.runId} — routes ${run.counts.providerRoute}/providers ${run.counts.provider}/evidence ${run.counts.evidence}/decisions ${run.counts.decision}/artifacts ${run.counts.artifact}`;
|
|
186
|
+
if (run.verdict === "passed")
|
|
187
|
+
console.log(status.ok(line));
|
|
188
|
+
else if (run.verdict === "partial")
|
|
189
|
+
console.log(status.warn(line));
|
|
190
|
+
else
|
|
191
|
+
console.log(status.fail(line));
|
|
192
|
+
for (const mismatch of run.mismatches) {
|
|
193
|
+
console.log(label(" mismatch", `${mismatch.field}: graph=${String(mismatch.graph)} manifest=${String(mismatch.manifest)}`));
|
|
194
|
+
}
|
|
195
|
+
for (const dangler of run.danglers) {
|
|
196
|
+
console.log(label(" dangler", `${dangler.type} missing ${dangler.missing} (${dangler.from} -> ${dangler.to})`));
|
|
197
|
+
}
|
|
198
|
+
for (const note of run.notes)
|
|
199
|
+
console.log(label(" note", note));
|
|
200
|
+
}
|
|
201
|
+
const verdictLine = `Verdict: ${report.verdict}`;
|
|
202
|
+
if (report.verdict === "passed")
|
|
203
|
+
console.log(status.ok(verdictLine));
|
|
204
|
+
else if (report.verdict === "partial")
|
|
205
|
+
console.log(status.warn(verdictLine));
|
|
206
|
+
else
|
|
207
|
+
console.log(status.fail(verdictLine));
|
|
208
|
+
}
|
package/dist/commands/merge.d.ts
CHANGED
package/dist/commands/merge.js
CHANGED
|
@@ -9,7 +9,95 @@ import { runQualityGate } from "../mcp/quality-gate.js";
|
|
|
9
9
|
import { readTextFile } from "../util/fs.js";
|
|
10
10
|
import { getOmkResourceSettings } from "../util/resource-profile.js";
|
|
11
11
|
import { defaultScopedRoleAgentFile, writeScopedAgentFile } from "../util/scoped-agent-file.js";
|
|
12
|
+
import { createOmkJsonEnvelope } from "../util/json-envelope.js";
|
|
13
|
+
import { emitJson } from "../util/cli-contract.js";
|
|
14
|
+
/**
|
|
15
|
+
* JSON path for `omk merge --json`.
|
|
16
|
+
* Read-only preview: resolves the run, collects worktree diffs (git diff +
|
|
17
|
+
* `git apply --check`) and selects a winner by strategy, but does NOT run the
|
|
18
|
+
* reviewer, tests, patch apply, or quality gate. Emits exactly one
|
|
19
|
+
* omk.contract.v1 envelope (no banner, no ANSI) and never calls process.exit.
|
|
20
|
+
*/
|
|
21
|
+
async function emitMergeJson(options) {
|
|
22
|
+
const started = Date.now();
|
|
23
|
+
const root = getProjectRoot();
|
|
24
|
+
const strategy = (options.strategy ?? "first").trim().toLowerCase();
|
|
25
|
+
const dryRun = Boolean(options.dryRun);
|
|
26
|
+
const emitNotApplicable = (runId, code, message) => {
|
|
27
|
+
emitJson(createOmkJsonEnvelope({
|
|
28
|
+
command: "merge",
|
|
29
|
+
status: "not-applicable",
|
|
30
|
+
ok: false,
|
|
31
|
+
...(runId ? { runId } : {}),
|
|
32
|
+
data: { runId, strategy, dryRun, merged: null, conflicts: [], applied: 0 },
|
|
33
|
+
warnings: [{ code, message, recoverable: true, severity: "warning" }],
|
|
34
|
+
durationMs: Date.now() - started,
|
|
35
|
+
}));
|
|
36
|
+
};
|
|
37
|
+
if (!(await isGitRepo())) {
|
|
38
|
+
emitNotApplicable(null, "INTERNAL_ERROR", "Not a git repository.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const runsDir = getOmkPath("runs");
|
|
42
|
+
if (!(await pathExists(runsDir))) {
|
|
43
|
+
emitNotApplicable(null, "RUN_ARTIFACT_MISSING", "No runs found.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let runId = options.run ?? options.runId ?? "latest";
|
|
47
|
+
if (runId === "latest") {
|
|
48
|
+
const entries = await readdir(runsDir, { withFileTypes: true });
|
|
49
|
+
const runs = entries.filter((e) => e.isDirectory()).sort().reverse();
|
|
50
|
+
if (runs.length === 0) {
|
|
51
|
+
emitNotApplicable(null, "RUN_ARTIFACT_MISSING", "No runs found.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
runId = runs[0].name;
|
|
55
|
+
}
|
|
56
|
+
const worktreesDir = getOmkPath(`worktrees/${runId}`);
|
|
57
|
+
if (!(await pathExists(worktreesDir))) {
|
|
58
|
+
emitNotApplicable(runId, "RUN_ARTIFACT_MISSING", "No worktrees found for run.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const workerNames = await readdir(worktreesDir, { withFileTypes: true }).then((e) => e.filter((d) => d.isDirectory()).map((d) => d.name));
|
|
62
|
+
const currentBranch = await getCurrentBranch();
|
|
63
|
+
if (!currentBranch) {
|
|
64
|
+
emitNotApplicable(runId, "INTERNAL_ERROR", "Could not determine current branch.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const workers = [];
|
|
68
|
+
for (const name of workerNames) {
|
|
69
|
+
const wtPath = join(worktreesDir, name);
|
|
70
|
+
const diffResult = await runShell("git", ["-C", wtPath, "diff", currentBranch], { timeout: 15000 });
|
|
71
|
+
if (diffResult.failed || !diffResult.stdout.trim())
|
|
72
|
+
continue;
|
|
73
|
+
const diff = diffResult.stdout;
|
|
74
|
+
const diffLines = diff.split("\n").length;
|
|
75
|
+
const applyCheck = await runShell("git", ["apply", "--check"], { cwd: root, input: diff, timeout: 15000 });
|
|
76
|
+
workers.push({ name, path: wtPath, diff, diffLines, canApply: !applyCheck.failed });
|
|
77
|
+
}
|
|
78
|
+
const winner = selectWinner(workers, strategy);
|
|
79
|
+
const data = {
|
|
80
|
+
runId,
|
|
81
|
+
strategy,
|
|
82
|
+
dryRun,
|
|
83
|
+
merged: winner?.name ?? null,
|
|
84
|
+
conflicts: workers.filter((w) => !w.canApply).map((w) => w.name),
|
|
85
|
+
applied: 0,
|
|
86
|
+
};
|
|
87
|
+
emitJson(createOmkJsonEnvelope({
|
|
88
|
+
command: "merge",
|
|
89
|
+
status: workers.length === 0 ? "not-applicable" : "dry-run",
|
|
90
|
+
ok: workers.length > 0,
|
|
91
|
+
runId,
|
|
92
|
+
data,
|
|
93
|
+
durationMs: Date.now() - started,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
12
96
|
export async function mergeCommand(options) {
|
|
97
|
+
if (options.json === true || process.argv.includes("--json")) {
|
|
98
|
+
await emitMergeJson(options);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
13
101
|
const root = getProjectRoot();
|
|
14
102
|
const strategy = (options.strategy ?? "first").trim().toLowerCase();
|
|
15
103
|
const dryRun = Boolean(options.dryRun);
|
|
@@ -11,7 +11,7 @@ import { writeMemoryRecallSummary } from "../../util/chat-startup.js";
|
|
|
11
11
|
import { normalizeProviderPolicy, parseProviderModelArg } from "../../providers/model-registry.js";
|
|
12
12
|
import { parseExecutionPromptPolicy } from "../../util/execution-selection.js";
|
|
13
13
|
import { analyzeUserIntent } from "../../goal/intake.js";
|
|
14
|
-
import { buildIntentFrame } from "../../goal/intent-frame.js";
|
|
14
|
+
import { buildIntentFrame, buildIntentFrameWithOuroboros } from "../../goal/intent-frame.js";
|
|
15
15
|
import { renderCapabilityRoutingArtifact } from "../../orchestration/capability-routing.js";
|
|
16
16
|
import { ensureCompletionArtifactContract } from "../../orchestration/completion-artifacts.js";
|
|
17
17
|
import { buildParallelRouteDecision, resolveParallelCommandExecutionDecision, createInteractiveRunState, createExecutableDagFromState } from "./orchestrator.js";
|
|
@@ -70,7 +70,7 @@ export async function parallelCommand(goal, options = {}) {
|
|
|
70
70
|
const { loadSpecDag } = await import("../dag-from-spec.js");
|
|
71
71
|
const specDag = await loadSpecDag(options.fromSpec, { parallel: true });
|
|
72
72
|
effectiveGoal = goal ?? `spec: ${specDag.nodes[0]?.name ?? options.fromSpec}`;
|
|
73
|
-
intentFrame =
|
|
73
|
+
intentFrame = await buildIntentFrameWithOuroboros(effectiveGoal);
|
|
74
74
|
specNodes = specDag.nodes.map((node) => {
|
|
75
75
|
const { status: _status, retries: _retries, ...def } = node;
|
|
76
76
|
void _status;
|
|
@@ -79,7 +79,7 @@ export async function parallelCommand(goal, options = {}) {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
|
-
intentFrame = intentFrame ??
|
|
82
|
+
intentFrame = intentFrame ?? (await buildIntentFrameWithOuroboros(effectiveGoal));
|
|
83
83
|
}
|
|
84
84
|
const resolvedIntent = options.intent ?? analyzeUserIntent(effectiveGoal);
|
|
85
85
|
const executionDecision = options.executionDecision ?? await resolveParallelCommandExecutionDecision({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { checkDeepSeekBalance } from "../providers/deepseek/deepseek-balance.js";
|
|
2
2
|
import { forceDisableDeepSeek, getDeepSeekProviderStatus, resolveDeepSeekApiKey, setDeepSeekApiKey, setDeepSeekEnabled, } from "../providers/deepseek/deepseek-config.js";
|
|
3
3
|
import { normalizeProviderId, providerDoctorStatus, readProviderRegistry, setProviderDefaults, setProviderConfig, setProviderEnabled, } from "../providers/model-registry.js";
|
|
4
|
+
import { toProviderHealth } from "../providers/provider-health.js";
|
|
4
5
|
import { maskSensitiveText } from "../util/secret-mask.js";
|
|
5
6
|
import { status, label, header, style } from "../util/theme.js";
|
|
6
7
|
const PROVIDER_PROFILES = [
|
|
@@ -95,7 +96,7 @@ export async function providerDoctorCommand(provider, options = {}) {
|
|
|
95
96
|
const normalized = normalizeProviderId(target);
|
|
96
97
|
const payload = await providerDoctorStatus(normalized === "auto" ? "kimi" : normalized);
|
|
97
98
|
if (options.json) {
|
|
98
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
99
|
+
console.log(JSON.stringify({ ...payload, health: toProviderHealth(payload) }, null, 2));
|
|
99
100
|
}
|
|
100
101
|
else {
|
|
101
102
|
console.log(header("Provider doctor"));
|
|
@@ -125,13 +126,14 @@ export async function providerDoctorCommand(provider, options = {}) {
|
|
|
125
126
|
disableForRun: true,
|
|
126
127
|
};
|
|
127
128
|
if (options.json) {
|
|
128
|
-
|
|
129
|
+
const deepseekDoctor = {
|
|
129
130
|
...result,
|
|
130
131
|
enabled: providerStatus.enabled,
|
|
131
132
|
disabledBy: providerStatus.disabledBy,
|
|
132
133
|
apiKeySet: providerStatus.apiKeySet,
|
|
133
134
|
apiKeySource: providerStatus.apiKeySource,
|
|
134
|
-
}
|
|
135
|
+
};
|
|
136
|
+
console.log(JSON.stringify({ ...deepseekDoctor, health: toProviderHealth(deepseekDoctor) }, null, 2));
|
|
135
137
|
}
|
|
136
138
|
else {
|
|
137
139
|
console.log(header("Provider doctor"));
|
package/dist/commands/star.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { starGitHubRepo, getStarPromptSummary } from "../util/first-run-star.js";
|
|
1
|
+
import { starGitHubRepo, getStarPromptSummary, openRepoInBrowser, parseGitHubRepoSlug } from "../util/first-run-star.js";
|
|
2
2
|
import { OMK_REPO_URL } from "../util/version.js";
|
|
3
|
+
import { style } from "../util/theme.js";
|
|
3
4
|
export async function starCommand(options = {}) {
|
|
4
5
|
if (options.status) {
|
|
5
6
|
const summary = await getStarPromptSummary();
|
|
@@ -25,6 +26,10 @@ export async function starCommand(options = {}) {
|
|
|
25
26
|
}
|
|
26
27
|
catch (e) {
|
|
27
28
|
console.error("Failed to star:", e instanceof Error ? e.message : String(e));
|
|
29
|
+
const slug = parseGitHubRepoSlug(OMK_REPO_URL);
|
|
30
|
+
if (slug)
|
|
31
|
+
console.error(style.gray(`Visit ${style.cream(`https://github.com/${slug}`)} to star manually.`));
|
|
32
|
+
await openRepoInBrowser(OMK_REPO_URL);
|
|
28
33
|
process.exit(1);
|
|
29
34
|
}
|
|
30
35
|
}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface SummaryLatestCommandOptions {
|
|
2
|
+
json?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export declare function summaryLatestCommand(options?: SummaryLatestCommandOptions): Promise<void>;
|
|
2
5
|
export declare function summaryShowCommand(runId?: string): Promise<void>;
|
package/dist/commands/summary.js
CHANGED
|
@@ -2,6 +2,8 @@ import { readFile, writeFile, readdir, stat } from "fs/promises";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { getProjectRoot, pathExists, getRunsDir, getRunPath } from "../util/fs.js";
|
|
4
4
|
import { style, header, status, label } from "../util/theme.js";
|
|
5
|
+
import { createOmkJsonEnvelope } from "../util/json-envelope.js";
|
|
6
|
+
import { emitJson } from "../util/cli-contract.js";
|
|
5
7
|
function computeProviderRouting(nodes) {
|
|
6
8
|
const byProvider = {};
|
|
7
9
|
const fallbacks = [];
|
|
@@ -257,7 +259,107 @@ function generateReportMd(s) {
|
|
|
257
259
|
lines.push("");
|
|
258
260
|
return lines.join("\n");
|
|
259
261
|
}
|
|
260
|
-
|
|
262
|
+
function deriveRunRollupStatus(s) {
|
|
263
|
+
if (s.failed > 0)
|
|
264
|
+
return "failed";
|
|
265
|
+
if (s.blocked > 0)
|
|
266
|
+
return "blocked";
|
|
267
|
+
if (s.running > 0 || s.pending > 0)
|
|
268
|
+
return "running";
|
|
269
|
+
if (s.totalNodes > 0 && s.done === s.totalNodes)
|
|
270
|
+
return "passed";
|
|
271
|
+
return "partial";
|
|
272
|
+
}
|
|
273
|
+
function buildSummaryJsonData(summary) {
|
|
274
|
+
const data = {
|
|
275
|
+
runId: summary.runId,
|
|
276
|
+
status: deriveRunRollupStatus(summary),
|
|
277
|
+
nodes: { total: summary.totalNodes, passed: summary.done, failed: summary.failed },
|
|
278
|
+
successRate: summary.successRate,
|
|
279
|
+
durationMs: summary.durationMs,
|
|
280
|
+
startedAt: summary.startedAt,
|
|
281
|
+
};
|
|
282
|
+
if (summary.completedAt)
|
|
283
|
+
data.completedAt = summary.completedAt;
|
|
284
|
+
if (summary.providerRouting.attempts > 0) {
|
|
285
|
+
data.providerRoute = {
|
|
286
|
+
attempts: summary.providerRouting.attempts,
|
|
287
|
+
byProvider: summary.providerRouting.byProvider,
|
|
288
|
+
fallbacks: summary.providerRouting.fallbacks.length,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const evidenceRefs = summary.nodes.flatMap((node) => (node.evidence ?? []).map((e) => ({ nodeId: node.id, gate: e.gate, passed: e.passed })));
|
|
292
|
+
if (evidenceRefs.length > 0)
|
|
293
|
+
data.evidenceRefs = evidenceRefs;
|
|
294
|
+
return data;
|
|
295
|
+
}
|
|
296
|
+
function emptySummaryJsonData(runId) {
|
|
297
|
+
return {
|
|
298
|
+
runId,
|
|
299
|
+
status: "no-runs",
|
|
300
|
+
nodes: { total: 0, passed: 0, failed: 0 },
|
|
301
|
+
successRate: 0,
|
|
302
|
+
durationMs: 0,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Read-only JSON path for `omk summary --json`.
|
|
307
|
+
* Emits EXACTLY ONE omk.contract.v1 envelope to stdout (no banner, no ANSI),
|
|
308
|
+
* never calls process.exit, and never writes summary.md/report.md side effects.
|
|
309
|
+
*/
|
|
310
|
+
async function emitSummaryJson() {
|
|
311
|
+
const started = Date.now();
|
|
312
|
+
const root = getProjectRoot();
|
|
313
|
+
const latestId = await findLatestRunId(root);
|
|
314
|
+
if (!latestId) {
|
|
315
|
+
emitJson(createOmkJsonEnvelope({
|
|
316
|
+
command: "summary",
|
|
317
|
+
status: "not-applicable",
|
|
318
|
+
ok: false,
|
|
319
|
+
data: emptySummaryJsonData(null),
|
|
320
|
+
warnings: [
|
|
321
|
+
{ code: "RUN_ARTIFACT_MISSING", message: "No runs found.", recoverable: true, severity: "warning" },
|
|
322
|
+
],
|
|
323
|
+
durationMs: Date.now() - started,
|
|
324
|
+
}));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const state = await loadRunState(root, latestId);
|
|
328
|
+
if (!state) {
|
|
329
|
+
emitJson(createOmkJsonEnvelope({
|
|
330
|
+
command: "summary",
|
|
331
|
+
status: "not-applicable",
|
|
332
|
+
ok: false,
|
|
333
|
+
runId: latestId,
|
|
334
|
+
data: emptySummaryJsonData(latestId),
|
|
335
|
+
warnings: [
|
|
336
|
+
{
|
|
337
|
+
code: "RUN_ARTIFACT_MISSING",
|
|
338
|
+
message: `Run state not found for ${latestId}`,
|
|
339
|
+
recoverable: true,
|
|
340
|
+
severity: "warning",
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
durationMs: Date.now() - started,
|
|
344
|
+
}));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const summary = computeSummary(state);
|
|
348
|
+
emitJson(createOmkJsonEnvelope({
|
|
349
|
+
command: "summary",
|
|
350
|
+
status: "passed",
|
|
351
|
+
ok: true,
|
|
352
|
+
runId: summary.runId,
|
|
353
|
+
data: buildSummaryJsonData(summary),
|
|
354
|
+
durationMs: Date.now() - started,
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
export async function summaryLatestCommand(options = {}) {
|
|
358
|
+
const jsonMode = options.json === true || process.argv.includes("--json");
|
|
359
|
+
if (jsonMode) {
|
|
360
|
+
await emitSummaryJson();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
261
363
|
const root = await getProjectRoot();
|
|
262
364
|
const latestId = await findLatestRunId(root);
|
|
263
365
|
if (!latestId) {
|
package/dist/commands/team.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { TeamRuntimeStatus } from "../contracts/orchestration.js";
|
|
|
2
2
|
export declare function teamCommand(options?: {
|
|
3
3
|
workers?: string;
|
|
4
4
|
runId?: string;
|
|
5
|
+
json?: boolean;
|
|
5
6
|
}): Promise<void>;
|
|
6
7
|
export declare function writeTeamRunState(root: string, runId: string, workerCount: number, session?: string, profile?: string): Promise<string>;
|
|
7
8
|
export declare function buildExpectedTeamRuntimeStatus(session: string, statePath: string, workerCount: number, runtimeStatus: TeamRuntimeStatus["status"], presentWindows?: Map<string, number>): TeamRuntimeStatus;
|