Files
zeekay 3de1e92a15 Bump to 2026.5.12; finish HanzoBot* → Bot* identifier sweep so build passes
The big rebrand left a residue of HanzoBot* identifiers inside camelCase
compounds (resolveHanzoBotAgentDir, HanzoBotPackageRoot, hanzoBotRoot,
HanzoBotTmpDir, …) that the canonical bot-root.ts / tmp-bot-dir.ts had
always exported as Bot* / bot*. The earlier \b-anchored sweep missed
them because the H is mid-identifier with no word boundary. Replace
HanzoBot(?=[A-Z][a-z]) → Bot (and hanzoBot equivalent) everywhere, plus
HanzoBotKit → BotKit (the on-disk module is apps/shared/BotKit).

Also one stray pi-tools.ts type narrowing (senderId is string|null but
the target wants string|undefined) — coerce with ?? undefined.

pnpm build → ✔ Build complete in 7s.
2026-05-12 20:41:07 -07:00

111 lines
3.1 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type {
BotPluginApi,
BotPluginCommandDefinition,
PluginCommandContext,
} from "@hanzo/bot/plugin-sdk/phone-control";
import { describe, expect, it, vi } from "vitest";
import registerPhoneControl from "./index.js";
function createApi(params: {
stateDir: string;
getConfig: () => Record<string, unknown>;
writeConfig: (next: Record<string, unknown>) => Promise<void>;
registerCommand: (command: BotPluginCommandDefinition) => void;
}): BotPluginApi {
return {
id: "phone-control",
name: "phone-control",
source: "test",
config: {},
pluginConfig: {},
runtime: {
state: {
resolveStateDir: () => params.stateDir,
},
config: {
loadConfig: () => params.getConfig(),
writeConfigFile: (next: Record<string, unknown>) => params.writeConfig(next),
},
} as BotPluginApi["runtime"],
logger: { info() {}, warn() {}, error() {} },
registerTool() {},
registerHook() {},
registerHttpRoute() {},
registerChannel() {},
registerGatewayMethod() {},
registerCli() {},
registerService() {},
registerProvider() {},
registerContextEngine() {},
registerCommand: params.registerCommand,
resolvePath(input: string) {
return input;
},
on() {},
};
}
function createCommandContext(args: string): PluginCommandContext {
return {
channel: "test",
isAuthorizedSender: true,
commandBody: `/phone ${args}`,
args,
config: {},
};
}
describe("phone-control plugin", () => {
it("arms sms.send as part of the writes group", async () => {
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "bot-phone-control-test-"));
try {
let config: Record<string, unknown> = {
gateway: {
nodes: {
allowCommands: [],
denyCommands: ["calendar.add", "contacts.add", "reminders.add", "sms.send"],
},
},
};
const writeConfigFile = vi.fn(async (next: Record<string, unknown>) => {
config = next;
});
let command: BotPluginCommandDefinition | undefined;
registerPhoneControl(
createApi({
stateDir,
getConfig: () => config,
writeConfig: writeConfigFile,
registerCommand: (nextCommand) => {
command = nextCommand;
},
}),
);
expect(command?.name).toBe("phone");
const res = await command?.handler(createCommandContext("arm writes 30s"));
const text = String(res?.text ?? "");
const nodes = (
config.gateway as { nodes?: { allowCommands?: string[]; denyCommands?: string[] } }
).nodes;
expect(writeConfigFile).toHaveBeenCalledTimes(1);
expect(nodes?.allowCommands).toEqual([
"calendar.add",
"contacts.add",
"reminders.add",
"sms.send",
]);
expect(nodes?.denyCommands).toEqual([]);
expect(text).toContain("sms.send");
} finally {
await fs.rm(stateDir, { recursive: true, force: true });
}
});
});