test: cover auto-reply command gating

This commit is contained in:
Peter Steinberger
2026-01-11 02:27:07 +01:00
parent e0bf86f06c
commit 2d74119a08
10 changed files with 104 additions and 32 deletions

View File

@@ -0,0 +1,66 @@
import { describe, expect, it } from "vitest";
import type { ClawdbotConfig } from "../../config/config.js";
import type { MsgContext } from "../templating.js";
import { buildCommandContext, handleCommands } from "./commands.js";
import { parseInlineDirectives } from "./directive-handling.js";
function buildParams(commandBody: string, cfg: ClawdbotConfig) {
const ctx = {
Body: commandBody,
CommandBody: commandBody,
CommandSource: "text",
CommandAuthorized: true,
Provider: "whatsapp",
Surface: "whatsapp",
} as MsgContext;
const command = buildCommandContext({
ctx,
cfg,
isGroup: false,
triggerBodyNormalized: commandBody.trim().toLowerCase(),
commandAuthorized: true,
});
return {
ctx,
cfg,
command,
directives: parseInlineDirectives(commandBody),
sessionKey: "agent:main:main",
workspaceDir: "/tmp",
defaultGroupActivation: () => "mention",
resolvedVerboseLevel: "off" as const,
resolvedReasoningLevel: "off" as const,
resolveDefaultThinkingLevel: async () => undefined,
provider: "whatsapp",
model: "test-model",
contextTokens: 0,
isGroup: false,
};
}
describe("handleCommands gating", () => {
it("blocks /config when disabled", async () => {
const cfg = {
commands: { config: false, debug: false, text: true },
whatsapp: { allowFrom: ["*"] },
} as ClawdbotConfig;
const params = buildParams("/config show", cfg);
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("/config is disabled");
});
it("blocks /debug when disabled", async () => {
const cfg = {
commands: { config: false, debug: false, text: true },
whatsapp: { allowFrom: ["*"] },
} as ClawdbotConfig;
const params = buildParams("/debug show", cfg);
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("/debug is disabled");
});
});

View File

@@ -613,7 +613,10 @@ export async function handleCommands(params: {
);
return { shouldContinue: false };
}
return { shouldContinue: false, reply: { text: buildCommandsMessage(cfg) } };
return {
shouldContinue: false,
reply: { text: buildCommandsMessage(cfg) },
};
}
const statusRequested =

View File

