chore: Lint extensions folder.

This commit is contained in:
cpojer
2026-01-31 22:13:48 +09:00
parent 4f2166c503
commit 230ca789e2
221 changed files with 4006 additions and 1583 deletions

View File

@@ -6,21 +6,29 @@ import type { ResolvedZalouserAccount, ZalouserAccountConfig, ZalouserConfig } f
function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
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);
}
export function listZalouserAccountIds(cfg: OpenClawConfig): string[] {
const ids = listConfiguredAccountIds(cfg);
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
return ids.sort((a, b) => a.localeCompare(b));
if (ids.length === 0) {
return [DEFAULT_ACCOUNT_ID];
}
return ids.toSorted((a, b) => a.localeCompare(b));
}
export function resolveDefaultZalouserAccountId(cfg: OpenClawConfig): string {
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);
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
return DEFAULT_ACCOUNT_ID;
}
return ids[0] ?? DEFAULT_ACCOUNT_ID;
}
@@ -29,7 +37,9 @@ function resolveAccountConfig(
accountId: string,
): ZalouserAccountConfig | undefined {
const accounts = (cfg.channels?.zalouser as ZalouserConfig | undefined)?.accounts;
if (!accounts || typeof accounts !== "object") return undefined;
if (!accounts || typeof accounts !== "object") {
return undefined;
}
return accounts[accountId] as ZalouserAccountConfig | undefined;
}
@@ -41,9 +51,15 @@ function mergeZalouserAccountConfig(cfg: OpenClawConfig, accountId: string): Zal
}
function resolveZcaProfile(config: ZalouserAccountConfig, accountId: string): string {
if (config.profile?.trim()) return config.profile.trim();
if (process.env.ZCA_PROFILE?.trim()) return process.env.ZCA_PROFILE.trim();
if (accountId !== DEFAULT_ACCOUNT_ID) return accountId;
if (config.profile?.trim()) {
return config.profile.trim();
}
if (process.env.ZCA_PROFILE?.trim()) {
return process.env.ZCA_PROFILE.trim();
}
if (accountId !== DEFAULT_ACCOUNT_ID) {
return accountId;
}
return "default";
}
@@ -111,7 +127,9 @@ export async function getZcaUserInfo(
profile: string,
): Promise<{ userId?: string; displayName?: string } | null> {
const result = await runZca(["me", "info", "-j"], { profile, timeout: 10000 });
if (!result.ok) return null;
if (!result.ok) {
return null;
}
return parseJsonOutput<{ userId?: string; displayName?: string }>(result.stdout);
}

View File

