pi-mono/packages/coding-agent/test/experimental-session-worker-lifecycle.test.ts
Armin Ronacher f8a6e670d4 feat(agent): replace remote sessions with routed services
Remove the raw Session RPC projection and route framed service calls through fenced presentation attachments.
2026-08-19 23:10:15 +02:00

162 lines
5.9 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from "vitest";
import { WorkerLifecycle } from "../src/experimental/session-worker.ts";
const GENERATION = "generation-1";
function createLifecycle(options: { initialDemandGraceMs?: number; orphanDemandGraceMs?: number } = {}) {
const retire = vi.fn();
const lifecycle = new WorkerLifecycle({
initialServerConnectionId: GENERATION,
initialDemandGraceMs: options.initialDemandGraceMs ?? 100,
orphanDemandGraceMs: options.orphanDemandGraceMs ?? 200,
onRetire: retire,
});
return { lifecycle, retire };
}
afterEach(() => {
vi.useRealTimers();
});
describe("Session worker lifecycle", () => {
test("retires only after client demand and Harness activity are both gone", async () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
lifecycle.operationStarted("run", "main", "operation-1");
lifecycle.setDemand(GENERATION, "attachment-1", false);
expect(retire).not.toHaveBeenCalled();
lifecycle.operationStopped("run", "main", "operation-1");
await vi.runAllTicks();
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test("retains the worker until every presentation attachment is released", () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
lifecycle.setDemand(GENERATION, "attachment-2", true);
lifecycle.setDemand(GENERATION, "attachment-1", false);
expect(retire).not.toHaveBeenCalled();
lifecycle.setDemand(GENERATION, "attachment-2", false);
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test("tracks a nested compaction independently from its enclosing run", () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
lifecycle.operationStarted("run", "main", "operation-1");
lifecycle.operationStarted("compaction", "main", "operation-1");
lifecycle.setDemand(GENERATION, "attachment-1", false);
lifecycle.operationStopped("compaction", "main", "operation-1");
expect(retire).not.toHaveBeenCalled();
lifecycle.operationStopped("run", "main", "operation-1");
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test.each(["compaction", "navigation"] as const)("clears suspended %s activity", (kind) => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
lifecycle.operationStarted(kind, "main", "operation-1");
lifecycle.setDemand(GENERATION, "attachment-1", false);
lifecycle.operationStopped(kind, "main", "operation-1");
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test("does not retire while a demand acknowledgement holds reconciliation", () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
const release = lifecycle.holdRetirement();
lifecycle.setDemand(GENERATION, "attachment-1", false);
expect(retire).not.toHaveBeenCalled();
release();
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test("holds retirement only for requests from the active attachment", () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
const release = lifecycle.beginRequest(GENERATION, "attachment-1");
lifecycle.setDemand(GENERATION, "attachment-1", false);
expect(retire).not.toHaveBeenCalled();
release();
expect(retire).toHaveBeenCalledOnce();
expect(() => lifecycle.beginRequest(GENERATION, "attachment-1")).toThrow(/retiring/);
lifecycle.close();
});
test("rejects requests from stale generations and attachments", () => {
vi.useFakeTimers();
const { lifecycle } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
expect(() => lifecycle.beginRequest("stale", "attachment-1")).toThrow(/stale server generation/);
expect(() => lifecycle.beginRequest(GENERATION, "wrong-attachment")).toThrow(/active attachment/);
lifecycle.close();
});
test("retains disconnected-generation demand for the orphan grace", async () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
lifecycle.serverDisconnected(GENERATION);
vi.advanceTimersByTime(199);
expect(retire).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
await vi.runAllTicks();
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test("allows a replacement generation to retain the worker", async () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
lifecycle.setDemand(GENERATION, "attachment-1", true);
lifecycle.serverDisconnected(GENERATION);
lifecycle.serverConnected("generation-2");
lifecycle.setDemand("generation-2", "attachment-2", true);
vi.advanceTimersByTime(200);
expect(retire).not.toHaveBeenCalled();
lifecycle.setDemand("generation-2", "attachment-2", false);
await vi.runAllTicks();
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test("retires a launched worker that never receives initial demand", async () => {
vi.useFakeTimers();
const { lifecycle, retire } = createLifecycle();
vi.advanceTimersByTime(100);
await vi.runAllTicks();
expect(retire).toHaveBeenCalledOnce();
lifecycle.close();
});
test("rejects demand after retirement has won the race", () => {
vi.useFakeTimers();
const { lifecycle } = createLifecycle();
vi.advanceTimersByTime(100);
expect(() => lifecycle.setDemand(GENERATION, "attachment-1", true)).toThrow(/retiring/);
lifecycle.close();
});
test("rejects demand from a stale server generation", () => {
vi.useFakeTimers();
const { lifecycle } = createLifecycle();
expect(() => lifecycle.setDemand("stale", "attachment-1", true)).toThrow(/stale server generation/);
lifecycle.close();
});
});