mirror of
https://github.com/hanzoai/bot.git
synced 2026-10-30 17:24:26 +00:00
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.
256 lines
11 KiB
TypeScript
256 lines
11 KiB
TypeScript
import type { PluginRuntime } from "@hanzo/bot/plugin-sdk/test-utils";
|
|
import { removeAckReactionAfterReply, shouldAckReaction } from "@hanzo/bot/plugin-sdk/test-utils";
|
|
import { vi } from "vitest";
|
|
|
|
type DeepPartial<T> = {
|
|
[K in keyof T]?: T[K] extends (...args: never[]) => unknown
|
|
? T[K]
|
|
: T[K] extends ReadonlyArray<unknown>
|
|
? T[K]
|
|
: T[K] extends object
|
|
? DeepPartial<T[K]>
|
|
: T[K];
|
|
};
|
|
|
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function mergeDeep<T>(base: T, overrides: DeepPartial<T>): T {
|
|
const result: Record<string, unknown> = { ...(base as Record<string, unknown>) };
|
|
for (const [key, overrideValue] of Object.entries(overrides as Record<string, unknown>)) {
|
|
if (overrideValue === undefined) {
|
|
continue;
|
|
}
|
|
const baseValue = result[key];
|
|
if (isObject(baseValue) && isObject(overrideValue)) {
|
|
result[key] = mergeDeep(baseValue, overrideValue);
|
|
continue;
|
|
}
|
|
result[key] = overrideValue;
|
|
}
|
|
return result as T;
|
|
}
|
|
|
|
export function createPluginRuntimeMock(overrides: DeepPartial<PluginRuntime> = {}): PluginRuntime {
|
|
const base: PluginRuntime = {
|
|
version: "1.0.0-test",
|
|
config: {
|
|
loadConfig: vi.fn(() => ({})) as unknown as PluginRuntime["config"]["loadConfig"],
|
|
writeConfigFile: vi.fn() as unknown as PluginRuntime["config"]["writeConfigFile"],
|
|
},
|
|
system: {
|
|
enqueueSystemEvent: vi.fn() as unknown as PluginRuntime["system"]["enqueueSystemEvent"],
|
|
requestHeartbeatNow: vi.fn() as unknown as PluginRuntime["system"]["requestHeartbeatNow"],
|
|
runCommandWithTimeout: vi.fn() as unknown as PluginRuntime["system"]["runCommandWithTimeout"],
|
|
formatNativeDependencyHint: vi.fn(
|
|
() => "",
|
|
) as unknown as PluginRuntime["system"]["formatNativeDependencyHint"],
|
|
},
|
|
media: {
|
|
loadWebMedia: vi.fn() as unknown as PluginRuntime["media"]["loadWebMedia"],
|
|
detectMime: vi.fn() as unknown as PluginRuntime["media"]["detectMime"],
|
|
mediaKindFromMime: vi.fn() as unknown as PluginRuntime["media"]["mediaKindFromMime"],
|
|
isVoiceCompatibleAudio:
|
|
vi.fn() as unknown as PluginRuntime["media"]["isVoiceCompatibleAudio"],
|
|
getImageMetadata: vi.fn() as unknown as PluginRuntime["media"]["getImageMetadata"],
|
|
resizeToJpeg: vi.fn() as unknown as PluginRuntime["media"]["resizeToJpeg"],
|
|
},
|
|
tts: {
|
|
textToSpeechTelephony: vi.fn() as unknown as PluginRuntime["tts"]["textToSpeechTelephony"],
|
|
},
|
|
stt: {
|
|
transcribeAudioFile: vi.fn() as unknown as PluginRuntime["stt"]["transcribeAudioFile"],
|
|
},
|
|
tools: {
|
|
createMemoryGetTool: vi.fn() as unknown as PluginRuntime["tools"]["createMemoryGetTool"],
|
|
createMemorySearchTool:
|
|
vi.fn() as unknown as PluginRuntime["tools"]["createMemorySearchTool"],
|
|
registerMemoryCli: vi.fn() as unknown as PluginRuntime["tools"]["registerMemoryCli"],
|
|
},
|
|
channel: {
|
|
text: {
|
|
chunkByNewline: vi.fn((text: string) => (text ? [text] : [])),
|
|
chunkMarkdownText: vi.fn((text: string) => [text]),
|
|
chunkMarkdownTextWithMode: vi.fn((text: string) => (text ? [text] : [])),
|
|
chunkText: vi.fn((text: string) => (text ? [text] : [])),
|
|
chunkTextWithMode: vi.fn((text: string) => (text ? [text] : [])),
|
|
resolveChunkMode: vi.fn(
|
|
() => "length",
|
|
) as unknown as PluginRuntime["channel"]["text"]["resolveChunkMode"],
|
|
resolveTextChunkLimit: vi.fn(() => 4000),
|
|
hasControlCommand: vi.fn(() => false),
|
|
resolveMarkdownTableMode: vi.fn(
|
|
() => "code",
|
|
) as unknown as PluginRuntime["channel"]["text"]["resolveMarkdownTableMode"],
|
|
convertMarkdownTables: vi.fn((text: string) => text),
|
|
},
|
|
reply: {
|
|
dispatchReplyWithBufferedBlockDispatcher: vi.fn(
|
|
async () => undefined,
|
|
) as unknown as PluginRuntime["channel"]["reply"]["dispatchReplyWithBufferedBlockDispatcher"],
|
|
createReplyDispatcherWithTyping:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["reply"]["createReplyDispatcherWithTyping"],
|
|
resolveEffectiveMessagesConfig:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["reply"]["resolveEffectiveMessagesConfig"],
|
|
resolveHumanDelayConfig:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["reply"]["resolveHumanDelayConfig"],
|
|
dispatchReplyFromConfig:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["reply"]["dispatchReplyFromConfig"],
|
|
withReplyDispatcher: vi.fn(async ({ dispatcher, run, onSettled }) => {
|
|
try {
|
|
return await run();
|
|
} finally {
|
|
dispatcher.markComplete();
|
|
try {
|
|
await dispatcher.waitForIdle();
|
|
} finally {
|
|
await onSettled?.();
|
|
}
|
|
}
|
|
}) as unknown as PluginRuntime["channel"]["reply"]["withReplyDispatcher"],
|
|
finalizeInboundContext: vi.fn(
|
|
(ctx: Record<string, unknown>) => ctx,
|
|
) as unknown as PluginRuntime["channel"]["reply"]["finalizeInboundContext"],
|
|
formatAgentEnvelope: vi.fn(
|
|
(opts: { body: string }) => opts.body,
|
|
) as unknown as PluginRuntime["channel"]["reply"]["formatAgentEnvelope"],
|
|
formatInboundEnvelope: vi.fn(
|
|
(opts: { body: string }) => opts.body,
|
|
) as unknown as PluginRuntime["channel"]["reply"]["formatInboundEnvelope"],
|
|
resolveEnvelopeFormatOptions: vi.fn(() => ({
|
|
template: "channel+name+time",
|
|
})) as unknown as PluginRuntime["channel"]["reply"]["resolveEnvelopeFormatOptions"],
|
|
},
|
|
routing: {
|
|
resolveAgentRoute: vi.fn(() => ({
|
|
agentId: "main",
|
|
accountId: "default",
|
|
sessionKey: "agent:main:test:dm:peer",
|
|
})) as unknown as PluginRuntime["channel"]["routing"]["resolveAgentRoute"],
|
|
},
|
|
pairing: {
|
|
buildPairingReply: vi.fn(
|
|
() => "Pairing code: TESTCODE",
|
|
) as unknown as PluginRuntime["channel"]["pairing"]["buildPairingReply"],
|
|
readAllowFromStore: vi
|
|
.fn()
|
|
.mockResolvedValue(
|
|
[],
|
|
) as unknown as PluginRuntime["channel"]["pairing"]["readAllowFromStore"],
|
|
upsertPairingRequest: vi.fn().mockResolvedValue({
|
|
code: "TESTCODE",
|
|
created: true,
|
|
}) as unknown as PluginRuntime["channel"]["pairing"]["upsertPairingRequest"],
|
|
},
|
|
media: {
|
|
fetchRemoteMedia:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["media"]["fetchRemoteMedia"],
|
|
saveMediaBuffer: vi.fn().mockResolvedValue({
|
|
path: "/tmp/test-media.jpg",
|
|
contentType: "image/jpeg",
|
|
}) as unknown as PluginRuntime["channel"]["media"]["saveMediaBuffer"],
|
|
},
|
|
session: {
|
|
resolveStorePath: vi.fn(
|
|
() => "/tmp/sessions.json",
|
|
) as unknown as PluginRuntime["channel"]["session"]["resolveStorePath"],
|
|
readSessionUpdatedAt: vi.fn(
|
|
() => undefined,
|
|
) as unknown as PluginRuntime["channel"]["session"]["readSessionUpdatedAt"],
|
|
recordSessionMetaFromInbound:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["session"]["recordSessionMetaFromInbound"],
|
|
recordInboundSession:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["session"]["recordInboundSession"],
|
|
updateLastRoute:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["session"]["updateLastRoute"],
|
|
},
|
|
mentions: {
|
|
buildMentionRegexes: vi.fn(() => [
|
|
/\bbert\b/i,
|
|
]) as unknown as PluginRuntime["channel"]["mentions"]["buildMentionRegexes"],
|
|
matchesMentionPatterns: vi.fn((text: string, regexes: RegExp[]) =>
|
|
regexes.some((regex) => regex.test(text)),
|
|
) as unknown as PluginRuntime["channel"]["mentions"]["matchesMentionPatterns"],
|
|
matchesMentionWithExplicit: vi.fn(
|
|
(params: { text: string; mentionRegexes: RegExp[]; explicitWasMentioned?: boolean }) =>
|
|
params.explicitWasMentioned === true
|
|
? true
|
|
: params.mentionRegexes.some((regex) => regex.test(params.text)),
|
|
) as unknown as PluginRuntime["channel"]["mentions"]["matchesMentionWithExplicit"],
|
|
},
|
|
reactions: {
|
|
shouldAckReaction,
|
|
removeAckReactionAfterReply,
|
|
},
|
|
groups: {
|
|
resolveGroupPolicy: vi.fn(
|
|
() => "open",
|
|
) as unknown as PluginRuntime["channel"]["groups"]["resolveGroupPolicy"],
|
|
resolveRequireMention: vi.fn(
|
|
() => false,
|
|
) as unknown as PluginRuntime["channel"]["groups"]["resolveRequireMention"],
|
|
},
|
|
debounce: {
|
|
createInboundDebouncer: vi.fn(
|
|
(params: { onFlush: (items: unknown[]) => Promise<void> }) => ({
|
|
enqueue: async (item: unknown) => {
|
|
await params.onFlush([item]);
|
|
},
|
|
flushKey: vi.fn(),
|
|
}),
|
|
) as unknown as PluginRuntime["channel"]["debounce"]["createInboundDebouncer"],
|
|
resolveInboundDebounceMs: vi.fn(
|
|
() => 0,
|
|
) as unknown as PluginRuntime["channel"]["debounce"]["resolveInboundDebounceMs"],
|
|
},
|
|
commands: {
|
|
resolveCommandAuthorizedFromAuthorizers: vi.fn(
|
|
() => false,
|
|
) as unknown as PluginRuntime["channel"]["commands"]["resolveCommandAuthorizedFromAuthorizers"],
|
|
isControlCommandMessage:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["commands"]["isControlCommandMessage"],
|
|
shouldComputeCommandAuthorized:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["commands"]["shouldComputeCommandAuthorized"],
|
|
shouldHandleTextCommands:
|
|
vi.fn() as unknown as PluginRuntime["channel"]["commands"]["shouldHandleTextCommands"],
|
|
},
|
|
discord: {} as PluginRuntime["channel"]["discord"],
|
|
activity: {} as PluginRuntime["channel"]["activity"],
|
|
line: {} as PluginRuntime["channel"]["line"],
|
|
slack: {} as PluginRuntime["channel"]["slack"],
|
|
telegram: {} as PluginRuntime["channel"]["telegram"],
|
|
signal: {} as PluginRuntime["channel"]["signal"],
|
|
imessage: {} as PluginRuntime["channel"]["imessage"],
|
|
whatsapp: {} as PluginRuntime["channel"]["whatsapp"],
|
|
},
|
|
events: {
|
|
onAgentEvent: vi.fn(() => () => {}) as unknown as PluginRuntime["events"]["onAgentEvent"],
|
|
onSessionTranscriptUpdate: vi.fn(
|
|
() => () => {},
|
|
) as unknown as PluginRuntime["events"]["onSessionTranscriptUpdate"],
|
|
},
|
|
logging: {
|
|
shouldLogVerbose: vi.fn(() => false),
|
|
getChildLogger: vi.fn(() => ({
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
debug: vi.fn(),
|
|
})),
|
|
},
|
|
state: {
|
|
resolveStateDir: vi.fn(() => "/tmp/bot"),
|
|
},
|
|
subagent: {
|
|
run: vi.fn(),
|
|
waitForRun: vi.fn(),
|
|
getSessionMessages: vi.fn(),
|
|
getSession: vi.fn(),
|
|
deleteSession: vi.fn(),
|
|
},
|
|
};
|
|
|
|
return mergeDeep(base, overrides);
|
|
}
|