fix(gateway,agent): only enforce session sendPolicy=deny when delivering

Co-authored-by: wenxu007 <270593229+wenxu007@users.noreply.github.com>
This commit is contained in:
clawsweeper
2026-05-02 23:52:32 +00:00
parent 3d64fcaf1f
commit 9eb981eb4b
3 changed files with 61 additions and 15 deletions

View File

@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
- Maintainer workflow: push prepared PR heads through GitHub's verified commit API by default and require an explicit override before git-protocol pushes can publish unsigned commits. Thanks @BunsDev.
- Feishu: resolve setup/status probes through the selected/default account so multi-account configs with account-scoped app credentials show as configured and probeable. Fixes #72930. Thanks @brokemac79.
- Gateway/responses: emit every client tool call from `/v1/responses` JSON and SSE responses when the agent invokes multiple client tools in a single turn, so multi-tool plans, graph orchestration calls, and similar batched flows no longer drop every call but the last. Fixes #52288. Thanks @CharZhou and @bonelli.
- Gateway/agent: enforce `session.sendPolicy=deny` on gateway agent requests only when `deliver: true`, so non-delivery smoke checks and internal agent runs are no longer rejected with `send blocked by session policy` while outbound delivery remains gated. Fixes #73381. Thanks @wenxu007.
- Slack/reactions: treat missing no_reaction remove responses as idempotent success and route own-reaction cleanup through the remove helper, so concurrent cleanup no longer surfaces Slack race errors. Fixes #50733. (#76304) Thanks @martingarramon and @Hollychou924.
- Control UI/Gateway: avoid full session-list reloads for locally applied message-phase session updates, carry known session keys through transcript-file update events, and defer media provider listing when explicit generation model config is present. Refs #76236, #76203, #76188, #76107, and #76166. Thanks @BunsDev.
- Install/update: prune the obsolete `plugin-runtime-deps` state directory during packaged postinstall so upgrades from pre-2026.5.2 releases reclaim old bundled-plugin dependency caches without touching external plugin installs.

View File

@@ -36,6 +36,7 @@ const mocks = vi.hoisted(() => ({
loadConfigReturn: {} as Record<string, unknown>,
loadVoiceWakeRoutingConfig: vi.fn(),
resolveVoiceWakeRouteByTrigger: vi.fn(),
resolveSendPolicy: vi.fn(() => "allow"),
}));
vi.mock("../session-utils.js", async () => {
@@ -128,7 +129,8 @@ vi.mock("../../infra/voicewake-routing.js", () => ({
}));
vi.mock("../../sessions/send-policy.js", () => ({
resolveSendPolicy: () => "allow",
resolveSendPolicy: (...args: unknown[]) =>
(mocks.resolveSendPolicy as (...args: unknown[]) => unknown)(...args),
}));
vi.mock("../../utils/delivery-context.js", async () => {
@@ -410,6 +412,7 @@ describe("gateway agent handler", () => {
mocks.resolveExplicitAgentSessionKey.mockReset().mockReturnValue(undefined);
mocks.resolveBareResetBootstrapFileAccess.mockReset().mockReturnValue(true);
mocks.listAgentIds.mockReset().mockReturnValue(["main"]);
mocks.resolveSendPolicy.mockReset().mockReturnValue("allow");
});
it("preserves ACP metadata from the current stored session entry", async () => {
@@ -2792,6 +2795,46 @@ describe("gateway agent handler", () => {
);
});
it("allows non-delivery agent invocations when sendPolicy is deny", async () => {
mocks.agentCommand.mockClear();
primeMainAgentRun();
mocks.resolveSendPolicy.mockReturnValue("deny");
const respond = await runMainAgent("smoke", "non-delivery-deny");
expect(respond).not.toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({ message: "send blocked by session policy" }),
);
await waitForAssertion(() => expect(mocks.agentCommand).toHaveBeenCalledTimes(1));
});
it("blocks delivery agent invocations when sendPolicy is deny", async () => {
primeMainAgentRun();
mocks.resolveSendPolicy.mockReturnValue("deny");
mocks.agentCommand.mockClear();
const respond = vi.fn();
await invokeAgent(
{
message: "smoke",
agentId: "main",
sessionKey: "agent:main:main",
idempotencyKey: "delivery-deny",
deliver: true,
},
{ respond, reqId: "delivery-deny" },
);
expect(respond).toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({ message: "send blocked by session policy" }),
);
expect(mocks.agentCommand).not.toHaveBeenCalled();
});
describe("groupId session-entry persistence validation", () => {
async function captureGroupEntryFields(
sessionKey: string,

View File

@@ -1069,20 +1069,22 @@ export const agentHandlers: GatewayRequestHandlers = {
claudeCliSessionId: entry?.claudeCliSessionId,
};
sessionEntry = mergeSessionEntry(entry, nextEntryPatch);
const sendPolicy = resolveSendPolicy({
cfg,
entry,
sessionKey: canonicalKey,
channel: entry?.channel,
chatType: entry?.chatType,
});
if (sendPolicy === "deny") {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, "send blocked by session policy"),
);
return;
if (request.deliver === true) {
const sendPolicy = resolveSendPolicy({
cfg,
entry,
sessionKey: canonicalKey,
channel: entry?.channel,
chatType: entry?.chatType,
});
if (sendPolicy === "deny") {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, "send blocked by session policy"),
);
return;
}
}
resolvedSessionId = sessionId;
const canonicalSessionKey = canonicalKey;