From cae169cd4f2dcb54fd2208738aedd4df5bb373b4 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Fri, 3 Apr 2026 12:43:37 -0400 Subject: [PATCH] fix: preserve approval account bindings --- CHANGELOG.md | 1 + extensions/matrix/src/exec-approvals.test.ts | 10 +++++++--- extensions/telegram/src/exec-approvals.test.ts | 10 +++++++--- src/infra/exec-approval-session-target.test.ts | 14 +++++++++----- src/infra/exec-approval-session-target.ts | 4 ++-- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a61aa5e412..de4c8ea7b81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ Docs: https://docs.openclaw.ai - Control UI/chat: keep the Stop button visible during tool-only execution so abortable runs do not fall back to Send while tools are still running. (#54528) thanks @chziyue. - Discord/voice: make READY auto-join fire-and-forget while keeping the shorter initial voice-connect timeout separate from the longer playback-start wait. (#60345) Thanks @geekhuashan. - Agents/skills: add inherited `agents.defaults.skills` allowlists, make per-agent `agents.list[].skills` replace defaults instead of merging, and scope embedded, session, sandbox, and cron skill snapshots through the effective runtime agent. (#59992) Thanks @gumadeiras. +- Matrix/Telegram exec approvals: recover stored same-channel account bindings even when session reply state drifted to another channel, so foreign-channel approvals route to the bound account instead of fanning out or being rejected as ambiguous. (#60417) thanks @gumadeiras. ## 2026.4.2 diff --git a/extensions/matrix/src/exec-approvals.test.ts b/extensions/matrix/src/exec-approvals.test.ts index 10a76bb4497..89c5e0cc7cf 100644 --- a/extensions/matrix/src/exec-approvals.test.ts +++ b/extensions/matrix/src/exec-approvals.test.ts @@ -276,9 +276,13 @@ describe("matrix exec approvals", () => { "agent:ops-agent:matrix:channel:!room:example.org": { sessionId: "main", updatedAt: 1, - lastChannel: "matrix", - lastTo: "room:!room:example.org", - lastAccountId: "ops", + origin: { + provider: "matrix", + accountId: "ops", + }, + lastChannel: "slack", + lastTo: "channel:C999", + lastAccountId: "work", }, }), "utf-8", diff --git a/extensions/telegram/src/exec-approvals.test.ts b/extensions/telegram/src/exec-approvals.test.ts index 5e1a048efd8..9b3157e1a1c 100644 --- a/extensions/telegram/src/exec-approvals.test.ts +++ b/extensions/telegram/src/exec-approvals.test.ts @@ -128,9 +128,13 @@ describe("telegram exec approvals", () => { "agent:ops:telegram:direct:123": { sessionId: "main", updatedAt: 1, - lastChannel: "telegram", - lastTo: "123", - lastAccountId: "ops", + origin: { + provider: "telegram", + accountId: "ops", + }, + lastChannel: "slack", + lastTo: "channel:C999", + lastAccountId: "work", }, }), "utf-8", diff --git a/src/infra/exec-approval-session-target.test.ts b/src/infra/exec-approval-session-target.test.ts index f1d34ab8b04..6b47bedc3a2 100644 --- a/src/infra/exec-approval-session-target.test.ts +++ b/src/infra/exec-approval-session-target.test.ts @@ -251,15 +251,19 @@ describe("exec approval session target", () => { "agent:main:matrix:channel:!ops:example.org": { sessionId: "main", updatedAt: 1, - lastChannel: "matrix", - lastTo: "room:!ops:example.org", - lastAccountId: "ops", + origin: { + provider: "matrix", + accountId: "ops", + }, + lastChannel: "slack", + lastTo: "channel:C123", + lastAccountId: "work", }, }); const request = buildRequest({ sessionKey: "agent:main:matrix:channel:!ops:example.org", - turnSourceChannel: "slack", - turnSourceTo: "channel:C123", + turnSourceChannel: "discord", + turnSourceTo: "channel:D123", turnSourceAccountId: "work", }); diff --git a/src/infra/exec-approval-session-target.ts b/src/infra/exec-approval-session-target.ts index 107b1429cef..de267df7310 100644 --- a/src/infra/exec-approval-session-target.ts +++ b/src/infra/exec-approval-session-target.ts @@ -172,9 +172,9 @@ function resolveApprovalRequestSessionAccountBinding(params: { ? resolveApprovalRequestStoredSessionTarget(params) : resolveApprovalRequestSessionTarget(params); const sessionBinding = resolveApprovalRequestSessionBinding(params); - const channel = normalizeOptionalChannel(sessionTarget?.channel ?? sessionBinding?.channel); + const channel = normalizeOptionalChannel(sessionBinding?.channel ?? sessionTarget?.channel); const accountId = normalizeOptionalAccountId( - sessionTarget?.accountId ?? sessionBinding?.accountId, + sessionBinding?.accountId ?? sessionTarget?.accountId, ); return channel || accountId ? { channel, accountId } : null; }