omp-conductor 0.18.2 → 0.19.0
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 +105 -40
- package/REFERENCE.md +865 -30
- package/package.json +1 -1
- package/schema/config.schema.json +26 -0
- package/src/admission.ts +212 -26
- package/src/ask.ts +288 -1
- package/src/briefs/orchestrator.md +6 -5
- package/src/cli.ts +5 -1
- package/src/command-help.ts +9 -1
- package/src/command-manifest.ts +36 -3
- package/src/commands/arm.ts +5 -1
- package/src/commands/context.ts +2 -0
- package/src/commands/message.ts +26 -2
- package/src/commands/reconcile-units.ts +104 -0
- package/src/commands/release-composition.ts +232 -0
- package/src/commands/resume.ts +2 -27
- package/src/commands/setup.ts +101 -16
- package/src/commands/stats.ts +11 -30
- package/src/commands/tail.ts +31 -1
- package/src/commands/upgrade.ts +20 -3
- package/src/commands/verb.ts +2 -1
- package/src/config-schema.ts +19 -0
- package/src/config.ts +80 -0
- package/src/credential-class.ts +366 -0
- package/src/daemon.ts +1218 -288
- package/src/dashboard/app.js +504 -2
- package/src/dashboard/controls.ts +336 -0
- package/src/dashboard/index.html +30 -0
- package/src/dashboard/server.ts +271 -30
- package/src/dashboard/style.css +116 -0
- package/src/dashboard/transcript.ts +173 -0
- package/src/doctor.ts +377 -20
- package/src/failure-class.ts +59 -0
- package/src/fleet.ts +497 -15
- package/src/host.ts +6 -130
- package/src/omp.ts +29 -0
- package/src/orchestrator-tick.ts +343 -88
- package/src/pause.ts +233 -0
- package/src/settlement.ts +159 -2
- package/src/setup-answers.ts +97 -0
- package/src/setup-host.ts +321 -1155
- package/src/setup-install.ts +204 -27
- package/src/setup-wizard.ts +111 -50
- package/src/setup.ts +33 -0
- package/src/spend-telemetry.ts +117 -0
- package/src/stats.ts +35 -0
- package/src/status-render.ts +348 -19
- package/src/store.ts +1229 -55
- package/src/telegram-freshness.ts +269 -0
- package/src/to-spec.ts +27 -0
- package/src/types.ts +697 -4
- package/src/unblock.ts +22 -0
- package/src/unit-reconcile.ts +303 -0
- package/src/upgrade-verify.ts +8 -1
- package/src/upgrade.ts +299 -12
- package/src/verbs/actions.ts +124 -10
- package/src/verbs/protocol.ts +70 -2
- package/src/verbs/server.ts +447 -8
- package/src/wake.ts +48 -0
- package/src/worker.ts +403 -3
package/src/host.ts
CHANGED
|
@@ -187,21 +187,7 @@ export function rssBytesFromHealthz(body: string | undefined): number | undefine
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
// -------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* The dedicated worker account earlier boundary slices created on this host.
|
|
194
|
-
*
|
|
195
|
-
* Worker sessions no longer launch under it (#894 restored the fleet-account
|
|
196
|
-
* runtime v0.18.0 used): the constants below survive only so `setup host` can
|
|
197
|
-
* keep staging and recognising the units it once installed until #895 retires
|
|
198
|
-
* that host lifecycle end to end. Nothing on the session-launch path reads
|
|
199
|
-
* them.
|
|
200
|
-
*/
|
|
201
|
-
export const WORKER_ACCOUNT = "omp-worker";
|
|
202
|
-
|
|
203
|
-
/** The worker account's system home: its harness config, caches and state. */
|
|
204
|
-
export const WORKER_HOME_DIR = "/var/lib/omp-worker";
|
|
190
|
+
// ------------------------------------------------------- harness resolution --
|
|
205
191
|
|
|
206
192
|
/**
|
|
207
193
|
* The harness package omp-conductor loads a session from. Held here, beside
|
|
@@ -214,36 +200,14 @@ export const OMP_HARNESS_PACKAGE = "@oh-my-pi/pi-coding-agent";
|
|
|
214
200
|
export const OMP_NATIVES_PACKAGE = "@oh-my-pi/pi-natives";
|
|
215
201
|
|
|
216
202
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* to decide whether the child is there. The fleet account's home is granted to
|
|
223
|
-
* the worker search-only by design (#798), so a bare `@oh-my-pi/pi-coding-agent`
|
|
224
|
-
* import from `<fleet home>/node_modules/omp-conductor` did not find the
|
|
225
|
-
* operator's install at all and fell through to Bun's auto-install, which
|
|
226
|
-
* downloaded a *different* harness version into the worker's own cache whose
|
|
227
|
-
* native addon then failed to load — every dispatch on the host stopped before
|
|
228
|
-
* session start.
|
|
229
|
-
*
|
|
230
|
-
* The fix is a read-only bind of the operator's `node_modules` at a path whose
|
|
231
|
-
* every ancestor is world-readable. Worker children are launched from it, so
|
|
232
|
-
* the entry module, the peer import and every transitive import resolve inside
|
|
233
|
-
* one tree the worker can enumerate — and, because a bind shares inodes with
|
|
234
|
-
* its source, it is the operator's exact build rather than a copy that can
|
|
235
|
-
* drift.
|
|
236
|
-
*
|
|
237
|
-
* Deliberately **not** under {@link WORKER_HOME_DIR}: the worker owns its home
|
|
238
|
-
* between setups, and a symlink planted where a root-run mount point goes would
|
|
239
|
-
* redirect that mount to an arbitrary target (#816).
|
|
203
|
+
* The mount point releases 0.18.1-era bound the operator's install at, kept
|
|
204
|
+
* only as the retirement target `setup host` and `reconcile-units` remove
|
|
205
|
+
* (#895). Nothing mounts it any more: worker sessions launch under the fleet
|
|
206
|
+
* account again (#894), so the harness resolves through ordinary Node/Bun
|
|
207
|
+
* resolution from this package's own install root.
|
|
240
208
|
*/
|
|
241
209
|
export const WORKER_HARNESS_DIR = "/var/lib/omp-worker-harness";
|
|
242
210
|
|
|
243
|
-
/** The bound `node_modules` itself. The leaf name is load-bearing: it is what
|
|
244
|
-
* Node/Bun resolution looks for walking up from the entry module. */
|
|
245
|
-
export const WORKER_HARNESS_NODE_MODULES = join(WORKER_HARNESS_DIR, "node_modules");
|
|
246
|
-
|
|
247
211
|
/**
|
|
248
212
|
* The install root a module path sits in — its nearest ancestor named
|
|
249
213
|
* `node_modules` — or `undefined` when it has none, which is this package
|
|
@@ -256,91 +220,3 @@ export function packageNodeModulesRoot(modulePath: string): string | undefined {
|
|
|
256
220
|
const idx = parts.lastIndexOf("node_modules");
|
|
257
221
|
return idx < 0 ? undefined : `/${parts.slice(0, idx + 1).join("/")}`;
|
|
258
222
|
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* `path` as a worker session sees it through the harness binding, or
|
|
262
|
-
* `undefined` when it is not inside `packageRoot` — a test seam pointing at a
|
|
263
|
-
* file elsewhere, or a source checkout with no install root at all.
|
|
264
|
-
*
|
|
265
|
-
* Production uses {@link WORKER_HARNESS_NODE_MODULES}; an explicit binding
|
|
266
|
-
* root lets the Linux regression build the same inode-sharing tree under its
|
|
267
|
-
* private temporary directory instead of touching the live host mount.
|
|
268
|
-
*/
|
|
269
|
-
export function workerHarnessPath(
|
|
270
|
-
path: string,
|
|
271
|
-
packageRoot: string | undefined,
|
|
272
|
-
bindingRoot: string = WORKER_HARNESS_NODE_MODULES,
|
|
273
|
-
): string | undefined {
|
|
274
|
-
if (packageRoot === undefined) return undefined;
|
|
275
|
-
const absolute = resolve(path);
|
|
276
|
-
const prefix = `${packageRoot}/`;
|
|
277
|
-
if (!absolute.startsWith(prefix)) return undefined;
|
|
278
|
-
return join(bindingRoot, absolute.slice(prefix.length));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** One path's filesystem identity. Identity rather than bytes, because that is
|
|
282
|
-
* exactly what distinguishes a live bind of the operator's install (same
|
|
283
|
-
* device and inode) from an empty mount point or a copy that has drifted. */
|
|
284
|
-
function pathIdentity(path: string): string | undefined {
|
|
285
|
-
try {
|
|
286
|
-
const st = statSync(path);
|
|
287
|
-
return `${st.dev}:${st.ino}`;
|
|
288
|
-
} catch {
|
|
289
|
-
return undefined;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/** Read-only facts {@link harnessBindingProblem} decides on; injected by tests. */
|
|
294
|
-
export interface HarnessBindingDeps {
|
|
295
|
-
/** This module's own directory — the install root is derived from it. */
|
|
296
|
-
moduleDir?: string;
|
|
297
|
-
/** `dev:ino` of one path, or `undefined` when it cannot be stat'ed. */
|
|
298
|
-
identity?: (path: string) => string | undefined;
|
|
299
|
-
/** Alternate binding root for the isolated Linux currentness regression. */
|
|
300
|
-
bindingRoot?: string;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Why a worker session could not load the operator's harness through the
|
|
305
|
-
* binding, or `undefined` when it can.
|
|
306
|
-
*
|
|
307
|
-
* Both halves of the launch are checked, by filesystem identity: this package's
|
|
308
|
-
* own directory (the child's entry module comes from it) and the harness
|
|
309
|
-
* package directory (its peer import resolves to it). A mount point that is
|
|
310
|
-
* empty, stale, or bound to some other tree fails on the identity comparison
|
|
311
|
-
* rather than being taken on faith — which is the whole point, since the
|
|
312
|
-
* symptom this replaces was a *successful* import of the wrong build.
|
|
313
|
-
*/
|
|
314
|
-
export function harnessBindingProblem(deps: HarnessBindingDeps = {}): string | undefined {
|
|
315
|
-
const moduleDir = deps.moduleDir ?? import.meta.dir;
|
|
316
|
-
const identity = deps.identity ?? pathIdentity;
|
|
317
|
-
const bindingRoot = deps.bindingRoot ?? WORKER_HARNESS_NODE_MODULES;
|
|
318
|
-
const packageRoot = packageNodeModulesRoot(moduleDir);
|
|
319
|
-
if (packageRoot === undefined) {
|
|
320
|
-
return (
|
|
321
|
-
`omp-conductor is running from ${moduleDir}, which is not inside a node_modules install root — ` +
|
|
322
|
-
"a worker session resolves its harness through a read-only bind of that root, so worker dispatch " +
|
|
323
|
-
"needs the installed package (install omp-conductor with its harness peer, then re-run `omp-conductor setup host`)"
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
for (const dir of [moduleDir, join(packageRoot, OMP_HARNESS_PACKAGE)]) {
|
|
327
|
-
const source = identity(dir);
|
|
328
|
-
if (source === undefined) {
|
|
329
|
-
return (
|
|
330
|
-
`${dir} is missing or unreadable — ${OMP_HARNESS_PACKAGE} must be installed alongside omp-conductor ` +
|
|
331
|
-
"for a worker session to load the same harness build the operator runs"
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
// Non-null by construction: `dir` is inside `packageRoot`.
|
|
335
|
-
const bound = workerHarnessPath(dir, packageRoot, bindingRoot) ?? dir;
|
|
336
|
-
if (identity(bound) !== source) {
|
|
337
|
-
return (
|
|
338
|
-
`${bound} does not resolve to ${dir} — the worker harness binding of ${packageRoot} at ` +
|
|
339
|
-
`${bindingRoot} is missing or stale, so a worker session would resolve a different ` +
|
|
340
|
-
"harness build (or none). Run `omp-conductor setup host` to establish it"
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return undefined;
|
|
345
|
-
}
|
|
346
|
-
|
package/src/omp.ts
CHANGED
|
@@ -641,6 +641,24 @@ export interface CreateSessionOptions {
|
|
|
641
641
|
* its verb channel to this pid, and a channel accepts nothing until it is bound.
|
|
642
642
|
*/
|
|
643
643
|
onSpawn?: (pid: number) => void;
|
|
644
|
+
/**
|
|
645
|
+
* The session's Herdr representation (#840), opened from the exact pid the
|
|
646
|
+
* spawn below reports and released when the child is gone.
|
|
647
|
+
*
|
|
648
|
+
* Injected rather than imported so this module keeps knowing nothing about
|
|
649
|
+
* Herdr: `createSession` owns the process, and whoever launches a *worker*
|
|
650
|
+
* owns whether that process should be visible in an operator's workspace. A
|
|
651
|
+
* caller that passes nothing gets today's behaviour exactly.
|
|
652
|
+
*
|
|
653
|
+
* `open` is called with the same pid, at the same instant, as
|
|
654
|
+
* {@link CreateSessionOptions.onSpawn} — the representation is of the
|
|
655
|
+
* authoritative child or of nothing. It must never throw and never block the
|
|
656
|
+
* launch: visibility is not a precondition for work.
|
|
657
|
+
*/
|
|
658
|
+
pane?: {
|
|
659
|
+
open(pid: number): void;
|
|
660
|
+
release(): void;
|
|
661
|
+
};
|
|
644
662
|
/**
|
|
645
663
|
* Pre-spawn admission gate (#374). Consulted once, immediately after the
|
|
646
664
|
* socket bind await and immediately before `Bun.spawn` — the last window a
|
|
@@ -805,6 +823,11 @@ export async function createSession(opts: CreateSessionOptions): Promise<AgentSe
|
|
|
805
823
|
// Synchronously, before a single await: the child cannot have connected yet, so
|
|
806
824
|
// the caller's channel is bound before it can be reached.
|
|
807
825
|
if (child.pid !== undefined) opts.onSpawn?.(child.pid);
|
|
826
|
+
// The workspace representation is of THIS pid or of nothing (#840). After the
|
|
827
|
+
// verb-channel bind above, because a pane is a convenience and the bind is a
|
|
828
|
+
// correctness boundary — and never before the child exists, which is what
|
|
829
|
+
// makes "bound to the exact session-host pid" true rather than nominal.
|
|
830
|
+
if (child.pid !== undefined) opts.pane?.open(child.pid);
|
|
808
831
|
|
|
809
832
|
let stderrTail = "";
|
|
810
833
|
const drain = async (stream: ReadableStream<Uint8Array> | undefined, prefix: string): Promise<void> => {
|
|
@@ -965,6 +988,12 @@ export async function createSession(opts: CreateSessionOptions): Promise<AgentSe
|
|
|
965
988
|
onExit();
|
|
966
989
|
const exitCode = typeof code === "number" ? code : null;
|
|
967
990
|
emitSessionExit(exitCode);
|
|
991
|
+
// The pid this pane spoke for is gone, so conductor stops speaking for it
|
|
992
|
+
// (#840) — on every exit path, crash and clean dispose alike, because a
|
|
993
|
+
// representation that outlives its process is a worker the workspace still
|
|
994
|
+
// shows as live. What happens to the pane itself is #841's policy; this only
|
|
995
|
+
// gives back the authority.
|
|
996
|
+
opts.pane?.release();
|
|
968
997
|
// A child that cannot start writes `start-error` over the socket, not the
|
|
969
998
|
// pipes — and its exit can be dispatched before the accept of a connection
|
|
970
999
|
// that already completed in the kernel. Let the socket settle before the
|