diff --git a/CHANGELOG.md b/CHANGELOG.md index 3351ba545e16..c84e11f285d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Gateway/approvals: treat `turnSourceTo` as optional in `canBridgeNoDeviceChatApprovalFromBackend`, matching the existing optional handling of `turnSourceAccountId` and `turnSourceThreadId`. Channels without a recipient concept (webchat, control-ui) leave `turnSourceTo` null on both the approval snapshot and the replay params, so the prior required-string check rejected every backend replay with `APPROVAL_CLIENT_MISMATCH`. Cross-channel replay is still gated by the required `turnSourceChannel` and `sessionKey` checks. Fixes #82132. (#82136) Thanks @ottodeng. - Cron: load runtime plugins before isolated cron model and delivery resolution so external channels can be selected for scheduled runs. (#82111) Thanks @medns. - Twitch: keep gateway accounts running until shutdown instead of treating successful monitor startup as a clean channel exit, preventing immediate auto-restart loops. Fixes #60071. (#81853) Thanks @edenfunf. - Agents/auto-reply: honor `agents.defaults.silentReply` and per-surface group silent-reply policy when generic agent-run failure fallbacks decide whether to send visible fallback text. Fixes #82060. (#82086) Thanks @taozengabc. diff --git a/src/gateway/node-invoke-system-run-approval.test.ts b/src/gateway/node-invoke-system-run-approval.test.ts index 2a20453cebf7..3012c819967c 100644 --- a/src/gateway/node-invoke-system-run-approval.test.ts +++ b/src/gateway/node-invoke-system-run-approval.test.ts @@ -689,6 +689,52 @@ describe("sanitizeSystemRunParamsForForwarding", () => { expectAllowOnceForwardingResult(result); }); + test("accepts trusted backend webchat replay when turnSourceTo is null on both sides (regression #82132)", () => { + const sessionKey = "agent:main:main"; + const record = makeChatRecord({ + sessionKey, + turnSourceChannel: "webchat", + turnSourceTo: null, + turnSourceAccountId: null, + turnSourceThreadId: null, + systemRunPlan: { + argv: ["echo", "SAFE"], + cwd: null, + commandText: "echo SAFE", + agentId: "main", + sessionKey, + }, + systemRunBinding: buildSystemRunApprovalBinding({ + argv: ["echo", "SAFE"], + cwd: null, + agentId: "main", + sessionKey, + }).binding, + }); + + const result = sanitizeSystemRunParamsForForwarding({ + rawParams: { + command: ["echo", "SAFE"], + rawCommand: "echo SAFE", + agentId: "main", + sessionKey, + turnSourceChannel: "webchat", + turnSourceTo: null, + turnSourceAccountId: null, + turnSourceThreadId: null, + runId: "approval-1", + approved: true, + approvalDecision: "allow-once", + }, + nodeId: "node-1", + client: trustedBackendClient, + execApprovalManager: manager(record), + nowMs: now, + }); + + expectAllowOnceForwardingResult(result); + }); + test("rejects trusted backend chat replay when session binding changes", () => { const result = sanitizeSystemRunParamsForForwarding({ rawParams: { diff --git a/src/gateway/node-invoke-system-run-approval.ts b/src/gateway/node-invoke-system-run-approval.ts index b51022fc968e..c2c91247192a 100644 --- a/src/gateway/node-invoke-system-run-approval.ts +++ b/src/gateway/node-invoke-system-run-approval.ts @@ -158,7 +158,13 @@ function canBridgeNoDeviceChatApprovalFromBackend(params: { actual: params.rawParams.turnSourceChannel, lowercase: true, }) && - matchesRequiredString({ + // turnSourceTo is channel-specific: required for messaging channels with a + // recipient (e.g. telegram chat id), null for channels without a "to" + // concept (webchat, control-ui). matchesRequiredString returns false on + // null expected, which broke webchat node exec approval replay. Treat it + // as optional so null-on-both-sides matches; required fields below + // (turnSourceChannel, sessionKey) still gate cross-channel replays. + matchesOptionalString({ expected: request.turnSourceTo, actual: params.rawParams.turnSourceTo, }) &&