fix: disable restart by default

This commit is contained in:
Peter Steinberger
2026-01-09 05:49:11 +00:00
parent db22207014
commit 0a026fea1c
12 changed files with 53 additions and 6 deletions

View File

@@ -12,9 +12,9 @@ describe("gateway tool", () => {
const kill = vi.spyOn(process, "kill").mockImplementation(() => true);
try {
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "gateway",
);
const tool = createClawdbotTools({
config: { commands: { restart: true } },
}).find((candidate) => candidate.name === "gateway");
expect(tool).toBeDefined();
if (!tool) throw new Error("missing gateway tool");

View File

@@ -43,7 +43,10 @@ export function createClawdbotTools(options?: {
}),
createTelegramTool(),
createWhatsAppTool(),
createGatewayTool({ agentSessionKey: options?.agentSessionKey }),
createGatewayTool({
agentSessionKey: options?.agentSessionKey,
config: options?.config,
}),
createAgentsListTool({ agentSessionKey: options?.agentSessionKey }),
createSessionsListTool({
agentSessionKey: options?.agentSessionKey,

View File

@@ -1,5 +1,6 @@
import { Type } from "@sinclair/typebox";
import type { ClawdbotConfig } from "../../config/config.js";
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
import { callGatewayTool } from "./gateway.js";
@@ -45,6 +46,7 @@ const GatewayToolSchema = Type.Union([
export function createGatewayTool(opts?: {
agentSessionKey?: string;
config?: ClawdbotConfig;
}): AnyAgentTool {
return {
label: "Gateway",
@@ -56,6 +58,11 @@ export function createGatewayTool(opts?: {
const params = args as Record<string, unknown>;
const action = readStringParam(params, "action", { required: true });
if (action === "restart") {
if (opts?.config?.commands?.restart !== true) {
throw new Error(
"Gateway restart is disabled. Set commands.restart=true to enable.",
);
}
const delayMs =
typeof params.delayMs === "number" && Number.isFinite(params.delayMs)
? Math.floor(params.delayMs)

View File

@@ -181,7 +181,7 @@ describe("trigger handling", () => {
});
});
it("restarts even with prefix/whitespace", async () => {
it("rejects /restart by default", async () => {
await withTempHome(async (home) => {
const res = await getReplyFromConfig(
{
@@ -193,6 +193,24 @@ describe("trigger handling", () => {
makeCfg(home),
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toContain("/restart is disabled");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("restarts when enabled", async () => {
await withTempHome(async (home) => {
const cfg = { ...makeCfg(home), commands: { restart: true } };
const res = await getReplyFromConfig(
{
Body: "/restart",
From: "+1001",
To: "+2000",
},
{},
cfg,
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(
text?.startsWith("⚙️ Restarting") ||
text?.startsWith("⚠️ Restart failed"),

View File

@@ -504,6 +504,14 @@ export async function handleCommands(params: {
);
return { shouldContinue: false };
}
if (cfg.commands?.restart !== true) {
return {
shouldContinue: false,
reply: {
text: "⚠️ /restart is disabled. Set commands.restart=true to enable.",
},
};
}
const hasSigusr1Listener = process.listenerCount("SIGUSR1") > 0;
if (hasSigusr1Listener) {
scheduleGatewaySigusr1Restart({ reason: "/restart" });

View File

@@ -350,7 +350,7 @@ export function buildStatusMessage(args: StatusArgs): string {
export function buildHelpMessage(): string {
return [
" Help",
"Shortcuts: /new reset | /compact [instructions] | /restart relink",
"Shortcuts: /new reset | /compact [instructions] | /restart relink (if enabled)",
"Options: /think <level> | /verbose on|off | /reasoning on|off | /elevated on|off | /model <id> | /cost on|off",
].join("\n");
}

View File

@@ -98,6 +98,7 @@ const FIELD_LABELS: Record<string, string> = {
"agent.imageModel.fallbacks": "Image Model Fallbacks",
"commands.native": "Native Commands",
"commands.text": "Text Commands",
"commands.restart": "Allow Restart",
"commands.useAccessGroups": "Use Access Groups",
"ui.seamColor": "Accent Color",
"browser.controlUrl": "Browser Control URL",
@@ -159,6 +160,8 @@ const FIELD_HELP: Record<string, string> = {
"commands.native":
"Register native commands with connectors that support it (Discord/Slack/Telegram).",
"commands.text": "Allow text command parsing (slash commands only).",
"commands.restart":
"Allow /restart and gateway restart tool actions (default: false).",
"commands.useAccessGroups":
"Enforce access-group allowlists/policies for commands.",
"session.agentToAgent.maxPingPongTurns":

View File

@@ -789,6 +789,8 @@ export type CommandsConfig = {
native?: boolean;
/** Enable text command parsing (default: true). */
text?: boolean;
/** Allow restart commands/tools (default: false). */
restart?: boolean;
/** Enforce access-group allowlists/policies for commands (default: true). */
useAccessGroups?: boolean;
};

View File

@@ -520,6 +520,7 @@ const CommandsSchema = z
.object({
native: z.boolean().optional(),
text: z.boolean().optional(),
restart: z.boolean().optional(),
useAccessGroups: z.boolean().optional(),
})
.optional();