fix(backend): restart idle Pi sessions on resume
This commit is contained in:
@@ -100,10 +100,14 @@ export function sessionRoutes(
|
|||||||
});
|
});
|
||||||
app.post("/sessions/:id/resume", async (req, reply) => {
|
app.post("/sessions/:id/resume", async (req, reply) => {
|
||||||
const id = (req.params as any).id;
|
const id = (req.params as any).id;
|
||||||
if (d.mgr.get(id)) {
|
const existing = d.mgr.get(id);
|
||||||
// Idempotent resume: reattach the browser to the existing Pi runtime.
|
if (existing) {
|
||||||
// Do not respawn it (which would discard a pending reviewer widget).
|
const state = existing.bridge.turnState();
|
||||||
return reply.code(200).send({ id, alreadyActive: true });
|
if (state === "running" || state === "waiting") {
|
||||||
|
return reply.code(200).send({ id, alreadyActive: true });
|
||||||
|
}
|
||||||
|
d.mgr.teardown(id);
|
||||||
|
d.hub.clear(id);
|
||||||
}
|
}
|
||||||
const manifest = (await d.tht.sessionShow(id, d.getSettings().workspace)) as { status?: string; archived?: boolean } | null;
|
const manifest = (await d.tht.sessionShow(id, d.getSettings().workspace)) as { status?: string; archived?: boolean } | null;
|
||||||
if (manifest?.status === "finalized" || manifest?.archived) {
|
if (manifest?.status === "finalized" || manifest?.archived) {
|
||||||
|
|||||||
@@ -133,6 +133,56 @@ test("POST /sessions/:id/resume usa il thinking globale se manca nel manifest",
|
|||||||
expect(configured.thinking).toBe("low");
|
expect(configured.thinking).toBe("low");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.each(["running", "waiting"])(
|
||||||
|
"POST resume preserves a %s runtime",
|
||||||
|
async (state) => {
|
||||||
|
let tornDown = false;
|
||||||
|
const existing = { bridge: { turnState: () => state } } as any;
|
||||||
|
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||||
|
mgr: {
|
||||||
|
get: () => existing,
|
||||||
|
teardown: () => { tornDown = true; },
|
||||||
|
} as any,
|
||||||
|
thtRunner: {} as any,
|
||||||
|
getSettings: () => ({ workspace: "psd" }) as any,
|
||||||
|
});
|
||||||
|
const response = await app.inject({ method: "POST", url: "/sessions/s1/resume" });
|
||||||
|
expect(response.json()).toEqual({ id: "s1", alreadyActive: true });
|
||||||
|
expect(tornDown).toBe(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each(["idle", "failed"])(
|
||||||
|
"POST resume replaces a %s runtime and starts the persisted session",
|
||||||
|
async (state) => {
|
||||||
|
const order: string[] = [];
|
||||||
|
const oldRuntime = { bridge: { turnState: () => state } } as any;
|
||||||
|
const newRuntime = { bridge: { onClientEvent: () => {}, emitClientEvent: () => {} } } as any;
|
||||||
|
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||||
|
mgr: {
|
||||||
|
get: () => oldRuntime,
|
||||||
|
teardown: (id: string) => order.push(`teardown:${id}`),
|
||||||
|
createFor: () => { order.push("create"); return newRuntime; },
|
||||||
|
configure: async () => {},
|
||||||
|
start: () => order.push("start"),
|
||||||
|
} as any,
|
||||||
|
thtRunner: {
|
||||||
|
sessionShow: async () => ({
|
||||||
|
status: "open", archived: false,
|
||||||
|
provider: "local-qwen", model: "qwen3.6-35b-a3b", thinking: "low",
|
||||||
|
}),
|
||||||
|
reopenSession: async () => order.push("reopen"),
|
||||||
|
} as any,
|
||||||
|
readiness: { ensure: async () => ({ ok: true }) } as any,
|
||||||
|
getSettings: () => ({ workspace: "local", thinking: "medium" }) as any,
|
||||||
|
});
|
||||||
|
const response = await app.inject({ method: "POST", url: "/sessions/s1/resume" });
|
||||||
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
|
expect(response.json()).toEqual({ id: "s1" });
|
||||||
|
expect(order).toEqual(["teardown:s1", "reopen", "create", "start"]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test("POST /sessions/:id/response inoltra al bridge (no error)", async () => {
|
test("POST /sessions/:id/response inoltra al bridge (no error)", async () => {
|
||||||
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||||
thtRunner: {
|
thtRunner: {
|
||||||
|
|||||||
Reference in New Issue
Block a user