Files
zeekay acaeda400a Eradicate OpenClaw — resolve merge conflicts and finish the rebrand
Three coordinated things:

1. Resolve the committed upstream/main merge conflicts. 94 files were
   sitting with raw <<<<<<< HEAD ... >>>>>>> upstream/main blocks. Every
   conflict had HanzoBot/Bot on HEAD and OpenClaw on upstream. Resolved
   by keeping HEAD everywhere — 540 conflict blocks.

2. Rewrite remaining OpenClaw text refs.
     OPENCLAW   → HANZO_BOT
     OpenClaw   → HanzoBot          (PascalCase type identifiers)
     openclaw   → bot               (codebase convention — botToken,
                                    botUsername etc.; avoids invalid
                                    hyphenated JS identifiers)
     openclaw[_]/openclaw[A-Z] → bot[_]/bot[A-Z]   (compound)
     ai.openclaw.x → ai.hanzo.bot.x  (JVM package path)

3. Delete or rename openclaw-named file paths (116 of them). Dead
   duplicates with a Bot-named canonical from the partial migration:
   deleted. Otherwise renamed.
     ai/openclaw/**           → deleted (ai/hanzo/bot/** canonical)
     Sources/OpenClaw*/       → deleted (Sources/Bot* canonical)
     Tests/OpenClawIPCTests/  → deleted (Tests/BotIPCTests canonical)
     OpenClawKit/             → deleted (BotKit canonical)
     openclaw-tools.*.ts      → deleted (bot-tools.*.ts canonical)
     openclaw-root.ts etc.    → deleted (bot-root.ts canonical)
     types.openclaw.ts        → deleted (types.bot.ts canonical)
     extensions/*/openclaw.plugin.json → renamed hanzo-bot.plugin.json
     docs/start/openclaw.md   → renamed hanzo-bot.md (+ zh-CN)
     docs/assets/openclaw-*.png, whatsapp-openclaw*.jpg → deleted
     scripts/.../openclaw-*   → deleted (bot/hanzo-bot parallels)
     openclaw.mjs             → deleted (hanzo-bot.mjs is package.bin)

Then patched 65 broken JS/TS import strings where hanzo-bot had ended
up inside an import path ('./types.hanzo-bot.js' → './types.bot.js'
etc., since the on-disk filename uses the brand-neutral bot- prefix).

Pre-existing lint debt cleaned up to get oxlint --type-aware to 0/0:
removed the dead i18n test that referenced a path no longer existing;
defined the missing DIDConfig and WalletConfig types in types.base.ts
(they were imported but never declared); dropped unused imports;
collapsed redundant type assertion and a tautological meta.bot lookup
that the openclaw→bot rename made redundant.

The a2ui.bundle.js generated artifact is kept at its pre-rebrand
contents — it gets regenerated by 'pnpm canvas:a2ui:bundle' from
sources I cannot rebuild in this commit; the source side is clean.

Verified: 0 occurrences of openclaw (case-insensitive) in tree source
content, 0 file or directory paths with openclaw in the name; oxlint
--type-aware src test reports 0/0.
2026-05-11 17:56:29 -07:00

195 lines
6.2 KiB
TypeScript

import { afterAll, afterEach, beforeAll, vi } from "vitest";
// Ensure Vitest environment is properly set
process.env.VITEST = "true";
// Config validation walks plugin manifests; keep an aggressive cache in tests to avoid
// repeated filesystem discovery across suites/workers.
process.env.BOT_PLUGIN_MANIFEST_CACHE_MS ??= "60000";
// Vitest vm forks can load transitive lockfile helpers many times per worker.
// Raise listener budget to avoid noisy MaxListeners warnings and warning-stack overhead.
const TEST_PROCESS_MAX_LISTENERS = 128;
if (process.getMaxListeners() > 0 && process.getMaxListeners() < TEST_PROCESS_MAX_LISTENERS) {
process.setMaxListeners(TEST_PROCESS_MAX_LISTENERS);
}
import type {
ChannelId,
ChannelOutboundAdapter,
ChannelPlugin,
} from "../src/channels/plugins/types.js";
import type { BotConfig } from "../src/config/config.js";
import type { OutboundSendDeps } from "../src/infra/outbound/deliver.js";
import { withIsolatedTestHome } from "./test-env.js";
// Set HOME/state isolation before importing any runtime HanzoBot modules.
const testEnv = withIsolatedTestHome();
afterAll(() => testEnv.cleanup());
const [
{ installProcessWarningFilter },
{ getActivePluginRegistry, setActivePluginRegistry },
{ createTestRegistry },
] = await Promise.all([
import("../src/infra/warning-filter.js"),
import("../src/plugins/runtime.js"),
import("../src/test-utils/channel-plugins.js"),
]);
installProcessWarningFilter();
const pickSendFn = (id: ChannelId, deps?: OutboundSendDeps) => {
switch (id) {
case "discord":
return deps?.sendDiscord;
case "slack":
return deps?.sendSlack;
case "telegram":
return deps?.sendTelegram;
case "whatsapp":
return deps?.sendWhatsApp;
case "signal":
return deps?.sendSignal;
case "imessage":
return deps?.sendIMessage;
default:
return undefined;
}
};
const createStubOutbound = (
id: ChannelId,
deliveryMode: ChannelOutboundAdapter["deliveryMode"] = "direct",
): ChannelOutboundAdapter => ({
deliveryMode,
sendText: async ({ deps, to, text }) => {
const send = pickSendFn(id, deps);
if (send) {
// oxlint-disable-next-line typescript/no-explicit-any
const result = await send(to, text, { verbose: false } as any);
return { channel: id, ...result };
}
return { channel: id, messageId: "test" };
},
sendMedia: async ({ deps, to, text, mediaUrl }) => {
const send = pickSendFn(id, deps);
if (send) {
// oxlint-disable-next-line typescript/no-explicit-any
const result = await send(to, text, { verbose: false, mediaUrl } as any);
return { channel: id, ...result };
}
return { channel: id, messageId: "test" };
},
});
const createStubPlugin = (params: {
id: ChannelId;
label?: string;
aliases?: string[];
deliveryMode?: ChannelOutboundAdapter["deliveryMode"];
preferSessionLookupForAnnounceTarget?: boolean;
}): ChannelPlugin => ({
id: params.id,
meta: {
id: params.id,
label: params.label ?? String(params.id),
selectionLabel: params.label ?? String(params.id),
docsPath: `/channels/${params.id}`,
blurb: "test stub.",
aliases: params.aliases,
preferSessionLookupForAnnounceTarget: params.preferSessionLookupForAnnounceTarget,
},
capabilities: { chatTypes: ["direct", "group"] },
config: {
listAccountIds: (cfg: BotConfig) => {
const channels = cfg.channels as Record<string, unknown> | undefined;
const entry = channels?.[params.id];
if (!entry || typeof entry !== "object") {
return [];
}
const accounts = (entry as { accounts?: Record<string, unknown> }).accounts;
const ids = accounts ? Object.keys(accounts).filter(Boolean) : [];
return ids.length > 0 ? ids : ["default"];
},
resolveAccount: (cfg: BotConfig, accountId?: string | null) => {
const channels = cfg.channels as Record<string, unknown> | undefined;
const entry = channels?.[params.id];
if (!entry || typeof entry !== "object") {
return {};
}
const accounts = (entry as { accounts?: Record<string, unknown> }).accounts;
const match = accountId ? accounts?.[accountId] : undefined;
return (match && typeof match === "object") || typeof match === "string" ? match : entry;
},
isConfigured: async (_account, cfg: BotConfig) => {
const channels = cfg.channels as Record<string, unknown> | undefined;
return Boolean(channels?.[params.id]);
},
},
outbound: createStubOutbound(params.id, params.deliveryMode),
});
const createDefaultRegistry = () =>
createTestRegistry([
{
pluginId: "discord",
plugin: createStubPlugin({ id: "discord", label: "Discord" }),
source: "test",
},
{
pluginId: "slack",
plugin: createStubPlugin({ id: "slack", label: "Slack" }),
source: "test",
},
{
pluginId: "telegram",
plugin: {
...createStubPlugin({ id: "telegram", label: "Telegram" }),
status: {
buildChannelSummary: async () => ({
configured: false,
tokenSource: process.env.TELEGRAM_BOT_TOKEN ? "env" : "none",
}),
},
},
source: "test",
},
{
pluginId: "whatsapp",
plugin: createStubPlugin({
id: "whatsapp",
label: "WhatsApp",
deliveryMode: "gateway",
preferSessionLookupForAnnounceTarget: true,
}),
source: "test",
},
{
pluginId: "signal",
plugin: createStubPlugin({ id: "signal", label: "Signal" }),
source: "test",
},
{
pluginId: "imessage",
plugin: createStubPlugin({ id: "imessage", label: "iMessage", aliases: ["imsg"] }),
source: "test",
},
]);
// Creating a fresh registry before every test is measurable overhead.
// The registry is immutable by default; tests that override it are restored in afterEach.
const DEFAULT_PLUGIN_REGISTRY = createDefaultRegistry();
beforeAll(() => {
setActivePluginRegistry(DEFAULT_PLUGIN_REGISTRY);
});
afterEach(() => {
if (getActivePluginRegistry() !== DEFAULT_PLUGIN_REGISTRY) {
setActivePluginRegistry(DEFAULT_PLUGIN_REGISTRY);
}
// Guard against leaked fake timers across test files/workers.
if (vi.isFakeTimers()) {
vi.useRealTimers();
}
});