omp-conductor 0.3.19 → 0.3.20
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 +11 -6
- package/package.json +1 -1
- package/src/cli.ts +6 -4
- package/src/upgrade.ts +261 -51
package/README.md
CHANGED
|
@@ -194,12 +194,17 @@ or installation, so an operator cannot drain one queue and kill another queue's
|
|
|
194
194
|
workers.
|
|
195
195
|
|
|
196
196
|
Ticks remain in their existing armed or disarmed state, so an ordinary update
|
|
197
|
-
does not halt the exact pane or require another Telegram arm challenge.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
197
|
+
does not halt the exact pane or require another Telegram arm challenge. Progress
|
|
198
|
+
names the Bun-global CLI, omp plugin, Herdr plugin, brief, reloads, and both
|
|
199
|
+
verification passes separately.
|
|
200
|
+
|
|
201
|
+
An installation, brief, reload, or verification failure pauses dispatch and
|
|
202
|
+
attempts to restore the exact CLI/plugin identities that were present before the
|
|
203
|
+
command. If rollback also fails, the error names every failed restoration and
|
|
204
|
+
keeps dispatch paused; it never brings a known mixed fleet back into service.
|
|
205
|
+
The command never publishes npm, edits an install root, or delegates lifecycle
|
|
206
|
+
steps to an AI session. It refuses to run inside a Herdr-managed session because
|
|
207
|
+
an updater that restarts itself cannot verify the result.
|
|
203
208
|
|
|
204
209
|
The bundled `skill://conductor-update` remains available as a natural-language
|
|
205
210
|
entry point, but it only runs this command; the lifecycle is implemented and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-conductor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
|
package/src/cli.ts
CHANGED
|
@@ -394,10 +394,12 @@ try {
|
|
|
394
394
|
project: flag(argv, "project"),
|
|
395
395
|
});
|
|
396
396
|
process.stdout.write(
|
|
397
|
-
result.alreadyCurrent
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
397
|
+
`${result.alreadyCurrent ? "already current" : "upgrade complete"}:\n` +
|
|
398
|
+
` Bun-global CLI omp-conductor@${result.version}\n` +
|
|
399
|
+
` omp plugin omp-conductor@${result.version}\n` +
|
|
400
|
+
` Herdr plugin herdr-conductor@${result.gitHead}\n` +
|
|
401
|
+
` orchestrator brief managed ORCHESTRATOR.md floor current; POLICY.md preserved\n` +
|
|
402
|
+
` dispatch ${result.dispatch}${result.alreadyCurrent ? "" : " restored"}\n`,
|
|
401
403
|
);
|
|
402
404
|
break;
|
|
403
405
|
}
|
package/src/upgrade.ts
CHANGED
|
@@ -125,33 +125,55 @@ function parseRegistry(raw: string): { version: string; gitHead: string } {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
function ompPluginVersion(raw: string): string | undefined {
|
|
128
|
+
let parsed: unknown;
|
|
128
129
|
try {
|
|
129
|
-
|
|
130
|
-
const plugin = parsed.npm?.find((entry) => entry.name === PACKAGE);
|
|
131
|
-
return typeof plugin?.version === "string" ? plugin.version : undefined;
|
|
130
|
+
parsed = JSON.parse(raw);
|
|
132
131
|
} catch {
|
|
133
|
-
|
|
132
|
+
throw new Error("omp plugin list returned invalid JSON");
|
|
134
133
|
}
|
|
134
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
135
|
+
throw new Error("omp plugin list returned an invalid inventory");
|
|
136
|
+
}
|
|
137
|
+
const npm = Reflect.get(parsed, "npm");
|
|
138
|
+
if (!Array.isArray(npm)) throw new Error("omp plugin list returned no npm inventory");
|
|
139
|
+
const plugin = npm.find(
|
|
140
|
+
(entry) => entry !== null && typeof entry === "object" && Reflect.get(entry, "name") === PACKAGE,
|
|
141
|
+
);
|
|
142
|
+
if (plugin === undefined) return undefined;
|
|
143
|
+
const version = Reflect.get(plugin, "version");
|
|
144
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
145
|
+
throw new Error("installed omp-conductor plugin has no version");
|
|
146
|
+
}
|
|
147
|
+
return version;
|
|
135
148
|
}
|
|
136
149
|
|
|
137
150
|
function herdrPluginSource(raw: string): string | undefined {
|
|
138
151
|
const line = raw.split("\n").find((candidate) => /^- herdr-conductor\b/.test(candidate.trim()));
|
|
139
|
-
|
|
152
|
+
if (line === undefined) return undefined;
|
|
153
|
+
const source = line.match(/\[([^\]]+)\]\s*$/)?.[1];
|
|
154
|
+
if (source === undefined) {
|
|
155
|
+
throw new Error("cannot determine installed Herdr plugin source");
|
|
156
|
+
}
|
|
157
|
+
return source;
|
|
140
158
|
}
|
|
141
159
|
|
|
142
|
-
|
|
160
|
+
interface InstalledSurfaces {
|
|
143
161
|
cliVersion: string;
|
|
144
162
|
ompVersion?: string;
|
|
145
163
|
herdrSource?: string;
|
|
146
|
-
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function inspectSurfaces(deps: UpgradeDeps): Promise<InstalledSurfaces> {
|
|
147
167
|
const session = deps.env["HERDR_SESSION"] || "fleet";
|
|
148
168
|
const [cli, omp, herdr] = await Promise.all([
|
|
149
169
|
mustRun(deps, "omp-conductor", ["--version"]),
|
|
150
170
|
mustRun(deps, "omp", ["plugin", "list", "--json"]),
|
|
151
171
|
mustRun(deps, "herdr", ["--session", session, "plugin", "list"]),
|
|
152
172
|
]);
|
|
173
|
+
const cliVersion = cli.stdout.trim();
|
|
174
|
+
if (cliVersion.length === 0) throw new Error("installed omp-conductor CLI has no version");
|
|
153
175
|
return {
|
|
154
|
-
cliVersion
|
|
176
|
+
cliVersion,
|
|
155
177
|
ompVersion: ompPluginVersion(omp.stdout),
|
|
156
178
|
herdrSource: herdrPluginSource(herdr.stdout),
|
|
157
179
|
};
|
|
@@ -173,6 +195,19 @@ function surfacesCurrent(
|
|
|
173
195
|
);
|
|
174
196
|
}
|
|
175
197
|
|
|
198
|
+
function previousHerdrInstall(source: string): readonly [string, readonly string[]] | undefined {
|
|
199
|
+
if (source.startsWith("local:")) {
|
|
200
|
+
return ["herdr", ["plugin", "link", source.slice("local:".length), "--enabled"]];
|
|
201
|
+
}
|
|
202
|
+
if (!source.startsWith("github:")) return undefined;
|
|
203
|
+
const at = source.lastIndexOf("@");
|
|
204
|
+
if (at < "github:".length) return undefined;
|
|
205
|
+
return [
|
|
206
|
+
"herdr",
|
|
207
|
+
["plugin", "install", source.slice("github:".length, at), "--ref", source.slice(at + 1), "--yes"],
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
|
|
176
211
|
async function waitForDrain(deps: UpgradeDeps, project?: string): Promise<void> {
|
|
177
212
|
let last = -1;
|
|
178
213
|
while (true) {
|
|
@@ -230,6 +265,122 @@ async function upgradeBrief(
|
|
|
230
265
|
await mustRun(deps, "omp-conductor", ["brief-upgrade", "--migrate", "--apply", ...selected]);
|
|
231
266
|
}
|
|
232
267
|
|
|
268
|
+
async function rollbackUpgrade(
|
|
269
|
+
deps: UpgradeDeps,
|
|
270
|
+
previous: InstalledSurfaces,
|
|
271
|
+
project: string | undefined,
|
|
272
|
+
installTouched: boolean,
|
|
273
|
+
briefChanged: boolean,
|
|
274
|
+
herdrReloadStarted: boolean,
|
|
275
|
+
daemonReloadStarted: boolean,
|
|
276
|
+
): Promise<void> {
|
|
277
|
+
const failures: string[] = [];
|
|
278
|
+
const restore = async (label: string, command: string, args: readonly string[]): Promise<void> => {
|
|
279
|
+
deps.log(`rollback: ${label}`);
|
|
280
|
+
try {
|
|
281
|
+
await mustRun(deps, command, args);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
failures.push(err instanceof Error ? err.message : String(err));
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const removeIfPresent = async (
|
|
287
|
+
label: string,
|
|
288
|
+
command: string,
|
|
289
|
+
args: readonly string[],
|
|
290
|
+
): Promise<void> => {
|
|
291
|
+
deps.log(`rollback: ${label}`);
|
|
292
|
+
try {
|
|
293
|
+
await deps.run(command, args);
|
|
294
|
+
} catch {
|
|
295
|
+
// Exact post-rollback identity verification decides whether absence is safe.
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
if (installTouched) {
|
|
300
|
+
await restore("Bun-global omp-conductor CLI", "bun", [
|
|
301
|
+
"add",
|
|
302
|
+
"-g",
|
|
303
|
+
`${PACKAGE}@${previous.cliVersion}`,
|
|
304
|
+
]);
|
|
305
|
+
if (previous.ompVersion === undefined) {
|
|
306
|
+
await removeIfPresent("remove newly installed omp plugin", "omp", [
|
|
307
|
+
"plugin",
|
|
308
|
+
"uninstall",
|
|
309
|
+
PACKAGE,
|
|
310
|
+
]);
|
|
311
|
+
} else {
|
|
312
|
+
await restore("omp plugin omp-conductor", "omp", [
|
|
313
|
+
"plugin",
|
|
314
|
+
"install",
|
|
315
|
+
`${PACKAGE}@${previous.ompVersion}`,
|
|
316
|
+
]);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (previous.herdrSource === undefined) {
|
|
320
|
+
await removeIfPresent("remove newly installed Herdr plugin", "herdr", [
|
|
321
|
+
"plugin",
|
|
322
|
+
"uninstall",
|
|
323
|
+
HERDR_PLUGIN,
|
|
324
|
+
]);
|
|
325
|
+
} else {
|
|
326
|
+
const install = previousHerdrInstall(previous.herdrSource);
|
|
327
|
+
if (install === undefined) {
|
|
328
|
+
failures.push(`cannot restore unknown Herdr source ${previous.herdrSource}`);
|
|
329
|
+
} else {
|
|
330
|
+
if (previous.herdrSource.startsWith("local:")) {
|
|
331
|
+
await removeIfPresent("remove managed Herdr plugin", "herdr", [
|
|
332
|
+
"plugin",
|
|
333
|
+
"uninstall",
|
|
334
|
+
HERDR_PLUGIN,
|
|
335
|
+
]);
|
|
336
|
+
}
|
|
337
|
+
await restore("Herdr plugin herdr-conductor", install[0], install[1]);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
if (briefChanged) {
|
|
343
|
+
try {
|
|
344
|
+
deps.log("rollback: ORCHESTRATOR.md package floor");
|
|
345
|
+
await upgradeBrief(deps, "overlay", project);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
failures.push(err instanceof Error ? err.message : String(err));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (herdrReloadStarted) {
|
|
352
|
+
await restore("restart herdr-fleet.service", "systemctl", ["restart", HERDR_UNIT]);
|
|
353
|
+
}
|
|
354
|
+
if (daemonReloadStarted) {
|
|
355
|
+
try {
|
|
356
|
+
deps.log("rollback: restart omp-conductor dispatch daemon");
|
|
357
|
+
await deps.restartDaemon();
|
|
358
|
+
} catch (err) {
|
|
359
|
+
failures.push(err instanceof Error ? err.message : String(err));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (installTouched) {
|
|
364
|
+
try {
|
|
365
|
+
const restored = await inspectSurfaces(deps);
|
|
366
|
+
if (
|
|
367
|
+
restored.cliVersion !== previous.cliVersion ||
|
|
368
|
+
restored.ompVersion !== previous.ompVersion ||
|
|
369
|
+
restored.herdrSource !== previous.herdrSource
|
|
370
|
+
) {
|
|
371
|
+
failures.push(
|
|
372
|
+
`restored identities differ: cli=${restored.cliVersion}, omp=${restored.ompVersion ?? "missing"}, ` +
|
|
373
|
+
`herdr=${restored.herdrSource ?? "missing"}`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
} catch (err) {
|
|
377
|
+
failures.push(err instanceof Error ? err.message : String(err));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (failures.length > 0) throw new Error(failures.join("; "));
|
|
382
|
+
}
|
|
383
|
+
|
|
233
384
|
export async function upgradeConductor(
|
|
234
385
|
options: UpgradeOptions = {},
|
|
235
386
|
overrides: Partial<UpgradeDeps> = {},
|
|
@@ -268,54 +419,113 @@ export async function upgradeConductor(
|
|
|
268
419
|
if (brief.kind === "missing") throw new Error("no ORCHESTRATOR.md exists for the configured project");
|
|
269
420
|
if (initial.herdr === "unknown") throw new Error("cannot determine whether herdr-fleet.service is active");
|
|
270
421
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (installNeeded) {
|
|
276
|
-
await mustRun(deps, "bun", ["add", "-g", `${PACKAGE}@${release.version}`]);
|
|
277
|
-
await mustRun(deps, "omp", ["plugin", "install", `${PACKAGE}@${release.version}`]);
|
|
278
|
-
if (surfaces.herdrSource?.startsWith("local:")) {
|
|
279
|
-
await mustRun(deps, "herdr", ["plugin", "unlink", HERDR_PLUGIN]);
|
|
280
|
-
}
|
|
281
|
-
await mustRun(deps, "herdr", [
|
|
282
|
-
"plugin",
|
|
283
|
-
"install",
|
|
284
|
-
HERDR_SOURCE,
|
|
285
|
-
"--ref",
|
|
286
|
-
release.gitHead,
|
|
287
|
-
"--yes",
|
|
288
|
-
]);
|
|
422
|
+
deps.log(`target release: omp-conductor@${release.version} (${release.gitHead})`);
|
|
423
|
+
if (!initial.paused) {
|
|
424
|
+
deps.log("safety: pausing new issue claims");
|
|
425
|
+
deps.setPaused(true);
|
|
289
426
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (initial.dispatch !== "stopped") {
|
|
296
|
-
await deps.restartDaemon();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const installed = await inspectSurfaces(deps);
|
|
300
|
-
if (!surfacesCurrent(installed, release.version, release.gitHead)) {
|
|
427
|
+
deps.log("drain: waiting for live omp worker sessions");
|
|
428
|
+
try {
|
|
429
|
+
await waitForDrain(deps, project);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
const failure = err instanceof Error ? err.message : String(err);
|
|
301
432
|
throw new Error(
|
|
302
|
-
`upgrade
|
|
303
|
-
`herdr=${installed.herdrSource ?? "missing"}`,
|
|
433
|
+
`upgrade failed while draining workers: ${failure}; no installation started; dispatch remains paused`,
|
|
304
434
|
);
|
|
305
435
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
436
|
+
deps.log("drain: complete");
|
|
437
|
+
|
|
438
|
+
let installTouched = false;
|
|
439
|
+
let briefChanged = false;
|
|
440
|
+
let herdrReloadStarted = false;
|
|
441
|
+
let daemonReloadStarted = false;
|
|
442
|
+
try {
|
|
443
|
+
if (installNeeded) {
|
|
444
|
+
installTouched = true;
|
|
445
|
+
deps.log(`install 1/3: Bun-global omp-conductor CLI → ${release.version}`);
|
|
446
|
+
await mustRun(deps, "bun", ["add", "-g", `${PACKAGE}@${release.version}`]);
|
|
447
|
+
deps.log(`install 2/3: omp plugin omp-conductor → ${release.version}`);
|
|
448
|
+
await mustRun(deps, "omp", ["plugin", "install", `${PACKAGE}@${release.version}`]);
|
|
449
|
+
if (surfaces.herdrSource?.startsWith("local:")) {
|
|
450
|
+
await mustRun(deps, "herdr", ["plugin", "unlink", HERDR_PLUGIN]);
|
|
451
|
+
}
|
|
452
|
+
deps.log(`install 3/3: Herdr plugin herdr-conductor → ${release.gitHead}`);
|
|
453
|
+
await mustRun(deps, "herdr", [
|
|
454
|
+
"plugin",
|
|
455
|
+
"install",
|
|
456
|
+
HERDR_SOURCE,
|
|
457
|
+
"--ref",
|
|
458
|
+
release.gitHead,
|
|
459
|
+
"--yes",
|
|
460
|
+
]);
|
|
461
|
+
} else {
|
|
462
|
+
deps.log("install: CLI, omp plugin, and Herdr plugin already pinned to the target release");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
deps.log("brief: refreshing managed ORCHESTRATOR.md floor while preserving POLICY.md");
|
|
466
|
+
briefChanged = true;
|
|
467
|
+
await upgradeBrief(deps, brief.kind, project);
|
|
468
|
+
|
|
469
|
+
if (initial.herdr === "active") {
|
|
470
|
+
herdrReloadStarted = true;
|
|
471
|
+
deps.log("reload: restarting herdr-fleet.service and recovering the orchestrator pane");
|
|
472
|
+
await mustRun(deps, "systemctl", ["restart", HERDR_UNIT]);
|
|
473
|
+
}
|
|
474
|
+
if (initial.dispatch !== "stopped") {
|
|
475
|
+
daemonReloadStarted = true;
|
|
476
|
+
deps.log("reload: restarting the omp-conductor dispatch daemon");
|
|
477
|
+
await deps.restartDaemon();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
deps.log("verify 1/2: package identities, services, pane, ticks, and paused dispatch");
|
|
481
|
+
const installed = await inspectSurfaces(deps);
|
|
482
|
+
if (!surfacesCurrent(installed, release.version, release.gitHead)) {
|
|
483
|
+
throw new Error(
|
|
484
|
+
`installed identities differ: cli=${installed.cliVersion}, omp=${installed.ompVersion ?? "missing"}, ` +
|
|
485
|
+
`herdr=${installed.herdrSource ?? "missing"}`,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (initial.herdr === "active") {
|
|
489
|
+
const active = await mustRun(deps, "systemctl", ["is-active", HERDR_UNIT]);
|
|
490
|
+
if (active.stdout.trim() !== "active") throw new Error(`${HERDR_UNIT} is not active after restart`);
|
|
491
|
+
}
|
|
492
|
+
await waitForRecovery(deps, initial, project);
|
|
493
|
+
deps.log("verify 2/2: recovered fleet remains stable");
|
|
494
|
+
await deps.sleep(1_000);
|
|
495
|
+
await waitForRecovery(deps, initial, project);
|
|
496
|
+
|
|
497
|
+
if (!initial.paused) deps.setPaused(false);
|
|
498
|
+
const restoredDispatch = deps.layers(project).dispatch;
|
|
499
|
+
if (restoredDispatch !== initial.dispatch) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`dispatch state is ${restoredDispatch}; expected to restore ${initial.dispatch}`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
} catch (err) {
|
|
505
|
+
try {
|
|
506
|
+
if (!deps.layers(project).paused) deps.setPaused(true);
|
|
507
|
+
} catch {
|
|
508
|
+
deps.setPaused(true);
|
|
509
|
+
}
|
|
510
|
+
const failure = err instanceof Error ? err.message : String(err);
|
|
511
|
+
deps.log(`upgrade failed: ${failure}`);
|
|
512
|
+
try {
|
|
513
|
+
await rollbackUpgrade(
|
|
514
|
+
deps,
|
|
515
|
+
surfaces,
|
|
516
|
+
project,
|
|
517
|
+
installTouched,
|
|
518
|
+
briefChanged,
|
|
519
|
+
herdrReloadStarted,
|
|
520
|
+
daemonReloadStarted,
|
|
521
|
+
);
|
|
522
|
+
} catch (rollbackErr) {
|
|
523
|
+
const rollback = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
524
|
+
throw new Error(
|
|
525
|
+
`upgrade failed: ${failure}; rollback incomplete: ${rollback}; dispatch remains paused`,
|
|
526
|
+
);
|
|
318
527
|
}
|
|
528
|
+
throw new Error(`upgrade failed: ${failure}; previous installation restored; dispatch remains paused`);
|
|
319
529
|
}
|
|
320
530
|
|
|
321
531
|
return {
|