fix: disable restart by default
This commit is contained in:
@@ -591,6 +591,7 @@ Controls how chat commands are enabled across connectors.
|
|||||||
commands: {
|
commands: {
|
||||||
native: false, // register native commands when supported
|
native: false, // register native commands when supported
|
||||||
text: true, // parse slash commands in chat messages
|
text: true, // parse slash commands in chat messages
|
||||||
|
restart: false, // allow /restart + gateway restart tool
|
||||||
useAccessGroups: true // enforce access-group allowlists/policies for commands
|
useAccessGroups: true // enforce access-group allowlists/policies for commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,6 +602,7 @@ Notes:
|
|||||||
- `commands.text: false` disables parsing chat messages for commands.
|
- `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: 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.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.
|
- `commands.useAccessGroups: false` allows commands to bypass access-group allowlists/policies.
|
||||||
|
|
||||||
### `web` (WhatsApp web provider)
|
### `web` (WhatsApp web provider)
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ Core actions:
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- Use `delayMs` (defaults to 2000) to avoid interrupting an in-flight reply.
|
- 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`
|
### `sessions_list` / `sessions_history` / `sessions_send` / `sessions_spawn`
|
||||||
List sessions, inspect transcript history, or send to another session.
|
List sessions, inspect transcript history, or send to another session.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Directives (`/think`, `/verbose`, `/reasoning`, `/elevated`) are parsed even whe
|
|||||||
commands: {
|
commands: {
|
||||||
native: false,
|
native: false,
|
||||||
text: true,
|
text: true,
|
||||||
|
restart: false,
|
||||||
useAccessGroups: true
|
useAccessGroups: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +55,7 @@ Text-only:
|
|||||||
Notes:
|
Notes:
|
||||||
- Commands accept an optional `:` between the command and args (e.g. `/think: high`, `/send: on`, `/help:`).
|
- 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).
|
- `/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.
|
- `/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.
|
- `/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);
|
const kill = vi.spyOn(process, "kill").mockImplementation(() => true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tool = createClawdbotTools().find(
|
const tool = createClawdbotTools({
|
||||||
(candidate) => candidate.name === "gateway",
|
config: { commands: { restart: true } },
|
||||||
);
|
}).find((candidate) => candidate.name === "gateway");
|
||||||
expect(tool).toBeDefined();
|
expect(tool).toBeDefined();
|
||||||
if (!tool) throw new Error("missing gateway tool");
|
if (!tool) throw new Error("missing gateway tool");
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ export function createClawdbotTools(options?: {
|
|||||||
}),
|
}),
|
||||||
createTelegramTool(),
|
createTelegramTool(),
|
||||||
createWhatsAppTool(),
|
createWhatsAppTool(),
|
||||||
createGatewayTool({ agentSessionKey: options?.agentSessionKey }),
|
createGatewayTool({
|
||||||
|
agentSessionKey: options?.agentSessionKey,
|
||||||
|
config: options?.config,
|
||||||
|
}),
|
||||||
createAgentsListTool({ agentSessionKey: options?.agentSessionKey }),
|
createAgentsListTool({ agentSessionKey: options?.agentSessionKey }),
|
||||||
createSessionsListTool({
|
createSessionsListTool({
|
||||||
agentSessionKey: options?.agentSessionKey,
|
agentSessionKey: options?.agentSessionKey,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
|
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
|
||||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||||
import { callGatewayTool } from "./gateway.js";
|
import { callGatewayTool } from "./gateway.js";
|
||||||
@@ -45,6 +46,7 @@ const GatewayToolSchema = Type.Union([
|
|||||||
|
|
||||||
export function createGatewayTool(opts?: {
|
export function createGatewayTool(opts?: {
|
||||||
agentSessionKey?: string;
|
agentSessionKey?: string;
|
||||||
|
config?: ClawdbotConfig;
|
||||||
}): AnyAgentTool {
|
}): AnyAgentTool {
|
||||||
return {
|
return {
|
||||||
label: "Gateway",
|
label: "Gateway",
|
||||||
@@ -56,6 +58,11 @@ export function createGatewayTool(opts?: {
|
|||||||
const params = args as Record<string, unknown>;
|
const params = args as Record<string, unknown>;
|
||||||
const action = readStringParam(params, "action", { required: true });
|
const action = readStringParam(params, "action", { required: true });
|
||||||
if (action === "restart") {
|
if (action === "restart") {
|
||||||
|
if (opts?.config?.commands?.restart !== true) {
|
||||||
|
throw new Error(
|
||||||
|
"Gateway restart is disabled. Set commands.restart=true to enable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
const delayMs =
|
const delayMs =
|
||||||
typeof params.delayMs === "number" && Number.isFinite(params.delayMs)
|
typeof params.delayMs === "number" && Number.isFinite(params.delayMs)
|
||||||
? Math.floor(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) => {
|
await withTempHome(async (home) => {
|
||||||
const res = await getReplyFromConfig(
|
const res = await getReplyFromConfig(
|
||||||
{
|
{
|
||||||
@@ -193,6 +193,24 @@ describe("trigger handling", () => {
|
|||||||
makeCfg(home),
|
makeCfg(home),
|
||||||
);
|
);
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
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(
|
expect(
|
||||||
text?.startsWith("⚙️ Restarting") ||
|
text?.startsWith("⚙️ Restarting") ||
|
||||||
text?.startsWith("⚠️ Restart failed"),
|
text?.startsWith("⚠️ Restart failed"),
|
||||||
|
|||||||
@@ -504,6 +504,14 @@ export async function handleCommands(params: {
|
|||||||
);
|
);
|
||||||
return { shouldContinue: false };
|
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;
|
const hasSigusr1Listener = process.listenerCount("SIGUSR1") > 0;
|
||||||
if (hasSigusr1Listener) {
|
if (hasSigusr1Listener) {
|
||||||
scheduleGatewaySigusr1Restart({ reason: "/restart" });
|
scheduleGatewaySigusr1Restart({ reason: "/restart" });
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||||||
export function buildHelpMessage(): string {
|
export function buildHelpMessage(): string {
|
||||||
return [
|
return [
|
||||||
"ℹ️ Help",
|
"ℹ️ 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",
|
"Options: /think <level> | /verbose on|off | /reasoning on|off | /elevated on|off | /model <id> | /cost on|off",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
"agent.imageModel.fallbacks": "Image Model Fallbacks",
|
"agent.imageModel.fallbacks": "Image Model Fallbacks",
|
||||||
"commands.native": "Native Commands",
|
"commands.native": "Native Commands",
|
||||||
"commands.text": "Text Commands",
|
"commands.text": "Text Commands",
|
||||||
|
"commands.restart": "Allow Restart",
|
||||||
"commands.useAccessGroups": "Use Access Groups",
|
"commands.useAccessGroups": "Use Access Groups",
|
||||||
"ui.seamColor": "Accent Color",
|
"ui.seamColor": "Accent Color",
|
||||||
"browser.controlUrl": "Browser Control URL",
|
"browser.controlUrl": "Browser Control URL",
|
||||||
@@ -159,6 +160,8 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"commands.native":
|
"commands.native":
|
||||||
"Register native commands with connectors that support it (Discord/Slack/Telegram).",
|
"Register native commands with connectors that support it (Discord/Slack/Telegram).",
|
||||||
"commands.text": "Allow text command parsing (slash commands only).",
|
"commands.text": "Allow text command parsing (slash commands only).",
|
||||||
|
"commands.restart":
|
||||||
|
"Allow /restart and gateway restart tool actions (default: false).",
|
||||||
"commands.useAccessGroups":
|
"commands.useAccessGroups":
|
||||||
"Enforce access-group allowlists/policies for commands.",
|
"Enforce access-group allowlists/policies for commands.",
|
||||||
"session.agentToAgent.maxPingPongTurns":
|
"session.agentToAgent.maxPingPongTurns":
|
||||||
|
|||||||
@@ -789,6 +789,8 @@ export type CommandsConfig = {
|
|||||||
native?: boolean;
|
native?: boolean;
|
||||||
/** Enable text command parsing (default: true). */
|
/** Enable text command parsing (default: true). */
|
||||||
text?: boolean;
|
text?: boolean;
|
||||||
|
/** Allow restart commands/tools (default: false). */
|
||||||
|
restart?: boolean;
|
||||||
/** Enforce access-group allowlists/policies for commands (default: true). */
|
/** Enforce access-group allowlists/policies for commands (default: true). */
|
||||||
useAccessGroups?: boolean;
|
useAccessGroups?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -520,6 +520,7 @@ const CommandsSchema = z
|
|||||||
.object({
|
.object({
|
||||||
native: z.boolean().optional(),
|
native: z.boolean().optional(),
|
||||||
text: z.boolean().optional(),
|
text: z.boolean().optional(),
|
||||||
|
restart: z.boolean().optional(),
|
||||||
useAccessGroups: z.boolean().optional(),
|
useAccessGroups: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.optional();
|
.optional();
|
||||||
|
|||||||
Reference in New Issue
Block a user