omp-conductor 0.18.0 → 0.18.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/README.md +34 -0
- package/REFERENCE.md +60 -10
- package/agents/to-spec.md +90 -0
- package/package.json +2 -1
- package/schema/config.schema.json +29 -0
- package/src/admission.ts +204 -75
- package/src/ask.ts +268 -7
- package/src/board.ts +17 -3
- package/src/briefs/orchestrator.md +42 -14
- package/src/briefs/to-spec.md +84 -0
- package/src/briefs/worker.md +2 -1
- package/src/cli.ts +2 -0
- package/src/command-help.ts +11 -0
- package/src/command-manifest.ts +22 -0
- package/src/commands/context.ts +1 -0
- package/src/commands/drain.ts +176 -0
- package/src/commands/extend.ts +6 -10
- package/src/commands/status.ts +5 -1
- package/src/commands/watch.ts +50 -2
- package/src/commands/worker.ts +9 -10
- package/src/config-schema.ts +24 -0
- package/src/config.ts +42 -1
- package/src/daemon.ts +965 -36
- package/src/dashboard/app.js +4 -1
- package/src/dashboard/server.ts +5 -2
- package/src/decisions.ts +235 -17
- package/src/diff-flags.ts +75 -1
- package/src/doctor.ts +52 -0
- package/src/escalate.ts +9 -3
- package/src/failure-class.ts +28 -2
- package/src/fleet.ts +146 -22
- package/src/gitops.ts +188 -81
- package/src/graph-health.ts +35 -1
- package/src/graph.ts +66 -1
- package/src/harness-loader.ts +59 -0
- package/src/host.ts +567 -2
- package/src/lifecycle.ts +122 -1
- package/src/omp.ts +227 -20
- package/src/orchestrator-tick.ts +1386 -15
- package/src/orchestrator.ts +12 -0
- package/src/privileged.ts +1 -4
- package/src/release-policy.ts +503 -9
- package/src/session-host.ts +99 -5
- package/src/settlement.ts +69 -17
- package/src/setup-host.ts +1205 -6
- package/src/setup-install.ts +28 -0
- package/src/setup-wizard.ts +13 -2
- package/src/setup.ts +29 -13
- package/src/shell.ts +15 -0
- package/src/status-render.ts +78 -11
- package/src/store.ts +443 -42
- package/src/to-spec.ts +387 -0
- package/src/tracker/github.ts +104 -14
- package/src/types.ts +343 -13
- package/src/upgrade-verify.ts +209 -2
- package/src/upgrade.ts +175 -1
- package/src/verbs/protocol.ts +39 -0
- package/src/verbs/server.ts +730 -56
- package/src/verbs/socket.ts +24 -5
- package/src/worker.ts +25 -2
- package/src/worktree.ts +29 -12
package/src/graph-health.ts
CHANGED
|
@@ -33,6 +33,19 @@ export type CodeGraphHealth =
|
|
|
33
33
|
indexer: CheckState;
|
|
34
34
|
mcpMount: McpMountState;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Session-recorded graph-tools observations over this project's runs
|
|
38
|
+
* (#726): what dispatched sessions' registries actually held at start.
|
|
39
|
+
* `recorded: 0` means no run has ever recorded the fact — "observed" is
|
|
40
|
+
* never claimed without one, which is the whole honesty contract of the
|
|
41
|
+
* state. This is the runtime counterpart to `prerequisites.mcpMount`,
|
|
42
|
+
* which is deliberately a *config* finding.
|
|
43
|
+
*/
|
|
44
|
+
session: {
|
|
45
|
+
recorded: number;
|
|
46
|
+
present: number;
|
|
47
|
+
absent: number;
|
|
48
|
+
};
|
|
36
49
|
repos: CodeGraphRepoHealth[];
|
|
37
50
|
timer: {
|
|
38
51
|
enabled: "enabled" | "disabled" | "unknown";
|
|
@@ -61,6 +74,7 @@ export function pendingCodeGraph(project: ProjectConfig, now = Date.now()): Code
|
|
|
61
74
|
status: "unknown",
|
|
62
75
|
checkedAt: new Date(now).toISOString(),
|
|
63
76
|
prerequisites: { indexer: "unknown", mcpMount: "unknown" },
|
|
77
|
+
session: { recorded: 0, present: 0, absent: 0 },
|
|
64
78
|
repos: repos.map((repo) => ({
|
|
65
79
|
name: repo.name,
|
|
66
80
|
path: repo.graphProject,
|
|
@@ -78,6 +92,12 @@ export interface CodeGraphProbeDeps {
|
|
|
78
92
|
exists(path: string): boolean;
|
|
79
93
|
run(command: string, args: readonly string[]): Promise<ReadOnlyCommandResult>;
|
|
80
94
|
now(): number;
|
|
95
|
+
/**
|
|
96
|
+
* Session-recorded graph-tools observations for the probed project (#726).
|
|
97
|
+
* Absent, the probe reports no observation — which is exactly what a caller
|
|
98
|
+
* that has no store must report, and never a claim that tools were absent.
|
|
99
|
+
*/
|
|
100
|
+
graphToolsObservations?(): { recorded: number; present: number; absent: number };
|
|
81
101
|
}
|
|
82
102
|
|
|
83
103
|
async function runReadOnly(command: string, args: readonly string[]): Promise<ReadOnlyCommandResult> {
|
|
@@ -104,7 +124,7 @@ async function runReadOnly(command: string, args: readonly string[]): Promise<Re
|
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
126
|
|
|
107
|
-
const DEFAULT_DEPS: CodeGraphProbeDeps = {
|
|
127
|
+
export const DEFAULT_DEPS: CodeGraphProbeDeps = {
|
|
108
128
|
prereqs: resolvePrereqs,
|
|
109
129
|
exists: existsSync,
|
|
110
130
|
run: runReadOnly,
|
|
@@ -241,6 +261,19 @@ export async function probeCodeGraph(
|
|
|
241
261
|
if (indexer === "missing") reasons.push("indexer is not present on PATH");
|
|
242
262
|
if (mcpMount === "unconfigured") reasons.push("worker MCP configuration does not mount the indexer");
|
|
243
263
|
|
|
264
|
+
// The runtime half (#726): what dispatched sessions actually had in their
|
|
265
|
+
// registries. `recorded: 0` (no run ever recorded an observation, or no
|
|
266
|
+
// store-backed reader) contributes nothing — "observed" is only ever claimed
|
|
267
|
+
// from a run that recorded it. An observed absence is the signal this
|
|
268
|
+
// feature exists to surface: config says "mounted" while sessions ran
|
|
269
|
+
// without the tools, which is exactly the blind spot `mcp.json` alone had.
|
|
270
|
+
const session = deps.graphToolsObservations?.() ?? { recorded: 0, present: 0, absent: 0 };
|
|
271
|
+
if (session.recorded > 0 && session.absent > 0) {
|
|
272
|
+
reasons.push(
|
|
273
|
+
`graph tools observed absent from the session registry in ${session.absent} of ${session.recorded} runs`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
244
277
|
// The units are per project (`cbm-reindex-<project>.*`): probing the shared
|
|
245
278
|
// stem would report project B's health against project A's timer, and with
|
|
246
279
|
// both installed the answer would be whichever the probe guessed (#720).
|
|
@@ -301,6 +334,7 @@ export async function probeCodeGraph(
|
|
|
301
334
|
status: uncertain ? "unknown" : reasons.length === 0 ? "healthy" : "degraded",
|
|
302
335
|
checkedAt,
|
|
303
336
|
prerequisites: { indexer, mcpMount },
|
|
337
|
+
session,
|
|
304
338
|
repos,
|
|
305
339
|
timer: { enabled, active },
|
|
306
340
|
refresh: refresh.health,
|
package/src/graph.ts
CHANGED
|
@@ -29,7 +29,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "n
|
|
|
29
29
|
import { homedir, userInfo } from "node:os";
|
|
30
30
|
import { dirname, join } from "node:path";
|
|
31
31
|
import { expandHome, stateDir } from "./config.ts";
|
|
32
|
-
import type { ProjectConfig, RepoTarget } from "./types.ts";
|
|
32
|
+
import type { GraphToolsObservation, ProjectConfig, RepoTarget } from "./types.ts";
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* The indexer's own CLI, invoked by name rather than by path so the generated
|
|
@@ -38,6 +38,71 @@ import type { ProjectConfig, RepoTarget } from "./types.ts";
|
|
|
38
38
|
*/
|
|
39
39
|
const INDEXER = "codebase-memory-mcp";
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The MCP server name the graph tools arrive under in a session's registry,
|
|
43
|
+
* in the spellings the harness prefixes tools with (#726). `graphToolsPresent`
|
|
44
|
+
* matches against these, so the runtime observation and `resolvePrereqs`'s
|
|
45
|
+
* config check share one identity and cannot drift apart. The underscore form
|
|
46
|
+
* is the harness's normalised spelling for a server name; the verbatim form
|
|
47
|
+
* covers servers that keep their hyphens.
|
|
48
|
+
*/
|
|
49
|
+
export const GRAPH_MCP_SERVER_NAMES: readonly string[] = [INDEXER, INDEXER.replace(/-/g, "_")];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether the code-graph tools are in a live session's registry (#726).
|
|
53
|
+
*
|
|
54
|
+
* Takes the session's own enabled tool names and matches the harness's MCP
|
|
55
|
+
* mounting convention (`mcp__<server>_<tool>`). This is the runtime
|
|
56
|
+
* observation: unlike {@link resolvePrereqs}'s `mounted` (which reads
|
|
57
|
+
* `mcp.json` and can only say what a session should mount), it answers what
|
|
58
|
+
* the session actually had, so "the model ignored a tool it had" and "the
|
|
59
|
+
* tool was missing" stop looking identical from outside the session.
|
|
60
|
+
*/
|
|
61
|
+
export function graphToolsPresent(toolNames: readonly string[]): boolean {
|
|
62
|
+
return toolNames.some(
|
|
63
|
+
(name) =>
|
|
64
|
+
name.startsWith("mcp__") &&
|
|
65
|
+
GRAPH_MCP_SERVER_NAMES.some((server) => name.startsWith(`mcp__${server}_`)),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Poll a session's registry for the code-graph observation (#726).
|
|
71
|
+
*
|
|
72
|
+
* Measured against the real harness rather than assumed: MCP wiring finalises
|
|
73
|
+
* after `createAgentSession` resolves (a deferred discovery pass), so the
|
|
74
|
+
* registry read can throw in the window right after session creation, and the
|
|
75
|
+
* graph tools surface under the *enabled* names — the active names stop at
|
|
76
|
+
* the core tools. This therefore polls the enabled surface until the read
|
|
77
|
+
* stops throwing, bounded by `deadlineMs`; a surface that never becomes
|
|
78
|
+
* readable (or a build that does not expose one — `getEnabledToolNames` is
|
|
79
|
+
* `undefined`) records *no* observation — never "graph tools absent", which
|
|
80
|
+
* is the `present: false` truth value of a surface that was read.
|
|
81
|
+
*
|
|
82
|
+
* @param getEnabledToolNames the session's `getEnabledToolNames` read, already
|
|
83
|
+
* bound to its session (the SDK method dereferences private state), or
|
|
84
|
+
* `undefined` when the harness surface is absent
|
|
85
|
+
*/
|
|
86
|
+
export async function observeGraphTools(
|
|
87
|
+
getEnabledToolNames: (() => string[]) | undefined,
|
|
88
|
+
options: { intervalMs?: number; deadlineMs?: number } = {},
|
|
89
|
+
): Promise<GraphToolsObservation | undefined> {
|
|
90
|
+
if (getEnabledToolNames === undefined) return undefined;
|
|
91
|
+
const intervalMs = options.intervalMs ?? 250;
|
|
92
|
+
const deadlineMs = options.deadlineMs ?? 15_000;
|
|
93
|
+
const start = Date.now();
|
|
94
|
+
for (;;) {
|
|
95
|
+
try {
|
|
96
|
+
return { present: graphToolsPresent(getEnabledToolNames()), at: Date.now() };
|
|
97
|
+
} catch {
|
|
98
|
+
if (Date.now() - start >= deadlineMs) return undefined;
|
|
99
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
100
|
+
setTimeout(resolve, intervalMs);
|
|
101
|
+
await promise;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
41
106
|
/** The shared prefix of every reindex artefact; `cbm` is the indexer's own prefix. */
|
|
42
107
|
export const REINDEX_UNIT = "cbm-reindex";
|
|
43
108
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
OMP_HARNESS_PACKAGE,
|
|
6
|
+
OMP_NATIVES_PACKAGE,
|
|
7
|
+
packageNodeModulesRoot,
|
|
8
|
+
} from "./host.ts";
|
|
9
|
+
|
|
10
|
+
export interface HarnessAttestation {
|
|
11
|
+
path: string;
|
|
12
|
+
version: string;
|
|
13
|
+
nativePath: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Resolve the installed peer from this package's directory, never Bun's ambient cache. */
|
|
17
|
+
export function resolveHarnessEntry(moduleDir: string = import.meta.dir): string {
|
|
18
|
+
return Bun.resolveSync(OMP_HARNESS_PACKAGE, moduleDir);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The installed peer version belonging to a resolved harness entry. */
|
|
22
|
+
export function harnessVersion(entry: string): string | undefined {
|
|
23
|
+
const root = packageNodeModulesRoot(entry);
|
|
24
|
+
if (root === undefined) return undefined;
|
|
25
|
+
try {
|
|
26
|
+
const parsed: unknown = JSON.parse(
|
|
27
|
+
readFileSync(join(root, OMP_HARNESS_PACKAGE, "package.json"), "utf8"),
|
|
28
|
+
);
|
|
29
|
+
if (parsed === null || typeof parsed !== "object") return undefined;
|
|
30
|
+
const version = Reflect.get(parsed, "version");
|
|
31
|
+
return typeof version === "string" && version !== "" ? version : undefined;
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Exercise the worker's real import contract: explicitly anchored peer and
|
|
39
|
+
* native-addon resolution, with the caller responsible for passing
|
|
40
|
+
* `--no-install` so neither import can fall through to Bun's package cache.
|
|
41
|
+
*/
|
|
42
|
+
export async function probeHarness(moduleDir: string = import.meta.dir): Promise<HarnessAttestation> {
|
|
43
|
+
const path = resolveHarnessEntry(moduleDir);
|
|
44
|
+
await import(path);
|
|
45
|
+
const version = harnessVersion(path);
|
|
46
|
+
if (version === undefined) throw new Error(`cannot read the harness version for ${path}`);
|
|
47
|
+
const nativePath = Bun.resolveSync(OMP_NATIVES_PACKAGE, dirname(path));
|
|
48
|
+
await import(nativePath);
|
|
49
|
+
return { path, version, nativePath };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (import.meta.main) {
|
|
53
|
+
try {
|
|
54
|
+
process.stdout.write(`${JSON.stringify(await probeHarness())}\n`);
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
process.stderr.write(`${cause instanceof Error ? cause.stack ?? cause.message : String(cause)}\n`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|