From dfdc281f554dab2c22c5545b40f77319303019c4 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 10 Apr 2026 08:59:27 +0100 Subject: [PATCH] fix(cycles): split small runtime seams --- extensions/msteams/src/monitor-handler.ts | 7 +- extensions/msteams/src/sso.ts | 2 +- extensions/qqbot/src/gateway.ts | 12 +-- extensions/zalo/src/monitor.ts | 7 +- extensions/zalo/src/monitor.types.ts | 4 + extensions/zalo/src/monitor.webhook.ts | 2 +- extensions/zalo/src/setup-allow-from.ts | 93 +++++++++++++++++++++++ extensions/zalo/src/setup-core.ts | 8 +- extensions/zalo/src/setup-surface.ts | 88 +-------------------- src/cli/gateway-rpc.runtime.ts | 2 +- src/cli/gateway-rpc.ts | 10 +-- src/cli/gateway-rpc.types.ts | 7 ++ 12 files changed, 120 insertions(+), 122 deletions(-) create mode 100644 extensions/zalo/src/monitor.types.ts create mode 100644 extensions/zalo/src/setup-allow-from.ts create mode 100644 src/cli/gateway-rpc.types.ts diff --git a/extensions/msteams/src/monitor-handler.ts b/extensions/msteams/src/monitor-handler.ts index 5b7957e0e9c..2f5104ef82e 100644 --- a/extensions/msteams/src/monitor-handler.ts +++ b/extensions/msteams/src/monitor-handler.ts @@ -6,6 +6,8 @@ import { buildFileInfoCard, parseFileConsentInvoke, uploadToConsentUrl } from ". import { extractMSTeamsConversationMessageId, normalizeMSTeamsConversationId } from "./inbound.js"; import { resolveMSTeamsSenderAccess } from "./monitor-handler/access.js"; import { createMSTeamsMessageHandler } from "./monitor-handler/message-handler.js"; +export type { MSTeamsAccessTokenProvider } from "./attachments/types.js"; +import type { MSTeamsAccessTokenProvider } from "./attachments/types.js"; import type { MSTeamsMonitorLogger } from "./monitor-types.js"; import { getPendingUpload, removePendingUpload } from "./pending-uploads.js"; import { withRevokedProxyFallback } from "./revoked-context.js"; @@ -14,7 +16,6 @@ import type { MSTeamsTurnContext } from "./sdk-types.js"; import { handleSigninTokenExchangeInvoke, handleSigninVerifyStateInvoke, - type MSTeamsSsoDeps, parseSigninTokenExchangeValue, parseSigninVerifyStateValue, } from "./sso.js"; @@ -22,10 +23,6 @@ import { buildGroupWelcomeText, buildWelcomeCard } from "./welcome-card.js"; export type { MSTeamsMessageHandlerDeps } from "./monitor-handler.types.js"; import type { MSTeamsMessageHandlerDeps } from "./monitor-handler.types.js"; -export type MSTeamsAccessTokenProvider = { - getAccessToken: (scope: string) => Promise; -}; - export type MSTeamsActivityHandler = { onMessage: ( handler: (context: unknown, next: () => Promise) => Promise, diff --git a/extensions/msteams/src/sso.ts b/extensions/msteams/src/sso.ts index f0b3181bb39..4cb9d05c4a6 100644 --- a/extensions/msteams/src/sso.ts +++ b/extensions/msteams/src/sso.ts @@ -24,7 +24,7 @@ * that ack; these helpers encapsulate token exchange and persistence. */ -import type { MSTeamsAccessTokenProvider } from "./monitor-handler.js"; +import type { MSTeamsAccessTokenProvider } from "./attachments/types.js"; import type { MSTeamsSsoTokenStore } from "./sso-token-store.js"; import { buildUserAgent } from "./user-agent.js"; diff --git a/extensions/qqbot/src/gateway.ts b/extensions/qqbot/src/gateway.ts index 3c20626d633..f6111387f8f 100644 --- a/extensions/qqbot/src/gateway.ts +++ b/extensions/qqbot/src/gateway.ts @@ -17,7 +17,7 @@ import { startBackgroundTokenRefresh, stopBackgroundTokenRefresh, } from "./api.js"; -import { qqbotPlugin } from "./channel.js"; +import { formatQQBotAllowFrom } from "./channel-config-shared.js"; import { formatVoiceText, processAttachments } from "./inbound-attachments.js"; import { flushKnownUsers, recordKnownUser } from "./known-users.js"; import { createMessageQueue, type QueuedMessage } from "./message-queue.js"; @@ -748,13 +748,9 @@ export async function startGateway(ctx: GatewayContext): Promise { const toAddress = fromAddress; const rawAllowFrom = account.config?.allowFrom ?? []; - const normalizedAllowFrom = qqbotPlugin.config?.formatAllowFrom - ? qqbotPlugin.config.formatAllowFrom({ - cfg: cfg, - accountId: account.accountId, - allowFrom: rawAllowFrom, - }) - : rawAllowFrom.map((e: string) => e.replace(/^qqbot:/i, "").toUpperCase()); + const normalizedAllowFrom = formatQQBotAllowFrom({ + allowFrom: rawAllowFrom, + }); const normalizedSenderId = event.senderId.replace(/^qqbot:/i, "").toUpperCase(); const allowAll = normalizedAllowFrom.length === 0 || normalizedAllowFrom.some((e) => e === "*"); diff --git a/extensions/zalo/src/monitor.ts b/extensions/zalo/src/monitor.ts index 3b0e9cd72ff..84d3f769018 100644 --- a/extensions/zalo/src/monitor.ts +++ b/extensions/zalo/src/monitor.ts @@ -36,11 +36,8 @@ import { warnMissingProviderGroupPolicyFallbackOnce, } from "./runtime-api.js"; import { getZaloRuntime } from "./runtime.js"; - -export type ZaloRuntimeEnv = { - log?: (message: string) => void; - error?: (message: string) => void; -}; +export type { ZaloRuntimeEnv } from "./monitor.types.js"; +import type { ZaloRuntimeEnv } from "./monitor.types.js"; export type ZaloMonitorOptions = { token: string; diff --git a/extensions/zalo/src/monitor.types.ts b/extensions/zalo/src/monitor.types.ts new file mode 100644 index 00000000000..0ac455a2aa9 --- /dev/null +++ b/extensions/zalo/src/monitor.types.ts @@ -0,0 +1,4 @@ +export type ZaloRuntimeEnv = { + log?: (message: string) => void; + error?: (message: string) => void; +}; diff --git a/extensions/zalo/src/monitor.webhook.ts b/extensions/zalo/src/monitor.webhook.ts index 1b602c6a55f..de7d75094de 100644 --- a/extensions/zalo/src/monitor.webhook.ts +++ b/extensions/zalo/src/monitor.webhook.ts @@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime"; import type { ResolvedZaloAccount } from "./accounts.js"; import type { ZaloFetch, ZaloUpdate } from "./api.js"; -import type { ZaloRuntimeEnv } from "./monitor.js"; +import type { ZaloRuntimeEnv } from "./monitor.types.js"; import { createDedupeCache, createFixedWindowRateLimiter, diff --git a/extensions/zalo/src/setup-allow-from.ts b/extensions/zalo/src/setup-allow-from.ts new file mode 100644 index 00000000000..9a3284083a1 --- /dev/null +++ b/extensions/zalo/src/setup-allow-from.ts @@ -0,0 +1,93 @@ +import { + DEFAULT_ACCOUNT_ID, + formatDocsLink, + mergeAllowFromEntries, + type ChannelSetupDmPolicy, + type ChannelSetupWizard, + type OpenClawConfig, +} from "openclaw/plugin-sdk/setup"; +import { resolveZaloAccount } from "./accounts.js"; + +type ZaloAccountSetupConfig = { + enabled?: boolean; +}; + +export async function noteZaloTokenHelp( + prompter: Parameters>[0]["prompter"], +): Promise { + await prompter.note( + [ + "1) Open Zalo Bot Platform: https://bot.zaloplatforms.com", + "2) Create a bot and get the token", + "3) Token looks like 12345689:abc-xyz", + "Tip: you can also set ZALO_BOT_TOKEN in your env.", + `Docs: ${formatDocsLink("/channels/zalo", "zalo")}`, + ].join("\n"), + "Zalo bot token", + ); +} + +export async function promptZaloAllowFrom(params: { + cfg: OpenClawConfig; + prompter: Parameters>[0]["prompter"]; + accountId: string; +}): Promise { + const { cfg, prompter, accountId } = params; + const resolved = resolveZaloAccount({ cfg, accountId }); + const existingAllowFrom = resolved.config.allowFrom ?? []; + const entry = await prompter.text({ + message: "Zalo allowFrom (user id)", + placeholder: "123456789", + initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined, + validate: (value) => { + const raw = String(value ?? "").trim(); + if (!raw) { + return "Required"; + } + if (!/^\d+$/.test(raw)) { + return "Use a numeric Zalo user id"; + } + return undefined; + }, + }); + const normalized = String(entry).trim(); + const unique = mergeAllowFromEntries(existingAllowFrom, [normalized]); + + if (accountId === DEFAULT_ACCOUNT_ID) { + return { + ...cfg, + channels: { + ...cfg.channels, + zalo: { + ...cfg.channels?.zalo, + enabled: true, + dmPolicy: "allowlist", + allowFrom: unique, + }, + }, + } as OpenClawConfig; + } + + const currentAccount = cfg.channels?.zalo?.accounts?.[accountId] as + | ZaloAccountSetupConfig + | undefined; + return { + ...cfg, + channels: { + ...cfg.channels, + zalo: { + ...cfg.channels?.zalo, + enabled: true, + accounts: { + ...cfg.channels?.zalo?.accounts, + [accountId]: { + ...currentAccount, + enabled: currentAccount?.enabled ?? true, + dmPolicy: "allowlist", + allowFrom: unique, + }, + }, + }, + }, + } as OpenClawConfig; +} diff --git a/extensions/zalo/src/setup-core.ts b/extensions/zalo/src/setup-core.ts index fa6c7960dd7..4b57bbd4160 100644 --- a/extensions/zalo/src/setup-core.ts +++ b/extensions/zalo/src/setup-core.ts @@ -9,6 +9,7 @@ import { type ChannelSetupWizard, } from "openclaw/plugin-sdk/setup"; import { resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js"; +import { promptZaloAllowFrom } from "./setup-allow-from.js"; const channel = "zalo" as const; @@ -109,14 +110,9 @@ export const zaloDmPolicy: ChannelSetupDmPolicy = { }, }; }, - promptAllowFrom: async (params) => - (await loadZaloSetupWizard()).dmPolicy?.promptAllowFrom?.(params) ?? params.cfg, + promptAllowFrom: promptZaloAllowFrom, }; -async function loadZaloSetupWizard(): Promise { - return (await import("./setup-surface.js")).zaloSetupWizard; -} - export function createZaloSetupWizardProxy( loadWizard: () => Promise, ): ChannelSetupWizard { diff --git a/extensions/zalo/src/setup-surface.ts b/extensions/zalo/src/setup-surface.ts index 214ca8ff1e6..0931aa1c7d5 100644 --- a/extensions/zalo/src/setup-surface.ts +++ b/extensions/zalo/src/setup-surface.ts @@ -2,27 +2,21 @@ import { buildSingleChannelSecretPromptState, createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, - formatDocsLink, hasConfiguredSecretInput, - mergeAllowFromEntries, promptSingleChannelSecretInput, runSingleChannelSecretStep, - type ChannelSetupDmPolicy, type ChannelSetupWizard, type OpenClawConfig, type SecretInput, } from "openclaw/plugin-sdk/setup"; import { resolveZaloAccount } from "./accounts.js"; +import { noteZaloTokenHelp } from "./setup-allow-from.js"; import { zaloDmPolicy } from "./setup-core.js"; const channel = "zalo" as const; type UpdateMode = "polling" | "webhook"; -type ZaloAccountSetupConfig = { - enabled?: boolean; -}; - function setZaloUpdateMode( cfg: OpenClawConfig, accountId: string, @@ -98,86 +92,6 @@ function setZaloUpdateMode( } as OpenClawConfig; } -async function noteZaloTokenHelp( - prompter: Parameters>[0]["prompter"], -): Promise { - await prompter.note( - [ - "1) Open Zalo Bot Platform: https://bot.zaloplatforms.com", - "2) Create a bot and get the token", - "3) Token looks like 12345689:abc-xyz", - "Tip: you can also set ZALO_BOT_TOKEN in your env.", - `Docs: ${formatDocsLink("/channels/zalo", "zalo")}`, - ].join("\n"), - "Zalo bot token", - ); -} - -async function promptZaloAllowFrom(params: { - cfg: OpenClawConfig; - prompter: Parameters>[0]["prompter"]; - accountId: string; -}): Promise { - const { cfg, prompter, accountId } = params; - const resolved = resolveZaloAccount({ cfg, accountId }); - const existingAllowFrom = resolved.config.allowFrom ?? []; - const entry = await prompter.text({ - message: "Zalo allowFrom (user id)", - placeholder: "123456789", - initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined, - validate: (value) => { - const raw = String(value ?? "").trim(); - if (!raw) { - return "Required"; - } - if (!/^\d+$/.test(raw)) { - return "Use a numeric Zalo user id"; - } - return undefined; - }, - }); - const normalized = String(entry).trim(); - const unique = mergeAllowFromEntries(existingAllowFrom, [normalized]); - - if (accountId === DEFAULT_ACCOUNT_ID) { - return { - ...cfg, - channels: { - ...cfg.channels, - zalo: { - ...cfg.channels?.zalo, - enabled: true, - dmPolicy: "allowlist", - allowFrom: unique, - }, - }, - } as OpenClawConfig; - } - - const currentAccount = cfg.channels?.zalo?.accounts?.[accountId] as - | ZaloAccountSetupConfig - | undefined; - return { - ...cfg, - channels: { - ...cfg.channels, - zalo: { - ...cfg.channels?.zalo, - enabled: true, - accounts: { - ...cfg.channels?.zalo?.accounts, - [accountId]: { - ...currentAccount, - enabled: currentAccount?.enabled ?? true, - dmPolicy: "allowlist", - allowFrom: unique, - }, - }, - }, - }, - } as OpenClawConfig; -} - export { zaloSetupAdapter } from "./setup-core.js"; export const zaloSetupWizard: ChannelSetupWizard = { diff --git a/src/cli/gateway-rpc.runtime.ts b/src/cli/gateway-rpc.runtime.ts index 3ce15b52fdb..145603dc5e1 100644 --- a/src/cli/gateway-rpc.runtime.ts +++ b/src/cli/gateway-rpc.runtime.ts @@ -1,6 +1,6 @@ import { callGateway } from "../gateway/call.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; -import type { GatewayRpcOpts } from "./gateway-rpc.js"; +import type { GatewayRpcOpts } from "./gateway-rpc.types.js"; import { withProgress } from "./progress.js"; export async function callGatewayFromCliRuntime( diff --git a/src/cli/gateway-rpc.ts b/src/cli/gateway-rpc.ts index ff4d320d718..97462e0cf42 100644 --- a/src/cli/gateway-rpc.ts +++ b/src/cli/gateway-rpc.ts @@ -1,12 +1,6 @@ import type { Command } from "commander"; - -export type GatewayRpcOpts = { - url?: string; - token?: string; - timeout?: string; - expectFinal?: boolean; - json?: boolean; -}; +export type { GatewayRpcOpts } from "./gateway-rpc.types.js"; +import type { GatewayRpcOpts } from "./gateway-rpc.types.js"; type GatewayRpcRuntimeModule = typeof import("./gateway-rpc.runtime.js"); diff --git a/src/cli/gateway-rpc.types.ts b/src/cli/gateway-rpc.types.ts new file mode 100644 index 00000000000..5ad47b5f328 --- /dev/null +++ b/src/cli/gateway-rpc.types.ts @@ -0,0 +1,7 @@ +export type GatewayRpcOpts = { + url?: string; + token?: string; + timeout?: string; + expectFinal?: boolean; + json?: boolean; +};