mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-10-02 08:35:15 +08:00
fix(daemon): relaunch after stopping the last seat (#325)
This commit is contained in:
@@ -501,10 +501,41 @@ export class SeatLifecycleService {
|
||||
).all(node.id) as Array<{ id: string }>).map((row) => row.id);
|
||||
const retiringGeneration = this.sessionRegistry.currentOccupantTenure(node.id)?.generationUuid ?? null;
|
||||
|
||||
const canonicalProbe = await this.probeLiveness(
|
||||
let canonicalProbe = await this.probeLiveness(
|
||||
canonicalSessionName,
|
||||
"fresh launch refuses rather than overwrite a possibly-live canonical session",
|
||||
);
|
||||
// `rig seat stop` persists an exited row after killing its managed session.
|
||||
// When that was the last tmux session, tmux exits too, so the next classified
|
||||
// probe sees unavailable transport instead of positive absence. Reopen an
|
||||
// empty server only when the persisted state proves this seat's managed
|
||||
// occupant was deliberately stopped and no other managed row remains live.
|
||||
// The probe below still decides absence; a recreated session or failed
|
||||
// transport remains a refusal.
|
||||
if ("code" in canonicalProbe) {
|
||||
const latest = this.latestSession(node.id);
|
||||
const hasCurrentBinding = this.sessionRegistry.getBindingForNode(node.id) !== null;
|
||||
const stoppedManagedOccupant = latest !== null
|
||||
&& latest.session_name === canonicalSessionName
|
||||
&& latest.status === "exited"
|
||||
&& latest.origin !== "claimed"
|
||||
&& !hasCurrentBinding
|
||||
&& this.nonTerminalSessions(node.id).length === 0;
|
||||
if (stoppedManagedOccupant) {
|
||||
try {
|
||||
const restored = await this.tmuxAdapter.startServer();
|
||||
if (restored.ok) {
|
||||
canonicalProbe = await this.probeLiveness(
|
||||
canonicalSessionName,
|
||||
"fresh launch refuses rather than overwrite a possibly-live canonical session",
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Keep the original classified refusal when the transport cannot be
|
||||
// restored; never translate a failed start into absence.
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("code" in canonicalProbe) return canonicalProbe;
|
||||
if (canonicalProbe.state === "present") {
|
||||
const currentSession = this.latestSession(node.id);
|
||||
|
||||
@@ -376,6 +376,22 @@ describe("SeatLifecycleService.launchFresh", () => {
|
||||
expect(sessionRegistry.getBindingForNode(seat.node.id)?.tmuxPane).toBe("%fresh");
|
||||
});
|
||||
|
||||
it("reopens an empty tmux server after an explicit stop before a fresh launch", async () => {
|
||||
const seat = seedSeat();
|
||||
const server = modelServerLifetime();
|
||||
|
||||
const stopped = await service.stopSeat({ seatRef: seat.sessionName, reason: "operator requested stop" });
|
||||
expect(stopped).toMatchObject({ ok: true });
|
||||
expect(server).toEqual({ up: false, starts: 0 });
|
||||
|
||||
const result = await service.launchFresh({ seatRef: seat.sessionName, fresh: true, reason: "start a new occupant" });
|
||||
|
||||
expect(result).toMatchObject({ ok: true, status: "ready", sessionName: seat.sessionName });
|
||||
expect(server).toEqual({ up: true, starts: 1 });
|
||||
expect(tmux.createSession).toHaveBeenCalledTimes(1);
|
||||
expect(alive.has(seat.sessionName)).toBe(true);
|
||||
});
|
||||
|
||||
it("proves older non-terminal rows absent on the restored server before superseding them", async () => {
|
||||
const seat = seedSeat({ clean: true });
|
||||
const older = sessionRegistry.registerSession(seat.node.id, "r00-dev-impl@fresh-rig");
|
||||
|
||||
Reference in New Issue
Block a user