Files
stack/packages/cli/src/console-supervisor.mjs
T
jason.woltje 1947a056fb fix(console): D1 review fixes for the detached dev-owner supervisor (#1873)
Build review P2: the supervisor ignores a lost reply pipe and keeps
supervising. Security P2: a stop signal during start is held until the
start settles, and the CLI's start timeout signals the supervisor's
process group. Security P1 (part): console start --dev-local-owner
refuses inside an agent run. Tests cover review, confirm and reverse on
the org autonomy route; TOOLS.md and the webui README are corrected.
2026-11-11 14:16:09 -06:00

40 lines
2.3 KiB
JavaScript

// D1 (#1873): the supervisor of a detached dev-local-owner Console. `mosaic console start
// --dev-local-owner --detach` starts this process in its own session (org-console.mjs
// detachedSupervisor). It starts the Console exactly as a foreground start does, with --supervised,
// and relaunches it when the owner restarts it (F3). Its one stdout line is the start's reply:
// { receipt, warnings } or { error, exitCode, warnings }. Later warnings go to stderr, the log.
import { consoleStart } from './org-console.mjs';
import { CliError } from './errors.mjs';
const { configDir, dataRoot, instance, port, bind, allowHosts } = JSON.parse(process.argv[2]);
const warnings = [];
let started = false;
const warn = s => { if (started) process.stderr.write(s); else warnings.push(s); };
// The starting CLI can be gone before the reply (Ctrl-C, a wrapper's timeout). The Console is
// started and its state names it, so a reply nobody reads is dropped and supervision goes on.
process.stdout.on('error', () => {});
const reply = value => new Promise(resolve => process.stdout.write(`${JSON.stringify(value)}\n`, () => resolve()));
// A stop signal during the start (the CLI's start timeout signals this process group) is kept and
// acted on once the start settles, so a failed start still releases the start lock.
let stopAsked = false;
const early = () => { stopAsked = true; };
for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, early);
let receipt;
try {
receipt = await consoleStart({ configDir, dataRoot, instance, env: process.env }, { devLocalOwner: true, port, bind, allowHosts, warn });
} catch (e) {
const exitCode = e instanceof CliError ? e.exitCode : 1;
if (!(e instanceof CliError)) process.stderr.write(`${e?.stack ?? e}\n`);
await reply({ ...(e instanceof CliError ? { error: e.message } : {}), exitCode, warnings });
process.exit(exitCode);
}
started = true;
// A stop signal ends supervision cleanly, as in the foreground (cli.mjs consoleCommand).
const stop = () => receipt.stop();
for (const signal of ['SIGINT', 'SIGTERM']) { process.off(signal, early); process.on(signal, stop); }
if (stopAsked) stop();
await reply({ receipt, warnings });
const code = await receipt.done;
if (code !== 0) process.stderr.write(`mosaic: console stopped unexpectedly (${code})\n`);
process.exit(code);