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