mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 07:57:39 +00:00
Slack: route modal interactions via private metadata
This commit is contained in:
@@ -202,6 +202,7 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
view: {
|
view: {
|
||||||
id: "V123",
|
id: "V123",
|
||||||
callback_id: "openclaw:deploy_form",
|
callback_id: "openclaw:deploy_form",
|
||||||
|
private_metadata: JSON.stringify({ channelId: "D123", channelType: "im" }),
|
||||||
state: {
|
state: {
|
||||||
values: {
|
values: {
|
||||||
env_block: {
|
env_block: {
|
||||||
@@ -226,7 +227,10 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(ack).toHaveBeenCalled();
|
expect(ack).toHaveBeenCalled();
|
||||||
expect(resolveSessionKey).toHaveBeenCalledWith({});
|
expect(resolveSessionKey).toHaveBeenCalledWith({
|
||||||
|
channelId: "D123",
|
||||||
|
channelType: "im",
|
||||||
|
});
|
||||||
expect(enqueueSystemEventMock).toHaveBeenCalledTimes(1);
|
expect(enqueueSystemEventMock).toHaveBeenCalledTimes(1);
|
||||||
const [eventText] = enqueueSystemEventMock.mock.calls[0] as [string];
|
const [eventText] = enqueueSystemEventMock.mock.calls[0] as [string];
|
||||||
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
||||||
@@ -235,6 +239,7 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
callbackId: string;
|
callbackId: string;
|
||||||
viewId: string;
|
viewId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
routedChannelId?: string;
|
||||||
inputs: Array<{ actionId: string; selectedValues?: string[]; inputValue?: string }>;
|
inputs: Array<{ actionId: string; selectedValues?: string[]; inputValue?: string }>;
|
||||||
};
|
};
|
||||||
expect(payload).toMatchObject({
|
expect(payload).toMatchObject({
|
||||||
@@ -243,6 +248,7 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
callbackId: "openclaw:deploy_form",
|
callbackId: "openclaw:deploy_form",
|
||||||
viewId: "V123",
|
viewId: "V123",
|
||||||
userId: "U777",
|
userId: "U777",
|
||||||
|
routedChannelId: "D123",
|
||||||
});
|
});
|
||||||
expect(payload.inputs).toEqual(
|
expect(payload.inputs).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
@@ -269,7 +275,7 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
view: {
|
view: {
|
||||||
id: "V900",
|
id: "V900",
|
||||||
callback_id: "openclaw:deploy_form",
|
callback_id: "openclaw:deploy_form",
|
||||||
private_metadata: "run:123",
|
private_metadata: JSON.stringify({ sessionKey: "agent:main:slack:channel:C99" }),
|
||||||
state: {
|
state: {
|
||||||
values: {
|
values: {
|
||||||
env_block: {
|
env_block: {
|
||||||
@@ -288,9 +294,12 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(ack).toHaveBeenCalled();
|
expect(ack).toHaveBeenCalled();
|
||||||
expect(resolveSessionKey).toHaveBeenCalledWith({});
|
expect(resolveSessionKey).not.toHaveBeenCalled();
|
||||||
expect(enqueueSystemEventMock).toHaveBeenCalledTimes(1);
|
expect(enqueueSystemEventMock).toHaveBeenCalledTimes(1);
|
||||||
const [eventText] = enqueueSystemEventMock.mock.calls[0] as [string];
|
const [eventText, options] = enqueueSystemEventMock.mock.calls[0] as [
|
||||||
|
string,
|
||||||
|
{ sessionKey?: string },
|
||||||
|
];
|
||||||
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
||||||
interactionType: string;
|
interactionType: string;
|
||||||
actionId: string;
|
actionId: string;
|
||||||
@@ -308,12 +317,13 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
viewId: "V900",
|
viewId: "V900",
|
||||||
userId: "U900",
|
userId: "U900",
|
||||||
isCleared: true,
|
isCleared: true,
|
||||||
privateMetadata: "run:123",
|
privateMetadata: JSON.stringify({ sessionKey: "agent:main:slack:channel:C99" }),
|
||||||
});
|
});
|
||||||
expect(payload.inputs).toEqual(
|
expect(payload.inputs).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({ actionId: "env_select", selectedValues: ["canary"] }),
|
expect.objectContaining({ actionId: "env_select", selectedValues: ["canary"] }),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
expect(options.sessionKey).toBe("agent:main:slack:channel:C99");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ type ModalInputSummary = {
|
|||||||
inputValue?: string;
|
inputValue?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ModalPrivateMetadata = {
|
||||||
|
sessionKey?: string;
|
||||||
|
channelId?: string;
|
||||||
|
channelType?: string;
|
||||||
|
};
|
||||||
|
|
||||||
function readOptionValues(options: unknown): string[] | undefined {
|
function readOptionValues(options: unknown): string[] | undefined {
|
||||||
if (!Array.isArray(options)) {
|
if (!Array.isArray(options)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -146,6 +152,53 @@ function summarizeViewState(values: unknown): ModalInputSummary[] {
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseModalPrivateMetadata(raw: unknown): ModalPrivateMetadata {
|
||||||
|
if (typeof raw !== "string" || raw.trim().length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||||
|
const sessionKey =
|
||||||
|
typeof parsed.sessionKey === "string" && parsed.sessionKey.trim().length > 0
|
||||||
|
? parsed.sessionKey
|
||||||
|
: undefined;
|
||||||
|
const channelId =
|
||||||
|
typeof parsed.channelId === "string" && parsed.channelId.trim().length > 0
|
||||||
|
? parsed.channelId
|
||||||
|
: undefined;
|
||||||
|
const channelType =
|
||||||
|
typeof parsed.channelType === "string" && parsed.channelType.trim().length > 0
|
||||||
|
? parsed.channelType
|
||||||
|
: undefined;
|
||||||
|
return { sessionKey, channelId, channelType };
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveModalSessionRouting(params: {
|
||||||
|
ctx: SlackMonitorContext;
|
||||||
|
privateMetadata: unknown;
|
||||||
|
}): { sessionKey: string; channelId?: string; channelType?: string } {
|
||||||
|
const metadata = parseModalPrivateMetadata(params.privateMetadata);
|
||||||
|
if (metadata.sessionKey) {
|
||||||
|
return { sessionKey: metadata.sessionKey };
|
||||||
|
}
|
||||||
|
if (metadata.channelId) {
|
||||||
|
return {
|
||||||
|
sessionKey: params.ctx.resolveSlackSystemEventSessionKey({
|
||||||
|
channelId: metadata.channelId,
|
||||||
|
channelType: metadata.channelType,
|
||||||
|
}),
|
||||||
|
channelId: metadata.channelId,
|
||||||
|
channelType: metadata.channelType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sessionKey: params.ctx.resolveSlackSystemEventSessionKey({}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContext }) {
|
export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContext }) {
|
||||||
const { ctx } = params;
|
const { ctx } = params;
|
||||||
if (typeof ctx.app.action !== "function") {
|
if (typeof ctx.app.action !== "function") {
|
||||||
@@ -292,6 +345,7 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
|
|||||||
view?: {
|
view?: {
|
||||||
id?: string;
|
id?: string;
|
||||||
callback_id?: string;
|
callback_id?: string;
|
||||||
|
private_metadata?: string;
|
||||||
state?: { values?: unknown };
|
state?: { values?: unknown };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -300,6 +354,10 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
|
|||||||
const userId = typedBody.user?.id ?? "unknown";
|
const userId = typedBody.user?.id ?? "unknown";
|
||||||
const viewId = typedBody.view?.id;
|
const viewId = typedBody.view?.id;
|
||||||
const inputs = summarizeViewState(typedBody.view?.state?.values);
|
const inputs = summarizeViewState(typedBody.view?.state?.values);
|
||||||
|
const sessionRouting = resolveModalSessionRouting({
|
||||||
|
ctx,
|
||||||
|
privateMetadata: typedBody.view?.private_metadata,
|
||||||
|
});
|
||||||
const eventPayload = {
|
const eventPayload = {
|
||||||
interactionType: "view_submission",
|
interactionType: "view_submission",
|
||||||
actionId: `view:${callbackId}`,
|
actionId: `view:${callbackId}`,
|
||||||
@@ -307,6 +365,9 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
|
|||||||
viewId,
|
viewId,
|
||||||
userId,
|
userId,
|
||||||
teamId: typedBody.team?.id,
|
teamId: typedBody.team?.id,
|
||||||
|
privateMetadata: typedBody.view?.private_metadata,
|
||||||
|
routedChannelId: sessionRouting.channelId,
|
||||||
|
routedChannelType: sessionRouting.channelType,
|
||||||
inputs,
|
inputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -315,7 +376,7 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
|
|||||||
);
|
);
|
||||||
|
|
||||||
enqueueSystemEvent(`Slack interaction: ${JSON.stringify(eventPayload)}`, {
|
enqueueSystemEvent(`Slack interaction: ${JSON.stringify(eventPayload)}`, {
|
||||||
sessionKey: ctx.resolveSlackSystemEventSessionKey({}),
|
sessionKey: sessionRouting.sessionKey,
|
||||||
contextKey: ["slack:interaction:view", callbackId, viewId, userId]
|
contextKey: ["slack:interaction:view", callbackId, viewId, userId]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(":"),
|
.join(":"),
|
||||||
@@ -357,6 +418,10 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
|
|||||||
const userId = typedBody.user?.id ?? "unknown";
|
const userId = typedBody.user?.id ?? "unknown";
|
||||||
const viewId = typedBody.view?.id;
|
const viewId = typedBody.view?.id;
|
||||||
const inputs = summarizeViewState(typedBody.view?.state?.values);
|
const inputs = summarizeViewState(typedBody.view?.state?.values);
|
||||||
|
const sessionRouting = resolveModalSessionRouting({
|
||||||
|
ctx,
|
||||||
|
privateMetadata: typedBody.view?.private_metadata,
|
||||||
|
});
|
||||||
const eventPayload = {
|
const eventPayload = {
|
||||||
interactionType: "view_closed",
|
interactionType: "view_closed",
|
||||||
actionId: `view:${callbackId}`,
|
actionId: `view:${callbackId}`,
|
||||||
@@ -366,6 +431,8 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
|
|||||||
teamId: typedBody.team?.id,
|
teamId: typedBody.team?.id,
|
||||||
isCleared: typedBody.is_cleared === true,
|
isCleared: typedBody.is_cleared === true,
|
||||||
privateMetadata: typedBody.view?.private_metadata,
|
privateMetadata: typedBody.view?.private_metadata,
|
||||||
|
routedChannelId: sessionRouting.channelId,
|
||||||
|
routedChannelType: sessionRouting.channelType,
|
||||||
inputs,
|
inputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -376,7 +443,7 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
|
|||||||
);
|
);
|
||||||
|
|
||||||
enqueueSystemEvent(`Slack interaction: ${JSON.stringify(eventPayload)}`, {
|
enqueueSystemEvent(`Slack interaction: ${JSON.stringify(eventPayload)}`, {
|
||||||
sessionKey: ctx.resolveSlackSystemEventSessionKey({}),
|
sessionKey: sessionRouting.sessionKey,
|
||||||
contextKey: ["slack:interaction:view-closed", callbackId, viewId, userId]
|
contextKey: ["slack:interaction:view-closed", callbackId, viewId, userId]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(":"),
|
.join(":"),
|
||||||
|
|||||||
Reference in New Issue
Block a user