mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:40:44 +00:00
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:
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user