fix(cycles): split small runtime seams

This commit is contained in:
Vincent Koc
2026-04-10 08:59:27 +01:00
parent c27ee0af42
commit dfdc281f55
12 changed files with 120 additions and 122 deletions

View File

@@ -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<string>;
};
export type MSTeamsActivityHandler = {
onMessage: (
handler: (context: unknown, next: () => Promise<void>) => Promise<void>,

View File

@@ -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";

View File

@@ -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<void> {
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 === "*");

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
export type ZaloRuntimeEnv = {
log?: (message: string) => void;
error?: (message: string) => void;
};

View File

@@ -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,

View File

@@ -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<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"],
): Promise<void> {
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<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
accountId: string;
}): Promise<OpenClawConfig> {
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;
}

View File

@@ -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<ChannelSetupWizard> {
return (await import("./setup-surface.js")).zaloSetupWizard;
}
export function createZaloSetupWizardProxy(
loadWizard: () => Promise<ChannelSetupWizard>,
): ChannelSetupWizard {

View File

@@ -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<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"],
): Promise<void> {
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<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
accountId: string;
}): Promise<OpenClawConfig> {
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 = {

View File

@@ -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(

View File

@@ -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");

View File

@@ -0,0 +1,7 @@
export type GatewayRpcOpts = {
url?: string;
token?: string;
timeout?: string;
expectFinal?: boolean;
json?: boolean;
};