diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f5332b5a03..bd1fa37af9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai - Subagents: nested sub-agents (sub-sub-agents) with configurable depth. Set `agents.defaults.subagents.maxSpawnDepth: 2` to allow sub-agents to spawn their own children. Includes `maxChildrenPerAgent` limit (default 5), depth-aware tool policy, and proper announce chain routing. (#14447) Thanks @tyler6204. - Discord: components v2 UI + embeds passthrough + exec approval UX refinements (CV2 containers, button layout, Discord-forwarding skip). Thanks @thewilloftheshadow. - Slack/Discord/Telegram: add per-channel ack reaction overrides (account/channel-level) to support platform-specific emoji formats. (#17092) Thanks @zerone0x. +- Channels: deduplicate probe/token resolution base types across core + extensions while preserving per-channel error typing. (#16986) Thanks @iyoda and @thewilloftheshadow. ### Fixes diff --git a/extensions/bluebubbles/src/probe.ts b/extensions/bluebubbles/src/probe.ts index 7b49ae698ed..e60c47dc643 100644 --- a/extensions/bluebubbles/src/probe.ts +++ b/extensions/bluebubbles/src/probe.ts @@ -1,9 +1,8 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import { buildBlueBubblesApiUrl, blueBubblesFetchWithTimeout } from "./types.js"; -export type BlueBubblesProbe = { - ok: boolean; +export type BlueBubblesProbe = BaseProbeResult & { status?: number | null; - error?: string | null; }; export type BlueBubblesServerInfo = { diff --git a/extensions/feishu/src/types.ts b/extensions/feishu/src/types.ts index dbfde807806..dad248aa9f4 100644 --- a/extensions/feishu/src/types.ts +++ b/extensions/feishu/src/types.ts @@ -1,3 +1,4 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import type { FeishuConfigSchema, FeishuGroupSchema, @@ -52,9 +53,7 @@ export type FeishuSendResult = { chatId: string; }; -export type FeishuProbeResult = { - ok: boolean; - error?: string; +export type FeishuProbeResult = BaseProbeResult & { appId?: string; botName?: string; botOpenId?: string; diff --git a/extensions/irc/src/types.ts b/extensions/irc/src/types.ts index 5446649aad2..ac6a5c9cb7b 100644 --- a/extensions/irc/src/types.ts +++ b/extensions/irc/src/types.ts @@ -1,3 +1,4 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import type { BlockStreamingCoalesceConfig, DmConfig, @@ -83,12 +84,10 @@ export type IrcInboundMessage = { isGroup: boolean; }; -export type IrcProbe = { - ok: boolean; +export type IrcProbe = BaseProbeResult & { host: string; port: number; tls: boolean; nick: string; latencyMs?: number; - error?: string; }; diff --git a/extensions/matrix/src/matrix/probe.ts b/extensions/matrix/src/matrix/probe.ts index 7bd54bdc400..5681b242c24 100644 --- a/extensions/matrix/src/matrix/probe.ts +++ b/extensions/matrix/src/matrix/probe.ts @@ -1,9 +1,8 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import { createMatrixClient, isBunRuntime } from "./client.js"; -export type MatrixProbe = { - ok: boolean; +export type MatrixProbe = BaseProbeResult & { status?: number | null; - error?: string | null; elapsedMs: number; userId?: string | null; }; diff --git a/extensions/mattermost/src/mattermost/probe.ts b/extensions/mattermost/src/mattermost/probe.ts index a02ca4935fd..cb468ec14db 100644 --- a/extensions/mattermost/src/mattermost/probe.ts +++ b/extensions/mattermost/src/mattermost/probe.ts @@ -1,9 +1,8 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import { normalizeMattermostBaseUrl, type MattermostUser } from "./client.js"; -export type MattermostProbe = { - ok: boolean; +export type MattermostProbe = BaseProbeResult & { status?: number | null; - error?: string | null; elapsedMs?: number | null; bot?: MattermostUser; }; diff --git a/extensions/msteams/src/probe.ts b/extensions/msteams/src/probe.ts index 6bbcc0b3c3c..b6732c658c4 100644 --- a/extensions/msteams/src/probe.ts +++ b/extensions/msteams/src/probe.ts @@ -1,11 +1,9 @@ -import type { MSTeamsConfig } from "openclaw/plugin-sdk"; +import type { BaseProbeResult, MSTeamsConfig } from "openclaw/plugin-sdk"; import { formatUnknownError } from "./errors.js"; import { loadMSTeamsSdkWithAuth } from "./sdk.js"; import { resolveMSTeamsCredentials } from "./token.js"; -export type ProbeMSTeamsResult = { - ok: boolean; - error?: string; +export type ProbeMSTeamsResult = BaseProbeResult & { appId?: string; graph?: { ok: boolean; diff --git a/extensions/twitch/src/probe.ts b/extensions/twitch/src/probe.ts index 56ea99146d5..41321103a45 100644 --- a/extensions/twitch/src/probe.ts +++ b/extensions/twitch/src/probe.ts @@ -1,3 +1,4 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import { StaticAuthProvider } from "@twurple/auth"; import { ChatClient } from "@twurple/chat"; import type { TwitchAccountConfig } from "./types.js"; @@ -6,9 +7,7 @@ import { normalizeToken } from "./utils/twitch.js"; /** * Result of probing a Twitch account */ -export type ProbeTwitchResult = { - ok: boolean; - error?: string; +export type ProbeTwitchResult = BaseProbeResult & { username?: string; elapsedMs: number; connected?: boolean; diff --git a/extensions/zalo/src/probe.ts b/extensions/zalo/src/probe.ts index ebdb37a34f3..c2d95fa1d28 100644 --- a/extensions/zalo/src/probe.ts +++ b/extensions/zalo/src/probe.ts @@ -1,9 +1,8 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import { getMe, ZaloApiError, type ZaloBotInfo, type ZaloFetch } from "./api.js"; -export type ZaloProbeResult = { - ok: boolean; +export type ZaloProbeResult = BaseProbeResult & { bot?: ZaloBotInfo; - error?: string; elapsedMs: number; }; diff --git a/extensions/zalo/src/token.ts b/extensions/zalo/src/token.ts index 480f66c8fad..b335f57a3c2 100644 --- a/extensions/zalo/src/token.ts +++ b/extensions/zalo/src/token.ts @@ -1,9 +1,8 @@ import { readFileSync } from "node:fs"; -import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk"; +import { type BaseTokenResolution, DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk"; import type { ZaloConfig } from "./types.js"; -export type ZaloTokenResolution = { - token: string; +export type ZaloTokenResolution = BaseTokenResolution & { source: "env" | "config" | "configFile" | "none"; }; diff --git a/extensions/zalouser/src/probe.ts b/extensions/zalouser/src/probe.ts index bfeb92ec586..6bdc962052f 100644 --- a/extensions/zalouser/src/probe.ts +++ b/extensions/zalouser/src/probe.ts @@ -1,11 +1,10 @@ +import type { BaseProbeResult } from "openclaw/plugin-sdk"; import type { ZcaUserInfo } from "./types.js"; import { runZca, parseJsonOutput } from "./zca.js"; -export interface ZalouserProbeResult { - ok: boolean; +export type ZalouserProbeResult = BaseProbeResult & { user?: ZcaUserInfo; - error?: string; -} +}; export async function probeZalouser( profile: string, diff --git a/src/channels/plugins/base-types-assignability.test.ts b/src/channels/plugins/base-types-assignability.test.ts new file mode 100644 index 00000000000..839146018fe --- /dev/null +++ b/src/channels/plugins/base-types-assignability.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expectTypeOf } from "vitest"; +import type { DiscordProbe } from "../../discord/probe.js"; +import type { DiscordTokenResolution } from "../../discord/token.js"; +import type { IMessageProbe } from "../../imessage/probe.js"; +import type { LineProbeResult } from "../../line/types.js"; +import type { SignalProbe } from "../../signal/probe.js"; +import type { SlackProbe } from "../../slack/probe.js"; +import type { TelegramProbe } from "../../telegram/probe.js"; +import type { TelegramTokenResolution } from "../../telegram/token.js"; +import type { BaseProbeResult, BaseTokenResolution } from "./types.js"; + +describe("BaseProbeResult assignability", () => { + it("TelegramProbe satisfies BaseProbeResult", () => { + expectTypeOf().toMatchTypeOf(); + }); + + it("DiscordProbe satisfies BaseProbeResult", () => { + expectTypeOf().toMatchTypeOf(); + }); + + it("SlackProbe satisfies BaseProbeResult", () => { + expectTypeOf().toMatchTypeOf(); + }); + + it("SignalProbe satisfies BaseProbeResult", () => { + expectTypeOf().toMatchTypeOf(); + }); + + it("IMessageProbe satisfies BaseProbeResult", () => { + expectTypeOf().toMatchTypeOf(); + }); + + it("LineProbeResult satisfies BaseProbeResult", () => { + expectTypeOf().toMatchTypeOf(); + }); +}); + +describe("BaseTokenResolution assignability", () => { + it("TelegramTokenResolution satisfies BaseTokenResolution", () => { + expectTypeOf().toMatchTypeOf(); + }); + + it("DiscordTokenResolution satisfies BaseTokenResolution", () => { + expectTypeOf().toMatchTypeOf(); + }); +}); diff --git a/src/channels/plugins/types.core.ts b/src/channels/plugins/types.core.ts index a2195597e0c..2178acd5eee 100644 --- a/src/channels/plugins/types.core.ts +++ b/src/channels/plugins/types.core.ts @@ -347,3 +347,15 @@ export type ChannelPollContext = { silent?: boolean; isAnonymous?: boolean; }; + +/** Minimal base for all channel probe results. Channel-specific probes extend this. */ +export type BaseProbeResult = { + ok: boolean; + error?: TError; +}; + +/** Minimal base for token resolution results. */ +export type BaseTokenResolution = { + token: string; + source: string; +}; diff --git a/src/channels/plugins/types.ts b/src/channels/plugins/types.ts index d7175f1765b..d3028e9970d 100644 --- a/src/channels/plugins/types.ts +++ b/src/channels/plugins/types.ts @@ -58,6 +58,8 @@ export type { ChannelThreadingContext, ChannelThreadingToolContext, ChannelToolSend, + BaseProbeResult, + BaseTokenResolution, } from "./types.core.js"; export type { ChannelPlugin } from "./types.plugin.js"; diff --git a/src/discord/probe.ts b/src/discord/probe.ts index fd01cdd5446..b199e89fdd5 100644 --- a/src/discord/probe.ts +++ b/src/discord/probe.ts @@ -1,13 +1,12 @@ +import type { BaseProbeResult } from "../channels/plugins/types.js"; import { resolveFetch } from "../infra/fetch.js"; import { fetchWithTimeout } from "../utils/fetch-timeout.js"; import { normalizeDiscordToken } from "./token.js"; const DISCORD_API_BASE = "https://discord.com/api/v10"; -export type DiscordProbe = { - ok: boolean; +export type DiscordProbe = BaseProbeResult & { status?: number | null; - error?: string | null; elapsedMs: number; bot?: { id?: string | null; username?: string | null }; application?: DiscordApplicationSummary; diff --git a/src/discord/token.ts b/src/discord/token.ts index 2187fbc32b4..5f265994044 100644 --- a/src/discord/token.ts +++ b/src/discord/token.ts @@ -1,10 +1,10 @@ +import type { BaseTokenResolution } from "../channels/plugins/types.js"; import type { OpenClawConfig } from "../config/config.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; export type DiscordTokenSource = "env" | "config" | "none"; -export type DiscordTokenResolution = { - token: string; +export type DiscordTokenResolution = BaseTokenResolution & { source: DiscordTokenSource; }; diff --git a/src/imessage/probe.ts b/src/imessage/probe.ts index 9226d48b1e2..27b228e8d58 100644 --- a/src/imessage/probe.ts +++ b/src/imessage/probe.ts @@ -1,3 +1,4 @@ +import type { BaseProbeResult } from "../channels/plugins/types.js"; import type { RuntimeEnv } from "../runtime.js"; import { detectBinary } from "../commands/onboard-helpers.js"; import { loadConfig } from "../config/config.js"; @@ -8,9 +9,7 @@ import { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS } from "./constants.js"; // Re-export for backwards compatibility export { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS } from "./constants.js"; -export type IMessageProbe = { - ok: boolean; - error?: string | null; +export type IMessageProbe = BaseProbeResult & { fatal?: boolean; }; diff --git a/src/line/types.ts b/src/line/types.ts index dbd157cad71..8a797016255 100644 --- a/src/line/types.ts +++ b/src/line/types.ts @@ -7,6 +7,7 @@ import type { StickerMessage, LocationMessage, } from "@line/bot-sdk"; +import type { BaseProbeResult } from "../channels/plugins/types.js"; export type LineTokenSource = "config" | "env" | "file" | "none"; @@ -86,16 +87,14 @@ export interface LineSendResult { chatId: string; } -export interface LineProbeResult { - ok: boolean; +export type LineProbeResult = BaseProbeResult & { bot?: { displayName?: string; userId?: string; basicId?: string; pictureUrl?: string; }; - error?: string; -} +}; export type LineFlexMessagePayload = { altText: string; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 6a8d967244c..48ad88aaccb 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -56,6 +56,8 @@ export type { ChannelThreadingContext, ChannelThreadingToolContext, ChannelToolSend, + BaseProbeResult, + BaseTokenResolution, } from "../channels/plugins/types.js"; export type { ChannelConfigSchema, ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { diff --git a/src/signal/probe.ts b/src/signal/probe.ts index 9a6238048ad..924f997015e 100644 --- a/src/signal/probe.ts +++ b/src/signal/probe.ts @@ -1,9 +1,8 @@ +import type { BaseProbeResult } from "../channels/plugins/types.js"; import { signalCheck, signalRpcRequest } from "./client.js"; -export type SignalProbe = { - ok: boolean; +export type SignalProbe = BaseProbeResult & { status?: number | null; - error?: string | null; elapsedMs: number; version?: string | null; }; diff --git a/src/slack/probe.ts b/src/slack/probe.ts index cde5e515737..fa67b1f191e 100644 --- a/src/slack/probe.ts +++ b/src/slack/probe.ts @@ -1,9 +1,8 @@ +import type { BaseProbeResult } from "../channels/plugins/types.js"; import { createSlackWebClient } from "./client.js"; -export type SlackProbe = { - ok: boolean; +export type SlackProbe = BaseProbeResult & { status?: number | null; - error?: string | null; elapsedMs?: number | null; bot?: { id?: string; name?: string }; team?: { id?: string; name?: string }; diff --git a/src/telegram/probe.ts b/src/telegram/probe.ts index cc65f987f5e..f988733f0ee 100644 --- a/src/telegram/probe.ts +++ b/src/telegram/probe.ts @@ -1,12 +1,11 @@ +import type { BaseProbeResult } from "../channels/plugins/types.js"; import { fetchWithTimeout } from "../utils/fetch-timeout.js"; import { makeProxyFetch } from "./proxy.js"; const TELEGRAM_API_BASE = "https://api.telegram.org"; -export type TelegramProbe = { - ok: boolean; +export type TelegramProbe = BaseProbeResult & { status?: number | null; - error?: string | null; elapsedMs: number; bot?: { id?: number | null; diff --git a/src/telegram/token.ts b/src/telegram/token.ts index ed11d3f7476..461fcf5259c 100644 --- a/src/telegram/token.ts +++ b/src/telegram/token.ts @@ -1,12 +1,12 @@ import fs from "node:fs"; +import type { BaseTokenResolution } from "../channels/plugins/types.js"; import type { OpenClawConfig } from "../config/config.js"; import type { TelegramAccountConfig } from "../config/types.telegram.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; export type TelegramTokenSource = "env" | "tokenFile" | "config" | "none"; -export type TelegramTokenResolution = { - token: string; +export type TelegramTokenResolution = BaseTokenResolution & { source: TelegramTokenSource; };