registry/registry/coder/modules/agentapi/testdata/agentapi-mock-shutdown.js
Mathias Fredriksson 2169fb00ee
feat(coder/modules/agentapi): add state persistence support (#736)
AgentAPI can now save and restore conversation state across workspace
restarts. The module exports env vars (AGENTAPI_STATE_FILE,
AGENTAPI_SAVE_STATE, AGENTAPI_LOAD_STATE, AGENTAPI_PID_FILE) that the
binary reads directly. No consumer module changes needed.

New variables: enable_state_persistence (default false),
state_file_path, pid_file_path. State and PID files default to
$HOME/<module_dir_name>/.

Requires agentapi >= v0.12.0. A shared version_at_least function in
lib.sh gates the env var exports and SIGUSR1 in the shutdown script.
Old binaries get a warning and graceful skip.

Shutdown script now does SIGUSR1 (state save), log snapshot capture
(existing, now fault-tolerant via subshell), then SIGTERM with wait.

Closes coder/internal#1257
Refs coder/internal#1256
Refs #696
2026-03-03 13:27:23 +02:00

103 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
// Mock AgentAPI server for shutdown script tests.
// Usage: MESSAGES='[...]' node agentapi-mock-shutdown.js [port]
const http = require("http");
const fs = require("fs");
const port = process.argv[2] || 3284;
// Write PID file for shutdown script.
if (process.env.AGENTAPI_PID_FILE) {
const path = require("path");
fs.mkdirSync(path.dirname(process.env.AGENTAPI_PID_FILE), {
recursive: true,
});
fs.writeFileSync(process.env.AGENTAPI_PID_FILE, String(process.pid));
}
// Handle SIGUSR1 (state save signal from shutdown script).
process.on("SIGUSR1", () => {
fs.writeFileSync(
"/tmp/sigusr1-received",
`SIGUSR1 received at ${Date.now()}\n`,
);
});
// Parse messages from environment or use default
let messages = [];
if (process.env.MESSAGES) {
try {
messages = JSON.parse(process.env.MESSAGES);
} catch (e) {
console.error("Failed to parse MESSAGES env var:", e.message);
process.exit(1);
}
}
// Presets for common test scenarios
if (process.env.PRESET === "normal") {
messages = [
{ id: 1, type: "input", content: "Hello", time: "2025-01-01T00:00:00Z" },
{
id: 2,
type: "output",
content: "Hi there",
time: "2025-01-01T00:00:01Z",
},
{
id: 3,
type: "input",
content: "How are you?",
time: "2025-01-01T00:00:02Z",
},
{
id: 4,
type: "output",
content: "Good!",
time: "2025-01-01T00:00:03Z",
},
{ id: 5, type: "input", content: "Great", time: "2025-01-01T00:00:04Z" },
];
} else if (process.env.PRESET === "many") {
messages = Array.from({ length: 15 }, (_, i) => ({
id: i + 1,
type: "input",
content: `Message ${i + 1}`,
time: "2025-01-01T00:00:00Z",
}));
} else if (process.env.PRESET === "huge") {
messages = [
{
id: 1,
type: "output",
content: "x".repeat(70000),
time: "2025-01-01T00:00:00Z",
},
];
}
const server = http.createServer((req, res) => {
if (req.url === "/messages") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ messages }));
} else if (req.url === "/status") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "stable" }));
} else {
res.writeHead(404);
res.end();
}
});
server.listen(port, () => {
console.error(`Mock AgentAPI listening on port ${port}`);
});
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
});
process.on("SIGINT", () => {
server.close(() => process.exit(0));
});