@@ -336,10 +336,7 @@ function renderGatewayServiceStartHints(
}
case "linux": {
const unit = resolveGatewaySystemdServiceName(profile);
return [
...base,
`systemctl --user start ${unit}.service`,
];
return [...base, `systemctl --user start ${unit}.service`];
}
case "win32": {
const task = resolveGatewayWindowsTaskName(profile);
@@ -726,7 +723,8 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
spacer();
}
if (service.runtime?.cachedLabel) {
const env = (service.command?.environment ?? process.env) as NodeJS.ProcessEnv;
const env = (service.command?.environment ??
process.env) as NodeJS.ProcessEnv;
const label = resolveGatewayLaunchAgentLabel(env.CLAWDBOT_PROFILE);
defaultRuntime.error(
errorText(
@@ -782,7 +780,8 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
);
}
if (process.platform === "linux") {
const env = (service.command?.environment ?? process.env) as NodeJS.ProcessEnv;
const env = (service.command?.environment ??
process.env) as NodeJS.ProcessEnv;
const unit = resolveGatewaySystemdServiceName(env.CLAWDBOT_PROFILE);
defaultRuntime.error(
errorText(

View File

@@ -505,15 +505,15 @@ export async function runNonInteractiveOnboarding(
runtime: daemonRuntimeRaw,
nodePath,
});
const environment = buildServiceEnvironment({
env: process.env,
port,
token: gatewayToken,
launchdLabel:
process.platform === "darwin"
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
: undefined,
});
const environment = buildServiceEnvironment({
env: process.env,
port,
token: gatewayToken,
launchdLabel:
process.platform === "darwin"
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
: undefined,
});
await service.install({
env: process.env,
stdout: process.stdout,

View File

@@ -1,9 +1,9 @@
import { describe, expect, it } from "vitest";
import {
formatGatewayServiceDescription,
GATEWAY_LAUNCH_AGENT_LABEL,
GATEWAY_SYSTEMD_SERVICE_NAME,
GATEWAY_WINDOWS_TASK_NAME,
formatGatewayServiceDescription,
resolveGatewayLaunchAgentLabel,
resolveGatewaySystemdServiceName,
resolveGatewayWindowsTaskName,
@@ -149,15 +149,15 @@ describe("formatGatewayServiceDescription", () => {
});
it("includes profile when set", () => {
expect(
formatGatewayServiceDescription({ profile: "work" }),
).toBe("Clawdbot Gateway (profile: work)");
expect(formatGatewayServiceDescription({ profile: "work" })).toBe(
"Clawdbot Gateway (profile: work)",
);
});
it("includes version when set", () => {
expect(
formatGatewayServiceDescription({ version: "2026.1.10" }),
).toBe("Clawdbot Gateway (v2026.1.10)");
expect(formatGatewayServiceDescription({ version: "2026.1.10" })).toBe(
"Clawdbot Gateway (v2026.1.10)",
);
});
it("includes profile and version when set", () => {

View File

@@ -103,7 +103,9 @@ function isClawdbotGatewayTaskName(name: string): boolean {
const normalized = name.trim().toLowerCase();
if (!normalized) return false;
const defaultName = resolveGatewayWindowsTaskName().toLowerCase();
return normalized === defaultName || normalized.startsWith("clawdbot gateway");
return (
normalized === defaultName || normalized.startsWith("clawdbot gateway")
);
}
function tryExtractPlistLabel(contents: string): string | null {

View File

@@ -5,9 +5,9 @@ import { promisify } from "node:util";
import { colorize, isRich, theme } from "../terminal/theme.js";
import {
formatGatewayServiceDescription,
GATEWAY_LAUNCH_AGENT_LABEL,
LEGACY_GATEWAY_LAUNCH_AGENT_LABELS,
formatGatewayServiceDescription,
resolveGatewayLaunchAgentLabel,
} from "./constants.js";
import { parseKeyValueOutput } from "./runtime-parse.js";
@@ -190,12 +190,11 @@ export function buildLaunchAgentPlist({
<key>WorkingDirectory</key>
<string>${plistEscape(workingDirectory)}</string>`
: "";
const commentXml =
comment && comment.trim()
? `
const commentXml = comment?.trim()
? `
<key>Comment</key>
<string>${plistEscape(comment.trim())}</string>`
: "";
: "";
const envXml = renderEnvDict(environment);
return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

View File

@@ -5,8 +5,8 @@ import { promisify } from "node:util";
import { colorize, isRich, theme } from "../terminal/theme.js";
import {
LEGACY_GATEWAY_WINDOWS_TASK_NAMES,
formatGatewayServiceDescription,
LEGACY_GATEWAY_WINDOWS_TASK_NAMES,
resolveGatewayWindowsTaskName,
} from "./constants.js";
import { parseKeyValueOutput } from "./runtime-parse.js";

View File

@@ -103,7 +103,10 @@ export function resolveGatewayService(): GatewayService {
await uninstallSystemdService(args);
},
stop: async (args) => {
await stopSystemdService({ stdout: args.stdout, profile: args.profile });
await stopSystemdService({
stdout: args.stdout,
profile: args.profile,
});
},
restart: async (args) => {
await restartSystemdService({

View File

@@ -6,8 +6,8 @@ import { promisify } from "node:util";
import { runCommandWithTimeout, runExec } from "../process/exec.js";
import { colorize, isRich, theme } from "../terminal/theme.js";
import {
LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES,
formatGatewayServiceDescription,
LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES,
resolveGatewaySystemdServiceName,
} from "./constants.js";
import { parseKeyValueOutput } from "./runtime-parse.js";