diff --git a/src/agents/tools/message-tool.test.ts b/src/agents/tools/message-tool.test.ts index 1a87e74ca0ec..e7edcb8b1e9e 100644 --- a/src/agents/tools/message-tool.test.ts +++ b/src/agents/tools/message-tool.test.ts @@ -1258,6 +1258,35 @@ describe("message tool explicit target guard", () => { expect(mocks.runMessageAction).not.toHaveBeenCalled(); }); + it.each([ + { + action: "poll", + params: { + action: "poll", + pollQuestion: "Lunch?", + pollOption: ["Pizza", "Sushi"], + }, + }, + { + action: "sticker", + params: { + action: "sticker", + stickerId: "sticker-1", + }, + }, + ] as const)("requires an explicit target for $action when configured", async ({ params }) => { + const tool = createMessageTool({ + runMessageAction: mocks.runMessageAction as never, + requireExplicitTarget: true, + currentChannelProvider: "slack", + currentChannelId: "channel:C123", + }); + + await expect(tool.execute("1", params)).rejects.toThrow(/Explicit message target required/i); + + expect(mocks.runMessageAction).not.toHaveBeenCalled(); + }); + it("allows upload-file when an explicit target is provided", async () => { mocks.runMessageAction.mockResolvedValueOnce({ kind: "action", diff --git a/src/agents/tools/message-tool.ts b/src/agents/tools/message-tool.ts index 447698fe5ce1..ef779d6fd331 100644 --- a/src/agents/tools/message-tool.ts +++ b/src/agents/tools/message-tool.ts @@ -50,7 +50,10 @@ import { runMessageAction, type MessageActionRunResult, } from "../../infra/outbound/message-action-runner.js"; -import { resolveAllowedMessageActions } from "../../infra/outbound/outbound-policy.js"; +import { + resolveAllowedMessageActions, + shouldApplyCrossContextMarker, +} from "../../infra/outbound/outbound-policy.js"; import { hasReplyPayloadContent } from "../../interactive/payload.js"; import { stringifyRouteThreadId } from "../../plugin-sdk/channel-route.js"; import { POLL_CREATION_PARAM_DEFS, SHARED_POLL_CREATION_PARAM_NAMES } from "../../poll-params.js"; @@ -83,18 +86,8 @@ import { const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES; const MESSAGE_TOOL_THREAD_READ_HINT = ' Use action="read" with threadId to fetch prior messages in a thread when you need conversation context you do not have yet.'; -const EXPLICIT_TARGET_ACTIONS = new Set([ - "send", - "sendWithEffect", - "sendAttachment", - "upload-file", - "reply", - "thread-reply", - "broadcast", -]); - function actionNeedsExplicitTarget(action: ChannelMessageActionName): boolean { - return EXPLICIT_TARGET_ACTIONS.has(action); + return action === "broadcast" || shouldApplyCrossContextMarker(action); } function normalizeMessageToolIdempotencyKeyPart(value: unknown): string | undefined {