test: cover auto-reply command gating
This commit is contained in:
66
src/auto-reply/reply/commands.test.ts
Normal file
66
src/auto-reply/reply/commands.test.ts
Normal 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -613,7 +613,10 @@ export async function handleCommands(params: {
|
|||||||
);
|
);
|
||||||
return { shouldContinue: false };
|
return { shouldContinue: false };
|
||||||
}
|
}
|
||||||
return { shouldContinue: false, reply: { text: buildCommandsMessage(cfg) } };
|
return {
|
||||||
|
shouldContinue: false,
|
||||||
|
reply: { text: buildCommandsMessage(cfg) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusRequested =
|
const statusRequested =
|
||||||
|
|||||||
@@ -336,10 +336,7 @@ function renderGatewayServiceStartHints(
|
|||||||
}
|
}
|
||||||
case "linux": {
|
case "linux": {
|
||||||
const unit = resolveGatewaySystemdServiceName(profile);
|
const unit = resolveGatewaySystemdServiceName(profile);
|
||||||
return [
|
return [...base, `systemctl --user start ${unit}.service`];
|
||||||
...base,
|
|
||||||
`systemctl --user start ${unit}.service`,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
case "win32": {
|
case "win32": {
|
||||||
const task = resolveGatewayWindowsTaskName(profile);
|
const task = resolveGatewayWindowsTaskName(profile);
|
||||||
@@ -726,7 +723,8 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
|||||||
spacer();
|
spacer();
|
||||||
}
|
}
|
||||||
if (service.runtime?.cachedLabel) {
|
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);
|
const label = resolveGatewayLaunchAgentLabel(env.CLAWDBOT_PROFILE);
|
||||||
defaultRuntime.error(
|
defaultRuntime.error(
|
||||||
errorText(
|
errorText(
|
||||||
@@ -782,7 +780,8 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (process.platform === "linux") {
|
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);
|
const unit = resolveGatewaySystemdServiceName(env.CLAWDBOT_PROFILE);
|
||||||
defaultRuntime.error(
|
defaultRuntime.error(
|
||||||
errorText(
|
errorText(
|
||||||
|
|||||||
@@ -505,15 +505,15 @@ export async function runNonInteractiveOnboarding(
|
|||||||
runtime: daemonRuntimeRaw,
|
runtime: daemonRuntimeRaw,
|
||||||
nodePath,
|
nodePath,
|
||||||
});
|
});
|
||||||
const environment = buildServiceEnvironment({
|
const environment = buildServiceEnvironment({
|
||||||
env: process.env,
|
env: process.env,
|
||||||
port,
|
port,
|
||||||
token: gatewayToken,
|
token: gatewayToken,
|
||||||
launchdLabel:
|
launchdLabel:
|
||||||
process.platform === "darwin"
|
process.platform === "darwin"
|
||||||
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
await service.install({
|
await service.install({
|
||||||
env: process.env,
|
env: process.env,
|
||||||
stdout: process.stdout,
|
stdout: process.stdout,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
formatGatewayServiceDescription,
|
||||||
GATEWAY_LAUNCH_AGENT_LABEL,
|
GATEWAY_LAUNCH_AGENT_LABEL,
|
||||||
GATEWAY_SYSTEMD_SERVICE_NAME,
|
GATEWAY_SYSTEMD_SERVICE_NAME,
|
||||||
GATEWAY_WINDOWS_TASK_NAME,
|
GATEWAY_WINDOWS_TASK_NAME,
|
||||||
formatGatewayServiceDescription,
|
|
||||||
resolveGatewayLaunchAgentLabel,
|
resolveGatewayLaunchAgentLabel,
|
||||||
resolveGatewaySystemdServiceName,
|
resolveGatewaySystemdServiceName,
|
||||||
resolveGatewayWindowsTaskName,
|
resolveGatewayWindowsTaskName,
|
||||||
@@ -149,15 +149,15 @@ describe("formatGatewayServiceDescription", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("includes profile when set", () => {
|
it("includes profile when set", () => {
|
||||||
expect(
|
expect(formatGatewayServiceDescription({ profile: "work" })).toBe(
|
||||||
formatGatewayServiceDescription({ profile: "work" }),
|
"Clawdbot Gateway (profile: work)",
|
||||||
).toBe("Clawdbot Gateway (profile: work)");
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes version when set", () => {
|
it("includes version when set", () => {
|
||||||
expect(
|
expect(formatGatewayServiceDescription({ version: "2026.1.10" })).toBe(
|
||||||
formatGatewayServiceDescription({ version: "2026.1.10" }),
|
"Clawdbot Gateway (v2026.1.10)",
|
||||||
).toBe("Clawdbot Gateway (v2026.1.10)");
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes profile and version when set", () => {
|
it("includes profile and version when set", () => {
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ function isClawdbotGatewayTaskName(name: string): boolean {
|
|||||||
const normalized = name.trim().toLowerCase();
|
const normalized = name.trim().toLowerCase();
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
const defaultName = resolveGatewayWindowsTaskName().toLowerCase();
|
const defaultName = resolveGatewayWindowsTaskName().toLowerCase();
|
||||||
return normalized === defaultName || normalized.startsWith("clawdbot gateway");
|
return (
|
||||||
|
normalized === defaultName || normalized.startsWith("clawdbot gateway")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryExtractPlistLabel(contents: string): string | null {
|
function tryExtractPlistLabel(contents: string): string | null {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { promisify } from "node:util";
|
|||||||
|
|
||||||
import { colorize, isRich, theme } from "../terminal/theme.js";
|
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||||
import {
|
import {
|
||||||
|
formatGatewayServiceDescription,
|
||||||
GATEWAY_LAUNCH_AGENT_LABEL,
|
GATEWAY_LAUNCH_AGENT_LABEL,
|
||||||
LEGACY_GATEWAY_LAUNCH_AGENT_LABELS,
|
LEGACY_GATEWAY_LAUNCH_AGENT_LABELS,
|
||||||
formatGatewayServiceDescription,
|
|
||||||
resolveGatewayLaunchAgentLabel,
|
resolveGatewayLaunchAgentLabel,
|
||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import { parseKeyValueOutput } from "./runtime-parse.js";
|
import { parseKeyValueOutput } from "./runtime-parse.js";
|
||||||
@@ -190,12 +190,11 @@ export function buildLaunchAgentPlist({
|
|||||||
<key>WorkingDirectory</key>
|
<key>WorkingDirectory</key>
|
||||||
<string>${plistEscape(workingDirectory)}</string>`
|
<string>${plistEscape(workingDirectory)}</string>`
|
||||||
: "";
|
: "";
|
||||||
const commentXml =
|
const commentXml = comment?.trim()
|
||||||
comment && comment.trim()
|
? `
|
||||||
? `
|
|
||||||
<key>Comment</key>
|
<key>Comment</key>
|
||||||
<string>${plistEscape(comment.trim())}</string>`
|
<string>${plistEscape(comment.trim())}</string>`
|
||||||
: "";
|
: "";
|
||||||
const envXml = renderEnvDict(environment);
|
const envXml = renderEnvDict(environment);
|
||||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { promisify } from "node:util";
|
|||||||
|
|
||||||
import { colorize, isRich, theme } from "../terminal/theme.js";
|
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||||
import {
|
import {
|
||||||
LEGACY_GATEWAY_WINDOWS_TASK_NAMES,
|
|
||||||
formatGatewayServiceDescription,
|
formatGatewayServiceDescription,
|
||||||
|
LEGACY_GATEWAY_WINDOWS_TASK_NAMES,
|
||||||
resolveGatewayWindowsTaskName,
|
resolveGatewayWindowsTaskName,
|
||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import { parseKeyValueOutput } from "./runtime-parse.js";
|
import { parseKeyValueOutput } from "./runtime-parse.js";
|
||||||
|
|||||||
@@ -103,7 +103,10 @@ export function resolveGatewayService(): GatewayService {
|
|||||||
await uninstallSystemdService(args);
|
await uninstallSystemdService(args);
|
||||||
},
|
},
|
||||||
stop: async (args) => {
|
stop: async (args) => {
|
||||||
await stopSystemdService({ stdout: args.stdout, profile: args.profile });
|
await stopSystemdService({
|
||||||
|
stdout: args.stdout,
|
||||||
|
profile: args.profile,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
restart: async (args) => {
|
restart: async (args) => {
|
||||||
await restartSystemdService({
|
await restartSystemdService({
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { promisify } from "node:util";
|
|||||||
import { runCommandWithTimeout, runExec } from "../process/exec.js";
|
import { runCommandWithTimeout, runExec } from "../process/exec.js";
|
||||||
import { colorize, isRich, theme } from "../terminal/theme.js";
|
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||||
import {
|
import {
|
||||||
LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES,
|
|
||||||
formatGatewayServiceDescription,
|
formatGatewayServiceDescription,
|
||||||
|
LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES,
|
||||||
resolveGatewaySystemdServiceName,
|
resolveGatewaySystemdServiceName,
|
||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import { parseKeyValueOutput } from "./runtime-parse.js";
|
import { parseKeyValueOutput } from "./runtime-parse.js";
|
||||||
|
|||||||
Reference in New Issue
Block a user