feat: add gateway restart tool

This commit is contained in:
Peter Steinberger
2025-12-25 18:05:37 +00:00
parent aafcd569b1
commit 42eb7640f9
6 changed files with 266 additions and 135 deletions

View File

@@ -0,0 +1,36 @@
import { describe, expect, it, vi } from "vitest";
import { createClawdisTools } from "./clawdis-tools.js";
describe("clawdis_gateway tool", () => {
it("schedules SIGUSR1 restart", async () => {
vi.useFakeTimers();
const kill = vi.spyOn(process, "kill").mockImplementation(() => true);
try {
const tool = createClawdisTools().find(
(candidate) => candidate.name === "clawdis_gateway",
);
expect(tool).toBeDefined();
if (!tool) throw new Error("missing clawdis_gateway tool");
const result = await tool.execute("call1", {
action: "restart",
delayMs: 0,
});
expect(result.details).toMatchObject({
ok: true,
pid: process.pid,
signal: "SIGUSR1",
delayMs: 0,
});
expect(kill).not.toHaveBeenCalled();
await vi.runAllTimersAsync();
expect(kill).toHaveBeenCalledWith(process.pid, "SIGUSR1");
} finally {
kill.mockRestore();
vi.useRealTimers();
}
});
});

View File

@@ -1370,11 +1370,62 @@ function createCronTool(): AnyAgentTool {
};
}
const GatewayToolSchema = Type.Union([
Type.Object({
action: Type.Literal("restart"),
delayMs: Type.Optional(Type.Number()),
reason: Type.Optional(Type.String()),
}),
]);
function createGatewayTool(): AnyAgentTool {
return {
label: "Clawdis Gateway",
name: "clawdis_gateway",
description:
"Restart the running gateway process in-place (SIGUSR1) without needing an external supervisor. Use delayMs to avoid interrupting an in-flight reply.",
parameters: GatewayToolSchema,
execute: async (_toolCallId, args) => {
const params = args as Record<string, unknown>;
const action = readStringParam(params, "action", { required: true });
if (action !== "restart") throw new Error(`Unknown action: ${action}`);
const delayMsRaw =
typeof params.delayMs === "number" && Number.isFinite(params.delayMs)
? Math.floor(params.delayMs)
: 2000;
const delayMs = Math.min(Math.max(delayMsRaw, 0), 60_000);
const reason =
typeof params.reason === "string" && params.reason.trim()
? params.reason.trim().slice(0, 200)
: undefined;
const pid = process.pid;
setTimeout(() => {
try {
process.kill(pid, "SIGUSR1");
} catch {
/* ignore */
}
}, delayMs);
return jsonResult({
ok: true,
pid,
signal: "SIGUSR1",
delayMs,
reason: reason ?? null,
});
},
};
}
export function createClawdisTools(): AnyAgentTool[] {
return [
createBrowserTool(),
createCanvasTool(),
createNodesTool(),
createCronTool(),
createGatewayTool(),
];
}