mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
Commands no longer refuse a daemon that is busy but alive. The CLI's status check before each command refused whenever it couldn't confirm the daemon was healthy, so right after a slow request an ordinary command such as `rig ps --nodes -A`, `rig capture` or `rig context profile` failed in about 128 ms with "Daemon did not respond — it may be busy or stopped". Now a daemon that isn't confirmed down gets the real request, under its normal timeout. A daemon confirmed down (stopped, stale, or a refused probe) keeps the clear "not running" message and start hint, and a probe the OS blocks and a live sibling home keep their own guidance. When a write then times out, it says the outcome is unknown and to check before any retry, including in the commands that catch their own errors (context, agent-image, specs, scope and fork). Each write is sent once and never retried. `rig start`'s UI health read is bounded at 5 seconds. `rig up` and `rig start` start a daemon when one is stopped or stale, or for a live sibling home or a blocked probe, as before. They no longer start one for a busy daemon of this home, which used to get a false "already running", or past an explicit HOME-MOVED marker. GitHub bundle imports still require a verified local daemon, and `rig status --json` is unchanged.
61 lines
3.3 KiB
TypeScript
61 lines
3.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { bootstrapCommand } from "../src/commands/bootstrap.js";
|
|
import { discoverCommand } from "../src/commands/discover.js";
|
|
import { workflowCommand } from "../src/commands/workflow.js";
|
|
import { workspaceCommand } from "../src/commands/workspace.js";
|
|
import { getDaemonStatus, type DaemonStatus, type LifecycleDeps } from "../src/daemon-lifecycle.js";
|
|
|
|
vi.mock("../src/daemon-lifecycle.js", async () => ({
|
|
...await vi.importActual<Record<string, unknown>>("../src/daemon-lifecycle.js"),
|
|
getDaemonStatus: vi.fn(),
|
|
}));
|
|
|
|
beforeEach(() => { process.exitCode = undefined; });
|
|
afterEach(() => { vi.restoreAllMocks(); process.exitCode = undefined; });
|
|
|
|
const entries = [
|
|
["bootstrap", bootstrapCommand, ["./fixture.yaml", "--plan"]],
|
|
["discover", discoverCommand, []],
|
|
["workflow", workflowCommand, ["list"]],
|
|
["workspace", workspaceCommand, ["doctor"]],
|
|
] as const;
|
|
|
|
describe("command daemon status evidence", () => {
|
|
for (const [name, makeCommand, args] of entries) {
|
|
// Changed: an inconclusive status used to refuse ("state not confirmed"). A daemon that may be busy
|
|
// gets the command's real request instead; the request's own answer or timeout decides.
|
|
it.each<DaemonStatus>([
|
|
{ state: "unverified", host: "127.0.0.1", port: 7433 },
|
|
{ state: "running", healthy: false, host: "127.0.0.1", port: 7433 },
|
|
])(`${name} makes its real request when the daemon may be busy, never asserting down`, async (status) => {
|
|
vi.mocked(getDaemonStatus).mockResolvedValue(status);
|
|
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const clientFactory = vi.fn(() => { throw new Error("stop at the request"); });
|
|
await makeCommand({ lifecycleDeps: {} as LifecycleDeps, clientFactory }).parseAsync(["node", "rig", ...args]).catch(() => {});
|
|
expect(clientFactory).toHaveBeenCalledWith("http://127.0.0.1:7433");
|
|
expect(error.mock.calls.flat().join("\n")).not.toContain("state not confirmed");
|
|
expect(error.mock.calls.flat().join("\n")).not.toContain("Daemon not running.");
|
|
});
|
|
|
|
it(`${name} preserves positively stopped status`, async () => {
|
|
vi.mocked(getDaemonStatus).mockResolvedValue({ state: "stopped" });
|
|
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const clientFactory = vi.fn();
|
|
await makeCommand({ lifecycleDeps: {} as LifecycleDeps, clientFactory }).parseAsync(["node", "rig", ...args]);
|
|
expect(error.mock.calls.flat().join("\n")).toContain("Daemon not running.");
|
|
expect(process.exitCode).toBe(1);
|
|
expect(clientFactory).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it(`${name} retains the selected home and live-sibling diagnostic`, async () => {
|
|
vi.mocked(getDaemonStatus).mockResolvedValue({ state: "unverified", siblingHint: { resolvedHome: "selected-home", siblingHome: "live-sibling" } });
|
|
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const clientFactory = vi.fn();
|
|
await makeCommand({ lifecycleDeps: {} as LifecycleDeps, clientFactory }).parseAsync(["node", "rig", ...args]);
|
|
expect(error.mock.calls.flat().join("\n")).toContain("resolved selected-home, live sibling live-sibling");
|
|
expect(process.exitCode).toBe(1);
|
|
expect(clientFactory).not.toHaveBeenCalled();
|
|
});
|
|
}
|
|
});
|