fix: release finalized Pi runtimes
This commit is contained in:
@@ -111,13 +111,21 @@ export function sessionRoutes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.hub.publish(id, e.type, e);
|
d.hub.publish(id, e.type, e);
|
||||||
if (
|
if (e.type === "system_event" && e.event === "agent_end") {
|
||||||
e.type === "system_event"
|
if (d.mgr.get(id) !== rt && boundRuntimes.get(id) === rt) {
|
||||||
&& e.event === "agent_end"
|
boundRuntimes.delete(id);
|
||||||
&& d.mgr.get(id) !== rt
|
} else if (typeof runner.sessionShow === "function") {
|
||||||
&& boundRuntimes.get(id) === rt
|
void withSessionLifecycle(id, async () => {
|
||||||
) {
|
if (d.mgr.get(id) !== rt || boundRuntimes.get(id) !== rt) return;
|
||||||
boundRuntimes.delete(id);
|
const manifest = await runner.sessionShow(id, workspace);
|
||||||
|
if (manifest?.status !== "finalized") return;
|
||||||
|
if (d.mgr.teardownIfCurrent(id, rt) && boundRuntimes.get(id) === rt) {
|
||||||
|
boundRuntimes.delete(id);
|
||||||
|
}
|
||||||
|
}).catch((error: unknown) => {
|
||||||
|
console.error(`[session:${id}] terminal runtime cleanup failed:`, error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1304,6 +1304,47 @@ test("an unexpectedly exited runtime publishes its terminal sequence then releas
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("agent_end releases the Pi runtime after the session was finalized", async () => {
|
||||||
|
let listener: ((event: any) => void) | undefined;
|
||||||
|
const bridge = {
|
||||||
|
onClientEvent: (next: (event: any) => void) => { listener = next; },
|
||||||
|
emitClientEvent: (event: any) => listener?.(event),
|
||||||
|
};
|
||||||
|
const runtime = { bridge };
|
||||||
|
let current: any;
|
||||||
|
let teardownCalls = 0;
|
||||||
|
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||||
|
mgr: {
|
||||||
|
get: () => current,
|
||||||
|
createFor: () => { current = runtime; return runtime; },
|
||||||
|
configure: async () => {},
|
||||||
|
start: () => {},
|
||||||
|
teardownIfCurrent: (_id: string, expected: any) => {
|
||||||
|
if (current !== expected) return false;
|
||||||
|
teardownCalls += 1;
|
||||||
|
current = undefined;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
thtRunner: {
|
||||||
|
sessionNew: async () => ({ id: "s1" }),
|
||||||
|
sessionShow: async () => ({ status: "finalized" }),
|
||||||
|
searchPack: async () => {},
|
||||||
|
failSession: async () => {},
|
||||||
|
} as any,
|
||||||
|
readiness: { ensure: async () => ({ ok: true }) } as any,
|
||||||
|
getSettings: () => ({ workspace: "local" }) as any,
|
||||||
|
});
|
||||||
|
await app.inject({ method: "POST", url: "/sessions", payload: { question: "q" } });
|
||||||
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
|
|
||||||
|
bridge.emitClientEvent({ type: "system_event", event: "agent_end" });
|
||||||
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
|
|
||||||
|
expect(teardownCalls).toBe(1);
|
||||||
|
expect(current).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
test("POST resume tears down a created runtime when bridge binding fails", async () => {
|
test("POST resume tears down a created runtime when bridge binding fails", async () => {
|
||||||
const delivered: string[] = [];
|
const delivered: string[] = [];
|
||||||
const actualHub = new SseHub();
|
const actualHub = new SseHub();
|
||||||
|
|||||||
Reference in New Issue
Block a user