Amp-Thread-ID: https://ampcode.com/threads/T-019bc4e0-fcb1-77be-b0b5-0d498f0c7197 Co-authored-by: Amp <amp@ampcode.com>
177 lines
5.7 KiB
TypeScript
177 lines
5.7 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
export type CoreChannelDeps = {
|
|
chunkMarkdownText: (text: string, limit: number) => string[];
|
|
formatAgentEnvelope: (params: {
|
|
channel: string;
|
|
from: string;
|
|
timestamp?: number;
|
|
body: string;
|
|
}) => string;
|
|
dispatchReplyWithBufferedBlockDispatcher: (params: {
|
|
ctx: unknown;
|
|
cfg: unknown;
|
|
dispatcherOptions: {
|
|
deliver: (payload: unknown) => Promise<void>;
|
|
onError?: (err: unknown, info: { kind: string }) => void;
|
|
};
|
|
}) => Promise<void>;
|
|
resolveAgentRoute: (params: {
|
|
cfg: unknown;
|
|
channel: string;
|
|
accountId: string;
|
|
peer: { kind: "dm" | "group" | "channel"; id: string };
|
|
}) => { sessionKey: string; accountId: string };
|
|
buildPairingReply: (params: { channel: string; idLine: string; code: string }) => string;
|
|
readChannelAllowFromStore: (channel: string) => Promise<string[]>;
|
|
upsertChannelPairingRequest: (params: {
|
|
channel: string;
|
|
id: string;
|
|
meta?: { name?: string };
|
|
pairingAdapter?: {
|
|
idLabel: string;
|
|
normalizeAllowEntry?: (entry: string) => string;
|
|
notifyApproval?: (params: { cfg: unknown; id: string; runtime?: unknown }) => Promise<void>;
|
|
};
|
|
}) => Promise<{ code: string; created: boolean }>;
|
|
fetchRemoteMedia: (params: { url: string }) => Promise<{ buffer: Buffer; contentType?: string }>;
|
|
saveMediaBuffer: (
|
|
buffer: Buffer,
|
|
contentType: string | undefined,
|
|
type: "inbound" | "outbound",
|
|
maxBytes: number,
|
|
) => Promise<{ path: string; contentType: string }>;
|
|
shouldLogVerbose: () => boolean;
|
|
};
|
|
|
|
let coreRootCache: string | null = null;
|
|
let coreDepsPromise: Promise<CoreChannelDeps> | null = null;
|
|
|
|
function findPackageRoot(startDir: string, name: string): string | null {
|
|
let dir = startDir;
|
|
for (;;) {
|
|
const pkgPath = path.join(dir, "package.json");
|
|
try {
|
|
if (fs.existsSync(pkgPath)) {
|
|
const raw = fs.readFileSync(pkgPath, "utf8");
|
|
const pkg = JSON.parse(raw) as { name?: string };
|
|
if (pkg.name === name) return dir;
|
|
}
|
|
} catch {
|
|
// ignore parse errors
|
|
}
|
|
const parent = path.dirname(dir);
|
|
if (parent === dir) return null;
|
|
dir = parent;
|
|
}
|
|
}
|
|
|
|
function resolveClawdbotRoot(): string {
|
|
if (coreRootCache) return coreRootCache;
|
|
const override = process.env.CLAWDBOT_ROOT?.trim();
|
|
if (override) {
|
|
coreRootCache = override;
|
|
return override;
|
|
}
|
|
|
|
const candidates = new Set<string>();
|
|
if (process.argv[1]) {
|
|
candidates.add(path.dirname(process.argv[1]));
|
|
}
|
|
candidates.add(process.cwd());
|
|
try {
|
|
const urlPath = fileURLToPath(import.meta.url);
|
|
candidates.add(path.dirname(urlPath));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
|
|
for (const start of candidates) {
|
|
const found = findPackageRoot(start, "clawdbot");
|
|
if (found) {
|
|
coreRootCache = found;
|
|
return found;
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
"Unable to resolve Clawdbot root. Set CLAWDBOT_ROOT to the package root.",
|
|
);
|
|
}
|
|
|
|
async function importCoreModule<T>(relativePath: string): Promise<T> {
|
|
const root = resolveClawdbotRoot();
|
|
const distPath = path.join(root, "dist", relativePath);
|
|
if (!fs.existsSync(distPath)) {
|
|
throw new Error(
|
|
`Missing core module at ${distPath}. Run \`pnpm build\` or install the official package.`,
|
|
);
|
|
}
|
|
return (await import(pathToFileURL(distPath).href)) as T;
|
|
}
|
|
|
|
export async function loadCoreChannelDeps(): Promise<CoreChannelDeps> {
|
|
if (coreDepsPromise) return coreDepsPromise;
|
|
|
|
coreDepsPromise = (async () => {
|
|
const [
|
|
chunk,
|
|
envelope,
|
|
dispatcher,
|
|
routing,
|
|
pairingMessages,
|
|
pairingStore,
|
|
mediaFetch,
|
|
mediaStore,
|
|
globals,
|
|
] = await Promise.all([
|
|
importCoreModule<{ chunkMarkdownText: CoreChannelDeps["chunkMarkdownText"] }>(
|
|
"auto-reply/chunk.js",
|
|
),
|
|
importCoreModule<{ formatAgentEnvelope: CoreChannelDeps["formatAgentEnvelope"] }>(
|
|
"auto-reply/envelope.js",
|
|
),
|
|
importCoreModule<{
|
|
dispatchReplyWithBufferedBlockDispatcher: CoreChannelDeps["dispatchReplyWithBufferedBlockDispatcher"];
|
|
}>("auto-reply/reply/provider-dispatcher.js"),
|
|
importCoreModule<{ resolveAgentRoute: CoreChannelDeps["resolveAgentRoute"] }>(
|
|
"routing/resolve-route.js",
|
|
),
|
|
importCoreModule<{ buildPairingReply: CoreChannelDeps["buildPairingReply"] }>(
|
|
"pairing/pairing-messages.js",
|
|
),
|
|
importCoreModule<{
|
|
readChannelAllowFromStore: CoreChannelDeps["readChannelAllowFromStore"];
|
|
upsertChannelPairingRequest: CoreChannelDeps["upsertChannelPairingRequest"];
|
|
}>("pairing/pairing-store.js"),
|
|
importCoreModule<{ fetchRemoteMedia: CoreChannelDeps["fetchRemoteMedia"] }>(
|
|
"media/fetch.js",
|
|
),
|
|
importCoreModule<{ saveMediaBuffer: CoreChannelDeps["saveMediaBuffer"] }>(
|
|
"media/store.js",
|
|
),
|
|
importCoreModule<{ shouldLogVerbose: CoreChannelDeps["shouldLogVerbose"] }>(
|
|
"globals.js",
|
|
),
|
|
]);
|
|
|
|
return {
|
|
chunkMarkdownText: chunk.chunkMarkdownText,
|
|
formatAgentEnvelope: envelope.formatAgentEnvelope,
|
|
dispatchReplyWithBufferedBlockDispatcher:
|
|
dispatcher.dispatchReplyWithBufferedBlockDispatcher,
|
|
resolveAgentRoute: routing.resolveAgentRoute,
|
|
buildPairingReply: pairingMessages.buildPairingReply,
|
|
readChannelAllowFromStore: pairingStore.readChannelAllowFromStore,
|
|
upsertChannelPairingRequest: pairingStore.upsertChannelPairingRequest,
|
|
fetchRemoteMedia: mediaFetch.fetchRemoteMedia,
|
|
saveMediaBuffer: mediaStore.saveMediaBuffer,
|
|
shouldLogVerbose: globals.shouldLogVerbose,
|
|
};
|
|
})();
|
|
|
|
return coreDepsPromise;
|
|
}
|