mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
Managed compaction now works on a small Claude pane, such as a tile in a follow view. The /compact send's readiness probe and its final input check read the pane at a readable size, using the existing readable capture, which pins the window, waits for the redraw and restores the size exactly; the uncertain post-compact read does too. The managed send's idle wait includes the readable budget, still bounded by the attempt's deadline, so the larger read can finish. Ordinary sends are unchanged, and a pane that still can't be classified stays unknown, never idle. An ended attempt now records its reason in COMPACTION-STATE.json. Reported by the Ops team on an 11x20 pane.
533 lines
25 KiB
TypeScript
533 lines
25 KiB
TypeScript
// Slice-17 mini-req 7 — BARE-RIG FRONT DOOR (founder + arch reinforcements).
|
|
// bare `rig` (no args): BOTH stdin+stdout TTY → launch the TUI; a pipe or
|
|
// redirect on EITHER stream → fall through to the normal usage path with a
|
|
// fast exit (never hang a script); daemon-down / TUI-init-fail → helpful
|
|
// usage, NEVER a stack trace; --help/--version/subcommands are args and
|
|
// behave unchanged. New file; shipped CLI floors untouched.
|
|
import { describe, it, expect, vi } from "vitest";
|
|
import { spawnSync } from "node:child_process";
|
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { createServer } from "node:net";
|
|
import { runFrontDoor, resolveTuiPath, probeFrontDoor, type FrontDoorIo } from "../src/front-door.js";
|
|
import { diagnoseRuntimePosture, observeClaudePermission } from "../../daemon/src/domain/permission-drift.js";
|
|
import { DaemonClient, DaemonConnectionError, DaemonResponseError, DaemonTimeoutError } from "../src/client.js";
|
|
|
|
// A GUARANTEED-refused local port (bind an ephemeral port, capture it, close → connecting refuses).
|
|
// A REAL socket refusal, not a hand-built error — buys the actual Node/undici rejection shape.
|
|
async function refusedPort(): Promise<number> {
|
|
return await new Promise<number>((resolve, reject) => {
|
|
const srv = createServer();
|
|
srv.once("error", reject);
|
|
srv.listen(0, "127.0.0.1", () => {
|
|
const addr = srv.address();
|
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
srv.close(() => resolve(port));
|
|
});
|
|
});
|
|
}
|
|
|
|
function io(overrides: Partial<FrontDoorIo> = {}): FrontDoorIo & {
|
|
outLines: string[];
|
|
errLines: string[];
|
|
exits: number[];
|
|
launches: number;
|
|
} {
|
|
const outLines: string[] = [];
|
|
const errLines: string[] = [];
|
|
const exits: number[] = [];
|
|
const holder = { launches: 0 };
|
|
return {
|
|
outLines,
|
|
errLines,
|
|
exits,
|
|
get launches() {
|
|
return holder.launches;
|
|
},
|
|
stdinIsTTY: true,
|
|
stdoutIsTTY: true,
|
|
out: (l: string) => void outLines.push(l),
|
|
err: (l: string) => void errLines.push(l),
|
|
exit: (c: number) => void exits.push(c),
|
|
probeDaemon: async () => true,
|
|
launchTui: async () => {
|
|
holder.launches += 1;
|
|
return 0;
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("bare-rig front door — ownership rules", () => {
|
|
it("bare `rig` with BOTH streams TTY and the daemon up launches the TUI", async () => {
|
|
const deps = io();
|
|
const handled = await runFrontDoor(["node", "rig"], deps);
|
|
expect(handled).toBe(true);
|
|
expect(deps.launches).toBe(1);
|
|
expect(deps.errLines.join("\n")).toBe("");
|
|
});
|
|
|
|
it("stdin piped (echo x | rig) → NOT owned: falls through to the normal usage path", async () => {
|
|
const deps = io({ stdinIsTTY: false });
|
|
expect(await runFrontDoor(["node", "rig"], deps)).toBe(false);
|
|
expect(deps.launches).toBe(0);
|
|
});
|
|
|
|
it("stdout redirected (rig > file) → NOT owned: falls through to the normal usage path", async () => {
|
|
const deps = io({ stdoutIsTTY: false });
|
|
expect(await runFrontDoor(["node", "rig"], deps)).toBe(false);
|
|
expect(deps.launches).toBe(0);
|
|
});
|
|
|
|
it("ANY argument (--help / --version / a subcommand) → NOT owned, regardless of TTY", async () => {
|
|
for (const argv of [["node", "rig", "--help"], ["node", "rig", "--version"], ["node", "rig", "ps"], ["node", "rig", "context", "list"]]) {
|
|
const deps = io();
|
|
expect(await runFrontDoor(argv, deps), argv.join(" ")).toBe(false);
|
|
expect(deps.launches).toBe(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("bare-rig front door — first-impression degrade (never a stack trace)", () => {
|
|
it("BLOCKER 1: transport connect (daemon DOWN) LAUNCHES the crash-cart TUI, not a degrade-and-exit", async () => {
|
|
const deps = io({
|
|
probeDaemon: async () => ({
|
|
state: "diagnostic" as const,
|
|
diagnostic: {
|
|
transport: { state: "connect" as const },
|
|
cwdRead: { state: "unknown" as const },
|
|
commandPath: { state: "unknown" as const },
|
|
enforcement: { axis: "not_applicable" as const, state: "unknown" as const, expected: null, effective: null, sourcePath: null, reason: "transport_unavailable" },
|
|
observedAt: "2026-08-08T00:00:00.000Z",
|
|
},
|
|
}),
|
|
});
|
|
const handled = await runFrontDoor(["node", "rig"], deps);
|
|
expect(handled).toBe(true);
|
|
// The daemon-down state is exactly what the crash-cart cockpit exists for — bare `rig` must REACH it.
|
|
expect(deps.launches).toBe(1);
|
|
expect(deps.exits).toEqual([0]); // exits with the TUI's code, not a forced degrade exit(1)
|
|
const text = deps.errLines.join("\n");
|
|
expect(text).not.toMatch(/runtime posture: TRANSPORT_CONNECT/); // no degrade-and-exit for daemon-down
|
|
});
|
|
|
|
it("BLOCKER 1 PRODUCTION PATH: bare rig + daemon down via a REAL refused socket → the crash-cart TUI launches", async () => {
|
|
// r1/guard round-6 rule: a stubbed error is a claim about Node's behavior — buy it with a REAL socket.
|
|
// The REAL DaemonClient hits a REAL closed port (guaranteed-refused via bind→close); the real
|
|
// probeFrontDoor classification + openMissionControl launch decision run over the actual undici
|
|
// rejection shape. No fabricated error at the seam under test.
|
|
const port = await refusedPort();
|
|
const realClient = new DaemonClient(`http://127.0.0.1:${port}`, { timeoutMs: 1500 });
|
|
const deps = io({ probeDaemon: () => probeFrontDoor({ client: realClient, env: {} }) });
|
|
const handled = await runFrontDoor(["node", "rig"], deps);
|
|
expect(handled).toBe(true);
|
|
expect(deps.launches).toBe(1); // bare-rig + daemon-down → TUI launched into the crash-cart path
|
|
expect(deps.errLines.join("\n")).not.toMatch(/runtime posture/); // not a degrade-and-exit
|
|
});
|
|
|
|
it.each([
|
|
["cwd denied", "visible", "available", "aligned", "CWD_READ_DENIED"],
|
|
["command missing", "visible", "missing", "aligned", "COMMAND_PATH_MISSING"],
|
|
["permission drift", "visible", "available", "drift", "PERMISSION_DRIFT"],
|
|
["permission unknown", "visible", "available", "unknown", "UNKNOWN_EFFECTIVE"],
|
|
] as const)("renders an axis-specific verdict for %s", async (_label, _healthyCwd, commandState, enforcementState, verdict) => {
|
|
const cwdState = _label === "cwd denied" ? "denied" as const : "visible" as const;
|
|
const deps = io({
|
|
probeDaemon: async () => ({
|
|
state: "diagnostic" as const,
|
|
diagnostic: {
|
|
transport: { state: "healthy" as const },
|
|
cwdRead: { state: cwdState },
|
|
commandPath: { state: commandState },
|
|
enforcement: {
|
|
axis: "permission" as const,
|
|
state: enforcementState,
|
|
expected: "acceptEdits",
|
|
effective: enforcementState === "unknown" ? null : { defaultMode: enforcementState === "drift" ? "manual" : "acceptEdits" },
|
|
sourcePath: "/work/.claude/settings.local.json",
|
|
...(enforcementState === "unknown" ? { reason: "settings_unparseable" } : {}),
|
|
},
|
|
observedAt: "2026-08-08T00:00:00.000Z",
|
|
},
|
|
}),
|
|
});
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
const text = deps.errLines.join("\n");
|
|
expect(text).toContain(`runtime posture: ${verdict}`);
|
|
expect(text).not.toMatch(/daemon not running/i);
|
|
});
|
|
|
|
it.each(["named_profile_unresolved", "applied_launch_unknown"] as const)(
|
|
"launches the read-only TUI when Codex sandbox enforcement is unknown only because %s",
|
|
async (reason) => {
|
|
const deps = io({
|
|
probeDaemon: async () => ({
|
|
state: "diagnostic" as const,
|
|
diagnostic: {
|
|
transport: { state: "healthy" as const },
|
|
cwdRead: { state: "visible" as const },
|
|
commandPath: { state: "available" as const },
|
|
enforcement: {
|
|
axis: "sandbox" as const,
|
|
state: "unknown" as const,
|
|
expected: null,
|
|
effective: null,
|
|
sourcePath: null,
|
|
reason,
|
|
},
|
|
observedAt: "2026-09-01T00:00:00.000Z",
|
|
},
|
|
}),
|
|
});
|
|
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
|
|
expect(deps.launches).toBe(1);
|
|
expect(deps.exits).toEqual([0]);
|
|
expect(deps.errLines).toEqual([]);
|
|
},
|
|
);
|
|
|
|
it.each([{}, { permissions: {} }, { permissions: { defaultMode: "auto" } }, { permissions: { defaultMode: "denyAll" } }])(
|
|
"opens the TUI from a managed Claude seat with manual defaults %j", async settings => {
|
|
const diagnostic = diagnoseRuntimePosture({ runtime: "claude-code", cwd: "/work",
|
|
applied: observeClaudePermission("--permission-mode acceptEdits"),
|
|
fs: { readFile: () => JSON.stringify(settings), cwdReadable: () => true,
|
|
commandAvailable: () => true, claudePermissionModes: () => ["acceptEdits", "auto"] } });
|
|
const deps = io({ probeDaemon: async () => ({ state: "diagnostic", diagnostic }) });
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
expect(deps.launches).toBe(1);
|
|
expect(deps.exits).toEqual([0]);
|
|
expect(deps.errLines).toEqual([]);
|
|
});
|
|
|
|
it.each(["unreadable", "invalid rules", "conflicting rules"])(
|
|
"keeps %s settings diagnostic with a managed launch flag", async problem => {
|
|
const diagnostic = diagnoseRuntimePosture({ runtime: "claude-code", cwd: "/work",
|
|
applied: observeClaudePermission("--permission-mode acceptEdits"),
|
|
fs: { readFile: () => {
|
|
if (problem === "unreadable") throw Object.assign(new Error("denied"), { code: "EACCES" });
|
|
return JSON.stringify({ permissions: problem === "invalid rules" ? { ask: 3 }
|
|
: { allow: ["Read(/**)"], deny: ["Read(/**)"] } });
|
|
}, cwdReadable: () => true, commandAvailable: () => true,
|
|
claudePermissionModes: () => ["acceptEdits"] } });
|
|
const deps = io({ probeDaemon: async () => ({ state: "diagnostic", diagnostic }) });
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
expect(deps.launches).toBe(0);
|
|
expect(deps.exits).toEqual([1]);
|
|
});
|
|
|
|
it("front-door unreadable settings stays UNKNOWN_EFFECTIVE and never becomes daemon-down", async () => {
|
|
const deps = io({
|
|
probeDaemon: async () => ({
|
|
state: "diagnostic" as const,
|
|
diagnostic: {
|
|
transport: { state: "healthy" as const },
|
|
cwdRead: { state: "visible" as const },
|
|
commandPath: { state: "available" as const },
|
|
enforcement: { axis: "permission" as const, state: "unknown" as const, expected: "acceptEdits", effective: null, sourcePath: "/work/.claude/settings.local.json", reason: "settings_unreadable" },
|
|
observedAt: "2026-08-08T00:00:00.000Z",
|
|
},
|
|
}),
|
|
});
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
const text = deps.errLines.join("\n");
|
|
expect(text).toContain("runtime posture: UNKNOWN_EFFECTIVE");
|
|
expect(text).toContain("reason=settings_unreadable");
|
|
expect(text).not.toMatch(/daemon not running/i);
|
|
expect(deps.launches).toBe(0);
|
|
expect(deps.exits).toEqual([1]);
|
|
});
|
|
|
|
it("permission drift renders all four axes plus expected/effective/source and never launches", async () => {
|
|
const deps = io({
|
|
probeDaemon: async () => ({
|
|
state: "diagnostic" as const,
|
|
diagnostic: {
|
|
transport: { state: "healthy" as const },
|
|
cwdRead: { state: "visible" as const },
|
|
commandPath: { state: "available" as const },
|
|
enforcement: {
|
|
axis: "permission" as const,
|
|
state: "drift" as const,
|
|
expected: "acceptEdits",
|
|
effective: { defaultMode: "denyAll", allow: [], ask: [], deny: ["Read(/outside/**)"] },
|
|
sourcePath: "/work/.claude/settings.local.json",
|
|
},
|
|
observedAt: "2026-08-08T00:00:00.000Z",
|
|
},
|
|
}),
|
|
});
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
const text = deps.errLines.join("\n");
|
|
expect(text).toContain("transport: healthy");
|
|
expect(text).toContain("cwd/read: visible");
|
|
expect(text).toContain("command/PATH: available");
|
|
expect(text).toContain("permission: DRIFT");
|
|
expect(text).toContain("expected=acceptEdits");
|
|
expect(text).toContain("effective=denyAll");
|
|
expect(text).toContain("source=/work/.claude/settings.local.json");
|
|
expect(text).not.toMatch(/daemon not running/i);
|
|
expect(deps.launches).toBe(0);
|
|
expect(deps.exits).toEqual([1]);
|
|
});
|
|
|
|
it.each(["timeout", "response"] as const)("BLOCKER 1: transport %s (daemon not ready) LAUNCHES the TUI (its probe makes the down/unverified call)", async (state) => {
|
|
const deps = io({
|
|
probeDaemon: async () => ({
|
|
state: "diagnostic" as const,
|
|
diagnostic: {
|
|
transport: { state, detail: `${state} detail` },
|
|
cwdRead: { state: "unknown" as const },
|
|
commandPath: { state: "unknown" as const },
|
|
enforcement: { axis: "not_applicable" as const, state: "unknown" as const, expected: null, effective: null, sourcePath: null, reason: "transport_unavailable" },
|
|
observedAt: "2026-08-08T00:00:00.000Z",
|
|
},
|
|
}),
|
|
});
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
expect(deps.launches).toBe(1); // not-ready → the TUI; its own crash-cart probe renders down vs unverified
|
|
expect(deps.errLines.join("\n")).not.toMatch(/runtime posture/); // no degrade-and-exit
|
|
});
|
|
|
|
it("normalizes the legacy `false` probe (daemon down) → LAUNCHES the crash-cart TUI", async () => {
|
|
const deps = io({ probeDaemon: async () => false });
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
expect(deps.launches).toBe(1); // false → connect diagnostic → daemon-down → the cockpit, not a degrade
|
|
expect(deps.errLines.join("\n")).not.toMatch(/runtime posture/);
|
|
});
|
|
|
|
it("normalizes a legacy transport-only timeout result (not ready) → LAUNCHES the TUI", async () => {
|
|
const deps = io({ probeDaemon: async () => ({ state: "timeout", message: "legacy timeout detail" }) });
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
expect(deps.launches).toBe(1); // timeout → not ready → TUI (its probe renders unverified)
|
|
expect(deps.errLines.join("\n")).not.toMatch(/runtime posture/);
|
|
});
|
|
|
|
it("TUI init failure → helpful usage with the failure line, exit 1, no stack trace", async () => {
|
|
const deps = io({
|
|
launchTui: async () => {
|
|
throw new Error("tui entry not found at /nope/main.js");
|
|
},
|
|
});
|
|
const handled = await runFrontDoor(["node", "rig"], deps);
|
|
expect(handled).toBe(true);
|
|
const text = deps.errLines.join("\n");
|
|
expect(text).toMatch(/tui entry not found/);
|
|
expect(text).toMatch(/rig --help/);
|
|
expect(text).not.toMatch(/\n\s+at /);
|
|
expect(deps.exits).toEqual([1]);
|
|
});
|
|
|
|
it("the TUI's own exit code propagates", async () => {
|
|
const deps = io({ launchTui: async () => 3 });
|
|
await runFrontDoor(["node", "rig"], deps);
|
|
expect(deps.exits).toEqual([3]);
|
|
});
|
|
});
|
|
|
|
describe("front-door probe routing", () => {
|
|
it("uses explicit current-seat whoami diagnostics for a managed seat", async () => {
|
|
const paths: string[] = [];
|
|
const result = await probeFrontDoor({
|
|
env: { OPENRIG_NODE_ID: "node/1" },
|
|
client: {
|
|
get: async (path: string) => {
|
|
paths.push(path);
|
|
return {
|
|
status: 200,
|
|
data: {
|
|
permissionDrift: {
|
|
transport: { state: "healthy" },
|
|
cwdRead: { state: "visible" },
|
|
commandPath: { state: "available" },
|
|
enforcement: { axis: "sandbox", state: "aligned", expected: "workspace-write", effective: "workspace-write", sourcePath: null },
|
|
observedAt: "2026-08-08T00:00:00.000Z",
|
|
},
|
|
},
|
|
};
|
|
},
|
|
},
|
|
});
|
|
expect(result).toEqual({ state: "ready" });
|
|
expect(paths).toEqual(["/api/whoami?nodeId=node%2F1&compact=1&diagnostics=permission"]);
|
|
});
|
|
|
|
it("uses cheap health for an unmanaged shell", async () => {
|
|
const paths: string[] = [];
|
|
const result = await probeFrontDoor({
|
|
env: {},
|
|
client: { get: async (path: string) => { paths.push(path); return { status: 200, data: { status: "ok" } }; } },
|
|
});
|
|
expect(result).toEqual({ state: "ready" });
|
|
expect(paths).toEqual(["/healthz"]);
|
|
});
|
|
|
|
it("normalizes a non-2xx daemon response into a complete four-axis diagnostic", async () => {
|
|
const result = await probeFrontDoor({
|
|
env: { OPENRIG_NODE_ID: "node-1" },
|
|
client: { get: async () => ({ status: 503, data: {} }) },
|
|
});
|
|
expect(result).toMatchObject({
|
|
state: "diagnostic",
|
|
diagnostic: {
|
|
transport: { state: "response" },
|
|
cwdRead: { state: "unknown" },
|
|
commandPath: { state: "unknown" },
|
|
enforcement: { state: "unknown", reason: "transport_unavailable" },
|
|
},
|
|
});
|
|
});
|
|
|
|
it("never reports ready when transport is not healthy even if every local axis aligns", async () => {
|
|
const result = await probeFrontDoor({
|
|
env: { OPENRIG_NODE_ID: "node-1" },
|
|
client: {
|
|
get: async () => ({
|
|
status: 200,
|
|
data: {
|
|
permissionDrift: {
|
|
transport: { state: "timeout", detail: "upstream timed out" },
|
|
cwdRead: { state: "visible" },
|
|
commandPath: { state: "available" },
|
|
enforcement: { axis: "permission", state: "aligned", expected: "acceptEdits", effective: "acceptEdits", sourcePath: null },
|
|
observedAt: "2026-08-08T00:00:00.000Z",
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
expect(result).toMatchObject({ state: "diagnostic", diagnostic: { transport: { state: "timeout", detail: "upstream timed out" } } });
|
|
});
|
|
|
|
it.each([
|
|
["connect", new DaemonConnectionError("refused")],
|
|
["timeout", new DaemonTimeoutError("slow")],
|
|
["response", new DaemonResponseError(502, "bad")],
|
|
] as const)("normalizes a %s exception into the same complete four-axis diagnostic", async (state, error) => {
|
|
const result = await probeFrontDoor({
|
|
env: { OPENRIG_NODE_ID: "node-1" },
|
|
client: { get: async () => { throw error; } },
|
|
});
|
|
expect(result).toMatchObject({
|
|
state: "diagnostic",
|
|
diagnostic: {
|
|
transport: { state },
|
|
cwdRead: { state: "unknown" },
|
|
commandPath: { state: "unknown" },
|
|
enforcement: { state: "unknown", reason: "transport_unavailable" },
|
|
},
|
|
});
|
|
expect((result as { diagnostic: { transport: { detail?: string } } }).diagnostic.transport.detail).toContain(error.message);
|
|
});
|
|
});
|
|
|
|
describe("resolveTuiPath — monorepo-first, bundled fallback (the daemon resolver pattern)", () => {
|
|
it("prefers the monorepo sibling dist, falls back to the bundled copy, null when neither", () => {
|
|
const base = "/repo/packages/cli/dist";
|
|
const mono = join("/repo/packages/cli/dist", "../../tui/dist/main.js");
|
|
const bundled = join("/repo/packages/cli/dist", "../tui/dist/main.js");
|
|
expect(resolveTuiPath(base, (p) => p === mono)).toBe(mono);
|
|
expect(resolveTuiPath(base, (p) => p === bundled)).toBe(bundled);
|
|
expect(resolveTuiPath(base, () => false)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("PUBLIC bin ownership (guard finding 1 — the wrapper is the real front door)", () => {
|
|
const binWrapper = join(__dirname, "..", "dist", "bin-wrapper.js");
|
|
const hasTmux = spawnSync("tmux", ["-V"], { encoding: "utf-8" }).status === 0;
|
|
it.skipIf(!existsSync(binWrapper) || !hasTmux)("bare PUBLIC bin under a REAL PTY reaches the crash-cart COCKPIT when the daemon is down (BLOCKER 1 ownership + reachability)", () => {
|
|
// tmux gives the process a real TTY on BOTH streams. A dead daemon URL is exactly the crash-cart's
|
|
// state: post-fix the front door LAUNCHES the TUI, which renders the daemon-down cockpit
|
|
// ("daemon not running") — stronger ownership proof than the old degrade message (it reaches the
|
|
// actual feature). Commander's usage bypass would mean the front door did NOT own the invocation.
|
|
// Isolated OPENRIG_HOME so the launched TUI never reads the live rig (fully contained + read-only).
|
|
const session = `frontdoor-pin-${process.pid}`;
|
|
const home = mkdtempSync(join(tmpdir(), "fd-tmux-home-"));
|
|
// A private server gives this test both a bounded IPC path and useful failure evidence.
|
|
const socket = join(home, "tmux.sock");
|
|
const exitFile = join(home, "child-exit");
|
|
const quote = (s: string) => "'" + s.replaceAll("'", "'\"'\"'") + "'";
|
|
const tmux = (args: string[]) => spawnSync("tmux", ["-S", socket, ...args], { encoding: "utf-8", timeout: 10000 });
|
|
const diagnostic = (r: ReturnType<typeof tmux>) => JSON.stringify({
|
|
status: r.status, signal: r.signal, error: r.error?.message, stderr: r.stderr,
|
|
childExit: existsSync(exitFile) ? readFileSync(exitFile, "utf8") : "still running or not started",
|
|
});
|
|
try {
|
|
const run = tmux(["new-session", "-d", "-s", session, "-x", "120", "-y", "30",
|
|
`OPENRIG_HOME=${quote(home)} OPENRIG_URL=http://127.0.0.1:9 ${quote(process.execPath)} ${quote(binWrapper)}; printf '%s' $? > ${quote(exitFile)}; sleep 15`]);
|
|
expect(run.status, diagnostic(run)).toBe(0);
|
|
let text = "";
|
|
for (let i = 0; i < 20 && !/EXPLORER|mission control could not start|Usage: rig/.test(text); i++) {
|
|
spawnSync("sleep", ["0.5"]);
|
|
const capture = tmux(["capture-pane", "-t", session, "-p"]);
|
|
expect(capture.status, diagnostic(capture)).toBe(0);
|
|
text = capture.stdout ?? "";
|
|
}
|
|
// Keep BOTH TTY streams on the child; capture-pane includes terminal error text.
|
|
// Do not reinterpret an empty capture or failed tmux client as a valid UI result.
|
|
const pane = tmux(["display-message", "-p", "-t", session, "#{pane_current_command} #{pane_dead} #{pane_dead_status}"]);
|
|
expect(text, `${diagnostic(pane)} pane=${pane.stdout}`).toMatch(/EXPLORER|mission control could not start/);
|
|
expect(text).not.toMatch(/Usage: rig \[options\] \[command\]/);
|
|
} finally {
|
|
tmux(["kill-server"]); // Only this test's private server.
|
|
rmSync(home, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// (Removed the in-process forced-TTY "reaches the degrade branch" belt: its premise was a FAST
|
|
// daemon-down DEGRADE, which BLOCKER 1's fix replaces with launching the crash-cart TUI. Forcing a
|
|
// TTY in-process would spawn an INTERACTIVE TUI child (no clean exit) — not a safe/deterministic unit
|
|
// test. Compiled-wrapper ownership is covered by the tmux cockpit test above (contained + killed) and
|
|
// the piped-baseline test below; the front-door LAUNCH decision is covered deterministically by the
|
|
// "PRODUCTION PATH … REAL probeFrontDoor" test earlier, with no real TUI spawned.)
|
|
|
|
it.skipIf(!existsSync(binWrapper))("piped PUBLIC bin keeps the commander usage baseline (no TUI, fast exit)", () => {
|
|
const started = Date.now();
|
|
const result = spawnSync(process.execPath, [binWrapper], {
|
|
input: "x\n",
|
|
timeout: 5000,
|
|
encoding: "utf-8",
|
|
});
|
|
expect(result.status, JSON.stringify({
|
|
elapsedMs: Date.now() - started, status: result.status, signal: result.signal,
|
|
error: result.error && { name: result.error.name, message: result.error.message, code: (result.error as NodeJS.ErrnoException).code },
|
|
stdout: result.stdout?.slice(-4096), stderr: result.stderr?.slice(-4096),
|
|
})).toBe(0);
|
|
expect(`${result.stderr}${result.stdout}`).toMatch(/Usage: rig/);
|
|
});
|
|
|
|
it.skipIf(!existsSync(binWrapper))("PUBLIC bin subcommands are untouched (rig --version via wrapper)", () => {
|
|
const result = spawnSync(process.execPath, [binWrapper, "--version"], { timeout: 5000, encoding: "utf-8" });
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout.trim()).toMatch(/^\d+\.\d+\.\d+/);
|
|
});
|
|
});
|
|
|
|
describe("script-safety integration (the compiled front door)", () => {
|
|
const cliEntry = join(__dirname, "..", "dist", "index.js");
|
|
it.skipIf(!existsSync(cliEntry))("`echo x | rig` returns usage fast — no hang (exit code preserves today's clean-help baseline)", () => {
|
|
const started = Date.now();
|
|
const result = spawnSync(process.execPath, [cliEntry], {
|
|
input: "x\n",
|
|
timeout: 5000,
|
|
encoding: "utf-8",
|
|
env: { ...process.env, OPENRIG_URL: "", OPENRIG_PORT: "" },
|
|
});
|
|
expect(Date.now() - started).toBeLessThan(5000);
|
|
// pre-slice-17 baseline: commander's clean-help path prints usage and
|
|
// exits 0 — the front door PRESERVES the piped path byte-for-byte
|
|
expect(result.status).toBe(0);
|
|
expect(`${result.stderr}${result.stdout}`).toMatch(/Usage: rig/);
|
|
expect(`${result.stderr}${result.stdout}`).not.toMatch(/\n\s+at /);
|
|
});
|
|
|
|
it.skipIf(!existsSync(cliEntry))("`rig --help` still exits 0 with the full usage (front-door regression)", () => {
|
|
const result = spawnSync(process.execPath, [cliEntry, "--help"], { timeout: 5000, encoding: "utf-8" });
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout).toMatch(/Usage: rig/);
|
|
expect(result.stdout).toMatch(/daemon/);
|
|
});
|
|
});
|