* exec: clean up PTY resources on timeout and exit * cli: harden resume cleanup and watchdog stalled runs * cli: productionize PTY and resume reliability paths * docs: add PTY process supervision architecture plan * docs: rewrite PTY supervision plan as pre-rewrite baseline * docs: switch PTY supervision plan to one-go execution * docs: add one-line root cause to PTY supervision plan * docs: add OS contracts and test matrix to PTY supervision plan * docs: define process-supervisor package placement and scope * docs: tie supervisor plan to existing CI lanes * docs: place PTY supervisor plan under src/process * refactor(process): route exec and cli runs through supervisor * docs(process): refresh PTY supervision plan * wip * fix(process): harden supervisor timeout and PTY termination * fix(process): harden supervisor adapters env and wait handling * ci: avoid failing formal conformance on comment permissions * test(ui): fix cron request mock argument typing * fix(ui): remove leftover conflict marker * fix: supervise PTY processes (#14257) (openclaw#14257) (thanks @onutc)
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { createRunRegistry } from "./registry.js";
|
|
|
|
describe("process supervisor run registry", () => {
|
|
it("finalize is idempotent and preserves first terminal metadata", () => {
|
|
const registry = createRunRegistry();
|
|
registry.add({
|
|
runId: "r1",
|
|
sessionId: "s1",
|
|
backendId: "b1",
|
|
state: "running",
|
|
startedAtMs: 1,
|
|
lastOutputAtMs: 1,
|
|
createdAtMs: 1,
|
|
updatedAtMs: 1,
|
|
});
|
|
|
|
const first = registry.finalize("r1", {
|
|
reason: "overall-timeout",
|
|
exitCode: null,
|
|
exitSignal: "SIGKILL",
|
|
});
|
|
const second = registry.finalize("r1", {
|
|
reason: "manual-cancel",
|
|
exitCode: 0,
|
|
exitSignal: null,
|
|
});
|
|
|
|
expect(first).not.toBeNull();
|
|
expect(first?.firstFinalize).toBe(true);
|
|
expect(first?.record.terminationReason).toBe("overall-timeout");
|
|
expect(first?.record.exitCode).toBeNull();
|
|
expect(first?.record.exitSignal).toBe("SIGKILL");
|
|
|
|
expect(second).not.toBeNull();
|
|
expect(second?.firstFinalize).toBe(false);
|
|
expect(second?.record.terminationReason).toBe("overall-timeout");
|
|
expect(second?.record.exitCode).toBeNull();
|
|
expect(second?.record.exitSignal).toBe("SIGKILL");
|
|
});
|
|
|
|
it("prunes oldest exited records once retention cap is exceeded", () => {
|
|
const registry = createRunRegistry({ maxExitedRecords: 2 });
|
|
registry.add({
|
|
runId: "r1",
|
|
sessionId: "s1",
|
|
backendId: "b1",
|
|
state: "running",
|
|
startedAtMs: 1,
|
|
lastOutputAtMs: 1,
|
|
createdAtMs: 1,
|
|
updatedAtMs: 1,
|
|
});
|
|
registry.add({
|
|
runId: "r2",
|
|
sessionId: "s2",
|
|
backendId: "b1",
|
|
state: "running",
|
|
startedAtMs: 2,
|
|
lastOutputAtMs: 2,
|
|
createdAtMs: 2,
|
|
updatedAtMs: 2,
|
|
});
|
|
registry.add({
|
|
runId: "r3",
|
|
sessionId: "s3",
|
|
backendId: "b1",
|
|
state: "running",
|
|
startedAtMs: 3,
|
|
lastOutputAtMs: 3,
|
|
createdAtMs: 3,
|
|
updatedAtMs: 3,
|
|
});
|
|
|
|
registry.finalize("r1", { reason: "exit", exitCode: 0, exitSignal: null });
|
|
registry.finalize("r2", { reason: "exit", exitCode: 0, exitSignal: null });
|
|
registry.finalize("r3", { reason: "exit", exitCode: 0, exitSignal: null });
|
|
|
|
expect(registry.get("r1")).toBeUndefined();
|
|
expect(registry.get("r2")?.state).toBe("exited");
|
|
expect(registry.get("r3")?.state).toBe("exited");
|
|
});
|
|
});
|