fix(slack): recover full rich text messages · openclaw/openclaw@710a83a
steipete
·
2026-05-11
·
via Recent Commits to openclaw:main
File tree
extensions/slack/src/monitor/message-handler
| Original file line number | Diff line number | Diff line change |
|---|
@@ -54,6 +54,7 @@ Docs: https://docs.openclaw.ai
|
54 | 54 | - Slack/ACP: route Slack channel and DM messages through configured ACP bindings when no runtime binding exists, keeping bound thread replies pinned to the persistent ACP session and dropping unavailable configured targets instead of falling back to `main`. (#73101) Thanks @Raasl. |
55 | 55 | - Slack: mark unresolved thread replies as ambiguous and skip them instead of treating them as root channel messages, keeping thread continuation on the SDK-backed participation store. (#75630) Thanks @soichiyo. |
56 | 56 | - Slack: let same-channel message tool sends opt out of inherited thread context with `topLevel: true` or `threadId: null`, allowing agents to post a new parent-channel message from inside a Slack thread. Fixes #79807. Thanks @vexclawx31. |
| 57 | +- Slack: prefer full rich-text block content over truncated socket-mode message previews so long inbound Slack messages reach agents intact. Fixes #79027. Thanks @BobAccentWebDev. |
57 | 58 | - Gateway/agents: keep structured reasons when active-run queueing fails and deprecate the legacy boolean queue helper, so steering and subagent wake diagnostics distinguish completed, non-streaming, and compacting runs. Fixes #80156. Thanks @markus-lassfolk. |
58 | 59 | - Agents/UI: compact exec and tool progress rows by hiding redundant shell tool names, replacing known workspace paths with short context markers, and preserving Discord trace scrubbing for compact command lines. |
59 | 60 | - ACPX: run and await the embedded ACP backend startup probe by default so the gateway `ready` signal no longer fires before the acpx runtime has either become usable or reported a probe failure; set `OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=0` to restore lazy startup. Fixes #79596. Thanks @bzelones. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -41,6 +41,11 @@ type SlackBlockLike = {
|
41 | 41 | title?: unknown; |
42 | 42 | }; |
43 | 43 | |
| 44 | +type SlackBlocksText = { |
| 45 | +text: string; |
| 46 | +hasRichText: boolean; |
| 47 | +}; |
| 48 | + |
44 | 49 | type SlackMediaModule = typeof import("../media.js"); |
45 | 50 | let slackMediaModulePromise: Promise<SlackMediaModule> | undefined; |
46 | 51 | |
@@ -220,33 +225,40 @@ function readSlackBlockText(block: unknown): string | undefined {
|
220 | 225 | } |
221 | 226 | } |
222 | 227 | |
223 | | -function resolveSlackBlocksText(blocks: unknown[] | undefined): string | undefined { |
| 228 | +function resolveSlackBlocksText(blocks: unknown[] | undefined): SlackBlocksText | undefined { |
224 | 229 | if (!blocks?.length) { |
225 | 230 | return undefined; |
226 | 231 | } |
227 | 232 | const parts: string[] = []; |
| 233 | +let hasRichText = false; |
228 | 234 | for (const block of blocks) { |
| 235 | +if (block && typeof block === "object" && (block as SlackBlockLike).type === "rich_text") { |
| 236 | +hasRichText = true; |
| 237 | +} |
229 | 238 | const text = readSlackBlockText(block); |
230 | 239 | if (text) { |
231 | 240 | parts.push(text); |
232 | 241 | } |
233 | 242 | } |
234 | | -return parts.length > 0 ? parts.join("\n") : undefined; |
| 243 | +return parts.length > 0 ? { text: parts.join("\n"), hasRichText } : undefined; |
235 | 244 | } |
236 | 245 | |
237 | 246 | function chooseSlackPrimaryText(params: { |
238 | 247 | messageText: string | undefined; |
239 | | -blocksText: string | undefined; |
| 248 | +blocksText: SlackBlocksText | undefined; |
240 | 249 | }): string | undefined { |
241 | 250 | const { messageText, blocksText } = params; |
242 | 251 | if (!blocksText) { |
243 | 252 | return messageText; |
244 | 253 | } |
245 | 254 | if (!messageText) { |
246 | | -return blocksText; |
| 255 | +return blocksText.text; |
| 256 | +} |
| 257 | +if (blocksText.hasRichText && blocksText.text.length > messageText.length) { |
| 258 | +return blocksText.text; |
247 | 259 | } |
248 | | -return blocksText.length > messageText.length && blocksText.startsWith(messageText) |
249 | | - ? blocksText |
| 260 | +return blocksText.text.length > messageText.length && blocksText.text.startsWith(messageText) |
| 261 | + ? blocksText.text |
250 | 262 | : messageText; |
251 | 263 | } |
252 | 264 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -485,6 +485,34 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
485 | 485 | expect(prepared.ctxPayload.BodyForAgent).toContain(fullText); |
486 | 486 | }); |
487 | 487 | |
| 488 | +it("recovers full Slack DM text when rich text differs from a truncated preview", async () => { |
| 489 | +const fullText = `First paragraph ${"keeps going ".repeat(14)} |
| 490 | +Second paragraph should still reach the agent after Slack's preview cutoff.`; |
| 491 | +const preview = `${fullText.slice(0, 200).replace(/\n/g, " ")}...`; |
| 492 | + |
| 493 | +const prepared = await prepareWithDefaultCtx( |
| 494 | +createSlackMessage({ |
| 495 | +text: preview, |
| 496 | +blocks: [ |
| 497 | +{ |
| 498 | +type: "rich_text", |
| 499 | +block_id: "b1", |
| 500 | +elements: [ |
| 501 | +{ |
| 502 | +type: "rich_text_section", |
| 503 | +elements: [{ type: "text", text: fullText }], |
| 504 | +}, |
| 505 | +], |
| 506 | +}, |
| 507 | +], |
| 508 | +}), |
| 509 | +); |
| 510 | + |
| 511 | +assertPrepared(prepared); |
| 512 | +expect(prepared.ctxPayload.RawBody).toBe(fullText); |
| 513 | +expect(prepared.ctxPayload.BodyForAgent).toContain(fullText); |
| 514 | +}); |
| 515 | + |
488 | 516 | it("ignores non-forward attachments when no direct text/files are present", async () => { |
489 | 517 | const prepared = await prepareWithDefaultCtx( |
490 | 518 | createSlackMessage({ |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。