openclaw/cli-invocation.mjs
Peter Steinberger 3bfccdb9bd
fix: preserve standalone CLI profiles and settle Doctor shutdown (#173154)
Preserve explicit profiles across standalone CLI respawns through one bootstrap-safe invocation owner. Select loose runtime/script arguments or direct compiled arguments from the embedded builder fact or adjacent manifest. Include the owner and compile-cache workers in packaged launchers and standalone builds.

Settle worker execution and native resource closure independently after snapshot failure. Release startup-refused broker claims only after native process and pipe closure, retain cleanup transport on operation failure, propagate Doctor snapshot errors, and cancel further inspections without losing maintenance restoration.

Document the private Unix-domain bind/connect allowance required by both loose and standalone snapshot workers. Keep network, home, Keychain, launchctl, and host-SQLite denials in the relocated verifier.

Validation: signed production-pinned JIT and bytecode artifacts at 4aa64f9ddc passed explicit-profile Doctor, Gateway/agent/restart campaigns, denied-IPC failure, and SIGTERM settlement in 364/375 ms with unchanged deadlines. Later source repairs passed owning-boundary regressions, scoped checks, and independent review. Signed artifact identity is preserved separately from the final CI head.

Exact-head CI for 9617855492767: 172 successful jobs, 19 skipped, zero failures (run 38833042547). Tested merge 58c3b87a8594 has the same tree as the prepared PR head. No CI exception, rerun, or timeout increase.
2026-10-15 18:24:34 -07:00

106 lines
3.9 KiB
JavaScript

// Bootstrap-safe ownership of OpenClaw executable identity and CLI invocation shape.
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
/** Packaging owns hashes; launch planning consumes only the artifact identity. */
export function readStandaloneRuntimeManifest(executable = process.execPath) {
if (!path.isAbsolute(executable)) {
return undefined;
}
const root = path.dirname(executable);
const manifestPath = path.join(root, "manifest.json");
let raw;
try {
raw = readFileSync(manifestPath, "utf8");
} catch (error) {
if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
return undefined;
}
throw error;
}
let manifest;
try {
manifest = JSON.parse(raw);
} catch (cause) {
throw new Error(`Invalid standalone runtime manifest: ${manifestPath}`, { cause });
}
const nonBlank = (value) => typeof value === "string" && value.trim().length > 0;
if (
manifest?.schemaVersion !== 1 ||
!["jit", "bytecode", "aot"].includes(manifest.mode) ||
!nonBlank(manifest.executable?.path) ||
!nonBlank(manifest.runtime?.path) ||
!nonBlank(manifest.openclaw?.version) ||
!nonBlank(manifest.bun?.version)
) {
throw new Error(`Invalid standalone runtime manifest: ${manifestPath}`);
}
const sidecarRelative = path.relative(root, path.resolve(root, manifest.runtime.path));
if (
path.isAbsolute(manifest.executable.path) ||
path.resolve(root, manifest.executable.path) !== path.resolve(executable) ||
path.isAbsolute(manifest.runtime.path) ||
!sidecarRelative ||
sidecarRelative === ".." ||
sidecarRelative.startsWith(`..${path.sep}`)
) {
throw new Error(`Invalid standalone runtime manifest: ${manifestPath}`);
}
return {
schemaVersion: 1,
mode: manifest.mode,
executable: { path: manifest.executable.path },
runtime: { path: manifest.runtime.path },
openclaw: { version: manifest.openclaw.version },
bun: { version: manifest.bun.version },
};
}
/** The compiler publishes this fact before any embedded module is loaded. */
export function getEmbeddedStandaloneRuntimeRoot() {
// eslint-disable-next-line no-underscore-dangle -- Matches the existing compiler bootstrap contract.
const rootUrl = globalThis.__openclawStandaloneRootUrl;
return typeof rootUrl === "string" ? fileURLToPath(rootUrl) : undefined;
}
function resolveStandaloneSidecarRoot(executable) {
if (executable === process.execPath) {
const embeddedRoot = getEmbeddedStandaloneRuntimeRoot();
if (embeddedRoot !== undefined) {
return embeddedRoot;
}
}
const manifest = readStandaloneRuntimeManifest(executable);
return manifest ? path.resolve(path.dirname(executable), manifest.runtime.path) : undefined;
}
export function isStandaloneRuntimeExecutable(executable = process.execPath) {
return resolveStandaloneSidecarRoot(executable) !== undefined;
}
/** A null entry explicitly selects a CLI command; undefined inherits the current script. */
export function resolveOpenClawCliInvocation({
execPath = process.execPath,
entryPath,
args = process.argv.slice(2),
execArgv = process.execArgv,
} = {}) {
const standaloneRoot = resolveStandaloneSidecarRoot(execPath);
if (standaloneRoot !== undefined || entryPath === null) {
if (standaloneRoot !== undefined && entryPath !== undefined && entryPath !== null) {
const relative = path.relative(standaloneRoot, path.resolve(entryPath));
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
throw new Error(
"A standalone OpenClaw executable cannot run another installation's entry script.",
);
}
}
return { command: execPath, args: [...args] };
}
const entry = entryPath ?? process.argv[1];
if (!entry) {
throw new Error("Cannot invoke the loose OpenClaw CLI without an entry script.");
}
return { command: execPath, args: [...execArgv, entry, ...args] };
}