From bd20c1e24d55e9d2328445795a795680f3dfe7b7 Mon Sep 17 00:00:00 2001 From: Colin Date: Mon, 16 Feb 2026 14:05:43 -0500 Subject: [PATCH] Slack: include stacked modal lifecycle context --- src/slack/monitor/events/interactions.test.ts | 36 +++++++++++++++ src/slack/monitor/events/interactions.ts | 45 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/slack/monitor/events/interactions.test.ts b/src/slack/monitor/events/interactions.test.ts index 241e571c2b6..23fede27927 100644 --- a/src/slack/monitor/events/interactions.test.ts +++ b/src/slack/monitor/events/interactions.test.ts @@ -30,6 +30,10 @@ type RegisteredViewHandler = (args: { view?: { id?: string; callback_id?: string; + root_view_id?: string; + previous_view_id?: string; + external_id?: string; + hash?: string; state?: { values?: Record>> }; }; }; @@ -44,6 +48,10 @@ type RegisteredViewClosedHandler = (args: { id?: string; callback_id?: string; private_metadata?: string; + root_view_id?: string; + previous_view_id?: string; + external_id?: string; + hash?: string; state?: { values?: Record>> }; }; is_cleared?: boolean; @@ -604,6 +612,10 @@ describe("registerSlackInteractionEvents", () => { view: { id: "V123", callback_id: "openclaw:deploy_form", + root_view_id: "VROOT", + previous_view_id: "VPREV", + external_id: "deploy-ext-1", + hash: "view-hash-1", private_metadata: JSON.stringify({ channelId: "D123", channelType: "im" }), state: { values: { @@ -642,6 +654,11 @@ describe("registerSlackInteractionEvents", () => { viewId: string; userId: string; routedChannelId?: string; + rootViewId?: string; + previousViewId?: string; + externalId?: string; + viewHash?: string; + isStackedView?: boolean; inputs: Array<{ actionId: string; selectedValues?: string[]; inputValue?: string }>; }; expect(payload).toMatchObject({ @@ -651,6 +668,11 @@ describe("registerSlackInteractionEvents", () => { viewId: "V123", userId: "U777", routedChannelId: "D123", + rootViewId: "VROOT", + previousViewId: "VPREV", + externalId: "deploy-ext-1", + viewHash: "view-hash-1", + isStackedView: true, }); expect(payload.inputs).toEqual( expect.arrayContaining([ @@ -939,6 +961,10 @@ describe("registerSlackInteractionEvents", () => { view: { id: "V900", callback_id: "openclaw:deploy_form", + root_view_id: "VROOT900", + previous_view_id: "VPREV900", + external_id: "deploy-ext-900", + hash: "view-hash-900", private_metadata: JSON.stringify({ sessionKey: "agent:main:slack:channel:C99" }), state: { values: { @@ -972,6 +998,11 @@ describe("registerSlackInteractionEvents", () => { userId: string; isCleared: boolean; privateMetadata: string; + rootViewId?: string; + previousViewId?: string; + externalId?: string; + viewHash?: string; + isStackedView?: boolean; inputs: Array<{ actionId: string; selectedValues?: string[] }>; }; expect(payload).toMatchObject({ @@ -982,6 +1013,11 @@ describe("registerSlackInteractionEvents", () => { userId: "U900", isCleared: true, privateMetadata: JSON.stringify({ sessionKey: "agent:main:slack:channel:C99" }), + rootViewId: "VROOT900", + previousViewId: "VPREV900", + externalId: "deploy-ext-900", + viewHash: "view-hash-900", + isStackedView: true, }); expect(payload.inputs).toEqual( expect.arrayContaining([ diff --git a/src/slack/monitor/events/interactions.ts b/src/slack/monitor/events/interactions.ts index 257e0d4fe44..d1c050c1b01 100644 --- a/src/slack/monitor/events/interactions.ts +++ b/src/slack/monitor/events/interactions.ts @@ -348,6 +348,31 @@ function resolveModalSessionRouting(params: { }; } +function summarizeSlackViewLifecycleContext(view: { + root_view_id?: string; + previous_view_id?: string; + external_id?: string; + hash?: string; +}): { + rootViewId?: string; + previousViewId?: string; + externalId?: string; + viewHash?: string; + isStackedView?: boolean; +} { + const rootViewId = view.root_view_id; + const previousViewId = view.previous_view_id; + const externalId = view.external_id; + const viewHash = view.hash; + return { + rootViewId, + previousViewId, + externalId, + viewHash, + isStackedView: Boolean(previousViewId), + }; +} + export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContext }) { const { ctx } = params; if (typeof ctx.app.action !== "function") { @@ -513,6 +538,10 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex id?: string; callback_id?: string; private_metadata?: string; + root_view_id?: string; + previous_view_id?: string; + external_id?: string; + hash?: string; state?: { values?: unknown }; }; }; @@ -532,6 +561,12 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex viewId, userId, teamId: typedBody.team?.id, + ...summarizeSlackViewLifecycleContext({ + root_view_id: typedBody.view?.root_view_id, + previous_view_id: typedBody.view?.previous_view_id, + external_id: typedBody.view?.external_id, + hash: typedBody.view?.hash, + }), privateMetadata: typedBody.view?.private_metadata, routedChannelId: sessionRouting.channelId, routedChannelType: sessionRouting.channelType, @@ -576,6 +611,10 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex id?: string; callback_id?: string; private_metadata?: string; + root_view_id?: string; + previous_view_id?: string; + external_id?: string; + hash?: string; state?: { values?: unknown }; }; is_cleared?: boolean; @@ -596,6 +635,12 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex viewId, userId, teamId: typedBody.team?.id, + ...summarizeSlackViewLifecycleContext({ + root_view_id: typedBody.view?.root_view_id, + previous_view_id: typedBody.view?.previous_view_id, + external_id: typedBody.view?.external_id, + hash: typedBody.view?.hash, + }), isCleared: typedBody.is_cleared === true, privateMetadata: typedBody.view?.private_metadata, routedChannelId: sessionRouting.channelId,