Files
lvmh/e2e/scenarios/agent-lifecycle.mjs
T

79 lines
3.5 KiB
JavaScript

// scenarios/agent-lifecycle.mjs — full plugin lifecycle against the real
// daemon: hello→welcome, persisted events, online flag, events after disconnect.
import { agentClient, agentEvent, agentHello, rest, sessionSnapshot, sid, waitUntil } from "../lib.mjs";
export async function run(ctx) {
const { r, base, token, agentUrl } = ctx;
const id = sid("e2e-life");
const snapshot = sessionSnapshot(id);
const agent = await agentClient(agentUrl, token);
const welcome = await agentHello(agent, snapshot);
r.check("hello → welcome received", welcome !== null && welcome.type === "welcome");
r.check("welcome echoes sessionId", welcome?.sessionId === id, JSON.stringify(welcome ?? null));
r.check("welcome lastSeq 0 on fresh session", welcome?.lastSeq === 0, `got ${welcome?.lastSeq}`);
r.check("welcome envelope v=1", welcome?.v === 1);
agentEvent(agent, id, 1, "message_end", {
message: { role: "user", id: "m1", text: "hello from e2e", thinking: null, toolCalls: [], toolCallId: null },
});
agentEvent(agent, id, 2, "tool_execution_start", { toolCallId: "tc1", toolName: "bash", args: { command: "ls" } });
agentEvent(agent, id, 3, "tool_execution_end", { toolCallId: "tc1", toolName: "bash", isError: false, resultPreview: "file1\nfile2" });
agentEvent(agent, id, 4, "agent_end", { usage: { inputTokens: 10, outputTokens: 5, totalCost: 0.25 } });
const listUrl = "/api/sessions";
const online = await waitUntil(async () => {
const res = await rest(base, token, listUrl);
return res.json?.find((s) => s.id === id)?.online === true;
});
r.check("GET /api/sessions shows online=true while agent connected", online);
const listed = (await rest(base, token, listUrl)).json.find((s) => s.id === id);
r.check(
"session snapshot fields exposed",
listed?.cwd === "/work/e2e" &&
listed?.model === "glm-5.3" &&
listed?.provider === "zai-renaud" &&
listed?.agent === false &&
listed?.repo === null &&
typeof listed?.startedAt === "number" &&
typeof listed?.lastEventAt === "number",
JSON.stringify(listed ?? null),
);
agent.close();
const offline = await waitUntil(async () => {
const res = await rest(base, token, listUrl);
return res.json?.find((s) => s.id === id)?.online === false;
});
r.check("online=false after agent disconnect", offline);
const eventsUrl = `/api/sessions/${id}/events?after=0`;
const persisted = await waitUntil(
async () => (await rest(base, token, eventsUrl)).json?.length === 4,
);
r.check("all 4 persisted events served after=0", persisted);
const ev = (await rest(base, token, eventsUrl)).json ?? [];
r.check(
"event seqs ascending [1,2,3,4]",
JSON.stringify(ev.map((e) => e.seq)) === "[1,2,3,4]",
JSON.stringify(ev.map((e) => e.seq)),
);
r.check(
"event types in order",
JSON.stringify(ev.map((e) => e.type)) ===
JSON.stringify(["message_end", "tool_execution_start", "tool_execution_end", "agent_end"]),
JSON.stringify(ev.map((e) => e.type)),
);
r.check(
"persisted frames re-serialized with envelope",
ev[0]?.v === 1 && ev[0]?.sessionId === id && typeof ev[0]?.ts === "number" && ev[0]?.message?.text === "hello from e2e",
JSON.stringify(ev[0] ?? null),
);
const tail = (await rest(base, token, `/api/sessions/${id}/events?after=2`)).json ?? [];
r.check("events?after=2 returns only seq≥3", JSON.stringify(tail.map((e) => e.seq)) === "[3,4]", JSON.stringify(tail.map((e) => e.seq)));
ctx.state.lifecycle = { id, snapshot, lastSeq: 4 };
}