@@ -6,7 +6,9 @@ describe("zalouser outbound chunker", () => {
it("chunks without empty strings and respects limit", () => {
const chunker = zalouserPlugin.outbound?.chunker;
expect(chunker).toBeTypeOf("function");
if (!chunker) return;
if (!chunker) {
return;
}
const limit = 10;
const chunks = chunker("hello world\nthis is a test", limit);

View File

@@ -85,7 +85,7 @@ function resolveZalouserGroupToolPolicy(
params: ChannelGroupContext,
): GroupToolPolicyConfig | undefined {
const account = resolveZalouserAccountSync({
cfg: params.cfg as OpenClawConfig,
cfg: params.cfg,
accountId: params.accountId ?? undefined,
});
const groups = account.config.groups ?? {};
@@ -96,7 +96,9 @@ function resolveZalouserGroupToolPolicy(
);
for (const key of candidates) {
const entry = groups[key];
if (entry?.tools) return entry.tools;
if (entry?.tools) {
return entry.tools;
}
}
return undefined;
}
@@ -111,9 +113,9 @@ export const zalouserDock: ChannelDock = {
outbound: { textChunkLimit: 2000 },
config: {
resolveAllowFrom: ({ cfg, accountId }) =>
(
resolveZalouserAccountSync({ cfg: cfg as OpenClawConfig, accountId }).config.allowFrom ?? []
).map((entry) => String(entry)),
(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) =>
String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
@@ -146,13 +148,12 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
reload: { configPrefixes: ["channels.zalouser"] },
configSchema: buildChannelConfigSchema(ZalouserConfigSchema),
config: {
listAccountIds: (cfg) => listZalouserAccountIds(cfg as OpenClawConfig),
resolveAccount: (cfg, accountId) =>
resolveZalouserAccountSync({ cfg: cfg as OpenClawConfig, accountId }),
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as OpenClawConfig),
listAccountIds: (cfg) => listZalouserAccountIds(cfg),
resolveAccount: (cfg, accountId) => resolveZalouserAccountSync({ cfg: cfg, accountId }),
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg),
setAccountEnabled: ({ cfg, accountId, enabled }) =>
setAccountEnabledInConfigSection({
cfg: cfg as OpenClawConfig,
cfg: cfg,
sectionKey: "zalouser",
accountId,
enabled,
@@ -160,7 +161,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
}),
deleteAccount: ({ cfg, accountId }) =>
deleteAccountFromConfigSection({
cfg: cfg as OpenClawConfig,
cfg: cfg,
sectionKey: "zalouser",
accountId,
clearBaseFields: [
@@ -188,9 +189,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
configured: undefined,
}),
resolveAllowFrom: ({ cfg, accountId }) =>
(
resolveZalouserAccountSync({ cfg: cfg as OpenClawConfig, accountId }).config.allowFrom ?? []
).map((entry) => String(entry)),
(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom ?? []).map((entry) =>
String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry).trim())
@@ -201,9 +202,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
security: {
resolveDmPolicy: ({ cfg, accountId, account }) => {
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
const useAccountPath = Boolean(
(cfg as OpenClawConfig).channels?.zalouser?.accounts?.[resolvedAccountId],
);
const useAccountPath = Boolean(cfg.channels?.zalouser?.accounts?.[resolvedAccountId]);
const basePath = useAccountPath
? `channels.zalouser.accounts.${resolvedAccountId}.`
: "channels.zalouser.";
@@ -228,7 +227,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
applyAccountName: ({ cfg, accountId, name }) =>
applyAccountNameToChannelSection({
cfg: cfg as OpenClawConfig,
cfg: cfg,
channelKey: "zalouser",
accountId,
name,
@@ -236,7 +235,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
validateInput: () => null,
applyAccountConfig: ({ cfg, accountId, input }) => {
const namedConfig = applyAccountNameToChannelSection({
cfg: cfg as OpenClawConfig,
cfg: cfg,
channelKey: "zalouser",
accountId,
name: input.name,
@@ -268,9 +267,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
...next.channels?.zalouser,
enabled: true,
accounts: {
...(next.channels?.zalouser?.accounts ?? {}),
...next.channels?.zalouser?.accounts,
[accountId]: {
...(next.channels?.zalouser?.accounts?.[accountId] ?? {}),
...next.channels?.zalouser?.accounts?.[accountId],
enabled: true,
},
},
@@ -282,13 +281,17 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
messaging: {
normalizeTarget: (raw) => {
const trimmed = raw?.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
return trimmed.replace(/^(zalouser|zlu):/i, "");
},
targetResolver: {
looksLikeId: (raw) => {
const trimmed = raw.trim();
if (!trimmed) return false;
if (!trimmed) {
return false;
}
return /^\d{3,}$/.test(trimmed);
},
hint: "<threadId>",
@@ -297,8 +300,10 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
directory: {
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 OpenClawConfig, accountId });
if (!ok) {
throw new Error("Missing dependency: `zca` not found in PATH");
}
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const result = await runZca(["me", "info", "-j"], {
profile: account.profile,
timeout: 10000,
@@ -308,7 +313,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
return null;
}
const parsed = parseJsonOutput<ZcaUserInfo>(result.stdout);
if (!parsed?.userId) return null;
if (!parsed?.userId) {
return null;
}
return mapUser({
id: String(parsed.userId),
name: parsed.displayName ?? null,
@@ -318,8 +325,10 @@ 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 OpenClawConfig, accountId });
if (!ok) {
throw new Error("Missing dependency: `zca` not found in PATH");
}
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const args = query?.trim() ? ["friend", "find", query.trim()] : ["friend", "list", "-j"];
const result = await runZca(args, { profile: account.profile, timeout: 15000 });
if (!result.ok) {
@@ -340,8 +349,10 @@ 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 OpenClawConfig, accountId });
if (!ok) {
throw new Error("Missing dependency: `zca` not found in PATH");
}
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const result = await runZca(["group", "list", "-j"], {
profile: account.profile,
timeout: 15000,
@@ -367,8 +378,10 @@ 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 OpenClawConfig, accountId });
if (!ok) {
throw new Error("Missing dependency: `zca` not found in PATH");
}
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const result = await runZca(["group", "members", groupId, "-j"], {
profile: account.profile,
timeout: 20000,
@@ -383,7 +396,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
? parsed
.map((m) => {
const id = m.userId ?? (m as { id?: string | number }).id;
if (!id) return null;
if (!id) {
return null;
}
return mapUser({
id: String(id),
name: (m as { displayName?: string }).displayName ?? null,
@@ -412,7 +427,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
}
try {
const account = resolveZalouserAccountSync({
cfg: cfg as OpenClawConfig,
cfg: cfg,
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
});
const args =
@@ -422,7 +437,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
: ["friend", "list", "-j"]
: ["group", "list", "-j"];
const result = await runZca(args, { profile: account.profile, timeout: 15000 });
if (!result.ok) throw new Error(result.stderr || "zca lookup failed");
if (!result.ok) {
throw new Error(result.stderr || "zca lookup failed");
}
if (kind === "user") {
const parsed = parseJsonOutput<ZcaFriend[]>(result.stdout) ?? [];
const matches = Array.isArray(parsed)
@@ -469,9 +486,11 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
idLabel: "zalouserUserId",
normalizeAllowEntry: (entry) => entry.replace(/^(zalouser|zlu):/i, ""),
notifyApproval: async ({ cfg, id }) => {
const account = resolveZalouserAccountSync({ cfg: cfg as OpenClawConfig });
const account = resolveZalouserAccountSync({ cfg: cfg });
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.", {
profile: account.profile,
});
@@ -480,7 +499,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
auth: {
login: async ({ cfg, accountId, runtime }) => {
const account = resolveZalouserAccountSync({
cfg: cfg as OpenClawConfig,
cfg: cfg,
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
});
const ok = await checkZcaInstalled();
@@ -501,8 +520,12 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
outbound: {
deliveryMode: "direct",
chunker: (text, limit) => {
if (!text) return [];
if (limit <= 0 || text.length <= limit) return [text];
if (!text) {
return [];
}
if (limit <= 0 || text.length <= limit) {
return [text];
}
const chunks: string[] = [];
let remaining = text;
while (remaining.length > limit) {
@@ -510,21 +533,27 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
const lastNewline = window.lastIndexOf("\n");
const lastSpace = window.lastIndexOf(" ");
let breakIdx = lastNewline > 0 ? lastNewline : lastSpace;
if (breakIdx <= 0) breakIdx = limit;
if (breakIdx <= 0) {
breakIdx = limit;
}
const rawChunk = remaining.slice(0, breakIdx);
const chunk = rawChunk.trimEnd();
if (chunk.length > 0) chunks.push(chunk);
if (chunk.length > 0) {
chunks.push(chunk);
}
const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
remaining = remaining.slice(nextStart).trimStart();
}
if (remaining.length) chunks.push(remaining);
if (remaining.length) {
chunks.push(remaining);
}
return chunks;
},
chunkerMode: "text",
textChunkLimit: 2000,
sendText: async ({ to, text, accountId, cfg }) => {
const account = resolveZalouserAccountSync({ cfg: cfg as OpenClawConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const result = await sendMessageZalouser(to, text, { profile: account.profile });
return {
channel: "zalouser",
@@ -534,7 +563,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
};
},
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
const account = resolveZalouserAccountSync({ cfg: cfg as OpenClawConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const result = await sendMessageZalouser(to, text, {
profile: account.profile,
mediaUrl,
@@ -591,7 +620,9 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
let userLabel = "";
try {
const userInfo = await getZcaUserInfo(account.profile);
if (userInfo?.displayName) userLabel = ` (${userInfo.displayName})`;
if (userInfo?.displayName) {
userLabel = ` (${userInfo.displayName})`;
}
ctx.setStatus({
accountId: account.accountId,
user: userInfo,
@@ -603,7 +634,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
const { monitorZalouserProvider } = await import("./monitor.js");
return monitorZalouserProvider({
account,
config: ctx.cfg as OpenClawConfig,
config: ctx.cfg,
runtime: ctx.runtime,
abortSignal: ctx.abortSignal,
statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch }),

View File

@@ -29,7 +29,9 @@ function buildNameIndex<T>(items: T[], nameFn: (item: T) => string | undefined):
const index = new Map<string, T[]>();
for (const item of items) {
const name = nameFn(item)?.trim().toLowerCase();
if (!name) continue;
if (!name) {
continue;
}
const list = index.get(name) ?? [];
list.push(item);
index.set(name, list);
@@ -46,7 +48,9 @@ function logVerbose(core: ZalouserCoreRuntime, runtime: RuntimeEnv, message: str
}
function isSenderAllowed(senderId: string, allowFrom: string[]): boolean {
if (allowFrom.includes("*")) return true;
if (allowFrom.includes("*")) {
return true;
}
const normalizedSenderId = senderId.toLowerCase();
return allowFrom.some((entry) => {
const normalized = entry.toLowerCase().replace(/^(zalouser|zlu):/i, "");
@@ -56,7 +60,9 @@ function isSenderAllowed(senderId: string, allowFrom: string[]): boolean {
function normalizeGroupSlug(raw?: string | null): string {
const trimmed = raw?.trim().toLowerCase() ?? "";
if (!trimmed) return "";
if (!trimmed) {
return "";
}
return trimmed
.replace(/^#/, "")
.replace(/[^a-z0-9]+/g, "-")
@@ -70,7 +76,9 @@ function isGroupAllowed(params: {
}): boolean {
const groups = params.groups ?? {};
const keys = Object.keys(groups);
if (keys.length === 0) return false;
if (keys.length === 0) {
return false;
}
const candidates = [
params.groupId,
`group:${params.groupId}`,
@@ -79,11 +87,15 @@ function isGroupAllowed(params: {
].filter(Boolean);
for (const candidate of candidates) {
const entry = groups[candidate];
if (!entry) continue;
if (!entry) {
continue;
}
return entry.allow !== false && entry.enabled !== false;
}
const wildcard = groups["*"];
if (wildcard) return wildcard.allow !== false && wildcard.enabled !== false;
if (wildcard) {
return wildcard.allow !== false && wildcard.enabled !== false;
}
return false;
}
@@ -104,7 +116,9 @@ function startZcaListener(
buffer = lines.pop() ?? "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
if (!trimmed) {
continue;
}
try {
const parsed = JSON.parse(trimmed) as ZcaMessage;
onMessage(parsed);
@@ -118,7 +132,9 @@ function startZcaListener(
proc.stderr?.on("data", (data: Buffer) => {
const text = data.toString().trim();
if (text) runtime.error(`[zalouser] zca stderr: ${text}`);
if (text) {
runtime.error(`[zalouser] zca stderr: ${text}`);
}
});
void promise.then((result) => {
@@ -147,7 +163,9 @@ async function processMessage(
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void,
): Promise<void> {
const { threadId, content, timestamp, metadata } = message;
if (!content?.trim()) return;
if (!content?.trim()) {
return;
}
const isGroup = metadata?.isGroup ?? false;
const senderId = metadata?.fromId ?? threadId;
@@ -476,7 +494,9 @@ export async function monitorZalouserProvider(
for (const entry of groupKeys) {
const cleaned = normalizeZalouserEntry(entry);
if (/^\d+$/.test(cleaned)) {
if (!nextGroups[cleaned]) nextGroups[cleaned] = groupsConfig[entry];
if (!nextGroups[cleaned]) {
nextGroups[cleaned] = groupsConfig[entry];
}
mapping.push(`${entry}${cleaned}`);
continue;
}
@@ -484,7 +504,9 @@ export async function monitorZalouserProvider(
const match = matches[0];
const id = match?.groupId ? String(match.groupId) : undefined;
if (id) {
if (!nextGroups[id]) nextGroups[id] = groupsConfig[entry];
if (!nextGroups[id]) {
nextGroups[id] = groupsConfig[entry];
}
mapping.push(`${entry}${id}`);
} else {
unresolved.push(entry);

View File

@@ -73,19 +73,29 @@ async function promptZalouserAllowFrom(params: {
const resolveUserId = async (input: string): Promise<string | null> => {
const trimmed = input.trim();
if (!trimmed) return null;
if (/^\d+$/.test(trimmed)) return trimmed;
if (!trimmed) {
return null;
}
if (/^\d+$/.test(trimmed)) {
return trimmed;
}
const ok = await checkZcaInstalled();
if (!ok) return null;
if (!ok) {
return null;
}
const result = await runZca(["friend", "find", trimmed], {
profile: resolved.profile,
timeout: 15000,
});
if (!result.ok) return null;
if (!result.ok) {
return null;
}
const parsed = parseJsonOutput<ZcaFriend[]>(result.stdout);
const rows = Array.isArray(parsed) ? parsed : [];
const match = rows[0];
if (!match?.userId) return null;
if (!match?.userId) {
return null;
}
if (rows.length > 1) {
await prompter.note(
`Multiple matches for "${trimmed}", using ${match.displayName ?? match.userId}.`,
@@ -140,9 +150,9 @@ async function promptZalouserAllowFrom(params: {
...cfg.channels?.zalouser,
enabled: true,
accounts: {
...(cfg.channels?.zalouser?.accounts ?? {}),
...cfg.channels?.zalouser?.accounts,
[accountId]: {
...(cfg.channels?.zalouser?.accounts?.[accountId] ?? {}),
...cfg.channels?.zalouser?.accounts?.[accountId],
enabled: cfg.channels?.zalouser?.accounts?.[accountId]?.enabled ?? true,
dmPolicy: "allowlist",
allowFrom: unique,
@@ -180,9 +190,9 @@ function setZalouserGroupPolicy(
...cfg.channels?.zalouser,
enabled: true,
accounts: {
...(cfg.channels?.zalouser?.accounts ?? {}),
...cfg.channels?.zalouser?.accounts,
[accountId]: {
...(cfg.channels?.zalouser?.accounts?.[accountId] ?? {}),
...cfg.channels?.zalouser?.accounts?.[accountId],
enabled: cfg.channels?.zalouser?.accounts?.[accountId]?.enabled ?? true,
groupPolicy,
},
@@ -219,9 +229,9 @@ function setZalouserGroupAllowlist(
...cfg.channels?.zalouser,
enabled: true,
accounts: {
...(cfg.channels?.zalouser?.accounts ?? {}),
...cfg.channels?.zalouser?.accounts,
[accountId]: {
...(cfg.channels?.zalouser?.accounts?.[accountId] ?? {}),
...cfg.channels?.zalouser?.accounts?.[accountId],
enabled: cfg.channels?.zalouser?.accounts?.[accountId]?.enabled ?? true,
groups,
},
@@ -241,14 +251,18 @@ async function resolveZalouserGroups(params: {
profile: account.profile,
timeout: 15000,
});
if (!result.ok) throw new Error(result.stderr || "Failed to list groups");
if (!result.ok) {
throw new Error(result.stderr || "Failed to list groups");
}
const groups = (parseJsonOutput<ZcaGroup[]>(result.stdout) ?? []).filter((group) =>
Boolean(group.groupId),
);
const byName = new Map<string, ZcaGroup[]>();
for (const group of groups) {
const name = group.name?.trim().toLowerCase();
if (!name) continue;
if (!name) {
continue;
}
const list = byName.get(name) ?? [];
list.push(group);
byName.set(name, list);
@@ -256,8 +270,12 @@ async function resolveZalouserGroups(params: {
return params.entries.map((input) => {
const trimmed = input.trim();
if (!trimmed) return { input, resolved: false };
if (/^\d+$/.test(trimmed)) return { input, resolved: true, id: trimmed };
if (!trimmed) {
return { input, resolved: false };
}
if (/^\d+$/.test(trimmed)) {
return { input, resolved: true, id: trimmed };
}
const matches = byName.get(trimmed.toLowerCase()) ?? [];
const match = matches[0];
return match?.groupId
@@ -271,16 +289,15 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
channel,
policyKey: "channels.zalouser.dmPolicy",
allowFromKey: "channels.zalouser.allowFrom",
getCurrent: (cfg) =>
((cfg as OpenClawConfig).channels?.zalouser?.dmPolicy ?? "pairing") as "pairing",
setPolicy: (cfg, policy) => setZalouserDmPolicy(cfg as OpenClawConfig, policy),
getCurrent: (cfg) => (cfg.channels?.zalouser?.dmPolicy ?? "pairing") as "pairing",
setPolicy: (cfg, policy) => setZalouserDmPolicy(cfg, policy),
promptAllowFrom: async ({ cfg, prompter, accountId }) => {
const id =
accountId && normalizeAccountId(accountId)
? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
: resolveDefaultZalouserAccountId(cfg as OpenClawConfig);
: resolveDefaultZalouserAccountId(cfg);
return promptZalouserAllowFrom({
cfg: cfg as OpenClawConfig,
cfg: cfg,
prompter,
accountId: id,
});
@@ -291,10 +308,10 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
channel,
dmPolicy,
getStatus: async ({ cfg }) => {
const ids = listZalouserAccountIds(cfg as OpenClawConfig);
const ids = listZalouserAccountIds(cfg);
let configured = false;
for (const accountId of ids) {
const account = resolveZalouserAccountSync({ cfg: cfg as OpenClawConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const isAuth = await checkZcaAuthenticated(account.profile);
if (isAuth) {
configured = true;
@@ -332,12 +349,12 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
}
const zalouserOverride = accountOverrides.zalouser?.trim();
const defaultAccountId = resolveDefaultZalouserAccountId(cfg as OpenClawConfig);
const defaultAccountId = resolveDefaultZalouserAccountId(cfg);
let accountId = zalouserOverride ? normalizeAccountId(zalouserOverride) : defaultAccountId;
if (shouldPromptAccountIds && !zalouserOverride) {
accountId = await promptAccountId({
cfg: cfg as OpenClawConfig,
cfg: cfg,
prompter,
label: "Zalo Personal",
currentId: accountId,
@@ -346,7 +363,7 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
});
}
let next = cfg as OpenClawConfig;
let next = cfg;
const account = resolveZalouserAccountSync({ cfg: next, accountId });
const alreadyAuthenticated = await checkZcaAuthenticated(account.profile);
@@ -411,9 +428,9 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
...next.channels?.zalouser,
enabled: true,
accounts: {
...(next.channels?.zalouser?.accounts ?? {}),
...next.channels?.zalouser?.accounts,
[accountId]: {
...(next.channels?.zalouser?.accounts?.[accountId] ?? {}),
...next.channels?.zalouser?.accounts?.[accountId],
enabled: true,
profile: account.profile,
},

View File

@@ -34,7 +34,9 @@ export async function sendMessageZalouser(
// Send text message
const args = ["msg", "send", threadId.trim(), text.slice(0, 2000)];
if (options.isGroup) args.push("-g");
if (options.isGroup) {
args.push("-g");
}
try {
const result = await runZca(args, { profile });
@@ -79,7 +81,9 @@ async function sendMediaZalouser(
if (options.caption) {
args.push("-m", options.caption.slice(0, 2000));
}
if (options.isGroup) args.push("-g");
if (options.isGroup) {
args.push("-g");
}
try {
const result = await runZca(args, { profile });
@@ -104,7 +108,9 @@ export async function sendImageZalouser(
if (options.caption) {
args.push("-m", options.caption.slice(0, 2000));
}
if (options.isGroup) args.push("-g");
if (options.isGroup) {
args.push("-g");
}
try {
const result = await runZca(args, { profile });
@@ -124,7 +130,9 @@ export async function sendLinkZalouser(
): Promise<ZalouserSendResult> {
const profile = options.profile || process.env.ZCA_PROFILE || "default";
const args = ["msg", "link", threadId.trim(), url.trim()];
if (options.isGroup) args.push("-g");
if (options.isGroup) {
args.push("-g");
}
try {
const result = await runZca(args, { profile });
@@ -140,7 +148,9 @@ export async function sendLinkZalouser(
function extractMessageId(stdout: string): string | undefined {
// Try to extract message ID from output
const match = stdout.match(/message[_\s]?id[:\s]+(\S+)/i);
if (match) return match[1];
if (match) {
return match[1];
}
// Return first word if it looks like an ID
const firstWord = stdout.trim().split(/\s+/)[0];
if (firstWord && /^[a-zA-Z0-9_-]+$/.test(firstWord)) {

View File

@@ -15,7 +15,9 @@ const asString = (value: unknown): string | undefined =>
typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined;
function readZalouserAccountStatus(value: ChannelAccountSnapshot): ZalouserAccountStatus | null {
if (!isRecord(value)) return null;
if (!isRecord(value)) {
return null;
}
return {
accountId: value.accountId,
enabled: value.enabled,
@@ -26,7 +28,9 @@ function readZalouserAccountStatus(value: ChannelAccountSnapshot): ZalouserAccou
}
function isMissingZca(lastError?: string): boolean {
if (!lastError) return false;
if (!lastError) {
return false;
}
const lower = lastError.toLowerCase();
return lower.includes("zca") && (lower.includes("not found") || lower.includes("enoent"));
}
@@ -37,10 +41,14 @@ export function collectZalouserStatusIssues(
const issues: ChannelStatusIssue[] = [];
for (const entry of accounts) {
const account = readZalouserAccountStatus(entry);
if (!account) continue;
if (!account) {
continue;
}
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
if (!enabled) continue;
if (!enabled) {
continue;
}
const configured = account.configured === true;
const lastError = asString(account.lastError)?.trim();

View File

@@ -62,7 +62,9 @@ export async function executeZalouserTool(
throw new Error("threadId and message required for send action");
}
const args = ["msg", "send", params.threadId, params.message];
if (params.isGroup) args.push("-g");
if (params.isGroup) {
args.push("-g");
}
const result = await runZca(args, { profile: params.profile });
if (!result.ok) {
throw new Error(result.stderr || "Failed to send message");
@@ -78,8 +80,12 @@ export async function executeZalouserTool(
throw new Error("url required for image action");
}
const args = ["msg", "image", params.threadId, "-u", params.url];
if (params.message) args.push("-m", params.message);
if (params.isGroup) args.push("-g");
if (params.message) {
args.push("-m", params.message);
}
if (params.isGroup) {
args.push("-g");
}
const result = await runZca(args, { profile: params.profile });
if (!result.ok) {
throw new Error(result.stderr || "Failed to send image");
@@ -92,7 +98,9 @@ export async function executeZalouserTool(
throw new Error("threadId and url required for link action");
}
const args = ["msg", "link", params.threadId, params.url];
if (params.isGroup) args.push("-g");
if (params.isGroup) {
args.push("-g");
}
const result = await runZca(args, { profile: params.profile });
if (!result.ok) {
throw new Error(result.stderr || "Failed to send link");
@@ -142,10 +150,12 @@ export async function executeZalouserTool(
});
}
default:
default: {
params.action satisfies never;
throw new Error(
`Unknown action: ${params.action}. Valid actions: send, image, link, friends, groups, me, status`,
`Unknown action: ${String(params.action)}. Valid actions: send, image, link, friends, groups, me, status`,
);
}
}
} catch (err) {
return json({

View File

@@ -109,6 +109,7 @@ export function runZcaInteractive(args: string[], options?: ZcaRunOptions): Prom
}
function stripAnsi(str: string): string {
// oxlint-disable-next-line no-control-regex
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
}