diff --git a/CHANGELOG.md b/CHANGELOG.md index a7fac9e5c19..53c2e0745bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ Docs: https://docs.openclaw.ai - Cron/Schedule errors: notify users when a job is auto-disabled after repeated schedule computation failures. (#29098) Thanks . - Cron/Schedule errors: notify users when a job is auto-disabled after repeated schedule computation failures. (#29098) Thanks . - File tools/tilde paths: expand `~/...` against the user home directory before workspace-root checks in host file read/write/edit paths, while preserving root-boundary enforcement so outside-root targets remain blocked. (#29779) Thanks @Glucksberg. +- Slack/HTTP mode startup: treat Slack HTTP accounts as configured when `botToken` + `signingSecret` are present (without requiring `appToken`) in channel config/runtime status so webhook mode is not silently skipped. (#30567) - Onboarding/Custom providers: raise default custom-provider model context window to the runtime hard minimum (16k) and auto-heal existing custom model entries below that threshold during reconfiguration, preventing immediate `Model context window too small (4096 tokens)` failures. (#21653) Thanks @r4jiv007. - Web UI/Assistant text: strip internal `...` scaffolding from rendered assistant messages (while preserving code-fence literals), preventing memory-context leakage in chat output for models that echo internal blocks. (#29851) Thanks @Valkster70. - Dashboard/Sessions: allow authenticated Control UI clients to delete and patch sessions while still blocking regular webchat clients from session mutation RPCs, fixing Dashboard session delete failures. (#21264) Thanks @jskoiz. diff --git a/extensions/slack/src/channel.test.ts b/extensions/slack/src/channel.test.ts index 60e760c9950..abd9f85fa35 100644 --- a/extensions/slack/src/channel.test.ts +++ b/extensions/slack/src/channel.test.ts @@ -1,3 +1,4 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk"; import { describe, expect, it, vi } from "vitest"; const handleSlackActionMock = vi.fn(); @@ -104,3 +105,42 @@ describe("slackPlugin outbound", () => { expect(result).toEqual({ channel: "slack", messageId: "m-media" }); }); }); + +describe("slackPlugin config", () => { + it("treats HTTP mode accounts with bot token + signing secret as configured", () => { + const cfg: OpenClawConfig = { + channels: { + slack: { + mode: "http", + botToken: "xoxb-http", + signingSecret: "secret-http", + }, + }, + }; + + const account = slackPlugin.config.resolveAccount(cfg, "default"); + const configured = slackPlugin.config.isConfigured?.(account, cfg); + const snapshot = slackPlugin.status?.buildAccountSnapshot?.({ account, runtime: undefined }); + + expect(configured).toBe(true); + expect(snapshot?.configured).toBe(true); + }); + + it("keeps socket mode requiring app token", () => { + const cfg: OpenClawConfig = { + channels: { + slack: { + mode: "socket", + botToken: "xoxb-socket", + }, + }, + }; + + const account = slackPlugin.config.resolveAccount(cfg, "default"); + const configured = slackPlugin.config.isConfigured?.(account, cfg); + const snapshot = slackPlugin.status?.buildAccountSnapshot?.({ account, runtime: undefined }); + + expect(configured).toBe(false); + expect(snapshot?.configured).toBe(false); + }); +}); diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index 003fd895774..2f6555cf6ca 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -51,6 +51,18 @@ function getTokenForOperation( return botToken ?? userToken; } +function isSlackAccountConfigured(account: ResolvedSlackAccount): boolean { + const mode = account.config.mode ?? "socket"; + const hasBotToken = Boolean(account.botToken?.trim()); + if (!hasBotToken) { + return false; + } + if (mode === "http") { + return Boolean(account.config.signingSecret?.trim()); + } + return Boolean(account.appToken?.trim()); +} + export const slackPlugin: ChannelPlugin = { id: "slack", meta: { @@ -116,12 +128,12 @@ export const slackPlugin: ChannelPlugin = { accountId, clearBaseFields: ["botToken", "appToken", "name"], }), - isConfigured: (account) => Boolean(account.botToken && account.appToken), + isConfigured: (account) => isSlackAccountConfigured(account), describeAccount: (account) => ({ accountId: account.accountId, name: account.name, enabled: account.enabled, - configured: Boolean(account.botToken && account.appToken), + configured: isSlackAccountConfigured(account), botTokenSource: account.botTokenSource, appTokenSource: account.appTokenSource, }), @@ -382,7 +394,7 @@ export const slackPlugin: ChannelPlugin = { return await getSlackRuntime().channel.slack.probeSlack(token, timeoutMs); }, buildAccountSnapshot: ({ account, runtime, probe }) => { - const configured = Boolean(account.botToken && account.appToken); + const configured = isSlackAccountConfigured(account); return { accountId: account.accountId, name: account.name,