feat: migrate zalouser plugin to sdk
# Conflicts: # CHANGELOG.md
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -15,6 +15,19 @@ Docs: https://docs.clawd.bot
|
||||
|
||||
## 2026.1.18-2
|
||||
|
||||
## 2026.1.17-6
|
||||
|
||||
### Changes
|
||||
- Plugins: add exclusive plugin slots with a dedicated memory slot selector.
|
||||
- Memory: ship core memory tools + CLI as the bundled `memory-core` plugin.
|
||||
- Docs: document plugin slots and memory plugin behavior.
|
||||
- Plugins: add the bundled BlueBubbles channel plugin (disabled by default).
|
||||
- Plugins: migrate bundled messaging extensions to the plugin SDK; resolve plugin-sdk imports in loader.
|
||||
- Plugins: migrate the Zalo plugin to the shared plugin SDK runtime.
|
||||
- Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime.
|
||||
|
||||
## 2026.1.17-5
|
||||
|
||||
### Changes
|
||||
- Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback.
|
||||
- Memory: add SQLite embedding cache to speed up reindexing and frequent updates.
|
||||
|
||||
@@ -2,12 +2,14 @@ import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
||||
|
||||
import { zalouserPlugin } from "./src/channel.js";
|
||||
import { ZalouserToolSchema, executeZalouserTool } from "./src/tool.js";
|
||||
import { setZalouserRuntime } from "./src/runtime.js";
|
||||
|
||||
const plugin = {
|
||||
id: "zalouser",
|
||||
name: "Zalo Personal",
|
||||
description: "Zalo personal account messaging via zca-cli",
|
||||
register(api: ClawdbotPluginApi) {
|
||||
setZalouserRuntime(api.runtime);
|
||||
// Register channel plugin (for onboarding & gateway)
|
||||
api.registerChannel(zalouserPlugin);
|
||||
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import { runZca, parseJsonOutput } from "./zca.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
type CoreConfig,
|
||||
type ResolvedZalouserAccount,
|
||||
type ZalouserAccountConfig,
|
||||
type ZalouserConfig,
|
||||
} from "./types.js";
|
||||
import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
||||
|
||||
function listConfiguredAccountIds(cfg: CoreConfig): string[] {
|
||||
import { runZca, parseJsonOutput } from "./zca.js";
|
||||
import type { ResolvedZalouserAccount, ZalouserAccountConfig, ZalouserConfig } from "./types.js";
|
||||
|
||||
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const accounts = (cfg.channels?.zalouser as ZalouserConfig | undefined)?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return [];
|
||||
return Object.keys(accounts).filter(Boolean);
|
||||
}
|
||||
|
||||
export function listZalouserAccountIds(cfg: CoreConfig): string[] {
|
||||
export function listZalouserAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const ids = listConfiguredAccountIds(cfg);
|
||||
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
||||
return ids.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
export function resolveDefaultZalouserAccountId(cfg: CoreConfig): string {
|
||||
export function resolveDefaultZalouserAccountId(cfg: ClawdbotConfig): string {
|
||||
const zalouserConfig = cfg.channels?.zalouser as ZalouserConfig | undefined;
|
||||
if (zalouserConfig?.defaultAccount?.trim()) return zalouserConfig.defaultAccount.trim();
|
||||
const ids = listZalouserAccountIds(cfg);
|
||||
@@ -27,14 +24,8 @@ export function resolveDefaultZalouserAccountId(cfg: CoreConfig): string {
|
||||
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
export function normalizeAccountId(accountId?: string | null): string {
|
||||
const trimmed = accountId?.trim();
|
||||
if (!trimmed) return DEFAULT_ACCOUNT_ID;
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
|
||||
function resolveAccountConfig(
|
||||
cfg: CoreConfig,
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): ZalouserAccountConfig | undefined {
|
||||
const accounts = (cfg.channels?.zalouser as ZalouserConfig | undefined)?.accounts;
|
||||
@@ -42,7 +33,10 @@ function resolveAccountConfig(
|
||||
return accounts[accountId] as ZalouserAccountConfig | undefined;
|
||||
}
|
||||
|
||||
function mergeZalouserAccountConfig(cfg: CoreConfig, accountId: string): ZalouserAccountConfig {
|
||||
function mergeZalouserAccountConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): ZalouserAccountConfig {
|
||||
const raw = (cfg.channels?.zalouser ?? {}) as ZalouserConfig;
|
||||
const { accounts: _ignored, defaultAccount: _ignored2, ...base } = raw;
|
||||
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
||||
@@ -62,7 +56,7 @@ export async function checkZcaAuthenticated(profile: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
export async function resolveZalouserAccount(params: {
|
||||
cfg: CoreConfig;
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | null;
|
||||
}): Promise<ResolvedZalouserAccount> {
|
||||
const accountId = normalizeAccountId(params.accountId);
|
||||
@@ -84,7 +78,7 @@ export async function resolveZalouserAccount(params: {
|
||||
}
|
||||
|
||||
export function resolveZalouserAccountSync(params: {
|
||||
cfg: CoreConfig;
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | null;
|
||||
}): ResolvedZalouserAccount {
|
||||
const accountId = normalizeAccountId(params.accountId);
|
||||
@@ -104,7 +98,9 @@ export function resolveZalouserAccountSync(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export async function listEnabledZalouserAccounts(cfg: CoreConfig): Promise<ResolvedZalouserAccount[]> {
|
||||
export async function listEnabledZalouserAccounts(
|
||||
cfg: ClawdbotConfig,
|
||||
): Promise<ResolvedZalouserAccount[]> {
|
||||
const ids = listZalouserAccountIds(cfg);
|
||||
const accounts = await Promise.all(
|
||||
ids.map((accountId) => resolveZalouserAccount({ cfg, accountId }))
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import type { ChannelAccountSnapshot, ChannelDirectoryEntry, ChannelPlugin } from "clawdbot/plugin-sdk";
|
||||
|
||||
import { formatPairingApproveHint } from "clawdbot/plugin-sdk";
|
||||
import type {
|
||||
ChannelAccountSnapshot,
|
||||
ChannelDirectoryEntry,
|
||||
ChannelPlugin,
|
||||
ClawdbotConfig,
|
||||
} from "clawdbot/plugin-sdk";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
deleteAccountFromConfigSection,
|
||||
formatPairingApproveHint,
|
||||
normalizeAccountId,
|
||||
setAccountEnabledInConfigSection,
|
||||
} from "clawdbot/plugin-sdk";
|
||||
import {
|
||||
listZalouserAccountIds,
|
||||
resolveDefaultZalouserAccountId,
|
||||
@@ -12,14 +22,7 @@ import {
|
||||
import { zalouserOnboardingAdapter } from "./onboarding.js";
|
||||
import { sendMessageZalouser } from "./send.js";
|
||||
import { checkZcaInstalled, parseJsonOutput, runZca, runZcaInteractive } from "./zca.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
type CoreConfig,
|
||||
type ZalouserConfig,
|
||||
type ZcaFriend,
|
||||
type ZcaGroup,
|
||||
type ZcaUserInfo,
|
||||
} from "./types.js";
|
||||
import type { ZcaFriend, ZcaGroup, ZcaUserInfo } from "./types.js";
|
||||
|
||||
const meta = {
|
||||
id: "zalouser",
|
||||
@@ -34,7 +37,7 @@ const meta = {
|
||||
};
|
||||
|
||||
function resolveZalouserQrProfile(accountId?: string | null): string {
|
||||
const normalized = String(accountId ?? "").trim();
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
if (!normalized || normalized === DEFAULT_ACCOUNT_ID) {
|
||||
return process.env.ZCA_PROFILE?.trim() || "default";
|
||||
}
|
||||
@@ -69,65 +72,6 @@ function mapGroup(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function deleteAccountFromConfigSection(params: {
|
||||
cfg: CoreConfig;
|
||||
accountId: string;
|
||||
}): CoreConfig {
|
||||
const { cfg, accountId } = params;
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
const { zalouser: _removed, ...restChannels } = cfg.channels ?? {};
|
||||
return { ...cfg, channels: restChannels };
|
||||
}
|
||||
const accounts = { ...(cfg.channels?.zalouser?.accounts ?? {}) };
|
||||
delete accounts[accountId];
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
zalouser: {
|
||||
...cfg.channels?.zalouser,
|
||||
accounts,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function setAccountEnabledInConfigSection(params: {
|
||||
cfg: CoreConfig;
|
||||
accountId: string;
|
||||
enabled: boolean;
|
||||
}): CoreConfig {
|
||||
const { cfg, accountId, enabled } = params;
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
zalouser: {
|
||||
...cfg.channels?.zalouser,
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
zalouser: {
|
||||
...cfg.channels?.zalouser,
|
||||
accounts: {
|
||||
...(cfg.channels?.zalouser?.accounts ?? {}),
|
||||
[accountId]: {
|
||||
...(cfg.channels?.zalouser?.accounts?.[accountId] ?? {}),
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
id: "zalouser",
|
||||
meta,
|
||||
@@ -143,20 +87,24 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
},
|
||||
reload: { configPrefixes: ["channels.zalouser"] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => listZalouserAccountIds(cfg as CoreConfig),
|
||||
listAccountIds: (cfg) => listZalouserAccountIds(cfg as ClawdbotConfig),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as CoreConfig),
|
||||
resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as ClawdbotConfig),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
cfg: cfg as CoreConfig,
|
||||
cfg: cfg as ClawdbotConfig,
|
||||
sectionKey: "zalouser",
|
||||
accountId,
|
||||
enabled,
|
||||
allowTopLevel: true,
|
||||
}),
|
||||
deleteAccount: ({ cfg, accountId }) =>
|
||||
deleteAccountFromConfigSection({
|
||||
cfg: cfg as CoreConfig,
|
||||
cfg: cfg as ClawdbotConfig,
|
||||
sectionKey: "zalouser",
|
||||
accountId,
|
||||
clearBaseFields: ["profile", "name", "dmPolicy", "allowFrom", "groupPolicy", "groups", "messagePrefix"],
|
||||
}),
|
||||
isConfigured: async (account) => {
|
||||
// Check if zca auth status is OK for this profile
|
||||
@@ -173,7 +121,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
configured: undefined,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
(resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? []).map(
|
||||
(resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId }).config.allowFrom ?? []).map(
|
||||
(entry) => String(entry),
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
@@ -187,7 +135,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(
|
||||
(cfg as CoreConfig).channels?.zalouser?.accounts?.[resolvedAccountId],
|
||||
(cfg as ClawdbotConfig).channels?.zalouser?.accounts?.[resolvedAccountId],
|
||||
);
|
||||
const basePath = useAccountPath
|
||||
? `channels.zalouser.accounts.${resolvedAccountId}.`
|
||||
@@ -227,7 +175,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
self: async ({ cfg, accountId, runtime }) => {
|
||||
const ok = await checkZcaInstalled();
|
||||
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
||||
const result = await runZca(["me", "info", "-j"], { profile: account.profile, timeout: 10000 });
|
||||
if (!result.ok) {
|
||||
runtime.error(result.stderr || "Failed to fetch profile");
|
||||
@@ -245,7 +193,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
listPeers: async ({ cfg, accountId, query, limit }) => {
|
||||
const ok = await checkZcaInstalled();
|
||||
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
||||
const args = query?.trim()
|
||||
? ["friend", "find", query.trim()]
|
||||
: ["friend", "list", "-j"];
|
||||
@@ -269,7 +217,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
listGroups: async ({ cfg, accountId, query, limit }) => {
|
||||
const ok = await checkZcaInstalled();
|
||||
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
||||
const result = await runZca(["group", "list", "-j"], { profile: account.profile, timeout: 15000 });
|
||||
if (!result.ok) {
|
||||
throw new Error(result.stderr || "Failed to list groups");
|
||||
@@ -293,7 +241,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
listGroupMembers: async ({ cfg, accountId, groupId, limit }) => {
|
||||
const ok = await checkZcaInstalled();
|
||||
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
||||
const result = await runZca(["group", "members", groupId, "-j"], {
|
||||
profile: account.profile,
|
||||
timeout: 20000,
|
||||
@@ -335,7 +283,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
}
|
||||
try {
|
||||
const account = resolveZalouserAccountSync({
|
||||
cfg: cfg as CoreConfig,
|
||||
cfg: cfg as ClawdbotConfig,
|
||||
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
||||
});
|
||||
const args =
|
||||
@@ -391,7 +339,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
idLabel: "zalouserUserId",
|
||||
normalizeAllowEntry: (entry) => entry.replace(/^(zalouser|zlu):/i, ""),
|
||||
notifyApproval: async ({ cfg, id }) => {
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig });
|
||||
const authenticated = await checkZcaAuthenticated(account.profile);
|
||||
if (!authenticated) throw new Error("Zalouser not authenticated");
|
||||
await sendMessageZalouser(id, "Your pairing request has been approved.", {
|
||||
@@ -402,7 +350,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
auth: {
|
||||
login: async ({ cfg, accountId, runtime }) => {
|
||||
const account = resolveZalouserAccountSync({
|
||||
cfg: cfg as CoreConfig,
|
||||
cfg: cfg as ClawdbotConfig,
|
||||
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
||||
});
|
||||
const ok = await checkZcaInstalled();
|
||||
@@ -445,7 +393,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
},
|
||||
textChunkLimit: 2000,
|
||||
sendText: async ({ to, text, accountId, cfg }) => {
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
||||
const result = await sendMessageZalouser(to, text, { profile: account.profile });
|
||||
return {
|
||||
channel: "zalouser",
|
||||
@@ -455,7 +403,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
};
|
||||
},
|
||||
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
||||
const result = await sendMessageZalouser(to, text, {
|
||||
profile: account.profile,
|
||||
mediaUrl,
|
||||
@@ -534,7 +482,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
const { monitorZalouserProvider } = await import("./monitor.js");
|
||||
return monitorZalouserProvider({
|
||||
account,
|
||||
config: ctx.cfg as CoreConfig,
|
||||
config: ctx.cfg as ClawdbotConfig,
|
||||
runtime: ctx.runtime,
|
||||
abortSignal: ctx.abortSignal,
|
||||
statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch }),
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
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 };
|
||||
}) => 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;
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
|
||||
import type { RuntimeEnv } from "clawdbot/plugin-sdk";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "clawdbot/plugin-sdk";
|
||||
import {
|
||||
finalizeInboundContext,
|
||||
formatAgentEnvelope,
|
||||
isControlCommandMessage,
|
||||
mergeAllowlist,
|
||||
recordSessionMetaFromInbound,
|
||||
@@ -11,20 +12,19 @@ import {
|
||||
shouldComputeCommandAuthorized,
|
||||
summarizeMapping,
|
||||
} from "clawdbot/plugin-sdk";
|
||||
import { loadCoreChannelDeps, type CoreChannelDeps } from "./core-bridge.js";
|
||||
import { sendMessageZalouser } from "./send.js";
|
||||
import type {
|
||||
CoreConfig,
|
||||
ResolvedZalouserAccount,
|
||||
ZcaFriend,
|
||||
ZcaGroup,
|
||||
ZcaMessage,
|
||||
} from "./types.js";
|
||||
import { getZalouserRuntime } from "./runtime.js";
|
||||
import { parseJsonOutput, runZca, runZcaStreaming } from "./zca.js";
|
||||
|
||||
export type ZalouserMonitorOptions = {
|
||||
account: ResolvedZalouserAccount;
|
||||
config: CoreConfig;
|
||||
config: ClawdbotConfig;
|
||||
runtime: RuntimeEnv;
|
||||
abortSignal: AbortSignal;
|
||||
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
|
||||
@@ -55,8 +55,10 @@ function buildNameIndex<T>(
|
||||
return index;
|
||||
}
|
||||
|
||||
function logVerbose(deps: CoreChannelDeps, runtime: RuntimeEnv, message: string): void {
|
||||
if (deps.shouldLogVerbose()) {
|
||||
type ZalouserCoreRuntime = ReturnType<typeof getZalouserRuntime>;
|
||||
|
||||
function logVerbose(core: ZalouserCoreRuntime, runtime: RuntimeEnv, message: string): void {
|
||||
if (core.logging.shouldLogVerbose()) {
|
||||
runtime.log(`[zalouser] ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -157,8 +159,8 @@ function startZcaListener(
|
||||
async function processMessage(
|
||||
message: ZcaMessage,
|
||||
account: ResolvedZalouserAccount,
|
||||
config: CoreConfig,
|
||||
deps: CoreChannelDeps,
|
||||
config: ClawdbotConfig,
|
||||
core: ZalouserCoreRuntime,
|
||||
runtime: RuntimeEnv,
|
||||
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
|
||||
): Promise<void> {
|
||||
@@ -176,13 +178,13 @@ async function processMessage(
|
||||
const groups = account.config.groups ?? {};
|
||||
if (isGroup) {
|
||||
if (groupPolicy === "disabled") {
|
||||
logVerbose(deps, runtime, `zalouser: drop group ${chatId} (groupPolicy=disabled)`);
|
||||
logVerbose(core, runtime, `zalouser: drop group ${chatId} (groupPolicy=disabled)`);
|
||||
return;
|
||||
}
|
||||
if (groupPolicy === "allowlist") {
|
||||
const allowed = isGroupAllowed({ groupId: chatId, groupName, groups });
|
||||
if (!allowed) {
|
||||
logVerbose(deps, runtime, `zalouser: drop group ${chatId} (not allowlisted)`);
|
||||
logVerbose(core, runtime, `zalouser: drop group ${chatId} (not allowlisted)`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -194,7 +196,7 @@ async function processMessage(
|
||||
const shouldComputeAuth = shouldComputeCommandAuthorized(rawBody, config);
|
||||
const storeAllowFrom =
|
||||
!isGroup && (dmPolicy !== "open" || shouldComputeAuth)
|
||||
? await deps.readChannelAllowFromStore("zalouser").catch(() => [])
|
||||
? await core.channel.pairing.readAllowFromStore("zalouser").catch(() => [])
|
||||
: [];
|
||||
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
||||
const useAccessGroups = config.commands?.useAccessGroups !== false;
|
||||
@@ -208,7 +210,7 @@ async function processMessage(
|
||||
|
||||
if (!isGroup) {
|
||||
if (dmPolicy === "disabled") {
|
||||
logVerbose(deps, runtime, `Blocked zalouser DM from ${senderId} (dmPolicy=disabled)`);
|
||||
logVerbose(core, runtime, `Blocked zalouser DM from ${senderId} (dmPolicy=disabled)`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,18 +219,18 @@ async function processMessage(
|
||||
|
||||
if (!allowed) {
|
||||
if (dmPolicy === "pairing") {
|
||||
const { code, created } = await deps.upsertChannelPairingRequest({
|
||||
const { code, created } = await core.channel.pairing.upsertPairingRequest({
|
||||
channel: "zalouser",
|
||||
id: senderId,
|
||||
meta: { name: senderName || undefined },
|
||||
});
|
||||
|
||||
if (created) {
|
||||
logVerbose(deps, runtime, `zalouser pairing request sender=${senderId}`);
|
||||
logVerbose(core, runtime, `zalouser pairing request sender=${senderId}`);
|
||||
try {
|
||||
await sendMessageZalouser(
|
||||
chatId,
|
||||
deps.buildPairingReply({
|
||||
core.channel.pairing.buildPairingReply({
|
||||
channel: "zalouser",
|
||||
idLine: `Your Zalo user id: ${senderId}`,
|
||||
code,
|
||||
@@ -238,7 +240,7 @@ async function processMessage(
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
} catch (err) {
|
||||
logVerbose(
|
||||
deps,
|
||||
core,
|
||||
runtime,
|
||||
`zalouser pairing reply failed for ${senderId}: ${String(err)}`,
|
||||
);
|
||||
@@ -246,7 +248,7 @@ async function processMessage(
|
||||
}
|
||||
} else {
|
||||
logVerbose(
|
||||
deps,
|
||||
core,
|
||||
runtime,
|
||||
`Blocked unauthorized zalouser sender ${senderId} (dmPolicy=${dmPolicy})`,
|
||||
);
|
||||
@@ -257,13 +259,13 @@ async function processMessage(
|
||||
}
|
||||
|
||||
if (isGroup && isControlCommandMessage(rawBody, config) && commandAuthorized !== true) {
|
||||
logVerbose(deps, runtime, `zalouser: drop control command from unauthorized sender ${senderId}`);
|
||||
logVerbose(core, runtime, `zalouser: drop control command from unauthorized sender ${senderId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const peer = isGroup ? { kind: "group" as const, id: chatId } : { kind: "group" as const, id: senderId };
|
||||
|
||||
const route = deps.resolveAgentRoute({
|
||||
const route = core.channel.routing.resolveAgentRoute({
|
||||
cfg: config,
|
||||
channel: "zalouser",
|
||||
accountId: account.accountId,
|
||||
@@ -275,7 +277,7 @@ async function processMessage(
|
||||
});
|
||||
|
||||
const fromLabel = isGroup ? `group:${chatId}` : senderName || `user:${senderId}`;
|
||||
const body = deps.formatAgentEnvelope({
|
||||
const body = formatAgentEnvelope({
|
||||
channel: "Zalo Personal",
|
||||
from: fromLabel,
|
||||
timestamp: timestamp ? timestamp * 1000 : undefined,
|
||||
@@ -313,7 +315,7 @@ async function processMessage(
|
||||
runtime.error?.(`zalouser: failed updating session meta: ${String(err)}`);
|
||||
});
|
||||
|
||||
await deps.dispatchReplyWithBufferedBlockDispatcher({
|
||||
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: config,
|
||||
dispatcherOptions: {
|
||||
@@ -324,7 +326,7 @@ async function processMessage(
|
||||
chatId,
|
||||
isGroup,
|
||||
runtime,
|
||||
deps,
|
||||
core,
|
||||
statusSink,
|
||||
});
|
||||
},
|
||||
@@ -343,10 +345,10 @@ async function deliverZalouserReply(params: {
|
||||
chatId: string;
|
||||
isGroup: boolean;
|
||||
runtime: RuntimeEnv;
|
||||
deps: CoreChannelDeps;
|
||||
core: ZalouserCoreRuntime;
|
||||
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
|
||||
}): Promise<void> {
|
||||
const { payload, profile, chatId, isGroup, runtime, deps, statusSink } = params;
|
||||
const { payload, profile, chatId, isGroup, runtime, core, statusSink } = params;
|
||||
|
||||
const mediaList = payload.mediaUrls?.length
|
||||
? payload.mediaUrls
|
||||
@@ -360,7 +362,7 @@ async function deliverZalouserReply(params: {
|
||||
const caption = first ? payload.text : undefined;
|
||||
first = false;
|
||||
try {
|
||||
logVerbose(deps, runtime, `Sending media to ${chatId}`);
|
||||
logVerbose(core, runtime, `Sending media to ${chatId}`);
|
||||
await sendMessageZalouser(chatId, caption ?? "", {
|
||||
profile,
|
||||
mediaUrl,
|
||||
@@ -375,8 +377,8 @@ async function deliverZalouserReply(params: {
|
||||
}
|
||||
|
||||
if (payload.text) {
|
||||
const chunks = deps.chunkMarkdownText(payload.text, ZALOUSER_TEXT_LIMIT);
|
||||
logVerbose(deps, runtime, `Sending ${chunks.length} text chunk(s) to ${chatId}`);
|
||||
const chunks = core.channel.text.chunkMarkdownText(payload.text, ZALOUSER_TEXT_LIMIT);
|
||||
logVerbose(core, runtime, `Sending ${chunks.length} text chunk(s) to ${chatId}`);
|
||||
for (const chunk of chunks) {
|
||||
try {
|
||||
await sendMessageZalouser(chatId, chunk, { profile, isGroup });
|
||||
@@ -394,7 +396,7 @@ export async function monitorZalouserProvider(
|
||||
let { account, config } = options;
|
||||
const { abortSignal, statusSink, runtime } = options;
|
||||
|
||||
const deps = await loadCoreChannelDeps();
|
||||
const core = getZalouserRuntime();
|
||||
let stopped = false;
|
||||
let proc: ChildProcess | null = null;
|
||||
let restartTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -506,7 +508,7 @@ export async function monitorZalouserProvider(
|
||||
}
|
||||
|
||||
logVerbose(
|
||||
deps,
|
||||
core,
|
||||
runtime,
|
||||
`[${account.accountId}] starting zca listener (profile=${account.profile})`,
|
||||
);
|
||||
@@ -515,16 +517,16 @@ export async function monitorZalouserProvider(
|
||||
runtime,
|
||||
account.profile,
|
||||
(msg) => {
|
||||
logVerbose(deps, runtime, `[${account.accountId}] inbound message`);
|
||||
logVerbose(core, runtime, `[${account.accountId}] inbound message`);
|
||||
statusSink?.({ lastInboundAt: Date.now() });
|
||||
processMessage(msg, account, config, deps, runtime, statusSink).catch((err) => {
|
||||
processMessage(msg, account, config, core, runtime, statusSink).catch((err) => {
|
||||
runtime.error(`[${account.accountId}] Failed to process message: ${String(err)}`);
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
runtime.error(`[${account.accountId}] zca listener error: ${String(err)}`);
|
||||
if (!stopped && !abortSignal.aborted) {
|
||||
logVerbose(deps, runtime, `[${account.accountId}] restarting listener in 5s...`);
|
||||
logVerbose(core, runtime, `[${account.accountId}] restarting listener in 5s...`);
|
||||
restartTimer = setTimeout(startListener, 5000);
|
||||
} else {
|
||||
resolveRunning?.();
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingDmPolicy,
|
||||
ClawdbotConfig,
|
||||
WizardPrompter,
|
||||
} from "clawdbot/plugin-sdk";
|
||||
import { promptChannelAccessConfig } from "clawdbot/plugin-sdk";
|
||||
import {
|
||||
addWildcardAllowFrom,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
promptAccountId,
|
||||
promptChannelAccessConfig,
|
||||
} from "clawdbot/plugin-sdk";
|
||||
|
||||
import {
|
||||
listZalouserAccountIds,
|
||||
resolveDefaultZalouserAccountId,
|
||||
resolveZalouserAccountSync,
|
||||
normalizeAccountId,
|
||||
checkZcaAuthenticated,
|
||||
} from "./accounts.js";
|
||||
import { runZca, runZcaInteractive, checkZcaInstalled, parseJsonOutput } from "./zca.js";
|
||||
import { DEFAULT_ACCOUNT_ID, type CoreConfig, type ZcaGroup } from "./types.js";
|
||||
import type { ZcaGroup } from "./types.js";
|
||||
|
||||
const channel = "zalouser" as const;
|
||||
|
||||
function setZalouserDmPolicy(
|
||||
cfg: CoreConfig,
|
||||
cfg: ClawdbotConfig,
|
||||
dmPolicy: "pairing" | "allowlist" | "open" | "disabled",
|
||||
): CoreConfig {
|
||||
): ClawdbotConfig {
|
||||
const allowFrom =
|
||||
dmPolicy === "open"
|
||||
? [...(cfg.channels?.zalouser?.allowFrom ?? []), "*"].filter(
|
||||
(v, i, a) => a.indexOf(v) === i,
|
||||
)
|
||||
? addWildcardAllowFrom(cfg.channels?.zalouser?.allowFrom)
|
||||
: undefined;
|
||||
return {
|
||||
...cfg,
|
||||
@@ -37,7 +41,7 @@ function setZalouserDmPolicy(
|
||||
...(allowFrom ? { allowFrom } : {}),
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
|
||||
async function noteZalouserHelp(prompter: WizardPrompter): Promise<void> {
|
||||
@@ -56,10 +60,10 @@ async function noteZalouserHelp(prompter: WizardPrompter): Promise<void> {
|
||||
}
|
||||
|
||||
async function promptZalouserAllowFrom(params: {
|
||||
cfg: CoreConfig;
|
||||
cfg: ClawdbotConfig;
|
||||
prompter: WizardPrompter;
|
||||
accountId: string;
|
||||
}): Promise<CoreConfig> {
|
||||
}): Promise<ClawdbotConfig> {
|
||||
const { cfg, prompter, accountId } = params;
|
||||
const resolved = resolveZalouserAccountSync({ cfg, accountId });
|
||||
const existingAllowFrom = resolved.config.allowFrom ?? [];
|
||||
@@ -93,7 +97,7 @@ async function promptZalouserAllowFrom(params: {
|
||||
allowFrom: unique,
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -114,14 +118,14 @@ async function promptZalouserAllowFrom(params: {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
|
||||
function setZalouserGroupPolicy(
|
||||
cfg: CoreConfig,
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
groupPolicy: "open" | "allowlist" | "disabled",
|
||||
): CoreConfig {
|
||||
): ClawdbotConfig {
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return {
|
||||
...cfg,
|
||||
@@ -133,7 +137,7 @@ function setZalouserGroupPolicy(
|
||||
groupPolicy,
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
return {
|
||||
...cfg,
|
||||
@@ -152,14 +156,14 @@ function setZalouserGroupPolicy(
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
|
||||
function setZalouserGroupAllowlist(
|
||||
cfg: CoreConfig,
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
groupKeys: string[],
|
||||
): CoreConfig {
|
||||
): ClawdbotConfig {
|
||||
const groups = Object.fromEntries(groupKeys.map((key) => [key, { allow: true }]));
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return {
|
||||
@@ -172,7 +176,7 @@ function setZalouserGroupAllowlist(
|
||||
groups,
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
return {
|
||||
...cfg,
|
||||
@@ -191,11 +195,11 @@ function setZalouserGroupAllowlist(
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
|
||||
async function resolveZalouserGroups(params: {
|
||||
cfg: CoreConfig;
|
||||
cfg: ClawdbotConfig;
|
||||
accountId: string;
|
||||
entries: string[];
|
||||
}): Promise<Array<{ input: string; resolved: boolean; id?: string }>> {
|
||||
@@ -226,65 +230,23 @@ async function resolveZalouserGroups(params: {
|
||||
});
|
||||
}
|
||||
|
||||
async function promptAccountId(params: {
|
||||
cfg: CoreConfig;
|
||||
prompter: WizardPrompter;
|
||||
label: string;
|
||||
currentId: string;
|
||||
listAccountIds: (cfg: CoreConfig) => string[];
|
||||
defaultAccountId: string;
|
||||
}): Promise<string> {
|
||||
const { cfg, prompter, label, currentId, listAccountIds, defaultAccountId } = params;
|
||||
const existingIds = listAccountIds(cfg);
|
||||
const options = [
|
||||
...existingIds.map((id) => ({
|
||||
value: id,
|
||||
label: id === defaultAccountId ? `${id} (default)` : id,
|
||||
})),
|
||||
{ value: "__new__", label: "Create new account" },
|
||||
];
|
||||
|
||||
const selected = await prompter.select({
|
||||
message: `${label} account`,
|
||||
options,
|
||||
initialValue: currentId,
|
||||
});
|
||||
|
||||
if (selected === "__new__") {
|
||||
const newId = await prompter.text({
|
||||
message: "New account ID",
|
||||
placeholder: "work",
|
||||
validate: (value) => {
|
||||
const raw = String(value ?? "").trim().toLowerCase();
|
||||
if (!raw) return "Required";
|
||||
if (!/^[a-z0-9_-]+$/.test(raw)) return "Use lowercase alphanumeric, dash, or underscore";
|
||||
if (existingIds.includes(raw)) return "Account already exists";
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
return String(newId).trim().toLowerCase();
|
||||
}
|
||||
|
||||
return selected as string;
|
||||
}
|
||||
|
||||
const dmPolicy: ChannelOnboardingDmPolicy = {
|
||||
label: "Zalo Personal",
|
||||
channel,
|
||||
policyKey: "channels.zalouser.dmPolicy",
|
||||
allowFromKey: "channels.zalouser.allowFrom",
|
||||
getCurrent: (cfg) => ((cfg as CoreConfig).channels?.zalouser?.dmPolicy ?? "pairing") as "pairing",
|
||||
setPolicy: (cfg, policy) => setZalouserDmPolicy(cfg as CoreConfig, policy),
|
||||
getCurrent: (cfg) => ((cfg as ClawdbotConfig).channels?.zalouser?.dmPolicy ?? "pairing") as "pairing",
|
||||
setPolicy: (cfg, policy) => setZalouserDmPolicy(cfg as ClawdbotConfig, policy),
|
||||
};
|
||||
|
||||
export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
channel,
|
||||
dmPolicy,
|
||||
getStatus: async ({ cfg }) => {
|
||||
const ids = listZalouserAccountIds(cfg as CoreConfig);
|
||||
const ids = listZalouserAccountIds(cfg as ClawdbotConfig);
|
||||
let configured = false;
|
||||
for (const accountId of ids) {
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
|
||||
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
|
||||
const isAuth = await checkZcaAuthenticated(account.profile);
|
||||
if (isAuth) {
|
||||
configured = true;
|
||||
@@ -316,14 +278,14 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
}
|
||||
|
||||
const zalouserOverride = accountOverrides.zalouser?.trim();
|
||||
const defaultAccountId = resolveDefaultZalouserAccountId(cfg as CoreConfig);
|
||||
const defaultAccountId = resolveDefaultZalouserAccountId(cfg as ClawdbotConfig);
|
||||
let accountId = zalouserOverride
|
||||
? normalizeAccountId(zalouserOverride)
|
||||
: defaultAccountId;
|
||||
|
||||
if (shouldPromptAccountIds && !zalouserOverride) {
|
||||
accountId = await promptAccountId({
|
||||
cfg: cfg as CoreConfig,
|
||||
cfg: cfg as ClawdbotConfig,
|
||||
prompter,
|
||||
label: "Zalo Personal",
|
||||
currentId: accountId,
|
||||
@@ -332,7 +294,7 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
});
|
||||
}
|
||||
|
||||
let next = cfg as CoreConfig;
|
||||
let next = cfg as ClawdbotConfig;
|
||||
const account = resolveZalouserAccountSync({ cfg: next, accountId });
|
||||
const alreadyAuthenticated = await checkZcaAuthenticated(account.profile);
|
||||
|
||||
@@ -390,7 +352,7 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
profile: account.profile !== "default" ? account.profile : undefined,
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
} else {
|
||||
next = {
|
||||
...next,
|
||||
@@ -409,7 +371,7 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
|
||||
if (forceAllowFrom) {
|
||||
|
||||
14
extensions/zalouser/src/runtime.ts
Normal file
14
extensions/zalouser/src/runtime.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { PluginRuntime } from "clawdbot/plugin-sdk";
|
||||
|
||||
let runtime: PluginRuntime | null = null;
|
||||
|
||||
export function setZalouserRuntime(next: PluginRuntime): void {
|
||||
runtime = next;
|
||||
}
|
||||
|
||||
export function getZalouserRuntime(): PluginRuntime {
|
||||
if (!runtime) {
|
||||
throw new Error("Zalouser runtime not initialized");
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
@@ -68,9 +68,6 @@ export type ListenOptions = CommonOptions & {
|
||||
prefix?: string;
|
||||
};
|
||||
|
||||
// Channel plugin config types
|
||||
export const DEFAULT_ACCOUNT_ID = "default";
|
||||
|
||||
export type ZalouserAccountConfig = {
|
||||
enabled?: boolean;
|
||||
name?: string;
|
||||
@@ -95,14 +92,6 @@ export type ZalouserConfig = {
|
||||
accounts?: Record<string, ZalouserAccountConfig>;
|
||||
};
|
||||
|
||||
export type CoreConfig = {
|
||||
channels?: {
|
||||
zalouser?: ZalouserConfig;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ResolvedZalouserAccount = {
|
||||
accountId: string;
|
||||
name?: string;
|
||||
|
||||
Reference in New Issue
Block a user