From fbd88e2c8f0018070f97954ff085012e4037833b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 18 Mar 2026 00:30:01 -0700 Subject: [PATCH] Main recovery: restore formatter and contract checks (#49570) * Extensions: fix oxfmt drift on main * Plugins: restore runtime barrel exports on main * Config: restore web search compatibility types * Telegram: align test harness with reply runtime * Plugin SDK: fix channel config accessor generics * CLI: remove redundant search provider casts * Tests: restore main typecheck coverage * Lobster: fix test import formatting * Extensions: route bundled seams through plugin-sdk * Tests: use extension env helper for xai * Image generation: fix main oxfmt drift * Config: restore latest main compatibility checks * Plugin SDK: align guardrail tests with lint * Telegram: type native command skill mock --- .../acpx/src/runtime-internals/events.ts | 2 +- extensions/amazon-bedrock/index.test.ts | 2 +- .../brave/src/brave-web-search-provider.ts | 4 +- extensions/copilot-proxy/runtime-api.ts | 2 +- extensions/device-pair/api.ts | 2 +- extensions/diagnostics-otel/api.ts | 2 +- extensions/diffs/api.ts | 2 +- extensions/discord/src/directory-config.ts | 13 +-- extensions/discord/src/runtime-api.ts | 1 + extensions/google/runtime-api.ts | 2 +- extensions/imessage/runtime-api.ts | 22 ++--- extensions/irc/src/runtime-api.ts | 54 +---------- extensions/line/src/channel.ts | 1 + extensions/llm-task/api.ts | 2 +- extensions/lobster/runtime-api.ts | 2 +- extensions/lobster/src/lobster-tool.test.ts | 2 +- extensions/memory-lancedb/api.ts | 2 +- extensions/open-prose/runtime-api.ts | 2 +- extensions/phone-control/runtime-api.ts | 2 +- extensions/qwen-portal-auth/index.ts | 4 +- extensions/qwen-portal-auth/runtime-api.ts | 2 +- extensions/signal/src/channel.setup.ts | 2 +- extensions/signal/src/channel.ts | 20 ++-- extensions/signal/src/shared.ts | 12 +-- extensions/slack/src/channel.ts | 2 + extensions/slack/src/directory-config.ts | 13 +-- extensions/slack/src/runtime-api.ts | 33 +++---- extensions/talk-voice/api.ts | 2 +- extensions/telegram/runtime-api.ts | 68 +++++++++---- .../bot-native-commands.menu-test-support.ts | 13 ++- .../telegram/src/bot-native-commands.test.ts | 8 +- .../bot.create-telegram-bot.test-harness.ts | 97 ++++++++++--------- .../src/bot.create-telegram-bot.test.ts | 4 +- .../telegram/src/bot.fetch-abort.test.ts | 4 +- .../telegram/src/bot.media.e2e-harness.ts | 19 +++- .../telegram/src/bot.media.test-utils.ts | 6 +- extensions/telegram/src/bot.test.ts | 4 +- extensions/telegram/src/directory-config.ts | 13 +-- extensions/thread-ownership/api.ts | 2 +- extensions/twitch/api.ts | 1 - extensions/voice-call/api.ts | 2 +- extensions/whatsapp/src/runtime-api.ts | 20 ++-- extensions/xai/web-search.test.ts | 2 +- extensions/zalo/src/actions.ts | 2 +- extensions/zalo/src/channel.runtime.ts | 15 +-- extensions/zalo/src/channel.ts | 16 +-- extensions/zalo/src/config-schema.ts | 2 +- extensions/zalo/src/monitor.ts | 38 ++++---- extensions/zalo/src/monitor.webhook.ts | 6 +- extensions/zalo/src/send.ts | 2 +- extensions/zalo/src/token.ts | 2 +- .../openclaw-tools.image-generation.test.ts | 12 ++- .../extra-params.google.test.ts | 2 +- ...e-aliases-schemas-without-dropping.test.ts | 2 +- .../pi-tools.model-provider-collision.test.ts | 5 +- src/agents/tools/image-generate-tool.test.ts | 9 +- src/agents/tools/image-generate-tool.ts | 19 +++- src/agents/xai.live.test.ts | 2 +- src/channels/plugins/setup-wizard-helpers.ts | 9 +- src/commands/config-validation.test.ts | 3 +- src/commands/configure.wizard.ts | 10 +- .../doctor-legacy-config.migrations.test.ts | 2 + src/commands/doctor-legacy-config.ts | 19 +++- src/config/types.tools.ts | 22 +++++ src/config/zod-schema.agent-runtime.ts | 51 ++++++++++ src/image-generation/providers/fal.ts | 17 +++- src/infra/outbound/outbound-session.test.ts | 2 +- src/infra/outbound/outbound.test.ts | 2 +- src/plugin-sdk/acp-runtime.ts | 14 ++- src/plugin-sdk/channel-config-helpers.ts | 19 ++-- src/plugin-sdk/core.ts | 2 + .../package-contract-guardrails.test.ts | 8 +- src/plugin-sdk/telegram.ts | 5 +- src/plugins/contracts/shape.contract.test.ts | 1 + src/secrets/runtime-web-tools.test.ts | 5 +- src/web-search/runtime.test.ts | 1 + src/web-search/runtime.ts | 1 + ui/src/ui/views/config.browser.test.ts | 2 + 78 files changed, 476 insertions(+), 327 deletions(-) diff --git a/extensions/acpx/src/runtime-internals/events.ts b/extensions/acpx/src/runtime-internals/events.ts index 3bbfed68495..ac5f91acd5a 100644 --- a/extensions/acpx/src/runtime-internals/events.ts +++ b/extensions/acpx/src/runtime-internals/events.ts @@ -1,4 +1,4 @@ -import type { AcpRuntimeEvent, AcpSessionUpdateTag } from "../runtime-api.js"; +import type { AcpRuntimeEvent, AcpSessionUpdateTag } from "../../runtime-api.js"; import { asOptionalBoolean, asOptionalString, diff --git a/extensions/amazon-bedrock/index.test.ts b/extensions/amazon-bedrock/index.test.ts index 87ce6f6dcd2..049ebc45810 100644 --- a/extensions/amazon-bedrock/index.test.ts +++ b/extensions/amazon-bedrock/index.test.ts @@ -25,7 +25,7 @@ describe("amazon-bedrock provider plugin", () => { const wrapped = provider.wrapStreamFn?.({ provider: "amazon-bedrock", modelId: "amazon.nova-micro-v1:0", - streamFn: (_model, _context, options) => options, + streamFn: (_model: unknown, _context: unknown, options: Record) => options, } as never); expect( diff --git a/extensions/brave/src/brave-web-search-provider.ts b/extensions/brave/src/brave-web-search-provider.ts index 370fe77e854..3e1a6f1533a 100644 --- a/extensions/brave/src/brave-web-search-provider.ts +++ b/extensions/brave/src/brave-web-search-provider.ts @@ -132,8 +132,8 @@ function resolveBraveConfig( : ({ apiKey: (searchConfig as Record | undefined)?.apiKey } as BraveConfig); } -function resolveBraveMode(brave: BraveConfig): "web" | "llm-context" { - return brave.mode === "llm-context" ? "llm-context" : "web"; +function resolveBraveMode(brave?: BraveConfig): "web" | "llm-context" { + return brave?.mode === "llm-context" ? "llm-context" : "web"; } function resolveBraveApiKey( diff --git a/extensions/copilot-proxy/runtime-api.ts b/extensions/copilot-proxy/runtime-api.ts index 9f59e519281..849136c6efb 100644 --- a/extensions/copilot-proxy/runtime-api.ts +++ b/extensions/copilot-proxy/runtime-api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/copilot-proxy.js"; +export * from "openclaw/plugin-sdk/copilot-proxy"; diff --git a/extensions/device-pair/api.ts b/extensions/device-pair/api.ts index 137cd4b89ba..299ad90f05d 100644 --- a/extensions/device-pair/api.ts +++ b/extensions/device-pair/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/device-pair.js"; +export * from "openclaw/plugin-sdk/device-pair"; diff --git a/extensions/diagnostics-otel/api.ts b/extensions/diagnostics-otel/api.ts index 077ad45965f..01d7aed8989 100644 --- a/extensions/diagnostics-otel/api.ts +++ b/extensions/diagnostics-otel/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/diagnostics-otel.js"; +export * from "openclaw/plugin-sdk/diagnostics-otel"; diff --git a/extensions/diffs/api.ts b/extensions/diffs/api.ts index a200daea1fd..e6fbaf9022a 100644 --- a/extensions/diffs/api.ts +++ b/extensions/diffs/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/diffs.js"; +export * from "openclaw/plugin-sdk/diffs"; diff --git a/extensions/discord/src/directory-config.ts b/extensions/discord/src/directory-config.ts index af921c25165..eef67a25200 100644 --- a/extensions/discord/src/directory-config.ts +++ b/extensions/discord/src/directory-config.ts @@ -1,18 +1,16 @@ import { applyDirectoryQueryAndLimit, collectNormalizedDirectoryIds, - inspectReadOnlyChannelAccount, toDirectoryEntries, type DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; -import type { InspectedDiscordAccount } from "../api.js"; +import { inspectDiscordAccount, type InspectedDiscordAccount } from "../api.js"; export async function listDiscordDirectoryPeersFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "discord", + const account = inspectDiscordAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedDiscordAccount | null; + }) as InspectedDiscordAccount | null; if (!account || !("config" in account)) { return []; } @@ -34,11 +32,10 @@ export async function listDiscordDirectoryPeersFromConfig(params: DirectoryConfi } export async function listDiscordDirectoryGroupsFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "discord", + const account = inspectDiscordAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedDiscordAccount | null; + }) as InspectedDiscordAccount | null; if (!account || !("config" in account)) { return []; } diff --git a/extensions/discord/src/runtime-api.ts b/extensions/discord/src/runtime-api.ts index 2aadbf90b9a..32fbf43e5e5 100644 --- a/extensions/discord/src/runtime-api.ts +++ b/extensions/discord/src/runtime-api.ts @@ -40,6 +40,7 @@ export type { ChannelMessageActionAdapter, ChannelMessageActionName, } from "openclaw/plugin-sdk/channel-runtime"; +export type { DiscordConfig } from "openclaw/plugin-sdk/discord"; export { assertMediaNotDataUrl, parseAvailableTags, diff --git a/extensions/google/runtime-api.ts b/extensions/google/runtime-api.ts index 3eaab2b0faf..7deb5b38f92 100644 --- a/extensions/google/runtime-api.ts +++ b/extensions/google/runtime-api.ts @@ -1 +1 @@ -export { normalizeGoogleModelId, parseGeminiAuth } from "openclaw/plugin-sdk/google"; +export * from "openclaw/plugin-sdk/google"; diff --git a/extensions/imessage/runtime-api.ts b/extensions/imessage/runtime-api.ts index 6cd9966f193..aa6d55c75e5 100644 --- a/extensions/imessage/runtime-api.ts +++ b/extensions/imessage/runtime-api.ts @@ -1,23 +1,19 @@ -export type { IMessageAccountConfig } from "../../src/config/types.imessage.js"; -export type { ChannelPlugin } from "../../src/channels/plugins/types.plugin.js"; export { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, buildChannelConfigSchema, - getChatChannelMeta, -} from "../../src/plugin-sdk/channel-plugin-common.js"; -export { + collectStatusIssuesFromLastError, formatTrimmedAllowFromEntries, - resolveIMessageConfigAllowFrom, - resolveIMessageConfigDefaultTo, -} from "../../src/plugin-sdk/channel-config-helpers.js"; -export { collectStatusIssuesFromLastError } from "../../src/plugin-sdk/status-helpers.js"; -export { resolveChannelMediaMaxBytes } from "../../src/channels/plugins/media-limits.js"; -export { + getChatChannelMeta, looksLikeIMessageTargetId, normalizeIMessageMessagingTarget, -} from "../../src/channels/plugins/normalize/imessage.js"; -export { IMessageConfigSchema } from "../../src/config/zod-schema.providers-core.js"; + resolveChannelMediaMaxBytes, + resolveIMessageConfigAllowFrom, + resolveIMessageConfigDefaultTo, + IMessageConfigSchema, + type ChannelPlugin, + type IMessageAccountConfig, +} from "openclaw/plugin-sdk/imessage"; export { resolveIMessageGroupRequireMention, resolveIMessageGroupToolPolicy, diff --git a/extensions/irc/src/runtime-api.ts b/extensions/irc/src/runtime-api.ts index eebfe798ede..93214aeda45 100644 --- a/extensions/irc/src/runtime-api.ts +++ b/extensions/irc/src/runtime-api.ts @@ -1,53 +1 @@ -export { - addWildcardAllowFrom, - buildBaseAccountStatusSnapshot, - buildBaseChannelStatusSummary, - buildChannelConfigSchema, - createAccountListHelpers, - createAccountStatusSink, - createLoggerBackedRuntime, - createNormalizedOutboundDeliverer, - createReplyPrefixOptions, - createScopedPairingAccess, - dispatchInboundReplyWithBase, - emptyPluginConfigSchema, - formatDocsLink, - formatPairingApproveHint, - formatTextWithAttachmentLinks, - getChatChannelMeta, - GROUP_POLICY_BLOCKED_LABEL, - isDangerousNameMatchingEnabled, - issuePairingChallenge, - logInboundDrop, - normalizeResolvedSecretInputString, - parseOptionalDelimitedEntries, - PAIRING_APPROVED_MESSAGE, - patchScopedAccountConfig, - readStoreAllowFromForDmPolicy, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveControlCommandGate, - resolveDefaultGroupPolicy, - resolveEffectiveAllowFromLists, - resolveOutboundMediaUrls, - runPassiveAccountLifecycle, - setAccountEnabledInConfigSection, - setTopLevelChannelAllowFrom, - setTopLevelChannelDmPolicyWithAllowFrom, - ToolPolicySchema, - warnMissingProviderGroupPolicyFallbackOnce, - type BaseProbeResult, - type BlockStreamingCoalesceConfig, - type ChannelPlugin, - type DmConfig, - type DmPolicy, - type GroupPolicy, - type GroupToolPolicyBySenderConfig, - type GroupToolPolicyConfig, - type MarkdownConfig, - type OpenClawConfig, - type OpenClawPluginApi, - type OutboundReplyPayload, - type PluginRuntime, - type RuntimeEnv, - type WizardPrompter, -} from "openclaw/plugin-sdk/irc"; +export * from "openclaw/plugin-sdk/irc"; diff --git a/extensions/line/src/channel.ts b/extensions/line/src/channel.ts index cd3fab965cc..33f2b7aa247 100644 --- a/extensions/line/src/channel.ts +++ b/extensions/line/src/channel.ts @@ -12,6 +12,7 @@ import { type ChannelStatusIssue, type LineConfig, type LineChannelData, + type OpenClawConfig, type ResolvedLineAccount, } from "../api.js"; import { lineConfigAdapter } from "./config-adapter.js"; diff --git a/extensions/llm-task/api.ts b/extensions/llm-task/api.ts index 25e5e13d5ca..8eebdd06e0b 100644 --- a/extensions/llm-task/api.ts +++ b/extensions/llm-task/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/llm-task.js"; +export * from "openclaw/plugin-sdk/llm-task"; diff --git a/extensions/lobster/runtime-api.ts b/extensions/lobster/runtime-api.ts index 24898e04cf5..7ab2351b77d 100644 --- a/extensions/lobster/runtime-api.ts +++ b/extensions/lobster/runtime-api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/lobster.js"; +export * from "openclaw/plugin-sdk/lobster"; diff --git a/extensions/lobster/src/lobster-tool.test.ts b/extensions/lobster/src/lobster-tool.test.ts index 8c010e20f11..778cb695d88 100644 --- a/extensions/lobster/src/lobster-tool.test.ts +++ b/extensions/lobster/src/lobster-tool.test.ts @@ -3,8 +3,8 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { PassThrough } from "node:stream"; -import type { OpenClawPluginApi, OpenClawPluginToolContext } from "../runtime-api.js"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import type { OpenClawPluginApi, OpenClawPluginToolContext } from "../runtime-api.js"; import { createWindowsCmdShimFixture, restorePlatformPathEnv, diff --git a/extensions/memory-lancedb/api.ts b/extensions/memory-lancedb/api.ts index ce6e02cf02f..c1bd12dd4b7 100644 --- a/extensions/memory-lancedb/api.ts +++ b/extensions/memory-lancedb/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/memory-lancedb.js"; +export * from "openclaw/plugin-sdk/memory-lancedb"; diff --git a/extensions/open-prose/runtime-api.ts b/extensions/open-prose/runtime-api.ts index 1a7ce98ffef..1601f81be1f 100644 --- a/extensions/open-prose/runtime-api.ts +++ b/extensions/open-prose/runtime-api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/open-prose.js"; +export * from "openclaw/plugin-sdk/open-prose"; diff --git a/extensions/phone-control/runtime-api.ts b/extensions/phone-control/runtime-api.ts index c113b9802be..2e9e0adeba2 100644 --- a/extensions/phone-control/runtime-api.ts +++ b/extensions/phone-control/runtime-api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/phone-control.js"; +export * from "openclaw/plugin-sdk/phone-control"; diff --git a/extensions/qwen-portal-auth/index.ts b/extensions/qwen-portal-auth/index.ts index 384f58f4845..c5789e6cc08 100644 --- a/extensions/qwen-portal-auth/index.ts +++ b/extensions/qwen-portal-auth/index.ts @@ -1,5 +1,7 @@ import { ensureAuthProfileStore, listProfilesForProvider } from "openclaw/plugin-sdk/agent-runtime"; import { QWEN_OAUTH_MARKER } from "openclaw/plugin-sdk/agent-runtime"; +import { loginQwenPortalOAuth } from "./oauth.js"; +import { buildQwenPortalProvider, QWEN_PORTAL_BASE_URL } from "./provider-catalog.js"; import { buildOauthProviderAuthResult, definePluginEntry, @@ -7,8 +9,6 @@ import { type ProviderAuthContext, type ProviderCatalogContext, } from "./runtime-api.js"; -import { loginQwenPortalOAuth } from "./oauth.js"; -import { buildQwenPortalProvider, QWEN_PORTAL_BASE_URL } from "./provider-catalog.js"; const PROVIDER_ID = "qwen-portal"; const PROVIDER_LABEL = "Qwen"; diff --git a/extensions/qwen-portal-auth/runtime-api.ts b/extensions/qwen-portal-auth/runtime-api.ts index ccd9abae569..232a2886110 100644 --- a/extensions/qwen-portal-auth/runtime-api.ts +++ b/extensions/qwen-portal-auth/runtime-api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/qwen-portal-auth.js"; +export * from "openclaw/plugin-sdk/qwen-portal-auth"; diff --git a/extensions/signal/src/channel.setup.ts b/extensions/signal/src/channel.setup.ts index df5337a4761..d51edcaca10 100644 --- a/extensions/signal/src/channel.setup.ts +++ b/extensions/signal/src/channel.setup.ts @@ -1,6 +1,6 @@ import { type ResolvedSignalAccount } from "./accounts.js"; -import { signalSetupAdapter } from "./setup-core.js"; import { type ChannelPlugin } from "./runtime-api.js"; +import { signalSetupAdapter } from "./setup-core.js"; import { createSignalPluginBase, signalSetupWizard } from "./shared.js"; export const signalSetupPlugin: ChannelPlugin = { diff --git a/extensions/signal/src/channel.ts b/extensions/signal/src/channel.ts index 85aaadbd2c1..1879c85a7b0 100644 --- a/extensions/signal/src/channel.ts +++ b/extensions/signal/src/channel.ts @@ -4,6 +4,16 @@ import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime"; import { buildOutboundBaseSessionKey } from "openclaw/plugin-sdk/core"; import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime"; import { type RoutePeer } from "openclaw/plugin-sdk/routing"; +import { resolveSignalAccount, type ResolvedSignalAccount } from "./accounts.js"; +import { markdownToSignalTextChunks } from "./format.js"; +import { + looksLikeUuid, + resolveSignalPeerId, + resolveSignalRecipient, + resolveSignalSender, +} from "./identity.js"; +import { signalMessageActions } from "./message-actions.js"; +import type { SignalProbe } from "./probe.js"; import { buildBaseAccountStatusSnapshot, buildBaseChannelStatusSummary, @@ -17,16 +27,6 @@ import { resolveChannelMediaMaxBytes, type ChannelPlugin, } from "./runtime-api.js"; -import { resolveSignalAccount, type ResolvedSignalAccount } from "./accounts.js"; -import { markdownToSignalTextChunks } from "./format.js"; -import { - looksLikeUuid, - resolveSignalPeerId, - resolveSignalRecipient, - resolveSignalSender, -} from "./identity.js"; -import { signalMessageActions } from "./message-actions.js"; -import type { SignalProbe } from "./probe.js"; import { getSignalRuntime } from "./runtime.js"; import { signalSetupAdapter } from "./setup-core.js"; import { diff --git a/extensions/signal/src/shared.ts b/extensions/signal/src/shared.ts index 1a0579e0236..1622dc207e4 100644 --- a/extensions/signal/src/shared.ts +++ b/extensions/signal/src/shared.ts @@ -4,6 +4,12 @@ import { createScopedDmSecurityResolver, } from "openclaw/plugin-sdk/channel-config-helpers"; import { createChannelPluginBase } from "openclaw/plugin-sdk/core"; +import { + listSignalAccountIds, + resolveDefaultSignalAccountId, + resolveSignalAccount, + type ResolvedSignalAccount, +} from "./accounts.js"; import { buildChannelConfigSchema, getChatChannelMeta, @@ -11,12 +17,6 @@ import { SignalConfigSchema, type ChannelPlugin, } from "./runtime-api.js"; -import { - listSignalAccountIds, - resolveDefaultSignalAccountId, - resolveSignalAccount, - type ResolvedSignalAccount, -} from "./accounts.js"; import { createSignalSetupWizardProxy } from "./setup-core.js"; export const SIGNAL_CHANNEL = "signal" as const; diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index 417f3b9a3b4..cbb86a1dff1 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -29,6 +29,8 @@ import { resolveSlackUserAllowlist } from "./resolve-users.js"; import { buildComputedAccountStatusSnapshot, DEFAULT_ACCOUNT_ID, + listSlackDirectoryGroupsFromConfig, + listSlackDirectoryPeersFromConfig, looksLikeSlackTargetId, normalizeSlackMessagingTarget, PAIRING_APPROVED_MESSAGE, diff --git a/extensions/slack/src/directory-config.ts b/extensions/slack/src/directory-config.ts index a74b2e4079d..8d7d4604ea1 100644 --- a/extensions/slack/src/directory-config.ts +++ b/extensions/slack/src/directory-config.ts @@ -1,20 +1,18 @@ import { applyDirectoryQueryAndLimit, collectNormalizedDirectoryIds, - inspectReadOnlyChannelAccount, listDirectoryGroupEntriesFromMapKeys, toDirectoryEntries, type DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; -import type { InspectedSlackAccount } from "../api.js"; +import { inspectSlackAccount, type InspectedSlackAccount } from "../api.js"; import { parseSlackTarget } from "./targets.js"; export async function listSlackDirectoryPeersFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "slack", + const account = inspectSlackAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedSlackAccount | null; + }) as InspectedSlackAccount | null; if (!account || !("config" in account)) { return []; } @@ -40,11 +38,10 @@ export async function listSlackDirectoryPeersFromConfig(params: DirectoryConfigP } export async function listSlackDirectoryGroupsFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "slack", + const account = inspectSlackAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedSlackAccount | null; + }) as InspectedSlackAccount | null; if (!account || !("config" in account)) { return []; } diff --git a/extensions/slack/src/runtime-api.ts b/extensions/slack/src/runtime-api.ts index 4988fa5d4f4..5dac68be756 100644 --- a/extensions/slack/src/runtime-api.ts +++ b/extensions/slack/src/runtime-api.ts @@ -1,34 +1,29 @@ -export type { OpenClawConfig } from "../../../src/config/config.js"; -export type { SlackAccountConfig } from "../../../src/config/types.slack.js"; -export type { ChannelPlugin } from "../../../src/channels/plugins/types.js"; - export { + buildComputedAccountStatusSnapshot, DEFAULT_ACCOUNT_ID, - buildChannelConfigSchema, - getChatChannelMeta, + looksLikeSlackTargetId, + normalizeSlackMessagingTarget, PAIRING_APPROVED_MESSAGE, -} from "../../../src/plugin-sdk/channel-plugin-common.js"; -export { buildComputedAccountStatusSnapshot } from "../../../src/plugin-sdk/status-helpers.js"; + projectCredentialSnapshotFields, + resolveConfiguredFromRequiredCredentialStatuses, + type ChannelPlugin, + type OpenClawConfig, + type SlackAccountConfig, +} from "openclaw/plugin-sdk/slack"; export { listSlackDirectoryGroupsFromConfig, listSlackDirectoryPeersFromConfig, } from "./directory-config.js"; export { - looksLikeSlackTargetId, - normalizeSlackMessagingTarget, -} from "../../../src/channels/plugins/normalize/slack.js"; -export { - projectCredentialSnapshotFields, - resolveConfiguredFromRequiredCredentialStatuses, -} from "../../../src/channels/account-snapshot-fields.js"; -export { SlackConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; -export { + buildChannelConfigSchema, + getChatChannelMeta, createActionGate, imageResultFromFile, jsonResult, readNumberParam, readReactionParams, readStringParam, -} from "../../../src/agents/tools/common.js"; -export { withNormalizedTimestamp } from "../../../src/agents/date-time.js"; + SlackConfigSchema, + withNormalizedTimestamp, +} from "openclaw/plugin-sdk/slack-core"; export { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js"; diff --git a/extensions/talk-voice/api.ts b/extensions/talk-voice/api.ts index 5f50f1a5247..a5ae821e944 100644 --- a/extensions/talk-voice/api.ts +++ b/extensions/talk-voice/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/talk-voice.js"; +export * from "openclaw/plugin-sdk/talk-voice"; diff --git a/extensions/telegram/runtime-api.ts b/extensions/telegram/runtime-api.ts index b645e653834..c069a35e40e 100644 --- a/extensions/telegram/runtime-api.ts +++ b/extensions/telegram/runtime-api.ts @@ -1,16 +1,18 @@ export type { + ChannelMessageActionAdapter, ChannelPlugin, OpenClawConfig, - TelegramActionConfig, -} from "../../src/plugin-sdk/telegram-core.js"; -export type { ChannelMessageActionAdapter } from "../../src/channels/plugins/types.js"; -export type { TelegramAccountConfig, TelegramNetworkConfig } from "../../src/config/types.js"; -export type { OpenClawPluginApi, + PluginRuntime, + TelegramAccountConfig, + TelegramActionConfig, + TelegramNetworkConfig, +} from "openclaw/plugin-sdk/telegram"; +export type { OpenClawPluginService, OpenClawPluginServiceContext, PluginLogger, -} from "../../src/plugins/types.js"; +} from "openclaw/plugin-sdk/core"; export type { AcpRuntime, AcpRuntimeCapabilities, @@ -20,12 +22,22 @@ export type { AcpRuntimeHandle, AcpRuntimeStatus, AcpRuntimeTurnInput, + AcpRuntimeErrorCode, AcpSessionUpdateTag, -} from "../../src/acp/runtime/types.js"; -export type { AcpRuntimeErrorCode } from "../../src/acp/runtime/errors.js"; -export { AcpRuntimeError } from "../../src/acp/runtime/errors.js"; +} from "openclaw/plugin-sdk/acp-runtime"; +export { AcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime"; -export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../src/routing/session-key.js"; +export { + buildTokenChannelStatusSummary, + clearAccountEntryFields, + DEFAULT_ACCOUNT_ID, + normalizeAccountId, + PAIRING_APPROVED_MESSAGE, + parseTelegramTopicConversation, + projectCredentialSnapshotFields, + resolveConfiguredFromCredentialStatuses, + resolveTelegramPollVisibility, +} from "openclaw/plugin-sdk/telegram"; export { buildChannelConfigSchema, getChatChannelMeta, @@ -37,13 +49,31 @@ export { readStringParam, resolvePollMaxSelections, TelegramConfigSchema, -} from "../../src/plugin-sdk/telegram-core.js"; -export { parseTelegramTopicConversation } from "../../src/acp/conversation-id.js"; -export { clearAccountEntryFields } from "../../src/channels/plugins/config-helpers.js"; -export { buildTokenChannelStatusSummary } from "../../src/plugin-sdk/status-helpers.js"; +} from "openclaw/plugin-sdk/telegram-core"; +export type { TelegramProbe } from "./src/probe.js"; +export { auditTelegramGroupMembership, collectTelegramUnmentionedGroupIds } from "./src/audit.js"; +export { telegramMessageActions } from "./src/channel-actions.js"; +export { monitorTelegramProvider } from "./src/monitor.js"; +export { probeTelegram } from "./src/probe.js"; export { - projectCredentialSnapshotFields, - resolveConfiguredFromCredentialStatuses, -} from "../../src/channels/account-snapshot-fields.js"; -export { resolveTelegramPollVisibility } from "../../src/poll-params.js"; -export { PAIRING_APPROVED_MESSAGE } from "../../src/channels/plugins/pairing-message.js"; + createForumTopicTelegram, + deleteMessageTelegram, + editForumTopicTelegram, + editMessageReplyMarkupTelegram, + editMessageTelegram, + pinMessageTelegram, + reactMessageTelegram, + renameForumTopicTelegram, + sendMessageTelegram, + sendPollTelegram, + sendStickerTelegram, + sendTypingTelegram, + unpinMessageTelegram, +} from "./src/send.js"; +export { + createTelegramThreadBindingManager, + getTelegramThreadBindingManager, + setTelegramThreadBindingIdleTimeoutBySessionKey, + setTelegramThreadBindingMaxAgeBySessionKey, +} from "./src/thread-bindings.js"; +export { resolveTelegramToken } from "./src/token.js"; diff --git a/extensions/telegram/src/bot-native-commands.menu-test-support.ts b/extensions/telegram/src/bot-native-commands.menu-test-support.ts index 5d0f90257e5..8b68368d84f 100644 --- a/extensions/telegram/src/bot-native-commands.menu-test-support.ts +++ b/extensions/telegram/src/bot-native-commands.menu-test-support.ts @@ -1,5 +1,6 @@ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; import { expect, vi } from "vitest"; +import type { SkillCommandSpec } from "../../../src/agents/skills.js"; import type { OpenClawConfig } from "../runtime-api.js"; import type { TelegramBotDeps } from "./bot-deps.js"; import { @@ -8,6 +9,12 @@ import { type NativeCommandTestParams as RegisterTelegramNativeCommandsParams, } from "./bot-native-commands.fixture-test-support.js"; +const EMPTY_REPLY_COUNTS = { + block: 0, + final: 0, + tool: 0, +} as const; + type RegisteredCommand = { command: string; description: string; @@ -21,7 +28,9 @@ type CreateCommandBotResult = { }; const skillCommandMocks = vi.hoisted(() => ({ - listSkillCommandsForAgents: vi.fn(() => []), + listSkillCommandsForAgents: vi.fn< + (params: { cfg: OpenClawConfig; agentIds?: string[] }) => SkillCommandSpec[] + >(() => []), })); const deliveryMocks = vi.hoisted(() => ({ @@ -86,7 +95,7 @@ export function createNativeCommandTestParams( enqueueSystemEvent: vi.fn(), dispatchReplyWithBufferedBlockDispatcher: vi.fn(async () => ({ queuedFinal: false, - counts: {}, + counts: EMPTY_REPLY_COUNTS, })), listSkillCommandsForAgents, wasSentByBot: vi.fn(() => false), diff --git a/extensions/telegram/src/bot-native-commands.test.ts b/extensions/telegram/src/bot-native-commands.test.ts index f2737d98f89..043baf9b2b6 100644 --- a/extensions/telegram/src/bot-native-commands.test.ts +++ b/extensions/telegram/src/bot-native-commands.test.ts @@ -37,6 +37,12 @@ import { waitForRegisteredCommands, } from "./bot-native-commands.menu-test-support.js"; +const EMPTY_REPLY_COUNTS = { + block: 0, + final: 0, + tool: 0, +} as const; + function createNativeCommandTestParams( cfg: OpenClawConfig, params: Partial[0]> = {}, @@ -48,7 +54,7 @@ function createNativeCommandTestParams( enqueueSystemEvent: vi.fn(), dispatchReplyWithBufferedBlockDispatcher: vi.fn(async () => ({ queuedFinal: false, - counts: {}, + counts: EMPTY_REPLY_COUNTS, })), listSkillCommandsForAgents: skillCommandMocks.listSkillCommandsForAgents, wasSentByBot: vi.fn(() => false), diff --git a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts index 648638bd23b..f2f8f89ce63 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test-harness.ts @@ -4,23 +4,21 @@ import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime"; import type { GetReplyOptions, ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; import type { MockFn } from "openclaw/plugin-sdk/testing"; import { beforeEach, vi } from "vitest"; +import type { TelegramBotDeps } from "./bot-deps.js"; -type AnyMock = MockFn<(...args: unknown[]) => unknown>; -type AnyAsyncMock = MockFn<(...args: unknown[]) => Promise>; +type AnyMock = ReturnType; +type AnyAsyncMock = ReturnType; type DispatchReplyWithBufferedBlockDispatcherFn = typeof import("openclaw/plugin-sdk/reply-runtime").dispatchReplyWithBufferedBlockDispatcher; type DispatchReplyWithBufferedBlockDispatcherResult = Awaited< ReturnType >; -type DispatchReplyHarnessParams = { - ctx: MsgContext; - replyOptions?: GetReplyOptions; - dispatcherOptions?: { - typingCallbacks?: { - start?: () => void | Promise; - }; - deliver?: (payload: ReplyPayload, info: { kind: "final" }) => void | Promise; - }; +type DispatchReplyHarnessParams = Parameters[0]; + +const EMPTY_REPLY_COUNTS: DispatchReplyWithBufferedBlockDispatcherResult["counts"] = { + block: 0, + final: 0, + tool: 0, }; const { sessionStorePath } = vi.hoisted(() => ({ @@ -39,12 +37,14 @@ vi.doMock("openclaw/plugin-sdk/web-media", () => ({ loadWebMedia, })); -const { loadConfig } = vi.hoisted((): { loadConfig: AnyMock } => ({ - loadConfig: vi.fn(() => ({})), -})); -const { resolveStorePathMock } = vi.hoisted((): { resolveStorePathMock: AnyMock } => ({ - resolveStorePathMock: vi.fn((storePath?: string) => storePath ?? sessionStorePath), +const { loadConfig } = vi.hoisted((): { loadConfig: MockFn<() => OpenClawConfig> } => ({ + loadConfig: vi.fn(() => ({}) as OpenClawConfig), })); +const { resolveStorePathMock } = vi.hoisted( + (): { resolveStorePathMock: MockFn } => ({ + resolveStorePathMock: vi.fn((storePath?: string) => storePath ?? sessionStorePath), + }), +); export function getLoadConfigMock(): AnyMock { return loadConfig; @@ -67,7 +67,7 @@ vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => { const { readChannelAllowFromStore, upsertChannelPairingRequest } = vi.hoisted( (): { - readChannelAllowFromStore: AnyAsyncMock; + readChannelAllowFromStore: MockFn; upsertChannelPairingRequest: AnyAsyncMock; } => ({ readChannelAllowFromStore: vi.fn(async () => [] as string[]), @@ -111,9 +111,9 @@ const skillCommandsHoisted = vi.hoisted(() => ({ async (params: DispatchReplyHarnessParams) => { const result: DispatchReplyWithBufferedBlockDispatcherResult = { queuedFinal: false, - counts: {} as DispatchReplyWithBufferedBlockDispatcherResult["counts"], + counts: EMPTY_REPLY_COUNTS, }; - await params.dispatcherOptions?.typingCallbacks?.start?.(); + await params.dispatcherOptions?.typingCallbacks?.onReplyStart?.(); const reply = await skillCommandsHoisted.replySpy(params.ctx, params.replyOptions); const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply]; for (const payload of payloads) { @@ -141,9 +141,10 @@ vi.doMock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => { }); const systemEventsHoisted = vi.hoisted(() => ({ - enqueueSystemEventSpy: vi.fn(), + enqueueSystemEventSpy: vi.fn(() => false), })); -export const enqueueSystemEventSpy: AnyMock = systemEventsHoisted.enqueueSystemEventSpy; +export const enqueueSystemEventSpy: MockFn = + systemEventsHoisted.enqueueSystemEventSpy; vi.doMock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { const actual = await importOriginal(); @@ -173,7 +174,7 @@ const grammySpies = vi.hoisted(() => ({ onSpy: vi.fn() as AnyMock, stopSpy: vi.fn() as AnyMock, commandSpy: vi.fn() as AnyMock, - botCtorSpy: vi.fn() as AnyMock, + botCtorSpy: vi.fn((_: string, __?: { client?: { fetch?: typeof fetch } }) => undefined), answerCallbackQuerySpy: vi.fn(async () => undefined) as AnyAsyncMock, sendChatActionSpy: vi.fn() as AnyMock, editMessageTextSpy: vi.fn(async () => ({ message_id: 88 })) as AnyAsyncMock, @@ -191,26 +192,26 @@ const grammySpies = vi.hoisted(() => ({ getFileSpy: vi.fn(async () => ({ file_path: "media/file.jpg" })) as AnyAsyncMock, })); -export const { - useSpy, - middlewareUseSpy, - onSpy, - stopSpy, - commandSpy, - botCtorSpy, - answerCallbackQuerySpy, - sendChatActionSpy, - editMessageTextSpy, - editMessageReplyMarkupSpy, - sendMessageDraftSpy, - setMessageReactionSpy, - setMyCommandsSpy, - getMeSpy, - sendMessageSpy, - sendAnimationSpy, - sendPhotoSpy, - getFileSpy, -} = grammySpies; +export const useSpy: MockFn<(arg: unknown) => void> = grammySpies.useSpy; +export const middlewareUseSpy: AnyMock = grammySpies.middlewareUseSpy; +export const onSpy: AnyMock = grammySpies.onSpy; +export const stopSpy: AnyMock = grammySpies.stopSpy; +export const commandSpy: AnyMock = grammySpies.commandSpy; +export const botCtorSpy: MockFn< + (token: string, options?: { client?: { fetch?: typeof fetch } }) => void +> = grammySpies.botCtorSpy; +export const answerCallbackQuerySpy: AnyAsyncMock = grammySpies.answerCallbackQuerySpy; +export const sendChatActionSpy: AnyMock = grammySpies.sendChatActionSpy; +export const editMessageTextSpy: AnyAsyncMock = grammySpies.editMessageTextSpy; +export const editMessageReplyMarkupSpy: AnyAsyncMock = grammySpies.editMessageReplyMarkupSpy; +export const sendMessageDraftSpy: AnyAsyncMock = grammySpies.sendMessageDraftSpy; +export const setMessageReactionSpy: AnyAsyncMock = grammySpies.setMessageReactionSpy; +export const setMyCommandsSpy: AnyAsyncMock = grammySpies.setMyCommandsSpy; +export const getMeSpy: AnyAsyncMock = grammySpies.getMeSpy; +export const sendMessageSpy: AnyAsyncMock = grammySpies.sendMessageSpy; +export const sendAnimationSpy: AnyAsyncMock = grammySpies.sendAnimationSpy; +export const sendPhotoSpy: AnyAsyncMock = grammySpies.sendPhotoSpy; +export const getFileSpy: AnyAsyncMock = grammySpies.getFileSpy; const runnerHoisted = vi.hoisted(() => ({ sequentializeMiddleware: vi.fn(async (_ctx: unknown, next?: () => Promise) => { @@ -224,7 +225,11 @@ const runnerHoisted = vi.hoisted(() => ({ export const sequentializeSpy: AnyMock = runnerHoisted.sequentializeSpy; export let sequentializeKey: ((ctx: unknown) => string) | undefined; export const throttlerSpy: AnyMock = runnerHoisted.throttlerSpy; -export const telegramBotRuntimeForTest = { +export const telegramBotRuntimeForTest: { + Bot: new (token: string, options?: { client?: { fetch?: typeof fetch } }) => unknown; + sequentialize: (keyFn: (ctx: unknown) => string) => unknown; + apiThrottler: () => unknown; +} = { Bot: class { api = { config: { use: grammySpies.useSpy }, @@ -259,7 +264,7 @@ export const telegramBotRuntimeForTest = { }, apiThrottler: () => runnerHoisted.throttlerSpy(), }; -export const telegramBotDepsForTest = { +export const telegramBotDepsForTest: TelegramBotDeps = { loadConfig, resolveStorePath: resolveStorePathMock, readChannelAllowFromStore, @@ -365,9 +370,9 @@ beforeEach(() => { async (params: DispatchReplyHarnessParams) => { const result: DispatchReplyWithBufferedBlockDispatcherResult = { queuedFinal: false, - counts: {} as DispatchReplyWithBufferedBlockDispatcherResult["counts"], + counts: EMPTY_REPLY_COUNTS, }; - await params.dispatcherOptions?.typingCallbacks?.start?.(); + await params.dispatcherOptions?.typingCallbacks?.onReplyStart?.(); const reply = await replySpy(params.ctx, params.replyOptions); const payloads = reply === undefined ? [] : Array.isArray(reply) ? reply : [reply]; for (const payload of payloads) { diff --git a/extensions/telegram/src/bot.create-telegram-bot.test.ts b/extensions/telegram/src/bot.create-telegram-bot.test.ts index b9098fc7b37..5c05d54a2c7 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test.ts @@ -39,7 +39,9 @@ const { getTelegramSequentialKey, setTelegramBotRuntimeForTest, } = await import("./bot.js"); -setTelegramBotRuntimeForTest(telegramBotRuntimeForTest); +setTelegramBotRuntimeForTest( + telegramBotRuntimeForTest as unknown as Parameters[0], +); const createTelegramBot = (opts: Parameters[0]) => createTelegramBotBase({ ...opts, diff --git a/extensions/telegram/src/bot.fetch-abort.test.ts b/extensions/telegram/src/bot.fetch-abort.test.ts index 7a58aa86e87..63e53a1ba5c 100644 --- a/extensions/telegram/src/bot.fetch-abort.test.ts +++ b/extensions/telegram/src/bot.fetch-abort.test.ts @@ -6,7 +6,9 @@ const { botCtorSpy, telegramBotDepsForTest } = const { telegramBotRuntimeForTest } = await import("./bot.create-telegram-bot.test-harness.js"); const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } = await import("./bot.js"); -setTelegramBotRuntimeForTest(telegramBotRuntimeForTest); +setTelegramBotRuntimeForTest( + telegramBotRuntimeForTest as unknown as Parameters[0], +); const createTelegramBot = (opts: Parameters[0]) => createTelegramBotBase({ ...opts, diff --git a/extensions/telegram/src/bot.media.e2e-harness.ts b/extensions/telegram/src/bot.media.e2e-harness.ts index e21308c7403..7054b69d06a 100644 --- a/extensions/telegram/src/bot.media.e2e-harness.ts +++ b/extensions/telegram/src/bot.media.e2e-harness.ts @@ -1,5 +1,12 @@ import { resetInboundDedupe } from "openclaw/plugin-sdk/reply-runtime"; import { beforeEach, vi, type Mock } from "vitest"; +import type { TelegramBotDeps } from "./bot-deps.js"; + +const EMPTY_REPLY_COUNTS = { + block: 0, + final: 0, + tool: 0, +} as const; export const useSpy: Mock = vi.fn(); export const middlewareUseSpy: Mock = vi.fn(); @@ -56,7 +63,11 @@ const apiStub: ApiStub = { setMyCommands: vi.fn(async () => undefined), }; -export const telegramBotRuntimeForTest = { +export const telegramBotRuntimeForTest: { + Bot: new (token: string) => unknown; + sequentialize: () => unknown; + apiThrottler: () => unknown; +} = { Bot: class { api = apiStub; use = middlewareUseSpy; @@ -84,12 +95,12 @@ const mediaHarnessDispatchReplyWithBufferedBlockDispatcher = vi.hoisted(() => for (const payload of payloads) { await params.dispatcherOptions?.deliver?.(payload, { kind: "final" }); } - return { queuedFinal: false, counts: {} }; + return { queuedFinal: false, counts: EMPTY_REPLY_COUNTS }; }), ); -export const telegramBotDepsForTest = { +export const telegramBotDepsForTest: TelegramBotDeps = { loadConfig: () => ({ - channels: { telegram: { dmPolicy: "open", allowFrom: ["*"] } }, + channels: { telegram: { dmPolicy: "open" as const, allowFrom: ["*"] } }, }), resolveStorePath: vi.fn((storePath?: string) => storePath ?? "/tmp/telegram-media-sessions.json"), readChannelAllowFromStore: vi.fn(async () => [] as string[]), diff --git a/extensions/telegram/src/bot.media.test-utils.ts b/extensions/telegram/src/bot.media.test-utils.ts index a98afa96b69..7c391642d67 100644 --- a/extensions/telegram/src/bot.media.test-utils.ts +++ b/extensions/telegram/src/bot.media.test-utils.ts @@ -107,7 +107,11 @@ beforeAll(async () => { onSpyRef = harness.onSpy; sendChatActionSpyRef = harness.sendChatActionSpy; const botModule = await import("./bot.js"); - botModule.setTelegramBotRuntimeForTest(harness.telegramBotRuntimeForTest); + botModule.setTelegramBotRuntimeForTest( + harness.telegramBotRuntimeForTest as unknown as Parameters< + typeof botModule.setTelegramBotRuntimeForTest + >[0], + ); createTelegramBotRef = (opts) => botModule.createTelegramBot({ ...opts, diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index 7df6fa8816b..2de1e06fc6d 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -35,7 +35,9 @@ const { normalizeTelegramCommandName } = await import("../../../src/config/telegram-custom-commands.js"); const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } = await import("./bot.js"); -setTelegramBotRuntimeForTest(telegramBotRuntimeForTest); +setTelegramBotRuntimeForTest( + telegramBotRuntimeForTest as unknown as Parameters[0], +); const createTelegramBot = (opts: Parameters[0]) => createTelegramBotBase({ ...opts, diff --git a/extensions/telegram/src/directory-config.ts b/extensions/telegram/src/directory-config.ts index 08b9c3597e2..5aeb9785779 100644 --- a/extensions/telegram/src/directory-config.ts +++ b/extensions/telegram/src/directory-config.ts @@ -2,19 +2,17 @@ import { mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers" import { applyDirectoryQueryAndLimit, collectNormalizedDirectoryIds, - inspectReadOnlyChannelAccount, listDirectoryGroupEntriesFromMapKeys, toDirectoryEntries, type DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; -import type { InspectedTelegramAccount } from "../api.js"; +import { inspectTelegramAccount, type InspectedTelegramAccount } from "../api.js"; export async function listTelegramDirectoryPeersFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "telegram", + const account = inspectTelegramAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedTelegramAccount | null; + }) as InspectedTelegramAccount | null; if (!account || !("config" in account)) { return []; } @@ -36,11 +34,10 @@ export async function listTelegramDirectoryPeersFromConfig(params: DirectoryConf } export async function listTelegramDirectoryGroupsFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "telegram", + const account = inspectTelegramAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedTelegramAccount | null; + }) as InspectedTelegramAccount | null; if (!account || !("config" in account)) { return []; } diff --git a/extensions/thread-ownership/api.ts b/extensions/thread-ownership/api.ts index 16e4afef70a..d94a5fd68e1 100644 --- a/extensions/thread-ownership/api.ts +++ b/extensions/thread-ownership/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/thread-ownership.js"; +export * from "openclaw/plugin-sdk/thread-ownership"; diff --git a/extensions/twitch/api.ts b/extensions/twitch/api.ts index 4743a12fb3b..68033283423 100644 --- a/extensions/twitch/api.ts +++ b/extensions/twitch/api.ts @@ -1,2 +1 @@ export * from "openclaw/plugin-sdk/twitch"; -export * from "./src/setup-surface.js"; diff --git a/extensions/voice-call/api.ts b/extensions/voice-call/api.ts index d0f69774b5e..ef9f7d7a3c0 100644 --- a/extensions/voice-call/api.ts +++ b/extensions/voice-call/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/voice-call.js"; +export * from "openclaw/plugin-sdk/voice-call"; diff --git a/extensions/whatsapp/src/runtime-api.ts b/extensions/whatsapp/src/runtime-api.ts index ce89a02eb76..a0f07404a91 100644 --- a/extensions/whatsapp/src/runtime-api.ts +++ b/extensions/whatsapp/src/runtime-api.ts @@ -1,22 +1,30 @@ export { + buildChannelConfigSchema, createActionGate, - createWhatsAppOutboundBase, DEFAULT_ACCOUNT_ID, formatWhatsAppConfigAllowFromEntries, - isWhatsAppGroupJid, + getChatChannelMeta, jsonResult, - normalizeWhatsAppTarget, + normalizeE164, readReactionParams, readStringParam, - resolveWhatsAppHeartbeatRecipients, - resolveWhatsAppMentionStripRegexes, + resolveWhatsAppGroupIntroHint, resolveWhatsAppOutboundTarget, ToolAuthorizationError, + WhatsAppConfigSchema, type ChannelPlugin, + type OpenClawConfig, +} from "openclaw/plugin-sdk/whatsapp-core"; + +export { + createWhatsAppOutboundBase, + isWhatsAppGroupJid, + normalizeWhatsAppTarget, + resolveWhatsAppHeartbeatRecipients, + resolveWhatsAppMentionStripRegexes, type ChannelMessageActionName, type DmPolicy, type GroupPolicy, - type OpenClawConfig, type WhatsAppAccountConfig, } from "openclaw/plugin-sdk/whatsapp"; diff --git a/extensions/xai/web-search.test.ts b/extensions/xai/web-search.test.ts index 0c15d09864c..29433ec7efa 100644 --- a/extensions/xai/web-search.test.ts +++ b/extensions/xai/web-search.test.ts @@ -3,7 +3,7 @@ import { resolveWebSearchProviderCredential, } from "openclaw/plugin-sdk/provider-web-search"; import { describe, expect, it } from "vitest"; -import { withEnv } from "../../src/test-utils/env.js"; +import { withEnv } from "../../test/helpers/extensions/env.js"; import { __testing } from "./web-search.js"; const { extractXaiWebSearchContent, resolveXaiInlineCitations, resolveXaiWebSearchModel } = diff --git a/extensions/zalo/src/actions.ts b/extensions/zalo/src/actions.ts index b6b5c5b95f3..89b284df789 100644 --- a/extensions/zalo/src/actions.ts +++ b/extensions/zalo/src/actions.ts @@ -1,11 +1,11 @@ import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime"; +import { listEnabledZaloAccounts } from "./accounts.js"; import type { ChannelMessageActionAdapter, ChannelMessageActionName, OpenClawConfig, } from "./runtime-api.js"; import { extractToolSend, jsonResult, readStringParam } from "./runtime-api.js"; -import { listEnabledZaloAccounts } from "./accounts.js"; const loadZaloActionsRuntime = createLazyRuntimeNamedExport( () => import("./actions.runtime.js"), diff --git a/extensions/zalo/src/channel.runtime.ts b/extensions/zalo/src/channel.runtime.ts index 39702a439fc..6b76e0e92eb 100644 --- a/extensions/zalo/src/channel.runtime.ts +++ b/extensions/zalo/src/channel.runtime.ts @@ -1,18 +1,15 @@ import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle"; import { probeZalo } from "./probe.js"; import { resolveZaloProxyFetch } from "./proxy.js"; -import { normalizeSecretInputString } from "./secret-input.js"; -import { sendMessageZalo } from "./send.js"; import { PAIRING_APPROVED_MESSAGE, type ChannelPlugin, type OpenClawConfig, } from "./runtime-api.js"; +import { normalizeSecretInputString } from "./secret-input.js"; +import { sendMessageZalo } from "./send.js"; -export async function notifyZaloPairingApproval(params: { - cfg: OpenClawConfig; - id: string; -}) { +export async function notifyZaloPairingApproval(params: { cfg: OpenClawConfig; id: string }) { const { resolveZaloAccount } = await import("./accounts.js"); const account = resolveZaloAccount({ cfg: params.cfg }); if (!account.token) { @@ -44,11 +41,7 @@ export async function probeZaloAccount(params: { } export async function startZaloGatewayAccount( - ctx: Parameters< - NonNullable< - NonNullable["startAccount"] - > - >[0], + ctx: Parameters["startAccount"]>>[0], ) { const account = ctx.account; const token = account.token.trim(); diff --git a/extensions/zalo/src/channel.ts b/extensions/zalo/src/channel.ts index a9cfea6f9ad..5434b3e144e 100644 --- a/extensions/zalo/src/channel.ts +++ b/extensions/zalo/src/channel.ts @@ -9,6 +9,14 @@ import { collectOpenProviderGroupPolicyWarnings, } from "openclaw/plugin-sdk/channel-policy"; import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; +import { + listZaloAccountIds, + resolveDefaultZaloAccountId, + resolveZaloAccount, + type ResolvedZaloAccount, +} from "./accounts.js"; +import { zaloMessageActions } from "./actions.js"; +import { ZaloConfigSchema } from "./config-schema.js"; import { buildBaseAccountStatusSnapshot, buildChannelConfigSchema, @@ -24,14 +32,6 @@ import { type ChannelPlugin, type OpenClawConfig, } from "./runtime-api.js"; -import { - listZaloAccountIds, - resolveDefaultZaloAccountId, - resolveZaloAccount, - type ResolvedZaloAccount, -} from "./accounts.js"; -import { zaloMessageActions } from "./actions.js"; -import { ZaloConfigSchema } from "./config-schema.js"; import { resolveZaloOutboundSessionRoute } from "./session-route.js"; import { zaloSetupAdapter } from "./setup-core.js"; import { zaloSetupWizard } from "./setup-surface.js"; diff --git a/extensions/zalo/src/config-schema.ts b/extensions/zalo/src/config-schema.ts index 70b863779c1..75d8027cf47 100644 --- a/extensions/zalo/src/config-schema.ts +++ b/extensions/zalo/src/config-schema.ts @@ -5,8 +5,8 @@ import { GroupPolicySchema, } from "openclaw/plugin-sdk/channel-config-schema"; import { z } from "zod"; -import { buildSecretInputSchema } from "./secret-input.js"; import { MarkdownConfigSchema } from "./runtime-api.js"; +import { buildSecretInputSchema } from "./secret-input.js"; const zaloAccountSchema = z.object({ name: z.string().optional(), diff --git a/extensions/zalo/src/monitor.ts b/extensions/zalo/src/monitor.ts index ee97207cf3b..8452fb661e2 100644 --- a/extensions/zalo/src/monitor.ts +++ b/extensions/zalo/src/monitor.ts @@ -1,25 +1,4 @@ import type { IncomingMessage, ServerResponse } from "node:http"; -import type { - MarkdownTableMode, - OpenClawConfig, - OutboundReplyPayload, -} from "./runtime-api.js"; -import { - createTypingCallbacks, - createScopedPairingAccess, - createReplyPrefixOptions, - issuePairingChallenge, - logTypingFailure, - resolveDirectDmAuthorizationOutcome, - resolveSenderCommandAuthorizationWithRuntime, - resolveOutboundMediaUrls, - resolveDefaultGroupPolicy, - resolveInboundRouteEnvelopeBuilderWithRuntime, - sendMediaWithLeadingCaption, - resolveWebhookPath, - waitForAbortSignal, - warnMissingProviderGroupPolicyFallbackOnce, -} from "./runtime-api.js"; import type { ResolvedZaloAccount } from "./accounts.js"; import { ZaloApiError, @@ -48,6 +27,23 @@ import { type ZaloWebhookTarget, } from "./monitor.webhook.js"; import { resolveZaloProxyFetch } from "./proxy.js"; +import type { MarkdownTableMode, OpenClawConfig, OutboundReplyPayload } from "./runtime-api.js"; +import { + createTypingCallbacks, + createScopedPairingAccess, + createReplyPrefixOptions, + issuePairingChallenge, + logTypingFailure, + resolveDirectDmAuthorizationOutcome, + resolveSenderCommandAuthorizationWithRuntime, + resolveOutboundMediaUrls, + resolveDefaultGroupPolicy, + resolveInboundRouteEnvelopeBuilderWithRuntime, + sendMediaWithLeadingCaption, + resolveWebhookPath, + waitForAbortSignal, + warnMissingProviderGroupPolicyFallbackOnce, +} from "./runtime-api.js"; import { getZaloRuntime } from "./runtime.js"; export type ZaloRuntimeEnv = { diff --git a/extensions/zalo/src/monitor.webhook.ts b/extensions/zalo/src/monitor.webhook.ts index e058dcc453c..02a82bf0544 100644 --- a/extensions/zalo/src/monitor.webhook.ts +++ b/extensions/zalo/src/monitor.webhook.ts @@ -1,5 +1,8 @@ import { timingSafeEqual } from "node:crypto"; import type { IncomingMessage, ServerResponse } from "node:http"; +import type { ResolvedZaloAccount } from "./accounts.js"; +import type { ZaloFetch, ZaloUpdate } from "./api.js"; +import type { ZaloRuntimeEnv } from "./monitor.js"; import { createDedupeCache, createFixedWindowRateLimiter, @@ -17,9 +20,6 @@ import { resolveClientIp, type OpenClawConfig, } from "./runtime-api.js"; -import type { ResolvedZaloAccount } from "./accounts.js"; -import type { ZaloFetch, ZaloUpdate } from "./api.js"; -import type { ZaloRuntimeEnv } from "./monitor.js"; const ZALO_WEBHOOK_REPLAY_WINDOW_MS = 5 * 60_000; diff --git a/extensions/zalo/src/send.ts b/extensions/zalo/src/send.ts index d83bd16114d..647ca3b9823 100644 --- a/extensions/zalo/src/send.ts +++ b/extensions/zalo/src/send.ts @@ -2,8 +2,8 @@ import { resolveZaloAccount } from "./accounts.js"; import type { ZaloFetch } from "./api.js"; import { sendMessage, sendPhoto } from "./api.js"; import { resolveZaloProxyFetch } from "./proxy.js"; -import { resolveZaloToken } from "./token.js"; import type { OpenClawConfig } from "./runtime-api.js"; +import { resolveZaloToken } from "./token.js"; export type ZaloSendOptions = { token?: string; diff --git a/extensions/zalo/src/token.ts b/extensions/zalo/src/token.ts index c593cb5b824..2ee4ffa4283 100644 --- a/extensions/zalo/src/token.ts +++ b/extensions/zalo/src/token.ts @@ -1,8 +1,8 @@ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id"; import { tryReadSecretFileSync } from "openclaw/plugin-sdk/infra-runtime"; +import type { BaseTokenResolution } from "./runtime-api.js"; import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js"; import type { ZaloConfig } from "./types.js"; -import type { BaseTokenResolution } from "./runtime-api.js"; export type ZaloTokenResolution = BaseTokenResolution & { source: "env" | "config" | "configFile" | "none"; diff --git a/src/agents/openclaw-tools.image-generation.test.ts b/src/agents/openclaw-tools.image-generation.test.ts index 9ad49f66371..cb5b9691009 100644 --- a/src/agents/openclaw-tools.image-generation.test.ts +++ b/src/agents/openclaw-tools.image-generation.test.ts @@ -17,7 +17,17 @@ function stubImageGenerationProviders() { id: "openai", defaultModel: "gpt-image-1", models: ["gpt-image-1"], - supportedSizes: ["1024x1024"], + capabilities: { + generate: { + supportsSize: true, + }, + edit: { + enabled: false, + }, + geometry: { + sizes: ["1024x1024"], + }, + }, generateImage: vi.fn(async () => { throw new Error("not used"); }), diff --git a/src/agents/pi-embedded-runner/extra-params.google.test.ts b/src/agents/pi-embedded-runner/extra-params.google.test.ts index 4cf33f5eeef..622e85b475c 100644 --- a/src/agents/pi-embedded-runner/extra-params.google.test.ts +++ b/src/agents/pi-embedded-runner/extra-params.google.test.ts @@ -18,7 +18,7 @@ describe("extra-params: Google thinking payload compatibility", () => { api: "google-generative-ai", provider: "google", id: "gemini-3.1-pro-preview", - } as Model<"openai-completions">, + } as unknown as Model<"openai-completions">, thinkingLevel: "high", payload: { contents: [], diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts index ca704b03e51..c704515ac6e 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts @@ -457,7 +457,7 @@ describe("createOpenClawCodingTools", () => { it("applies xai model compat for direct Grok tool cleanup", () => { const xaiTools = createOpenClawCodingTools({ modelProvider: "xai", - modelCompat: applyXaiModelCompat({}).compat, + modelCompat: applyXaiModelCompat({ compat: {} }).compat, senderIsOwner: true, }); diff --git a/src/agents/pi-tools.model-provider-collision.test.ts b/src/agents/pi-tools.model-provider-collision.test.ts index 04eaa575601..9d629839199 100644 --- a/src/agents/pi-tools.model-provider-collision.test.ts +++ b/src/agents/pi-tools.model-provider-collision.test.ts @@ -18,10 +18,7 @@ function toolNames(tools: AnyAgentTool[]): string[] { describe("applyModelProviderToolPolicy", () => { it("keeps web_search for non-xAI models", () => { - const filtered = __testing.applyModelProviderToolPolicy(baseTools, { - modelProvider: "openai", - modelId: "gpt-4o-mini", - }); + const filtered = __testing.applyModelProviderToolPolicy(baseTools); expect(toolNames(filtered)).toEqual(["read", "web_search", "exec"]); }); diff --git a/src/agents/tools/image-generate-tool.test.ts b/src/agents/tools/image-generate-tool.test.ts index 50df1718daf..f719d8552b5 100644 --- a/src/agents/tools/image-generate-tool.test.ts +++ b/src/agents/tools/image-generate-tool.test.ts @@ -392,10 +392,11 @@ describe("createImageGenerateTool", () => { throw new Error("expected image_generate tool"); } - await expect(tool.execute("call-bad-aspect", { prompt: "portrait", aspectRatio: "7:5" })) - .rejects.toThrow( - "aspectRatio must be one of 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, or 21:9", - ); + await expect( + tool.execute("call-bad-aspect", { prompt: "portrait", aspectRatio: "7:5" }), + ).rejects.toThrow( + "aspectRatio must be one of 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, or 21:9", + ); }); it("lists registered provider and model options", async () => { diff --git a/src/agents/tools/image-generate-tool.ts b/src/agents/tools/image-generate-tool.ts index 3ae12fda187..aeb20a83723 100644 --- a/src/agents/tools/image-generate-tool.ts +++ b/src/agents/tools/image-generate-tool.ts @@ -230,7 +230,9 @@ function normalizeReferenceImages(args: Record): string[] { return normalized; } -function parseImageGenerationModelRef(raw: string | undefined): { provider: string; model: string } | null { +function parseImageGenerationModelRef( + raw: string | undefined, +): { provider: string; model: string } | null { const trimmed = raw?.trim(); if (!trimmed) { return null; @@ -258,7 +260,8 @@ function resolveSelectedImageGenerationProvider(params: { } return listRuntimeImageGenerationProviders({ config: params.config }).find( (provider) => - provider.id === selectedRef.provider || (provider.aliases ?? []).includes(selectedRef.provider), + provider.id === selectedRef.provider || + (provider.aliases ?? []).includes(selectedRef.provider), ); } @@ -298,7 +301,9 @@ function validateImageGenerationCapabilities(params: { if (params.size) { if (!modeCaps.supportsSize) { - throw new ToolInputError(`${provider.id} ${isEdit ? "edit" : "generate"} does not support size overrides.`); + throw new ToolInputError( + `${provider.id} ${isEdit ? "edit" : "generate"} does not support size overrides.`, + ); } if ((geometry?.sizes?.length ?? 0) > 0 && !geometry?.sizes?.includes(params.size)) { throw new ToolInputError( @@ -309,7 +314,9 @@ function validateImageGenerationCapabilities(params: { if (params.aspectRatio) { if (!modeCaps.supportsAspectRatio) { - throw new ToolInputError(`${provider.id} ${isEdit ? "edit" : "generate"} does not support aspectRatio overrides.`); + throw new ToolInputError( + `${provider.id} ${isEdit ? "edit" : "generate"} does not support aspectRatio overrides.`, + ); } if ( (geometry?.aspectRatios?.length ?? 0) > 0 && @@ -323,7 +330,9 @@ function validateImageGenerationCapabilities(params: { if (params.resolution) { if (!modeCaps.supportsResolution) { - throw new ToolInputError(`${provider.id} ${isEdit ? "edit" : "generate"} does not support resolution overrides.`); + throw new ToolInputError( + `${provider.id} ${isEdit ? "edit" : "generate"} does not support resolution overrides.`, + ); } if ( (geometry?.resolutions?.length ?? 0) > 0 && diff --git a/src/agents/xai.live.test.ts b/src/agents/xai.live.test.ts index a8dcde278db..5d84287c4c3 100644 --- a/src/agents/xai.live.test.ts +++ b/src/agents/xai.live.test.ts @@ -26,7 +26,7 @@ type AssistantLikeMessage = { }; function resolveLiveXaiModel() { - return getModel("xai", "grok-4-1-fast-reasoning") ?? getModel("xai", "grok-4"); + return getModel("xai", "grok-4"); } async function collectDoneMessage( diff --git a/src/channels/plugins/setup-wizard-helpers.ts b/src/channels/plugins/setup-wizard-helpers.ts index 50a29404b30..23299816f5e 100644 --- a/src/channels/plugins/setup-wizard-helpers.ts +++ b/src/channels/plugins/setup-wizard-helpers.ts @@ -722,7 +722,14 @@ export function createAccountScopedGroupAccessSection(params: { }; } -type AccountScopedChannel = "discord" | "slack" | "telegram" | "imessage" | "signal"; +type AccountScopedChannel = + | "bluebubbles" + | "discord" + | "imessage" + | "line" + | "signal" + | "slack" + | "telegram"; type LegacyDmChannel = "discord" | "slack"; export function patchLegacyDmChannelConfig(params: { diff --git a/src/commands/config-validation.test.ts b/src/commands/config-validation.test.ts index 83876477b43..2c4852ba8b6 100644 --- a/src/commands/config-validation.test.ts +++ b/src/commands/config-validation.test.ts @@ -1,7 +1,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { PluginCompatibilityNotice } from "../plugins/status.js"; const readConfigFileSnapshot = vi.fn(); -const buildPluginCompatibilityNotices = vi.fn(() => []); +const buildPluginCompatibilityNotices = vi.fn((): PluginCompatibilityNotice[] => []); vi.mock("../config/config.js", () => ({ readConfigFileSnapshot, diff --git a/src/commands/configure.wizard.ts b/src/commands/configure.wizard.ts index 78cd0716376..c74909ae14b 100644 --- a/src/commands/configure.wizard.ts +++ b/src/commands/configure.wizard.ts @@ -184,13 +184,13 @@ async function promptWebToolsConfig( if (!entry) { return false; } - return hasExistingKey(nextConfig, provider as SP) || hasKeyInEnv(entry); + return hasExistingKey(nextConfig, provider) || hasKeyInEnv(entry); }; const existingProvider: SP = (() => { const stored = existingSearch?.provider; if (stored && SEARCH_PROVIDER_OPTIONS.some((e) => e.value === stored)) { - return stored as SP; + return stored; } return ( SEARCH_PROVIDER_OPTIONS.find((e) => hasKeyForProvider(e.value))?.value ?? defaultProvider @@ -242,8 +242,8 @@ async function promptWebToolsConfig( nextSearch = { ...nextSearch, provider: providerChoice }; const entry = SEARCH_PROVIDER_OPTIONS.find((e) => e.value === providerChoice)!; - const existingKey = resolveExistingKey(nextConfig, providerChoice as SP); - const keyConfigured = hasExistingKey(nextConfig, providerChoice as SP); + const existingKey = resolveExistingKey(nextConfig, providerChoice); + const keyConfigured = hasExistingKey(nextConfig, providerChoice); const envAvailable = entry.envKeys.some((k) => Boolean(process.env[k]?.trim())); const envVarNames = entry.envKeys.join(" / "); @@ -263,7 +263,7 @@ async function promptWebToolsConfig( const key = String(keyInput ?? "").trim(); if (key || existingKey) { - const applied = applySearchKey(nextConfig, providerChoice as SP, (key || existingKey)!); + const applied = applySearchKey(nextConfig, providerChoice, (key || existingKey)!); nextSearch = { ...applied.tools?.web?.search }; } else if (keyConfigured || envAvailable) { nextSearch = { ...nextSearch }; diff --git a/src/commands/doctor-legacy-config.migrations.test.ts b/src/commands/doctor-legacy-config.migrations.test.ts index 738827c31c6..b8ec52ca171 100644 --- a/src/commands/doctor-legacy-config.migrations.test.ts +++ b/src/commands/doctor-legacy-config.migrations.test.ts @@ -359,6 +359,8 @@ describe("normalizeCompatibilityConfigValues", () => { providers: { google: { apiKey: "existing-google-key", + baseUrl: "https://generativelanguage.googleapis.com", + models: [], }, }, }, diff --git a/src/commands/doctor-legacy-config.ts b/src/commands/doctor-legacy-config.ts index 8072b89854b..c3376bd74e9 100644 --- a/src/commands/doctor-legacy-config.ts +++ b/src/commands/doctor-legacy-config.ts @@ -474,6 +474,11 @@ export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): { }; const normalizeLegacyNanoBananaSkill = () => { + type ModelProviderEntry = Partial< + NonNullable["providers"]>[string] + >; + type ModelsConfigPatch = Partial>; + const rawSkills = next.skills; if (!isRecord(rawSkills)) { return; @@ -544,14 +549,20 @@ export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): { ? structuredClone(rawLegacyEntry.apiKey) : undefined); - const rawModels = isRecord(next.models) ? structuredClone(next.models) : {}; - const rawProviders = isRecord(rawModels.providers) ? { ...rawModels.providers } : {}; - const rawGoogle = isRecord(rawProviders.google) ? { ...rawProviders.google } : {}; + const rawModels = ( + isRecord(next.models) ? structuredClone(next.models) : {} + ) as ModelsConfigPatch; + const rawProviders = ( + isRecord(rawModels.providers) ? { ...rawModels.providers } : {} + ) as Record; + const rawGoogle = ( + isRecord(rawProviders.google) ? { ...rawProviders.google } : {} + ) as ModelProviderEntry; const hasGoogleApiKey = rawGoogle.apiKey !== undefined; if (!hasGoogleApiKey && legacyApiKey) { rawGoogle.apiKey = legacyApiKey; rawProviders.google = rawGoogle; - rawModels.providers = rawProviders; + rawModels.providers = rawProviders as NonNullable["providers"]; next = { ...next, models: rawModels as OpenClawConfig["models"], diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 0a1e68a16a7..6939b7b0d96 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -444,6 +444,14 @@ export type MemorySearchConfig = { }; }; +type WebSearchLegacyProviderConfig = { + apiKey?: SecretInput; + baseUrl?: string; + model?: string; + mode?: string; + inlineCitations?: boolean; +}; + export type ToolsConfig = { /** Base tool profile applied before allow/deny lists. */ profile?: ToolProfileId; @@ -465,6 +473,20 @@ export type ToolsConfig = { timeoutSeconds?: number; /** Cache TTL in minutes for search results. */ cacheTtlMinutes?: number; + /** @deprecated Legacy Brave credential path. */ + apiKey?: SecretInput; + /** @deprecated Legacy Brave scoped config. */ + brave?: WebSearchLegacyProviderConfig; + /** @deprecated Legacy Firecrawl scoped config. */ + firecrawl?: WebSearchLegacyProviderConfig; + /** @deprecated Legacy Gemini scoped config. */ + gemini?: WebSearchLegacyProviderConfig; + /** @deprecated Legacy Grok scoped config. */ + grok?: WebSearchLegacyProviderConfig; + /** @deprecated Legacy Kimi scoped config. */ + kimi?: WebSearchLegacyProviderConfig; + /** @deprecated Legacy Perplexity scoped config. */ + perplexity?: WebSearchLegacyProviderConfig; }; fetch?: { /** Enable web fetch tool (default: true). */ diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 2763697c2d9..10f0f8637e9 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -267,6 +267,57 @@ export const ToolsWebSearchSchema = z maxResults: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), cacheTtlMinutes: z.number().nonnegative().optional(), + apiKey: SecretInputSchema.optional().register(sensitive), + brave: z + .object({ + apiKey: SecretInputSchema.optional().register(sensitive), + baseUrl: z.string().optional(), + model: z.string().optional(), + mode: z.string().optional(), + }) + .strict() + .optional(), + firecrawl: z + .object({ + apiKey: SecretInputSchema.optional().register(sensitive), + baseUrl: z.string().optional(), + model: z.string().optional(), + }) + .strict() + .optional(), + gemini: z + .object({ + apiKey: SecretInputSchema.optional().register(sensitive), + baseUrl: z.string().optional(), + model: z.string().optional(), + }) + .strict() + .optional(), + grok: z + .object({ + apiKey: SecretInputSchema.optional().register(sensitive), + baseUrl: z.string().optional(), + model: z.string().optional(), + inlineCitations: z.boolean().optional(), + }) + .strict() + .optional(), + kimi: z + .object({ + apiKey: SecretInputSchema.optional().register(sensitive), + baseUrl: z.string().optional(), + model: z.string().optional(), + }) + .strict() + .optional(), + perplexity: z + .object({ + apiKey: SecretInputSchema.optional().register(sensitive), + baseUrl: z.string().optional(), + model: z.string().optional(), + }) + .strict() + .optional(), }) .strict() .optional(); diff --git a/src/image-generation/providers/fal.ts b/src/image-generation/providers/fal.ts index 4059859e534..8d0cd8ceaaf 100644 --- a/src/image-generation/providers/fal.ts +++ b/src/image-generation/providers/fal.ts @@ -94,14 +94,22 @@ function aspectRatioToEnum(aspectRatio: string | undefined): string | undefined return undefined; } -function aspectRatioToDimensions(aspectRatio: string, edge: number): { width: number; height: number } { +function aspectRatioToDimensions( + aspectRatio: string, + edge: number, +): { width: number; height: number } { const match = /^(\d+):(\d+)$/u.exec(aspectRatio.trim()); if (!match) { throw new Error(`Invalid fal aspect ratio: ${aspectRatio}`); } const widthRatio = Number.parseInt(match[1] ?? "", 10); const heightRatio = Number.parseInt(match[2] ?? "", 10); - if (!Number.isFinite(widthRatio) || !Number.isFinite(heightRatio) || widthRatio <= 0 || heightRatio <= 0) { + if ( + !Number.isFinite(widthRatio) || + !Number.isFinite(heightRatio) || + widthRatio <= 0 || + heightRatio <= 0 + ) { throw new Error(`Invalid fal aspect ratio: ${aspectRatio}`); } if (widthRatio >= heightRatio) { @@ -140,7 +148,10 @@ function resolveFalImageSize(params: { return { width: edge, height: edge }; } if (normalizedAspectRatio) { - return aspectRatioToEnum(normalizedAspectRatio) ?? aspectRatioToDimensions(normalizedAspectRatio, 1024); + return ( + aspectRatioToEnum(normalizedAspectRatio) ?? + aspectRatioToDimensions(normalizedAspectRatio, 1024) + ); } return undefined; } diff --git a/src/infra/outbound/outbound-session.test.ts b/src/infra/outbound/outbound-session.test.ts index c33c3edcf77..7a45f938bf8 100644 --- a/src/infra/outbound/outbound-session.test.ts +++ b/src/infra/outbound/outbound-session.test.ts @@ -41,7 +41,7 @@ describe("resolveOutboundSessionRoute", () => { from?: string; to?: string; threadId?: string | number; - chatType?: "direct" | "group"; + chatType?: "channel" | "direct" | "group"; }; }> = [ { diff --git a/src/infra/outbound/outbound.test.ts b/src/infra/outbound/outbound.test.ts index 7266f45d969..7dcdab184ed 100644 --- a/src/infra/outbound/outbound.test.ts +++ b/src/infra/outbound/outbound.test.ts @@ -972,7 +972,7 @@ describe("resolveOutboundSessionRoute", () => { from?: string; to?: string; threadId?: string | number; - chatType?: "direct" | "group"; + chatType?: "channel" | "direct" | "group"; }; }> = [ { diff --git a/src/plugin-sdk/acp-runtime.ts b/src/plugin-sdk/acp-runtime.ts index c50c36419bb..84435bb896a 100644 --- a/src/plugin-sdk/acp-runtime.ts +++ b/src/plugin-sdk/acp-runtime.ts @@ -1,6 +1,18 @@ // Public ACP runtime helpers for plugins that integrate with ACP control/session state. export { getAcpSessionManager } from "../acp/control-plane/manager.js"; -export { isAcpRuntimeError } from "../acp/runtime/errors.js"; +export { AcpRuntimeError, isAcpRuntimeError } from "../acp/runtime/errors.js"; +export type { AcpRuntimeErrorCode } from "../acp/runtime/errors.js"; +export type { + AcpRuntime, + AcpRuntimeCapabilities, + AcpRuntimeDoctorReport, + AcpRuntimeEnsureInput, + AcpRuntimeEvent, + AcpRuntimeHandle, + AcpRuntimeStatus, + AcpRuntimeTurnInput, + AcpSessionUpdateTag, +} from "../acp/runtime/types.js"; export { readAcpSessionEntry } from "../acp/runtime/session-meta.js"; export type { AcpSessionStoreEntry } from "../acp/runtime/session-meta.js"; diff --git a/src/plugin-sdk/channel-config-helpers.ts b/src/plugin-sdk/channel-config-helpers.ts index ee18f8bc9c9..d9a229657dd 100644 --- a/src/plugin-sdk/channel-config-helpers.ts +++ b/src/plugin-sdk/channel-config-helpers.ts @@ -41,8 +41,11 @@ export function resolveOptionalConfigString( } /** Build the shared allowlist/default target adapter surface for account-scoped channel configs. */ -export function createScopedAccountConfigAccessors(params: { - resolveAccount: (params: { cfg: OpenClawConfig; accountId?: string | null }) => ResolvedAccount; +export function createScopedAccountConfigAccessors< + ResolvedAccount, + Config extends OpenClawConfig = OpenClawConfig, +>(params: { + resolveAccount: (params: { cfg: Config; accountId?: string | null }) => ResolvedAccount; resolveAllowFrom: (account: ResolvedAccount) => Array | null | undefined; formatAllowFrom: (allowFrom: Array) => string[]; resolveDefaultTo?: (account: ResolvedAccount) => string | number | null | undefined; @@ -52,7 +55,9 @@ export function createScopedAccountConfigAccessors(params: { > { const base = { resolveAllowFrom: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId?: string | null }) => - mapAllowFromEntries(params.resolveAllowFrom(params.resolveAccount({ cfg, accountId }))), + mapAllowFromEntries( + params.resolveAllowFrom(params.resolveAccount({ cfg: cfg as Config, accountId })), + ), formatAllowFrom: ({ allowFrom }: { allowFrom: Array }) => params.formatAllowFrom(allowFrom), }; @@ -65,7 +70,7 @@ export function createScopedAccountConfigAccessors(params: { ...base, resolveDefaultTo: ({ cfg, accountId }) => resolveOptionalConfigString( - params.resolveDefaultTo?.(params.resolveAccount({ cfg, accountId })), + params.resolveDefaultTo?.(params.resolveAccount({ cfg: cfg as Config, accountId })), ), }; } @@ -160,7 +165,7 @@ export function createScopedChannelConfigAdapter< clearBaseFields: params.clearBaseFields, allowTopLevel: params.allowTopLevel, }), - ...createScopedAccountConfigAccessors({ + ...createScopedAccountConfigAccessors({ resolveAccount: resolveAccessorAccount, resolveAllowFrom: params.resolveAllowFrom, formatAllowFrom: params.formatAllowFrom, @@ -316,7 +321,7 @@ export function createTopLevelChannelConfigAdapter< deleteMode: params.deleteMode, clearBaseFields: params.clearBaseFields, }), - ...createScopedAccountConfigAccessors({ + ...createScopedAccountConfigAccessors({ resolveAccount: resolveAccessorAccount, resolveAllowFrom: params.resolveAllowFrom, formatAllowFrom: params.formatAllowFrom, @@ -438,7 +443,7 @@ export function createHybridChannelConfigAdapter< clearBaseFields: params.clearBaseFields, preserveSectionOnDefaultDelete: params.preserveSectionOnDefaultDelete, }), - ...createScopedAccountConfigAccessors({ + ...createScopedAccountConfigAccessors({ resolveAccount: resolveAccessorAccount, resolveAllowFrom: params.resolveAllowFrom, formatAllowFrom: params.formatAllowFrom, diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 124c37d6712..252063d2631 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -44,6 +44,7 @@ export type { ProviderThinkingPolicyContext, ProviderWrapStreamFnContext, OpenClawPluginService, + OpenClawPluginServiceContext, ProviderAuthContext, ProviderAuthDoctorHintContext, ProviderAuthMethodNonInteractiveContext, @@ -51,6 +52,7 @@ export type { ProviderAuthResult, OpenClawPluginCommandDefinition, OpenClawPluginDefinition, + PluginLogger, PluginInteractiveTelegramHandlerContext, } from "../plugins/types.js"; export type { OpenClawConfig } from "../config/config.js"; diff --git a/src/plugin-sdk/package-contract-guardrails.test.ts b/src/plugin-sdk/package-contract-guardrails.test.ts index 046562708cd..a637927098e 100644 --- a/src/plugin-sdk/package-contract-guardrails.test.ts +++ b/src/plugin-sdk/package-contract-guardrails.test.ts @@ -25,7 +25,7 @@ function collectPluginSdkPackageExports(): string[] { } subpaths.push(key.slice("./plugin-sdk/".length)); } - return subpaths.sort(); + return subpaths.toSorted(); } function collectPluginSdkSourceNames(): string[] { @@ -35,7 +35,7 @@ function collectPluginSdkSourceNames(): string[] { (entry) => entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts"), ) .map((entry) => entry.name.slice(0, -".ts".length)) - .sort(); + .toSorted(); } function collectTextFiles(rootRelativeDir: string): string[] { @@ -92,7 +92,7 @@ function collectPluginSdkSubpathReferences() { describe("plugin-sdk package contract guardrails", () => { it("keeps package.json exports aligned with built plugin-sdk entrypoints", () => { - expect(collectPluginSdkPackageExports()).toEqual([...pluginSdkEntrypoints].sort()); + expect(collectPluginSdkPackageExports()).toEqual([...pluginSdkEntrypoints].toSorted()); }); it("keeps repo openclaw/plugin-sdk/ references on exported built subpaths", () => { @@ -135,7 +135,7 @@ describe("plugin-sdk package contract guardrails", () => { failures.push( `src/plugin-sdk/${sourceName}.ts is referenced as openclaw/plugin-sdk/${sourceName} in ${matchingRefs .map((reference) => reference.file) - .sort() + .toSorted() .join(", ")}, but ${sourceName} is not exported as a public plugin-sdk subpath`, ); } diff --git a/src/plugin-sdk/telegram.ts b/src/plugin-sdk/telegram.ts index 4a180763b38..c4ec4f2cdff 100644 --- a/src/plugin-sdk/telegram.ts +++ b/src/plugin-sdk/telegram.ts @@ -26,6 +26,8 @@ export type { StickerMetadata } from "../../extensions/telegram/api.js"; export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; export { parseTelegramTopicConversation } from "../acp/conversation-id.js"; +export { clearAccountEntryFields } from "../channels/plugins/config-helpers.js"; +export { resolveTelegramPollVisibility } from "../poll-params.js"; export { PAIRING_APPROVED_MESSAGE, @@ -38,9 +40,6 @@ export { setAccountEnabledInConfigSection, } from "./channel-plugin-common.js"; -export { clearAccountEntryFields } from "../channels/plugins/config-helpers.js"; -export { resolveTelegramPollVisibility } from "../poll-params.js"; - export { projectCredentialSnapshotFields, resolveConfiguredFromCredentialStatuses, diff --git a/src/plugins/contracts/shape.contract.test.ts b/src/plugins/contracts/shape.contract.test.ts index ffc7c92360a..c5726c4fd0b 100644 --- a/src/plugins/contracts/shape.contract.test.ts +++ b/src/plugins/contracts/shape.contract.test.ts @@ -99,6 +99,7 @@ describe("plugin shape compatibility matrix", () => { envVars: ["HYBRID_SEARCH_KEY"], placeholder: "hsk_...", signupUrl: "https://example.com/signup", + credentialPath: "tools.web.search.hybrid-search.apiKey", getCredentialValue: () => "hsk-test", setCredentialValue(searchConfigTarget, value) { searchConfigTarget.apiKey = value; diff --git a/src/secrets/runtime-web-tools.test.ts b/src/secrets/runtime-web-tools.test.ts index 94f7b9be99f..7b0706a66d4 100644 --- a/src/secrets/runtime-web-tools.test.ts +++ b/src/secrets/runtime-web-tools.test.ts @@ -68,7 +68,10 @@ function createProviderSecretRefConfig( } function readProviderKey(config: OpenClawConfig, provider: ProviderUnderTest): unknown { - return config.plugins?.entries?.[providerPluginId(provider)]?.config?.webSearch?.apiKey; + const pluginConfig = config.plugins?.entries?.[providerPluginId(provider)]?.config as + | { webSearch?: { apiKey?: unknown } } + | undefined; + return pluginConfig?.webSearch?.apiKey; } function expectInactiveFirecrawlSecretRef(params: { diff --git a/src/web-search/runtime.test.ts b/src/web-search/runtime.test.ts index 68446d33a95..428ae25552c 100644 --- a/src/web-search/runtime.test.ts +++ b/src/web-search/runtime.test.ts @@ -21,6 +21,7 @@ describe("web search runtime", () => { placeholder: "custom-...", signupUrl: "https://example.com/signup", autoDetectOrder: 1, + credentialPath: "tools.web.search.custom.apiKey", getCredentialValue: () => "configured", setCredentialValue: () => {}, createTool: () => ({ diff --git a/src/web-search/runtime.ts b/src/web-search/runtime.ts index 4861ad12480..2c81f6748b4 100644 --- a/src/web-search/runtime.ts +++ b/src/web-search/runtime.ts @@ -199,5 +199,6 @@ export async function runWebSearch( export const __testing = { resolveSearchConfig, + resolveSearchProvider: resolveWebSearchProviderId, resolveWebSearchProviderId, }; diff --git a/ui/src/ui/views/config.browser.test.ts b/ui/src/ui/views/config.browser.test.ts index 4b546cfa0b7..6473c09404d 100644 --- a/ui/src/ui/views/config.browser.test.ts +++ b/ui/src/ui/views/config.browser.test.ts @@ -42,6 +42,8 @@ describe("config view", () => { themeMode: "system" as ThemeMode, setTheme: vi.fn(), setThemeMode: vi.fn(), + borderRadius: 50, + setBorderRadius: vi.fn(), gatewayUrl: "", assistantName: "OpenClaw", });