import http from "node:http"; import { readFile } from "node:fs/promises"; import path from "node:path"; const appBaseUrl = process.env.APP_BASE_URL || "http://127.0.0.1:3000"; const seedPath = path.join( process.cwd(), "agentic_pm_demo_codex_plans/data/work-packages.seed.json", ); function assert(condition, message) { if (!condition) { throw new Error(message); } } function buildFakeLlmResponse(joinedMessages) { if (joinedMessages.includes("Connection test.")) { return "Connected to the configured model."; } if (joinedMessages.includes('"mode": "ask"')) { return "Verification method explains how the requirement will be checked, such as by test, inspection, or analysis."; } if (joinedMessages.includes('"mode": "plan"')) { return "Please break this package into review tasks."; } if (joinedMessages.includes('"mode": "change"')) { return "Please update the objective to include cybersecurity acceptance criteria."; } return JSON.stringify({ choices: [ { message: { content: JSON.stringify({ assistantMessage: "Fallback JSON response.", boardAction: { type: "none", workPackageId: null }, }), }, }, ], }); } async function startFakeLlmServer() { const server = http.createServer((req, res) => { let body = ""; req.on("data", (chunk) => { body += chunk; }); req.on("end", () => { let payload = {}; try { payload = JSON.parse(body || "{}"); } catch { payload = {}; } const joinedMessages = Array.isArray(payload.messages) ? payload.messages.map((message) => String(message?.content || "")).join("\n\n") : ""; res.writeHead(200, { "content-type": "text/plain" }); res.end(buildFakeLlmResponse(joinedMessages)); }); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); if (!address || typeof address === "string") { throw new Error("Could not determine fake LLM server address."); } return { server, baseUrl: `http://127.0.0.1:${address.port}/v1`, }; } async function postJson(url, body) { const response = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), }); const payload = await response.json(); if (!response.ok) { throw new Error(`Request failed (${response.status}): ${JSON.stringify(payload)}`); } return payload; } async function main() { const seed = JSON.parse(await readFile(seedPath, "utf8")); const { server, baseUrl } = await startFakeLlmServer(); try { console.log(`Smoke target: ${appBaseUrl}`); console.log(`Fake LLM: ${baseUrl}`); const llmConfig = { apiKey: "live-key", baseUrl, model: "fake-model", }; const connection = await postJson(`${appBaseUrl}/api/test-connection`, { llmConfig, }); assert(connection.ok === true, "Connection test did not succeed."); assert( connection.message === "Connected to the configured model.", "Connection test message mismatch.", ); console.log("PASS connection"); const ask = await postJson(`${appBaseUrl}/api/chat`, { messages: [{ role: "user", content: "@SRS ask What does verification method mean?" }], workPackages: seed, selectedWorkPackageId: "wp-srs", parsedCommand: { referencedPackageName: "System Requirements Specification", mode: "ask", instruction: "What does verification method mean?", }, llmConfig, }); assert( typeof ask.assistantMessage === "string" && ask.assistantMessage.includes("Verification method explains"), "Ask response was not human-readable.", ); assert( !ask.assistantMessage.includes("Network or parsing error"), "Ask response fell back to network/parsing error.", ); assert(ask.boardAction?.type === "none", "Ask response changed the board."); console.log("PASS ask"); const plan = await postJson(`${appBaseUrl}/api/chat`, { messages: [{ role: "user", content: "@SRS plan Break this into review tasks." }], workPackages: seed, selectedWorkPackageId: "wp-srs", parsedCommand: { referencedPackageName: "System Requirements Specification", mode: "plan", instruction: "Break this into review tasks.", }, llmConfig, }); assert( typeof plan.assistantMessage === "string" && plan.assistantMessage.includes("Planned next steps for SRS"), "Plan response message mismatch.", ); assert(plan.boardAction?.type === "update", "Plan did not return an update."); assert(plan.boardAction?.workPackageId === "wp-srs", "Plan updated the wrong package."); assert( Array.isArray(plan.boardAction?.fields?.tasks) && plan.boardAction.fields.tasks.length > 0, "Plan did not provide tasks.", ); console.log("PASS plan"); const change = await postJson(`${appBaseUrl}/api/chat`, { messages: [ { role: "user", content: "@SRS change Add cybersecurity acceptance criteria." }, ], workPackages: seed, selectedWorkPackageId: "wp-srs", parsedCommand: { referencedPackageName: "System Requirements Specification", mode: "change", instruction: "Add cybersecurity acceptance criteria.", }, llmConfig, }); assert( typeof change.assistantMessage === "string" && change.assistantMessage.includes("Updated SRS"), "Change response message mismatch.", ); assert(change.boardAction?.type === "update", "Change did not return an update."); assert( change.boardAction?.workPackageId === "wp-srs", "Change updated the wrong package.", ); assert( String(change.boardAction?.fields?.objective || "").includes( "Add cybersecurity acceptance criteria.", ), "Change response did not update the objective.", ); console.log("PASS change"); console.log("Smoke test complete."); } finally { await new Promise((resolve, reject) => { server.close((error) => { if (error) { reject(error); return; } resolve(); }); }); } } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });