fix: disable restart by default
This commit is contained in:
@@ -591,6 +591,7 @@ Controls how chat commands are enabled across connectors.
|
||||
commands: {
|
||||
native: false, // register native commands when supported
|
||||
text: true, // parse slash commands in chat messages
|
||||
restart: false, // allow /restart + gateway restart tool
|
||||
useAccessGroups: true // enforce access-group allowlists/policies for commands
|
||||
}
|
||||
}
|
||||
@@ -601,6 +602,7 @@ Notes:
|
||||
- `commands.text: false` disables parsing chat messages for commands.
|
||||
- `commands.native: true` registers native commands on supported connectors (Discord/Slack/Telegram). Platforms without native commands still rely on text commands.
|
||||
- `commands.native: false` skips native registration; Discord/Telegram clear previously registered commands on startup. Slack commands are managed in the Slack app.
|
||||
- `commands.restart: true` enables `/restart` and the gateway tool restart action.
|
||||
- `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies.
|
||||
|
||||
### `web` (WhatsApp web provider)
|
||||
|
||||
@@ -182,6 +182,7 @@ Core actions:
|
||||
|
||||
Notes:
|
||||
- Use `delayMs` (defaults to 2000) to avoid interrupting an in-flight reply.
|
||||
- `restart` is disabled by default; enable with `commands.restart: true`.
|
||||
|
||||
### `sessions_list` / `sessions_history` / `sessions_send` / `sessions_spawn`
|
||||
List sessions, inspect transcript history, or send to another session.
|
||||
|
||||
@@ -18,6 +18,7 @@ Directives (`/think`, `/verbose`, `/reasoning`, `/elevated`) are parsed even whe
|
||||
commands: {
|
||||
native: false,
|
||||
text: true,
|
||||
restart: false,
|
||||
useAccessGroups: true
|
||||
}
|
||||
}
|
||||
@@ -54,6 +55,7 @@ Text-only:
|
||||
Notes:
|
||||
- Commands accept an optional `:` between the command and args (e.g. `/think: high`, `/send: on`, `/help:`).
|
||||
- `/cost` appends per-response token usage; it only shows dollar cost when the model uses an API key (OAuth hides cost).
|
||||
- `/restart` is disabled by default; set `commands.restart: true` to enable it.
|
||||
- `/verbose` is meant for debugging and extra visibility; keep it **off** in normal use.
|
||||
- `/reasoning` (and `/verbose`) are risky in group settings: they may reveal internal reasoning or tool output you did not intend to expose. Prefer leaving them off, especially in group chats.
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user