omp-conductor 0.18.0 → 0.18.2
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 +35 -1
- package/REFERENCE.md +61 -11
- package/agents/to-spec.md +94 -0
- package/package.json +2 -1
- package/schema/config.schema.json +35 -1
- package/src/admission.ts +204 -75
- package/src/arm-challenge.ts +250 -57
- package/src/ask.ts +268 -7
- package/src/board.ts +17 -3
- package/src/briefs/orchestrator.md +62 -21
- package/src/briefs/to-spec.md +88 -0
- package/src/briefs/worker.md +2 -1
- package/src/cli.ts +124 -1
- package/src/command-help.ts +11 -0
- package/src/command-manifest.ts +38 -5
- package/src/commands/arm.ts +1 -1
- package/src/commands/context.ts +1 -0
- package/src/commands/drain.ts +176 -0
- package/src/commands/extend.ts +6 -10
- package/src/commands/intake.ts +4 -19
- package/src/commands/status.ts +5 -1
- package/src/commands/watch.ts +51 -16
- package/src/commands/worker.ts +9 -10
- package/src/config-schema.ts +43 -6
- package/src/config.ts +65 -9
- package/src/daemon.ts +879 -41
- package/src/dashboard/app.js +4 -1
- package/src/dashboard/server.ts +5 -2
- package/src/decisions.ts +243 -17
- package/src/diff-flags.ts +75 -1
- package/src/doctor.ts +60 -82
- package/src/escalate.ts +31 -14
- package/src/failure-class.ts +28 -2
- package/src/fleet.ts +239 -240
- 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 +242 -2
- package/src/lifecycle.ts +122 -1
- package/src/omp-settings.ts +19 -0
- package/src/omp.ts +183 -21
- package/src/orchestrator-tick.ts +1591 -32
- package/src/orchestrator.ts +12 -0
- package/src/privileged.ts +1 -4
- package/src/release-policy.ts +503 -9
- package/src/session-host.ts +65 -6
- package/src/settlement.ts +69 -17
- package/src/setup-host.ts +1225 -9
- package/src/setup-install.ts +28 -0
- package/src/setup-wizard.ts +154 -3
- package/src/setup.ts +83 -17
- package/src/shell.ts +15 -0
- package/src/status-render.ts +216 -12
- package/src/store.ts +443 -42
- package/src/to-spec.ts +408 -0
- package/src/tracker/github.ts +104 -14
- package/src/types.ts +405 -19
- 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 +765 -56
- package/src/verbs/socket.ts +24 -5
- package/src/worker.ts +12 -2
- package/src/worktree.ts +29 -12
package/src/commands/worker.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* `worker` — pause/resume/stop one live worker session through the daemon's run-control HTTP endpoints.
|
|
3
3
|
*
|
|
4
4
|
* Moved out of cli.ts's switch by the per-verb module split (#462);
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* the daemon discovery resolves through {@link requireDaemonControl} so a
|
|
6
|
+
* live systemd-run daemon stays reachable when its pidfile is missing (#811).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { CommandContext } from "./context.ts";
|
|
10
10
|
import { findProject, loadConfig } from "../config.ts";
|
|
11
|
-
import {
|
|
11
|
+
import { requireDaemonControl } from "../lifecycle.ts";
|
|
12
12
|
|
|
13
13
|
export async function workerCommand(ctx: CommandContext): Promise<void> {
|
|
14
14
|
const sub = ctx.argv[1];
|
|
@@ -28,13 +28,12 @@ if (sub === "stop" && (reason === undefined || reason === "" || reason.length >
|
|
|
28
28
|
process.exit(2);
|
|
29
29
|
}
|
|
30
30
|
const project = findProject(loadConfig(), ctx.projectFlag);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
31
|
+
// The pidfile is authoritative when it names a live process; a missing or
|
|
32
|
+
// stale record falls back to the systemd unit, proved through its own
|
|
33
|
+
// /healthz answer, so a live unit-owned daemon is never reported stopped
|
|
34
|
+
// because the runtime directory was wiped (#811). The refusal chain is
|
|
35
|
+
// shared with `extend` through requireDaemonControl.
|
|
36
|
+
const daemon = await requireDaemonControl(project.name);
|
|
38
37
|
const response = await fetch(
|
|
39
38
|
`http://127.0.0.1:${daemon.port}/runs/${issue}/${sub}`,
|
|
40
39
|
{
|
package/src/config-schema.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
DEFAULT_AUTHORITY,
|
|
32
32
|
DEFAULT_CAPS,
|
|
33
33
|
DEFAULT_PROJECT_POLICY,
|
|
34
|
+
DEFAULT_REVIEW_ADJUDICATOR_ROLE,
|
|
34
35
|
DEFAULT_REVIEW_MAX_ROUNDS,
|
|
35
36
|
DEFAULT_REVIEW_STRICTNESS,
|
|
36
37
|
DRAFT_POLICIES,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
RELEASE_REQUIREMENTS,
|
|
42
43
|
RELEASE_SHAPES,
|
|
43
44
|
REPORT_SCOPES,
|
|
45
|
+
REVIEW_ADJUDICATOR_RE,
|
|
44
46
|
REVIEW_MAX_ROUNDS_MAX,
|
|
45
47
|
REVIEW_MAX_ROUNDS_MIN,
|
|
46
48
|
REVIEW_STRICTNESS,
|
|
@@ -265,11 +267,15 @@ const armSchema = z
|
|
|
265
267
|
|
|
266
268
|
/**
|
|
267
269
|
* The per-project review policy (#678): how strictly green PRs are reviewed
|
|
268
|
-
* and returned,
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
270
|
+
* and returned, the hard ceiling on review rounds per PR lifecycle, and which
|
|
271
|
+
* OMP model role carries the terminal review-ceiling adjudication (#875).
|
|
272
|
+
* Absent or a legacy config without the keys loads as `medium` with
|
|
273
|
+
* {@link DEFAULT_REVIEW_MAX_ROUNDS} rounds and
|
|
274
|
+
* {@link DEFAULT_REVIEW_ADJUDICATOR_ROLE} as adjudicator — the recommended
|
|
275
|
+
* defaults for a new project, materialised deterministically for every
|
|
276
|
+
* existing one, with the schema bounds and the runtime defaults read from the
|
|
277
|
+
* same constants. The adjudicator names a role only; a provider/model is
|
|
278
|
+
* refused here, because OMP owns exact provider selection.
|
|
273
279
|
*/
|
|
274
280
|
const reviewSchema = z
|
|
275
281
|
.object({
|
|
@@ -280,9 +286,16 @@ const reviewSchema = z
|
|
|
280
286
|
.min(REVIEW_MAX_ROUNDS_MIN)
|
|
281
287
|
.max(REVIEW_MAX_ROUNDS_MAX)
|
|
282
288
|
.default(DEFAULT_REVIEW_MAX_ROUNDS),
|
|
289
|
+
adjudicator: z
|
|
290
|
+
.string()
|
|
291
|
+
.regex(
|
|
292
|
+
REVIEW_ADJUDICATOR_RE,
|
|
293
|
+
"must name one OMP model role — a single role token like \"task\" (never a provider/model, which omp owns)",
|
|
294
|
+
)
|
|
295
|
+
.default(DEFAULT_REVIEW_ADJUDICATOR_ROLE),
|
|
283
296
|
})
|
|
284
297
|
.strict()
|
|
285
|
-
.describe("Review strictness
|
|
298
|
+
.describe("Review strictness, round ceiling and adjudicator role for green PRs");
|
|
286
299
|
|
|
287
300
|
const releasePolicySchema = z.union([
|
|
288
301
|
releasePolicyLegacyEnum,
|
|
@@ -336,6 +349,25 @@ const stateLabelsSchema = z
|
|
|
336
349
|
})
|
|
337
350
|
.partial();
|
|
338
351
|
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Host constraints (#721)
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
const hostConstraintsSchema = z
|
|
357
|
+
.object({
|
|
358
|
+
description: z.string().min(1).describe("What this host is and what else it runs"),
|
|
359
|
+
path: z
|
|
360
|
+
.string()
|
|
361
|
+
.min(1)
|
|
362
|
+
.describe('The non-interactive PATH a script or `ssh host "<cmd>"` invocation must export'),
|
|
363
|
+
conventions: z
|
|
364
|
+
.record(z.string().min(1), z.string().min(1))
|
|
365
|
+
.describe("Per-repo command conventions, keyed by the brief's `owner/repo` slug"),
|
|
366
|
+
})
|
|
367
|
+
.strict()
|
|
368
|
+
.partial()
|
|
369
|
+
.describe("Operator-authored host facts rendered into every worker brief");
|
|
370
|
+
|
|
339
371
|
// ---------------------------------------------------------------------------
|
|
340
372
|
// Project / root
|
|
341
373
|
// ---------------------------------------------------------------------------
|
|
@@ -409,6 +441,11 @@ const configSchema = z
|
|
|
409
441
|
.min(1, "must name a directory")
|
|
410
442
|
.optional()
|
|
411
443
|
.describe("Absolute directory for restorable conductor.db snapshots; defaults to <stateDir>/backups/db"),
|
|
444
|
+
// Cores and RAM are derived by host.ts at render time; only what the
|
|
445
|
+
// operator must type belongs in this block.
|
|
446
|
+
host: hostConstraintsSchema
|
|
447
|
+
.optional()
|
|
448
|
+
.describe("Host facts every worker brief renders; absent renders no section"),
|
|
412
449
|
projects: z.array(projectSchema).min(1, `"projects" must be a non-empty array — the dispatcher has nothing to service otherwise`),
|
|
413
450
|
})
|
|
414
451
|
.loose()
|
package/src/config.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
DEFAULT_PROJECT_POLICY,
|
|
29
29
|
DEFAULT_REPORT_POLICY,
|
|
30
30
|
DEFAULT_REPORT_SCOPE,
|
|
31
|
+
DEFAULT_REVIEW_ADJUDICATOR_ROLE,
|
|
31
32
|
DEFAULT_REVIEW_MAX_ROUNDS,
|
|
32
33
|
DEFAULT_REVIEW_POLICY,
|
|
33
34
|
DEFAULT_REVIEW_STRICTNESS,
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
type Caps,
|
|
50
51
|
type ConductorConfig,
|
|
51
52
|
type DigestCadence,
|
|
53
|
+
type HostConstraints,
|
|
52
54
|
type InterruptCategory,
|
|
53
55
|
type MergePreconditions,
|
|
54
56
|
type PlanUsageCap,
|
|
@@ -348,12 +350,20 @@ export function resolveArmProof(p: ProjectConfig): ArmProof {
|
|
|
348
350
|
* The loader always materialises `review` (finalizeProject), so this is only
|
|
349
351
|
* ever the fallback for a `ProjectConfig` that never went through it — a
|
|
350
352
|
* hand-built one in a test, or a config written before the key existed.
|
|
351
|
-
* Absent
|
|
352
|
-
*
|
|
353
|
-
*
|
|
353
|
+
* Absent (or a legacy config that stopped at strictness and maxRounds)
|
|
354
|
+
* resolves to {@link DEFAULT_REVIEW_POLICY}, the documented migration default
|
|
355
|
+
* every existing install deterministically upgrades to — the adjudicator role
|
|
356
|
+
* included, so no consumer ever reads a policy without one (#875). A fresh
|
|
357
|
+
* object each call, so mutating the result cannot reach the config it came
|
|
358
|
+
* from.
|
|
354
359
|
*/
|
|
355
360
|
export function resolveReview(p: ProjectConfig): ReviewPolicy {
|
|
356
|
-
|
|
361
|
+
const base = p.review ?? DEFAULT_REVIEW_POLICY;
|
|
362
|
+
return {
|
|
363
|
+
strictness: base.strictness ?? DEFAULT_REVIEW_STRICTNESS,
|
|
364
|
+
maxRounds: base.maxRounds ?? DEFAULT_REVIEW_MAX_ROUNDS,
|
|
365
|
+
adjudicator: base.adjudicator ?? DEFAULT_REVIEW_ADJUDICATOR_ROLE,
|
|
366
|
+
};
|
|
357
367
|
}
|
|
358
368
|
|
|
359
369
|
/**
|
|
@@ -888,11 +898,14 @@ function clauseFor(rel: readonly PropertyKey[], issue: {
|
|
|
888
898
|
return `policy must be an object with "merge" and "release" sections`;
|
|
889
899
|
}
|
|
890
900
|
if (rel.length === 1 && relStr === "review") {
|
|
891
|
-
return `review must be an object with "strictness" and "
|
|
901
|
+
return `review must be an object with "strictness", "maxRounds" and "adjudicator"`;
|
|
892
902
|
}
|
|
893
903
|
if (rel.length === 2 && rel[0] === "review" && rel[1] === "maxRounds") {
|
|
894
904
|
return `review.maxRounds must be an integer between ${REVIEW_MAX_ROUNDS_MIN} and ${REVIEW_MAX_ROUNDS_MAX}, found ${found()}`;
|
|
895
905
|
}
|
|
906
|
+
if (rel.length === 2 && rel[0] === "review" && rel[1] === "adjudicator") {
|
|
907
|
+
return `review.adjudicator must be a string naming one OMP model role, found ${found()}`;
|
|
908
|
+
}
|
|
896
909
|
if (rel.length === 2 && relStr === "policy") return `${pathStr} must be an object`;
|
|
897
910
|
if (rel.length === 1 && relStr === "caps") return `caps must be an object`;
|
|
898
911
|
if (rel.length === 2 && relStr === "caps" && rel[1] === "planUsage") {
|
|
@@ -943,7 +956,7 @@ function clauseFor(rel: readonly PropertyKey[], issue: {
|
|
|
943
956
|
if (issue.code === "unrecognized_keys") {
|
|
944
957
|
const keys = (issue.keys ?? []).join(", ");
|
|
945
958
|
if (pathStr === "policy") return `policy has unknown key(s): ${keys} — expected "merge" or "release"`;
|
|
946
|
-
if (pathStr === "review") return `review has unknown key(s): ${keys} — expected "strictness" and "
|
|
959
|
+
if (pathStr === "review") return `review has unknown key(s): ${keys} — expected "strictness", "maxRounds" and "adjudicator"`;
|
|
947
960
|
if (pathStr === "policy.merge") return `${pathStr} has unknown key(s): ${keys} — expected ${POLICY_MERGE_KEYS}`;
|
|
948
961
|
if (pathStr === "policy.release") return `${pathStr} has unknown key(s): ${keys} — expected ${POLICY_RELEASE_KEYS}`;
|
|
949
962
|
if (pathStr === "caps.planUsage") {
|
|
@@ -1092,8 +1105,48 @@ function finalize(data: unknown, path: string): ConductorConfig {
|
|
|
1092
1105
|
});
|
|
1093
1106
|
}
|
|
1094
1107
|
|
|
1108
|
+
const rawHost = root["host"] as Raw | undefined;
|
|
1109
|
+
const host = finalizeHost(rawHost);
|
|
1110
|
+
const rawDbBackupDir = root["dbBackupDir"];
|
|
1111
|
+
const dbBackupDir = typeof rawDbBackupDir === "string" ? rawDbBackupDir : undefined;
|
|
1112
|
+
|
|
1095
1113
|
if (problems.length > 0) throw new Error(problemEnvelope(path, problems));
|
|
1096
|
-
|
|
1114
|
+
// `ConductorConfig` is rebuilt here, so every optional top-level key must be
|
|
1115
|
+
// spelled out or it silently vanishes from every loaded config. `dbBackupDir`
|
|
1116
|
+
// and `host` are the two today; a third needs its own line below (#773).
|
|
1117
|
+
return {
|
|
1118
|
+
version: CONFIG_VERSION,
|
|
1119
|
+
defaults,
|
|
1120
|
+
projects,
|
|
1121
|
+
...(dbBackupDir === undefined ? {} : { dbBackupDir }),
|
|
1122
|
+
...(host === undefined ? {} : { host }),
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* The typed host-constraints block (#721), or `undefined` when nothing usable
|
|
1128
|
+
* was configured — matching the schema's "absent renders no section" promise.
|
|
1129
|
+
* Trimming and empty-drop mirror the other hand-edited string fields; an
|
|
1130
|
+
* object whose every key is empty loads as if the field were absent.
|
|
1131
|
+
*/
|
|
1132
|
+
function finalizeHost(parsed: Raw | undefined): HostConstraints | undefined {
|
|
1133
|
+
if (parsed === undefined) return undefined;
|
|
1134
|
+
const out: HostConstraints = {};
|
|
1135
|
+
const description = parsed["description"];
|
|
1136
|
+
if (typeof description === "string" && description.trim() !== "") out.description = description.trim();
|
|
1137
|
+
const path = parsed["path"];
|
|
1138
|
+
if (typeof path === "string" && path.trim() !== "") out.path = path.trim();
|
|
1139
|
+
const conventions = parsed["conventions"];
|
|
1140
|
+
if (typeof conventions === "object" && conventions !== null && !Array.isArray(conventions)) {
|
|
1141
|
+
const kept: Record<string, string> = {};
|
|
1142
|
+
for (const [slug, value] of Object.entries(conventions as Record<string, unknown>)) {
|
|
1143
|
+
if (typeof value === "string" && value.trim() !== "" && slug.trim() !== "") {
|
|
1144
|
+
kept[slug.trim()] = value.trim();
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (Object.keys(kept).length > 0) out.conventions = kept;
|
|
1148
|
+
}
|
|
1149
|
+
return Object.keys(out).length === 0 ? undefined : out;
|
|
1097
1150
|
}
|
|
1098
1151
|
|
|
1099
1152
|
function finalizeProject(
|
|
@@ -1279,14 +1332,17 @@ function finalizeArm(parsed: Raw | undefined): ProjectConfig["arm"] {
|
|
|
1279
1332
|
* {@link DEFAULT_REVIEW_POLICY}, the documented migration default, so an
|
|
1280
1333
|
* upgrade changes no existing project's behaviour beyond what its operators
|
|
1281
1334
|
* then choose in setup. zod has already rejected anything outside the
|
|
1282
|
-
* strictness vocabulary
|
|
1283
|
-
* absent-case
|
|
1335
|
+
* strictness vocabulary, the validated round range or the adjudicator role
|
|
1336
|
+
* token grammar, so this only fills the absent-case defaults — the
|
|
1337
|
+
* adjudicator included (#875), deterministically the same role a new
|
|
1338
|
+
* project's setup starts from.
|
|
1284
1339
|
*/
|
|
1285
1340
|
function finalizeReview(parsed: Raw | undefined): ProjectConfig["review"] {
|
|
1286
1341
|
if (parsed === undefined) return { ...DEFAULT_REVIEW_POLICY };
|
|
1287
1342
|
return {
|
|
1288
1343
|
strictness: (parsed["strictness"] as ReviewStrictness | undefined) ?? DEFAULT_REVIEW_STRICTNESS,
|
|
1289
1344
|
maxRounds: (parsed["maxRounds"] as number | undefined) ?? DEFAULT_REVIEW_MAX_ROUNDS,
|
|
1345
|
+
adjudicator: (parsed["adjudicator"] as string | undefined) ?? DEFAULT_REVIEW_ADJUDICATOR_ROLE,
|
|
1290
1346
|
};
|
|
1291
1347
|
}
|
|
1292
1348
|
|