fix(slack): extract attachment text for bot messages with empty text (#27616) (#27642)

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini
This commit is contained in:
lailoo
2026-03-02 00:49:51 +08:00
committed by GitHub
parent 949faff5ce
commit 43ddb41354
3 changed files with 49 additions and 1 deletions

View File

@@ -82,6 +82,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Slack/Bot attachment-only messages: when `allowBots: true`, bot messages with empty `text` now include non-forwarded attachment `text`/`fallback` content so webhook alerts are not silently dropped. (#27616)
- Slack/Security ingress mismatch guard: drop slash-command and interaction payloads when app/team identifiers do not match the active Slack account context (including nested `team.id` interaction payloads), preventing cross-app or cross-workspace payload injection into system-event handling. (#29091) Thanks @Solvely-Colin.
- Cron/Failure alerts: add configurable repeated-failure alerting with per-job overrides and Web UI cron editor support (`inherit|disabled|custom` with threshold/cooldown/channel/target fields). (#24789) Thanks xbrak.
- Cron/Isolated model defaults: resolve isolated cron `subagents.model` (including object-form `primary`) through allowlist-aware model selection so isolated cron runs honor subagent model defaults unless explicitly overridden by job payload model. (#11474) Thanks @AnonO6.

View File

@@ -255,6 +255,36 @@ describe("slack prepareSlackMessage inbound contract", () => {
expect(prepared!.ctxPayload.RawBody).toContain("[Slack file: file]");
});
it("extracts attachment text for bot messages with empty text when allowBots is true (#27616)", async () => {
const slackCtx = createInboundSlackCtx({
cfg: {
channels: {
slack: { enabled: true },
},
} as OpenClawConfig,
defaultRequireMention: false,
});
// oxlint-disable-next-line typescript/no-explicit-any
slackCtx.resolveUserName = async () => ({ name: "Bot" }) as any;
const account = createSlackAccount({ allowBots: true });
const message = createSlackMessage({
text: "",
bot_id: "B0AGV8EQYA3",
subtype: "bot_message",
attachments: [
{
text: "Readiness probe failed: Get http://10.42.13.132:8000/status: context deadline exceeded",
},
],
});
const prepared = await prepareMessageWith(slackCtx, account, message);
expect(prepared).toBeTruthy();
expect(prepared!.ctxPayload.RawBody).toContain("Readiness probe failed");
});
it("keeps channel metadata out of GroupSystemPrompt", async () => {
const slackCtx = createInboundSlackCtx({
cfg: {

View File

@@ -357,8 +357,25 @@ export async function prepareSlackMessage(params: {
: undefined;
const fileOnlyPlaceholder = fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : undefined;
// Bot messages (e.g. Prometheus, Gatus webhooks) often carry content only in
// non-forwarded attachments (is_share !== true). Extract their text/fallback
// so the message isn't silently dropped when `allowBots: true` (#27616).
const botAttachmentText =
isBotMessage && !attachmentContent?.text
? (message.attachments ?? [])
.map((a) => a.text?.trim() || a.fallback?.trim())
.filter(Boolean)
.join("\n")
: undefined;
const rawBody =
[(message.text ?? "").trim(), attachmentContent?.text, mediaPlaceholder, fileOnlyPlaceholder]
[
(message.text ?? "").trim(),
attachmentContent?.text,
botAttachmentText,
mediaPlaceholder,
fileOnlyPlaceholder,
]
.filter(Boolean)
.join("\n") || "";
if (!rawBody) {