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

179 lines
7.5 KiB
TypeScript

/**
* Hanzo Brain — Memory
*
* Pluggable memory layer. SQLite by default — single file at
* `~/.hanzo/brain/brain.db` (shared with hanzo-mcp, hanzo-dev, and any
* other Hanzo SDK on the same machine), zero infra, ships in the same
* static binary as the rest of the bot.
*
* Canonical scale-out backends (register via @hanzo/bot-memory-* sibling
* extensions, not in core):
* • qdrant — vector ANN at scale (`~/work/hanzo/vector` fork)
* • meilisearch — fast keyword FTS at scale (`~/work/hanzo/search` fork)
* • zapdb — canonical Hanzo store, ZAP-native, multi-language
* (`zap-proto/db`; in-flight migration from
* `luxfi/zapdb` Go + `~/work/luxcpp/zapdb` C++)
* • replicate — SQLite WAL → S3 backup (`~/work/hanzo/replicate`)
* • vfs — S3-backed streaming block FS (`~/work/hanzo/vfs`)
*
* `luxfi/database` is the Lux-flavored extension of zapdb (adds chain
* heads / archival / validator sets) — NOT a generic brain backend.
* Brain users pick `zapdb` directly.
*
* Third parties can plug in their own backend (Postgres, LanceDB, D1,
* libSQL, etc.) by calling `registerBackend("name", factory)` from
* another extension. The default selector picks SQLite unless config
* explicitly names another backend.
*
* Schema (pages, edges, facts, FTS5) is documented in `sqlite.ts`.
* Anyone implementing `BrainStore` against another store should mirror
* that shape so recipes and `@hanzo/bot-graph-links` Just Work.
*/
import type { Edge } from "@hanzo/bot-graph-links";
// ── Pluggable store contract ────────────────────────────────────────
export interface BrainStore {
init(): Promise<void>;
upsertPage(slug: string, content: string, frontmatter?: Record<string, unknown>): Promise<void>;
getPage(slug: string): Promise<{ slug: string; content: string; updated_at: string } | null>;
upsertEdges(source: string, edges: Edge[]): Promise<void>;
edgesFor(slug: string, dir?: "in" | "out" | "both"): Promise<Edge[]>;
upsertFact(fact: Fact): Promise<void>;
recall(entity: string, opts?: { limit?: number; since?: string }): Promise<Fact[]>;
hybridSearch(query: string, topK?: number): Promise<SearchHit[]>;
close(): Promise<void>;
}
export interface Fact {
id?: string;
subject: string; // entity slug (e.g. "people/alice")
predicate: string; // e.g. "preference", "status", "decision"
object: string; // value
source?: string; // page slug or session id
ts?: string; // ISO; defaults to now
confidence?: number; // 0..1
}
export interface SearchHit {
slug: string;
excerpt: string;
score: number;
source: "vector" | "keyword" | "fused";
}
export interface MemoryConfig {
backend?: string; // registered backend name. default: "sqlite"
dataDir?: string; // default ~/.hanzo/brain
dbPath?: string; // explicit file path; overrides dataDir
embeddingModel?: string;
embeddingApiKey?: string;
}
export type BackendFactory = (cfg: MemoryConfig) => Promise<BrainStore> | BrainStore;
// ── Backend registry ────────────────────────────────────────────────
const BACKENDS = new Map<string, BackendFactory>();
export function registerBackend(name: string, factory: BackendFactory): void {
BACKENDS.set(name, factory);
}
export function listBackends(): string[] {
return Array.from(BACKENDS.keys());
}
// Register the canonical SQLite backend at module load.
registerBackend("sqlite", async (cfg) => {
const { SqliteStore } = await import("./sqlite.js");
return new SqliteStore(cfg);
});
// Advertise canonical scale-out backends. Each is shipped as its own
// sibling extension (out-of-tree to keep the bot binary small); calling
// `open({ backend: "<name>" })` without the sibling installed throws a
// helpful install message rather than failing at lookup time. This is
// the "all interfaces supported" contract — listBackends() shows them
// all, you only install what you need.
const ADVERTISED: Record<string, string> = {
// Native Hanzo stack — ZAP transport + hanzo-consensus + zapdb storage.
// No libSQL / Turso — we ship our own. Canonical zapdb home is
// `zap-proto/db` (in-flight migration from luxfi/zapdb + luxcpp/zapdb).
qdrant: "@hanzo/bot-memory-qdrant", // vector ANN at scale, fork at ~/work/hanzo/vector
meilisearch: "@hanzo/bot-memory-meilisearch", // keyword FTS at scale, fork at ~/work/hanzo/search
postgres: "@hanzo/bot-memory-postgres", // multi-tenant team brain w/ pgvector
lancedb: "@hanzo/bot-memory-lancedb", // already shipped — embedded vector DB
zapdb: "@hanzo/bot-memory-zapdb", // canonical Hanzo store — zap-proto/db (Go + C++ ports), ZAP-native
replicate: "@hanzo/bot-memory-replicate", // SQLite WAL → S3 backup, ~/work/hanzo/replicate
vfs: "@hanzo/bot-memory-vfs", // S3 streaming block FS, ~/work/hanzo/vfs
};
for (const [name, pkg] of Object.entries(ADVERTISED)) {
if (!BACKENDS.has(name)) {
registerBackend(name, () => {
throw new Error(
`memory: backend "${name}" not installed. Run \`pnpm add ${pkg}\` ` +
`(or any registerBackend("${name}", factory) caller) to enable.`,
);
});
}
}
// ── Open ────────────────────────────────────────────────────────────
export async function open(cfg: MemoryConfig = {}): Promise<BrainStore> {
const name = cfg.backend ?? process.env.HANZO_BRAIN_BACKEND ?? "sqlite";
const factory = BACKENDS.get(name);
if (!factory) {
const available = Array.from(BACKENDS.keys()).join(", ");
throw new Error(
`memory: unknown backend "${name}". Registered backends: ${available}. ` +
`Call registerBackend("${name}", factory) from your extension to plug one in.`,
);
}
const store = await factory(cfg);
await store.init();
return store;
}
// ── HanzoBot plugin contract ────────────────────────────────────────
export interface MemoryApi {
store: BrainStore;
registerBackend: typeof registerBackend;
listBackends: typeof listBackends;
}
export default async function register(api: any, cfg: MemoryConfig = {}): Promise<MemoryApi> {
const store = await open(cfg);
// Wire bot lifecycle hooks. Each is a soft contract — if the bot core
// doesn't expose the hook, this plugin is inert for that surface.
if (typeof api?.memory?.registerBackend === "function") {
api.memory.registerBackend("hanzo-brain", store);
}
if (typeof api?.memory?.onUpsertEdges === "function") {
api.memory.onUpsertEdges((src: string, edges: Edge[]) => store.upsertEdges(src, edges));
}
if (typeof api?.tools?.register === "function") {
api.tools.register({
name: "brain.recall",
description: "Recall facts about an entity from the Hanzo Brain.",
input: { entity: "string", limit: "number?" },
handler: ({ entity, limit }: { entity: string; limit?: number }) =>
store.recall(entity, { limit }),
});
api.tools.register({
name: "brain.search",
description: "Hybrid search across all brain pages.",
input: { query: "string", topK: "number?" },
handler: ({ query, topK }: { query: string; topK?: number }) =>
store.hybridSearch(query, topK),
});
}
return { store, registerBackend, listBackends };
}