diff --git a/CHANGELOG.md b/CHANGELOG.md index 17fdf9ddecf..50cf029dab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ Docs: https://docs.openclaw.ai - Plugin SDK/testing: lazy-load TypeScript from the plugin test-contract runtime and add release checks for critical SDK contract entrypoint imports and bundle size, so published packages fail preflight before shipping ESM-incompatible or oversized contract helpers. Thanks @vincentkoc. - Channels/Microsoft Teams: treat configured `19:...@thread.tacv2` and legacy `19:...@thread.skype` team/channel IDs as already resolved during startup, avoiding false `channels unresolved` warnings while preserving Graph name lookup for display-name entries. Fixes #74683. Thanks @dseravalli. - CLI/browser: preserve parent flags while lazy-loading browser subcommands, so `openclaw browser --json open` and `openclaw browser --json tabs` keep machine-readable output after reparsing. Fixes #74574. Thanks @devintegeritsm. +- Exec/elevated: preserve `turnSourceChannel` as `messageProvider` on approval-followup runs so `tools.elevated.allowFrom.` checks no longer fail with `provider=null` after the user approves an async elevated command. Fixes #74646. Thanks @xhd2015. - Plugins/runtime-deps: add `openclaw plugins deps` inspection and repair with script-free package-manager defaults shared across plugin installers, so operators can repair missing bundled runtime deps without corrupting JSON output or blocking unrelated conflict-free deps. Thanks @vincentkoc. - Agents/output: strip internal `[tool calls omitted]` replay placeholders from user-facing replies while preserving visible reply whitespace. Fixes #74573. Thanks @blaspat. - Providers/Google Vertex: route authorized_user ADC credentials through OpenClaw's REST transport so Docker installs using gcloud application-default credentials no longer crash in the Google SDK before requests are sent. Fixes #74628. Thanks @frankhal2001-design. diff --git a/src/agents/bash-tools.exec-approval-followup.test.ts b/src/agents/bash-tools.exec-approval-followup.test.ts index db2f5931317..0d66d9a01ae 100644 --- a/src/agents/bash-tools.exec-approval-followup.test.ts +++ b/src/agents/bash-tools.exec-approval-followup.test.ts @@ -252,6 +252,28 @@ describe("exec approval followup", () => { expect(sendMessage).not.toHaveBeenCalled(); }); + it("preserves turnSourceChannel as messageProvider on the followup run when no deliverable route exists", async () => { + // Regression: #74646 — tools.elevated.allowFrom. fails in approval followup + await sendExecApprovalFollowup({ + approvalId: "req-elevated-74646", + sessionKey: "agent:main:telegram:-100123", + turnSourceChannel: "telegram", + resultText: "Exec completed: systemctl status gateway", + }); + + expect(callGatewayTool).toHaveBeenCalledWith( + "agent", + expect.any(Object), + expect.objectContaining({ + sessionKey: "agent:main:telegram:-100123", + deliver: false, + channel: "telegram", + }), + { expectFinal: true }, + ); + expect(sendMessage).not.toHaveBeenCalled(); + }); + it("throws when neither a session nor a deliverable route is available", async () => { await expect( sendExecApprovalFollowup({ diff --git a/src/agents/bash-tools.exec-approval-followup.ts b/src/agents/bash-tools.exec-approval-followup.ts index 40fcc352bfd..609cf8565aa 100644 --- a/src/agents/bash-tools.exec-approval-followup.ts +++ b/src/agents/bash-tools.exec-approval-followup.ts @@ -145,17 +145,22 @@ function buildAgentFollowupArgs(params: { resultText: string; deliveryTarget: ExternalBestEffortDeliveryTarget; sessionOnlyOriginChannel?: string; + turnSourceChannel?: string; turnSourceTo?: string; turnSourceAccountId?: string; turnSourceThreadId?: string | number; }) { const { deliveryTarget, sessionOnlyOriginChannel } = params; + // When the followup run has no deliverable route and no gateway-internal channel, + // preserve the raw turnSourceChannel so the spawned agent inherits messageProvider. + // Without this, tools.elevated.allowFrom. checks fail with provider=null. + const fallbackChannel = sessionOnlyOriginChannel ?? params.turnSourceChannel; return { sessionKey: params.sessionKey, message: buildExecApprovalFollowupPrompt(params.resultText), deliver: deliveryTarget.deliver, ...(deliveryTarget.deliver ? { bestEffortDeliver: true as const } : {}), - channel: deliveryTarget.deliver ? deliveryTarget.channel : sessionOnlyOriginChannel, + channel: deliveryTarget.deliver ? deliveryTarget.channel : fallbackChannel, to: deliveryTarget.deliver ? deliveryTarget.to : sessionOnlyOriginChannel @@ -241,6 +246,7 @@ export async function sendExecApprovalFollowup( resultText, deliveryTarget, sessionOnlyOriginChannel, + turnSourceChannel: params.turnSourceChannel, turnSourceTo: params.turnSourceTo, turnSourceAccountId: params.turnSourceAccountId, turnSourceThreadId: params.turnSourceThreadId,