Files
pi-goals/test/intercom-handshake.test.ts
T
wassname 88bfcc1c42 Fix symmetric supervision reconnect and cancel stale Ready attempts
Use explicit hello requests and replies, keep workers unready until model restoration succeeds, distinguish paused peers, and align all goal readers at the Log boundary. Add two-real-adapter handshake and failed-Ready/clear-during-wait regressions; warn once for unavailable supervisor context usage.
2026-09-08 19:27:09 +08:00

82 lines
4.2 KiB
TypeScript

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { expect, it, vi } from "vitest";
import { GoalIntercom } from "../src/intercom.js";
import { pairedIntercomFixture } from "./paired-intercom-fixture.js";
function endpoint(transport: ReturnType<typeof pairedIntercomFixture>["worker"]) {
const entries: any[] = [];
const ctx = { sessionManager: { getEntries: () => entries }, ui: { notify: vi.fn() } };
const link = new GoalIntercom({ events: transport.events, on: () => {}, appendEntry: (customType: string, data: unknown) => entries.push({ type: "custom", customType, data }) } as unknown as ExtensionAPI);
return { link, ctx, entries };
}
const settle = () => new Promise(resolve => setImmediate(resolve));
it("re-handshakes unchanged peers in either direction without hello ping-pong or lost advice", async () => {
const wire = pairedIntercomFixture();
const worker = endpoint(wire.worker);
const supervisor = endpoint(wire.supervisor);
worker.link.configure("binding", "worker", worker.ctx as any);
supervisor.link.configure("binding", "supervisor", supervisor.ctx as any);
supervisor.link.markReady();
await settle();
expect(worker.link.connected && supervisor.link.connected).toBe(true);
const deliver = vi.fn(); worker.link.onSteer = deliver;
for (const side of [worker, supervisor, worker]) {
const before = wire.worker.sent.length + wire.supervisor.sent.length;
side.link.configure("binding", side === worker ? "worker" : "supervisor", side.ctx as any, true);
await side.link.waitReady(100);
await settle();
expect(worker.link.connected && supervisor.link.connected).toBe(true);
expect(wire.worker.sent.length + wire.supervisor.sent.length - before).toBe(2);
}
const before = wire.worker.sent.length + wire.supervisor.sent.length;
worker.link.configure("binding", "worker", worker.ctx as any, true);
supervisor.link.configure("binding", "supervisor", supervisor.ctx as any, true);
await settle();
expect(worker.link.connected && supervisor.link.connected).toBe(true);
expect(wire.worker.sent.length + wire.supervisor.sent.length - before).toBe(4);
supervisor.link.steer("Read actual output.");
await settle();
expect(deliver).toHaveBeenCalledExactlyOnceWith("Read actual output.");
expect(worker.ctx.ui.notify).not.toHaveBeenCalled();
expect(supervisor.ctx.ui.notify).not.toHaveBeenCalled();
});
it("replays pending advice and views across either role's own readiness transition", async () => {
const wire = pairedIntercomFixture();
const worker = endpoint(wire.worker), supervisor = endpoint(wire.supervisor);
worker.link.configure("binding", "worker", worker.ctx as any);
supervisor.link.configure("binding", "supervisor", supervisor.ctx as any, true);
await settle();
wire.supervisor.drop = message => message.kind === "steer";
supervisor.link.steer("Pending advice.");
wire.supervisor.drop = () => false;
const deliver = vi.fn(); worker.link.onSteer = deliver;
supervisor.link.markNotReady(); await settle();
expect(worker.link.connected).toBe(false);
supervisor.link.markReady(); await settle();
expect(deliver).toHaveBeenCalledExactlyOnceWith("Pending advice.");
worker.link.markNotReady(); await settle();
const onView = vi.fn(); supervisor.link.onView = onView;
worker.link.view("Fresh view.", "settled");
expect(onView).not.toHaveBeenCalled();
worker.link.markReady(); await settle();
expect(onView).toHaveBeenCalledTimes(1);
expect(worker.link.connected && supervisor.link.connected).toBe(true);
wire.worker.connect(false); wire.worker.connect(true); await settle();
expect(worker.link.connected && supervisor.link.connected).toBe(true);
expect(deliver).toHaveBeenCalledTimes(1);
expect(onView).toHaveBeenCalledTimes(1);
});
it("cancels pending waits immediately on detach or reconfiguration", async () => {
const wire = pairedIntercomFixture();
const worker = endpoint(wire.worker);
worker.link.configure("binding", "worker", worker.ctx as any);
const cancelled = expect(worker.link.waitReady()).rejects.toThrow("plan detached");
worker.link.detach(); await cancelled;
worker.link.configure("next", "worker", worker.ctx as any);
const replaced = expect(worker.link.waitReady()).rejects.toThrow("reconfiguration");
worker.link.configure("third", "worker", worker.ctx as any); await replaced;
});