diff --git a/CHANGELOG.md b/CHANGELOG.md index 70dea246fc5..fbfc7f7c4f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai - Matrix/runtime: resolve the verification/bootstrap runtime from a distinct packaged Matrix entry so global npm installs stop failing on crypto bootstrap with missing-module or recursive runtime alias errors. (#59249) Thanks @gumadeiras. - Matrix/streaming: preserve ordered block flushes before tool, message, and agent boundaries, add explicit `channels.matrix.blockStreaming` opt-in so Matrix `streaming: "off"` stays final-only by default, and move MiniMax plain-text final handling into the MiniMax provider runtime instead of the shared core heuristic. (#59266) thanks @gumadeiras - Agents/compaction: resolve compaction wait before final reply/channel flush completion so slow end-of-run delivery drains no longer delay compaction completion. (#59308) thanks @gumadeiras +- Exec approvals: align approval UX, effective-policy reporting, and `allow-always` availability with the host policy so CLI, doctor, and approval surfaces explain the real host-effective decision path. (#59283) Thanks @gumadeiras. ## 2026.4.2 diff --git a/docs/cli/approvals.md b/docs/cli/approvals.md index 4a6da45429d..01ee54485a8 100644 --- a/docs/cli/approvals.md +++ b/docs/cli/approvals.md @@ -24,6 +24,19 @@ openclaw approvals get --node openclaw approvals get --gateway ``` +`openclaw approvals get` now shows the effective exec policy for local, gateway, and node targets: + +- requested `tools.exec` policy +- host approvals-file policy +- effective result after precedence rules are applied + +Precedence is intentional: + +- the host approvals file is the enforceable source of truth +- requested `tools.exec` policy can narrow or broaden intent, but the effective result is still derived from the host rules +- `--node` combines the node host approvals file with gateway `tools.exec` policy, because both still apply at runtime +- if gateway config is unavailable, the CLI falls back to the node approvals snapshot and notes that the final runtime policy could not be computed + ## Replace approvals from a file ```bash diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md index 9d8f61e0059..7b6f2d42331 100644 --- a/docs/tools/exec-approvals.md +++ b/docs/tools/exec-approvals.md @@ -17,6 +17,9 @@ Effective policy is the **stricter** of `tools.exec.*` and approvals defaults; i Host exec also uses the local approvals state on that machine. A host-local `ask: "always"` in `~/.openclaw/exec-approvals.json` keeps prompting even if session or config defaults request `ask: "on-miss"`. +Use `openclaw approvals get`, `openclaw approvals get --gateway`, or +`openclaw approvals get --node ` to inspect the requested policy, +host policy sources, and the effective result. If the companion app UI is **not available**, any request that requires a prompt is resolved by the **ask fallback** (default: deny). diff --git a/docs/tools/slash-commands.md b/docs/tools/slash-commands.md index da35723233f..595044cd110 100644 --- a/docs/tools/slash-commands.md +++ b/docs/tools/slash-commands.md @@ -80,7 +80,7 @@ Text + native (when enabled): - `/status` (show current status; includes provider usage/quota for the current model provider when available) - `/tasks` (list background tasks for the current session; shows active and recent task details with agent-local fallback counts) - `/allowlist` (list/add/remove allowlist entries) -- `/approve allow-once|allow-always|deny` (resolve exec approval prompts) +- `/approve ` (resolve exec approval prompts; use the pending approval message for the available decisions) - `/context [list|detail|json]` (explain “context”; `detail` shows per-file + per-tool + per-skill + system prompt size) - `/btw ` (ask an ephemeral side question about the current session without changing future session context; see [/tools/btw](/tools/btw)) - `/export-session [path]` (alias: `/export`) (export current session to HTML with full system prompt) diff --git a/extensions/discord/src/monitor/exec-approvals.test.ts b/extensions/discord/src/monitor/exec-approvals.test.ts index 4280c8cdaf6..cc1493ce935 100644 --- a/extensions/discord/src/monitor/exec-approvals.test.ts +++ b/extensions/discord/src/monitor/exec-approvals.test.ts @@ -1090,6 +1090,31 @@ describe("DiscordExecApprovalHandler delivery routing", () => { }), ); }); + + it("omits allow-always when exec approvals disallow it", async () => { + const handler = createHandler({ + enabled: true, + approvers: ["123"], + target: "dm", + }); + + mockSuccessfulDmDelivery({ throwOnUnexpectedRoute: true }); + + await handler.handleApprovalRequested( + createRequest({ + ask: "always", + allowedDecisions: ["allow-once", "deny"], + }), + ); + + const dmCall = mockRestPost.mock.calls.find( + ([route]) => route === Routes.channelMessages("dm-1"), + ); + const payload = JSON.stringify(dmCall?.[1]?.body); + expect(payload).toContain("Allow Once"); + expect(payload).toContain("Deny"); + expect(payload).not.toContain("Allow Always"); + }); }); describe("DiscordExecApprovalHandler resolve routing", () => { diff --git a/extensions/discord/src/monitor/exec-approvals.ts b/extensions/discord/src/monitor/exec-approvals.ts index 96c2fd3dc0f..4b86207dcaf 100644 --- a/extensions/discord/src/monitor/exec-approvals.ts +++ b/extensions/discord/src/monitor/exec-approvals.ts @@ -190,15 +190,36 @@ class ExecApprovalActionButton extends Button { } class ExecApprovalActionRow extends Row