diff --git a/src/slack/monitor/events/interactions.test.ts b/src/slack/monitor/events/interactions.test.ts index c94f10d04..15b895a49 100644 --- a/src/slack/monitor/events/interactions.test.ts +++ b/src/slack/monitor/events/interactions.test.ts @@ -707,7 +707,15 @@ describe("registerSlackInteractionEvents", () => { type: "rich_text_input", rich_text_value: { type: "rich_text", - elements: [{ type: "rich_text_section", elements: [] }], + elements: [ + { + type: "rich_text_section", + elements: [ + { type: "text", text: "Ship this now" }, + { type: "text", text: "with canary metrics" }, + ], + }, + ], }, }, }, @@ -736,6 +744,7 @@ describe("registerSlackInteractionEvents", () => { inputEmail?: string; inputUrl?: string; richTextValue?: unknown; + richTextPreview?: string; }>; }; expect(payload.inputs).toEqual( @@ -791,15 +800,72 @@ describe("registerSlackInteractionEvents", () => { expect.objectContaining({ actionId: "richtext_input", inputKind: "rich_text", + richTextPreview: "Ship this now with canary metrics", richTextValue: { type: "rich_text", - elements: [{ type: "rich_text_section", elements: [] }], + elements: [ + { + type: "rich_text_section", + elements: [ + { type: "text", text: "Ship this now" }, + { type: "text", text: "with canary metrics" }, + ], + }, + ], }, }), ]), ); }); + it("truncates rich text preview to keep payload summaries compact", async () => { + enqueueSystemEventMock.mockReset(); + const { ctx, getViewHandler } = createContext(); + registerSlackInteractionEvents({ ctx: ctx as never }); + const viewHandler = getViewHandler(); + expect(viewHandler).toBeTruthy(); + + const longText = "deploy ".repeat(40).trim(); + const ack = vi.fn().mockResolvedValue(undefined); + await viewHandler!({ + ack, + body: { + user: { id: "U555" }, + view: { + id: "V555", + callback_id: "openclaw:long_richtext", + state: { + values: { + richtext_block: { + richtext_input: { + type: "rich_text_input", + rich_text_value: { + type: "rich_text", + elements: [ + { + type: "rich_text_section", + elements: [{ type: "text", text: longText }], + }, + ], + }, + }, + }, + }, + }, + }, + }, + }); + + expect(ack).toHaveBeenCalled(); + const [eventText] = enqueueSystemEventMock.mock.calls[0] as [string]; + const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as { + inputs: Array<{ actionId: string; richTextPreview?: string }>; + }; + const richInput = payload.inputs.find((input) => input.actionId === "richtext_input"); + expect(richInput?.richTextPreview).toBeTruthy(); + expect((richInput?.richTextPreview ?? "").length).toBeLessThanOrEqual(120); + }); + it("captures modal close events and enqueues view closed event", async () => { enqueueSystemEventMock.mockReset(); const { ctx, getViewClosedHandler, resolveSessionKey } = createContext(); diff --git a/src/slack/monitor/events/interactions.ts b/src/slack/monitor/events/interactions.ts index c2ee8f523..0917d45f5 100644 --- a/src/slack/monitor/events/interactions.ts +++ b/src/slack/monitor/events/interactions.ts @@ -38,6 +38,7 @@ type InteractionSummary = { inputEmail?: string; inputUrl?: string; richTextValue?: unknown; + richTextPreview?: string; userId?: string; teamId?: string; triggerId?: string; @@ -66,6 +67,7 @@ type ModalInputSummary = { inputEmail?: string; inputUrl?: string; richTextValue?: unknown; + richTextPreview?: string; }; function readOptionValues(options: unknown): string[] | undefined { @@ -107,6 +109,35 @@ function uniqueNonEmptyStrings(values: string[]): string[] { return unique; } +function collectRichTextFragments(value: unknown, out: string[]): void { + if (!value || typeof value !== "object") { + return; + } + const typed = value as { text?: unknown; elements?: unknown }; + if (typeof typed.text === "string" && typed.text.trim().length > 0) { + out.push(typed.text.trim()); + } + if (Array.isArray(typed.elements)) { + for (const child of typed.elements) { + collectRichTextFragments(child, out); + } + } +} + +function summarizeRichTextPreview(value: unknown): string | undefined { + const fragments: string[] = []; + collectRichTextFragments(value, fragments); + if (fragments.length === 0) { + return undefined; + } + const joined = fragments.join(" ").replace(/\s+/g, " ").trim(); + if (!joined) { + return undefined; + } + const max = 120; + return joined.length <= max ? joined : `${joined.slice(0, max - 1)}…`; +} + function summarizeAction( action: Record, ): Omit { @@ -166,6 +197,7 @@ function summarizeAction( } } const richTextValue = actionType === "rich_text_input" ? typed.rich_text_value : undefined; + const richTextPreview = summarizeRichTextPreview(richTextValue); const inputKind = actionType === "number_input" ? "number" @@ -197,6 +229,7 @@ function summarizeAction( inputEmail, inputUrl, richTextValue, + richTextPreview, }; } @@ -242,6 +275,9 @@ function formatInteractionSelectionLabel(params: { if (typeof params.summary.selectedDateTime === "number") { return new Date(params.summary.selectedDateTime * 1000).toISOString(); } + if (params.summary.richTextPreview) { + return params.summary.richTextPreview; + } if (params.summary.value?.trim()) { return params.summary.value.trim(); }