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 { extractMSTeamsConversationMessageId, normalizeMSTeamsConversationId } from "./inbound.js";
import { resolveMSTeamsSenderAccess } from "./monitor-handler/access.js"; import { resolveMSTeamsSenderAccess } from "./monitor-handler/access.js";
import { createMSTeamsMessageHandler } from "./monitor-handler/message-handler.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 type { MSTeamsMonitorLogger } from "./monitor-types.js";
import { getPendingUpload, removePendingUpload } from "./pending-uploads.js"; import { getPendingUpload, removePendingUpload } from "./pending-uploads.js";
import { withRevokedProxyFallback } from "./revoked-context.js"; import { withRevokedProxyFallback } from "./revoked-context.js";
@@ -14,7 +16,6 @@ import type { MSTeamsTurnContext } from "./sdk-types.js";
import { import {
handleSigninTokenExchangeInvoke, handleSigninTokenExchangeInvoke,
handleSigninVerifyStateInvoke, handleSigninVerifyStateInvoke,
type MSTeamsSsoDeps,
parseSigninTokenExchangeValue, parseSigninTokenExchangeValue,
parseSigninVerifyStateValue, parseSigninVerifyStateValue,
} from "./sso.js"; } from "./sso.js";
@@ -22,10 +23,6 @@ import { buildGroupWelcomeText, buildWelcomeCard } from "./welcome-card.js";
export type { MSTeamsMessageHandlerDeps } from "./monitor-handler.types.js"; export type { MSTeamsMessageHandlerDeps } from "./monitor-handler.types.js";
import 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 = { export type MSTeamsActivityHandler = {
onMessage: ( onMessage: (
handler: (context: unknown, next: () => Promise<void>) => Promise<void>, handler: (context: unknown, next: () => Promise<void>) => Promise<void>,

View File

@@ -24,7 +24,7 @@
* that ack; these helpers encapsulate token exchange and persistence. * 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 type { MSTeamsSsoTokenStore } from "./sso-token-store.js";
import { buildUserAgent } from "./user-agent.js"; import { buildUserAgent } from "./user-agent.js";

View File

@@ -17,7 +17,7 @@ import {
startBackgroundTokenRefresh, startBackgroundTokenRefresh,
stopBackgroundTokenRefresh, stopBackgroundTokenRefresh,
} from "./api.js"; } from "./api.js";
import { qqbotPlugin } from "./channel.js"; import { formatQQBotAllowFrom } from "./channel-config-shared.js";
import { formatVoiceText, processAttachments } from "./inbound-attachments.js"; import { formatVoiceText, processAttachments } from "./inbound-attachments.js";
import { flushKnownUsers, recordKnownUser } from "./known-users.js"; import { flushKnownUsers, recordKnownUser } from "./known-users.js";
import { createMessageQueue, type QueuedMessage } from "./message-queue.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 toAddress = fromAddress;
const rawAllowFrom = account.config?.allowFrom ?? []; const rawAllowFrom = account.config?.allowFrom ?? [];
const normalizedAllowFrom = qqbotPlugin.config?.formatAllowFrom const normalizedAllowFrom = formatQQBotAllowFrom({
? qqbotPlugin.config.formatAllowFrom({ allowFrom: rawAllowFrom,
cfg: cfg, });
accountId: account.accountId,
allowFrom: rawAllowFrom,
})
: rawAllowFrom.map((e: string) => e.replace(/^qqbot:/i, "").toUpperCase());
const normalizedSenderId = event.senderId.replace(/^qqbot:/i, "").toUpperCase(); const normalizedSenderId = event.senderId.replace(/^qqbot:/i, "").toUpperCase();
const allowAll = const allowAll =
normalizedAllowFrom.length === 0 || normalizedAllowFrom.some((e) => e === "*"); normalizedAllowFrom.length === 0 || normalizedAllowFrom.some((e) => e === "*");

View File

@@ -36,11 +36,8 @@ import {
warnMissingProviderGroupPolicyFallbackOnce, warnMissingProviderGroupPolicyFallbackOnce,
} from "./runtime-api.js"; } from "./runtime-api.js";
import { getZaloRuntime } from "./runtime.js"; import { getZaloRuntime } from "./runtime.js";
export type { ZaloRuntimeEnv } from "./monitor.types.js";
export type ZaloRuntimeEnv = { import type { ZaloRuntimeEnv } from "./monitor.types.js";
log?: (message: string) => void;
error?: (message: string) => void;
};
export type ZaloMonitorOptions = { export type ZaloMonitorOptions = {
token: string; 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 { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
import type { ResolvedZaloAccount } from "./accounts.js"; import type { ResolvedZaloAccount } from "./accounts.js";
import type { ZaloFetch, ZaloUpdate } from "./api.js"; import type { ZaloFetch, ZaloUpdate } from "./api.js";
import type { ZaloRuntimeEnv } from "./monitor.js"; import type { ZaloRuntimeEnv } from "./monitor.types.js";
import { import {
createDedupeCache, createDedupeCache,
createFixedWindowRateLimiter, 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, type ChannelSetupWizard,
} from "openclaw/plugin-sdk/setup"; } from "openclaw/plugin-sdk/setup";
import { resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js"; import { resolveDefaultZaloAccountId, resolveZaloAccount } from "./accounts.js";
import { promptZaloAllowFrom } from "./setup-allow-from.js";
const channel = "zalo" as const; const channel = "zalo" as const;
@@ -109,14 +110,9 @@ export const zaloDmPolicy: ChannelSetupDmPolicy = {
}, },
}; };
}, },
promptAllowFrom: async (params) => promptAllowFrom: promptZaloAllowFrom,
(await loadZaloSetupWizard()).dmPolicy?.promptAllowFrom?.(params) ?? params.cfg,
}; };
async function loadZaloSetupWizard(): Promise<ChannelSetupWizard> {
return (await import("./setup-surface.js")).zaloSetupWizard;
}
export function createZaloSetupWizardProxy( export function createZaloSetupWizardProxy(
loadWizard: () => Promise<ChannelSetupWizard>, loadWizard: () => Promise<ChannelSetupWizard>,
): ChannelSetupWizard { ): ChannelSetupWizard {

View File

@@ -2,27 +2,21 @@ import {
buildSingleChannelSecretPromptState, buildSingleChannelSecretPromptState,
createStandardChannelSetupStatus, createStandardChannelSetupStatus,
DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID,
formatDocsLink,
hasConfiguredSecretInput, hasConfiguredSecretInput,
mergeAllowFromEntries,
promptSingleChannelSecretInput, promptSingleChannelSecretInput,
runSingleChannelSecretStep, runSingleChannelSecretStep,
type ChannelSetupDmPolicy,
type ChannelSetupWizard, type ChannelSetupWizard,
type OpenClawConfig, type OpenClawConfig,
type SecretInput, type SecretInput,
} from "openclaw/plugin-sdk/setup"; } from "openclaw/plugin-sdk/setup";
import { resolveZaloAccount } from "./accounts.js"; import { resolveZaloAccount } from "./accounts.js";
import { noteZaloTokenHelp } from "./setup-allow-from.js";
import { zaloDmPolicy } from "./setup-core.js"; import { zaloDmPolicy } from "./setup-core.js";
const channel = "zalo" as const; const channel = "zalo" as const;
type UpdateMode = "polling" | "webhook"; type UpdateMode = "polling" | "webhook";
type ZaloAccountSetupConfig = {
enabled?: boolean;
};
function setZaloUpdateMode( function setZaloUpdateMode(
cfg: OpenClawConfig, cfg: OpenClawConfig,
accountId: string, accountId: string,
@@ -98,86 +92,6 @@ function setZaloUpdateMode(
} as OpenClawConfig; } 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 { zaloSetupAdapter } from "./setup-core.js";
export const zaloSetupWizard: ChannelSetupWizard = { export const zaloSetupWizard: ChannelSetupWizard = {

View File

@@ -1,6 +1,6 @@
import { callGateway } from "../gateway/call.js"; import { callGateway } from "../gateway/call.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.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"; import { withProgress } from "./progress.js";
export async function callGatewayFromCliRuntime( export async function callGatewayFromCliRuntime(

View File

@@ -1,12 +1,6 @@
import type { Command } from "commander"; import type { Command } from "commander";
export type { GatewayRpcOpts } from "./gateway-rpc.types.js";
export type GatewayRpcOpts = { import type { GatewayRpcOpts } from "./gateway-rpc.types.js";
url?: string;
token?: string;
timeout?: string;
expectFinal?: boolean;
json?: boolean;
};
type GatewayRpcRuntimeModule = typeof import("./gateway-rpc.runtime.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;
};