mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-09 17:32:41 -10:00
feat(slice-22): --openrig-home flag for multi-instance daemon state
release-0.3.1 Slice 22 founder-walk-vm-populated-env Phase B — adds `--openrig-home <path>` to the CLI lifecycle so the operator can run two daemons in one Tart preview VM with fully isolated state per OQ-4 (a) (separate OPENRIG_HOME dirs per daemon). Existing daemon multi-instance support was env-driven (OPENRIG_PORT, OPENRIG_HOST, OPENRIG_DB, OPENRIG_HOME) and the CLI already had --port/--host/--db flags. This commit adds the matching --openrig-home flag end-to-end + tests, mirroring the user-explicit-vs-default pattern from the auth-bearer-tailscale-trust slice (host flag): omit OPENRIG_HOME from the spawn env when the opt is undefined (so the shell-set value passes through baseEnv); set it explicitly when the operator opts in. Audits BOTH startDaemon callers per banked feedback_lifecycle_exporter_audit_all_callers (the lesson from the auth-bearer slice's two-round forward-fix dance): - packages/cli/src/commands/daemon.ts — `rig daemon start --openrig-home <path>` - packages/cli/src/commands/up.ts — `rig up <source> --openrig-home <path>` Both threaded through StartOptions → buildDaemonEnv → spawn env. Tests (TDD red→green with distinct-value discriminators per banked feedback_poc_regression_must_discriminate): - openrigHome undefined → OPENRIG_HOME omitted from spawn env - openrigHome defined → OPENRIG_HOME set to value - baseEnv has OPENRIG_HOME, opt undefined → shell value preserved - baseEnv has OPENRIG_HOME=/shell/blank, opt=/opt/populated → opt wins - daemon-lifecycle.test.ts: 56/56 green (was 52; +4 new) - Full CLI+daemon sweep (env-vars cleaned): 4270/4272 (one environmental pre-existing fail unchanged from prior slices) - CLI tsc clean No new SC-29 EXCEPTIONS: OPENRIG_HOME pre-existed; this commit is purely additive CLI ergonomics on an existing env contract. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
3c75cb2282
commit
3d9a6f6190
4 files changed
+91
-2
No files matched your search
@@ -77,6 +77,11 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
.option("--port <port>", "Port to listen on")
|
||||
.option("--host <host>", "Host to bind on")
|
||||
.option("--db <path>", "Database path")
|
||||
// Slice 22 founder-walk-vm-populated-env — operator-explicit
|
||||
// OPENRIG_HOME directory. Enables running two daemons in the same
|
||||
// VM with fully isolated state (e.g., blank-slate vs populated
|
||||
// preview environments).
|
||||
.option("--openrig-home <path>", "OPENRIG_HOME directory for daemon state (transcripts, plugins, specs, etc.)")
|
||||
// V0.3.1 slice 05 kernel-rig-as-default — skip the kernel auto-boot
|
||||
// path. Used by test fixtures, headless CI, and operators who want
|
||||
// a no-kernel daemon for ad-hoc topology work. The daemon proceeds
|
||||
@@ -91,7 +96,7 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
// weaker "daemon-ready". Default 60s; override with --wait-for-kernel-ms.
|
||||
.option("--wait-for-kernel", "After daemon binds, also wait for kernel-agent readiness (default timeout 60s)")
|
||||
.option("--wait-for-kernel-ms <ms>", "Override --wait-for-kernel timeout in milliseconds")
|
||||
.action(async (opts: { port?: string; host?: string; db?: string; kernel?: boolean; waitForKernel?: boolean; waitForKernelMs?: string }) => {
|
||||
.action(async (opts: { port?: string; host?: string; db?: string; openrigHome?: string; kernel?: boolean; waitForKernel?: boolean; waitForKernelMs?: string }) => {
|
||||
try {
|
||||
const { ConfigStore } = await import("../config-store.js");
|
||||
const { SystemPreflight } = await import("../system-preflight.js");
|
||||
@@ -147,6 +152,10 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
// OPENRIG_NO_KERNEL env var so the daemon's kernel-boot
|
||||
// check in startup.ts honors the flag.
|
||||
skipKernelBoot: skipKernel,
|
||||
// Slice 22 founder-walk-vm-populated-env — operator-explicit
|
||||
// OPENRIG_HOME directory (omit when undefined so the daemon
|
||||
// falls through to its built-in default ~/.openrig).
|
||||
openrigHome: opts.openrigHome,
|
||||
},
|
||||
getDeps(),
|
||||
);
|
||||
|
||||
@@ -31,7 +31,10 @@ Examples:
|
||||
.option("--target <root>", "Target root directory for package installation (.rigbundle only; does not change agent cwd)")
|
||||
.option("--existing", "Treat <source> as an existing rig name; bypass library-spec name resolution")
|
||||
.option("--json", "JSON output for agents")
|
||||
.action(async (source: string, opts: { plan?: boolean; yes?: boolean; cwd?: string; target?: string; existing?: boolean; json?: boolean }) => {
|
||||
// Slice 22 founder-walk-vm-populated-env — operator-explicit
|
||||
// OPENRIG_HOME for the auto-started daemon's state directory.
|
||||
.option("--openrig-home <path>", "OPENRIG_HOME directory for the auto-started daemon (state, transcripts, plugins, specs)")
|
||||
.action(async (source: string, opts: { plan?: boolean; yes?: boolean; cwd?: string; target?: string; existing?: boolean; json?: boolean; openrigHome?: string }) => {
|
||||
const deps = getDepsF();
|
||||
|
||||
// Run preflight before auto-start
|
||||
@@ -96,6 +99,10 @@ Examples:
|
||||
transcriptsLines: resolvedConfig?.transcripts.lines,
|
||||
transcriptsPollIntervalSeconds: resolvedConfig?.transcripts.pollIntervalSeconds,
|
||||
workspaceRoot: resolvedConfig?.workspace.root,
|
||||
// Slice 22 founder-walk-vm-populated-env — operator-explicit
|
||||
// OPENRIG_HOME (omit when undefined so the daemon's default
|
||||
// homedir-relative ~/.openrig still applies).
|
||||
openrigHome: opts.openrigHome,
|
||||
}, deps.lifecycleDeps);
|
||||
status = await getDaemonStatus(deps.lifecycleDeps);
|
||||
} catch (err) {
|
||||
|
||||
@@ -44,6 +44,10 @@ export interface StartOptions {
|
||||
// Exposed at the CLI as `rig daemon start --no-kernel`; projected
|
||||
// into the daemon process env as OPENRIG_NO_KERNEL.
|
||||
skipKernelBoot?: boolean;
|
||||
// Slice 22 founder-walk-vm-populated-env — operator-explicit
|
||||
// OPENRIG_HOME directory for the daemon child process. Enables two
|
||||
// daemons in one VM to have fully isolated state (OQ-4 (a)).
|
||||
openrigHome?: string;
|
||||
}
|
||||
|
||||
export interface LifecycleDeps {
|
||||
@@ -269,6 +273,17 @@ export function buildDaemonEnv(
|
||||
// V0.3.1 slice 05 — projected via OPENRIG_NO_KERNEL env var so
|
||||
// the daemon's startup.ts kernel-boot path honors the flag.
|
||||
skipKernelBoot?: boolean;
|
||||
/**
|
||||
* Slice 22 founder-walk-vm-populated-env: operator-explicit
|
||||
* OPENRIG_HOME directory. When defined, sets OPENRIG_HOME in the
|
||||
* child process so the daemon writes its state (transcripts,
|
||||
* plugins, context-packs, agent-images, specs) under the chosen
|
||||
* directory. When undefined, the env-passthrough loop preserves
|
||||
* whatever OPENRIG_HOME the operator's shell already had (their
|
||||
* shell-level opt-in still wins). Enables two daemons in the same
|
||||
* VM to have fully isolated state per OQ-4 (a).
|
||||
*/
|
||||
openrigHome?: string;
|
||||
},
|
||||
): Record<string, string> {
|
||||
const env: Record<string, string> = {};
|
||||
@@ -297,6 +312,9 @@ export function buildDaemonEnv(
|
||||
if (opts.transcriptsPollIntervalSeconds !== undefined) {
|
||||
env["OPENRIG_TRANSCRIPTS_POLL_INTERVAL_SECONDS"] = String(opts.transcriptsPollIntervalSeconds);
|
||||
}
|
||||
if (opts.openrigHome !== undefined) {
|
||||
env["OPENRIG_HOME"] = opts.openrigHome;
|
||||
}
|
||||
if (opts.skipKernelBoot) {
|
||||
env["OPENRIG_NO_KERNEL"] = "1";
|
||||
}
|
||||
@@ -361,6 +379,7 @@ export async function startDaemon(opts: StartOptions, deps: LifecycleDeps): Prom
|
||||
transcriptsLines: opts.transcriptsLines,
|
||||
transcriptsPollIntervalSeconds: opts.transcriptsPollIntervalSeconds,
|
||||
skipKernelBoot: opts.skipKernelBoot,
|
||||
openrigHome: opts.openrigHome,
|
||||
}),
|
||||
stdio: ["ignore", logFd, logFd],
|
||||
detached: true,
|
||||
|
||||
@@ -981,6 +981,60 @@ describe("buildDaemonEnv", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Slice 22 founder-walk-vm-populated-env: --openrig-home / OPENRIG_HOME
|
||||
// flag flows through buildDaemonEnv so two daemons in one VM can have
|
||||
// fully isolated state directories (OQ-4 resolved to (a) fully isolated).
|
||||
describe("slice 22: openrigHome (OPENRIG_HOME) env projection", () => {
|
||||
it("omits OPENRIG_HOME when openrigHome opt is undefined (shell-env passthrough wins)", () => {
|
||||
const baseEnv: Record<string, string> = { HOME: "/Users/tester", PATH: "/usr/bin" };
|
||||
const result = buildDaemonEnv(baseEnv, {
|
||||
port: 7433,
|
||||
db: "/tmp/test.db",
|
||||
});
|
||||
expect(result["OPENRIG_HOME"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("sets OPENRIG_HOME when openrigHome opt is defined (operator opt-in)", () => {
|
||||
const baseEnv: Record<string, string> = { HOME: "/Users/tester", PATH: "/usr/bin" };
|
||||
const result = buildDaemonEnv(baseEnv, {
|
||||
port: 7433,
|
||||
db: "/tmp/test.db",
|
||||
openrigHome: "/Users/example/.openrig-blank",
|
||||
});
|
||||
expect(result["OPENRIG_HOME"]).toBe("/Users/example/.openrig-blank");
|
||||
});
|
||||
|
||||
it("preserves shell-set OPENRIG_HOME through baseEnv when opt is undefined", () => {
|
||||
const baseEnv: Record<string, string> = {
|
||||
HOME: "/Users/tester",
|
||||
PATH: "/usr/bin",
|
||||
OPENRIG_HOME: "/Users/example/.openrig-shell",
|
||||
};
|
||||
const result = buildDaemonEnv(baseEnv, {
|
||||
port: 7433,
|
||||
db: "/tmp/test.db",
|
||||
});
|
||||
expect(result["OPENRIG_HOME"]).toBe("/Users/example/.openrig-shell");
|
||||
});
|
||||
|
||||
it("opt-set OPENRIG_HOME overrides shell-set baseEnv value (distinct-value discriminator)", () => {
|
||||
// Per banked feedback_poc_regression_must_discriminate — distinct
|
||||
// values so we can prove the override branch fired and didn't pass
|
||||
// vacuously through baseEnv-passthrough.
|
||||
const baseEnv: Record<string, string> = {
|
||||
HOME: "/Users/tester",
|
||||
PATH: "/usr/bin",
|
||||
OPENRIG_HOME: "/Users/example/.openrig-shell-blank",
|
||||
};
|
||||
const result = buildDaemonEnv(baseEnv, {
|
||||
port: 7433,
|
||||
db: "/tmp/test.db",
|
||||
openrigHome: "/Users/example/.openrig-opt-populated",
|
||||
});
|
||||
expect(result["OPENRIG_HOME"]).toBe("/Users/example/.openrig-opt-populated");
|
||||
});
|
||||
});
|
||||
|
||||
describe("V1 pre-release CLI/daemon Item 1 — transcript rotation tunables projection", () => {
|
||||
it("projects transcriptsLines + transcriptsPollIntervalSeconds into OPENRIG_* env vars when provided", () => {
|
||||
const baseEnv: Record<string, string> = { HOME: "/Users/tester", PATH: "/usr/bin" };
|
||||
|
||||
Reference in new issue
Block a user