diff --git a/CHANGELOG.md b/CHANGELOG.md index 77fe817a9c4f..0e1dafd40c60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ Docs: https://docs.openclaw.ai ### Changes +- Exec defaults: make gateway/node host exec default to YOLO mode by requesting `security=full` with `ask=off`, and align host approval-file fallbacks plus docs/doctor reporting with that no-prompt default. +- Agents/compaction: add `agents.defaults.compaction.notifyUser` so the `🧹 Compacting context...` start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327. +- Plugins/hooks: add `before_agent_reply` so plugins can short-circuit the LLM with synthetic replies after inline actions. (#20067) Thanks @JoshuaLelon +- Providers/runtime: add provider-owned replay hook surfaces for transcript policy, replay cleanup, and reasoning-mode dispatch. (#59143) Thanks @jalehman. +- Diffs: add plugin-owned `viewerBaseUrl` so viewer links can use a stable proxy/public origin without passing `baseUrl` on every tool call. (#59341) Related #59227. Thanks @gumadeiras. - Matrix/plugin: emit spec-compliant `m.mentions` metadata across text sends, media captions, edits, poll fallback text, and action-driven edits so Matrix mentions notify reliably in clients like Element. (#59323) Thanks @gumadeiras. - Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and `feishu_drive` comment actions for document collaboration workflows. (#58497) thanks @wittam-01. - WhatsApp/reactions: add `reactionLevel` guidance for agent reactions. Thanks @mcaxtr. diff --git a/docs/cli/approvals.md b/docs/cli/approvals.md index 01ee54485a89..d3b0d0278db9 100644 --- a/docs/cli/approvals.md +++ b/docs/cli/approvals.md @@ -45,6 +45,48 @@ openclaw approvals set --node --file ./exec-approvals.json openclaw approvals set --gateway --file ./exec-approvals.json ``` +## "Never prompt" / YOLO example + +For a host that should never stop on exec approvals, set the host approvals defaults to `full` + `off`: + +```bash +openclaw approvals set --stdin <<'EOF' +{ + version: 1, + defaults: { + security: "full", + ask: "off", + askFallback: "full" + } +} +EOF +``` + +Node variant: + +```bash +openclaw approvals set --node --stdin <<'EOF' +{ + version: 1, + defaults: { + security: "full", + ask: "off", + askFallback: "full" + } +} +EOF +``` + +This changes the **host approvals file** only. To keep the requested OpenClaw policy aligned, also set: + +```bash +openclaw config set tools.exec.host gateway +openclaw config set tools.exec.security full +openclaw config set tools.exec.ask off +``` + +This matches the current host-default YOLO behavior. Tighten it if you want approvals. + ## Allowlist helpers ```bash diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md index 65ec381a05a2..a345c0bbcd0b 100644 --- a/docs/help/troubleshooting.md +++ b/docs/help/troubleshooting.md @@ -278,11 +278,11 @@ flowchart TD - If `tools.exec.host` is unset, the default is `auto`. - `host=auto` resolves to `sandbox` when a sandbox runtime is active, `gateway` otherwise. - - On `gateway` and `node`, unset `tools.exec.security` defaults to `allowlist`. - - Unset `tools.exec.ask` defaults to `on-miss`. - - Result: ordinary host commands can now pause with `Approval required` instead of running immediately. + - On `gateway` and `node`, unset `tools.exec.security` defaults to `full`. + - Unset `tools.exec.ask` defaults to `off`. + - Result: if you are seeing approvals, some host-local or per-session policy tightened exec away from the current defaults. - Restore the old gateway no-approval behavior: + Restore current default no-approval behavior: ```bash openclaw config set tools.exec.host gateway @@ -293,8 +293,8 @@ flowchart TD Safer alternatives: - - Set only `tools.exec.host=gateway` if you just want stable host routing and still want approvals. - - Keep `security=allowlist` with `ask=on-miss` if you want host exec but still want review on allowlist misses. + - Set only `tools.exec.host=gateway` if you just want stable host routing. + - Use `security=allowlist` with `ask=on-miss` if you want host exec but still want review on allowlist misses. - Enable sandbox mode if you want `host=auto` to resolve back to `sandbox`. Common log signatures: diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md index 7b6f2d42331d..c4792380fc83 100644 --- a/docs/tools/exec-approvals.md +++ b/docs/tools/exec-approvals.md @@ -91,6 +91,68 @@ Example schema: } ``` +## No-approval "YOLO" mode + +If you want host exec to run without approval prompts, you must open **both** policy layers: + +- requested exec policy in OpenClaw config (`tools.exec.*`) +- host-local approvals policy in `~/.openclaw/exec-approvals.json` + +This is now the default host behavior unless you tighten it explicitly: + +- `tools.exec.security`: `full` on `gateway`/`node` +- `tools.exec.ask`: `off` +- host `askFallback`: `full` + +If you want a more conservative setup, tighten either layer back to `allowlist` / `on-miss` +or `deny`. + +Persistent gateway-host "never prompt" setup: + +```bash +openclaw config set tools.exec.host gateway +openclaw config set tools.exec.security full +openclaw config set tools.exec.ask off +openclaw gateway restart +``` + +Then set the host approvals file to match: + +```bash +openclaw approvals set --stdin <<'EOF' +{ + version: 1, + defaults: { + security: "full", + ask: "off", + askFallback: "full" + } +} +EOF +``` + +For a node host, apply the same approvals file on that node instead: + +```bash +openclaw approvals set --node --stdin <<'EOF' +{ + version: 1, + defaults: { + security: "full", + ask: "off", + askFallback: "full" + } +} +EOF +``` + +Session-only shortcut: + +- `/exec security=full ask=off` changes only the current session. +- `/elevated full` is a break-glass shortcut that also skips exec approvals for that session. + +If the host approvals file stays stricter than config, the stricter host policy still wins. + ## Policy knobs ### Security (`exec.security`) diff --git a/docs/tools/exec.md b/docs/tools/exec.md index 6921068c28b4..155df0527f5f 100644 --- a/docs/tools/exec.md +++ b/docs/tools/exec.md @@ -54,8 +54,9 @@ Notes: - `tools.exec.notifyOnExit` (default: true): when true, backgrounded exec sessions enqueue a system event and request a heartbeat on exit. - `tools.exec.approvalRunningNoticeMs` (default: 10000): emit a single “running” notice when an approval-gated exec runs longer than this (0 disables). - `tools.exec.host` (default: `auto`; resolves to `sandbox` when sandbox runtime is active, `gateway` otherwise) -- `tools.exec.security` (default: `deny` for sandbox, `allowlist` for gateway + node when unset) -- `tools.exec.ask` (default: `on-miss`) +- `tools.exec.security` (default: `deny` for sandbox, `full` for gateway + node when unset) +- `tools.exec.ask` (default: `off`) +- No-approval host exec is the default for gateway + node. If you want approvals/allowlist behavior, tighten both `tools.exec.*` and the host `~/.openclaw/exec-approvals.json`; see [Exec approvals](/tools/exec-approvals#no-approval-yolo-mode). - `tools.exec.node` (default: unset) - `tools.exec.strictInlineEval` (default: false): when true, inline interpreter eval forms such as `python -c`, `node -e`, `ruby -e`, `perl -e`, `php -r`, `lua -e`, and `osascript -e` always require explicit approval. `allow-always` can still persist benign interpreter/script invocations, but inline-eval forms still prompt each time. - `tools.exec.pathPrepend`: list of directories to prepend to `PATH` for exec runs (gateway + sandbox only). diff --git a/src/agents/bash-tools.exec-host-node.ts b/src/agents/bash-tools.exec-host-node.ts index 3cc56b5c6c9e..418d28ec84e2 100644 --- a/src/agents/bash-tools.exec-host-node.ts +++ b/src/agents/bash-tools.exec-host-node.ts @@ -168,7 +168,7 @@ export async function executeNodeHostCommand( const resolved = resolveExecApprovalsFromFile({ file: approvalsFile as ExecApprovalsFile, agentId: params.agentId, - overrides: { security: "allowlist" }, + overrides: { security: "full" }, }); // Allowlist-only precheck; safe bins are node-local and may diverge. const allowlistEval = evaluateShellAllowlist({ diff --git a/src/agents/bash-tools.exec.approval-id.test.ts b/src/agents/bash-tools.exec.approval-id.test.ts index ddfc8fa27d94..4fea61dcc744 100644 --- a/src/agents/bash-tools.exec.approval-id.test.ts +++ b/src/agents/bash-tools.exec.approval-id.test.ts @@ -115,7 +115,7 @@ function expectPendingApprovalText( expect(pendingText).toContain( (options.allowedDecisions ?? "").includes("allow-always") ? "Background mode requires pre-approved policy" - : "Background mode requires host policy that allows pre-approval", + : "Background mode requires an effective policy that allows pre-approval", ); } return details; @@ -339,6 +339,7 @@ describe("exec approvals", () => { const tool = createExecTool({ host: "node", + security: "allowlist", ask: "on-miss", approvalRunningNoticeMs: 0, }); @@ -1131,7 +1132,7 @@ describe("exec approvals", () => { const tool = createExecTool({ host: "gateway", ask: "always", - security: "full", + security: "allowlist", trigger: "cron", approvalRunningNoticeMs: 0, }); @@ -1211,6 +1212,11 @@ describe("exec approvals", () => { }); it("explains cron no-route denials with a host-policy fix hint", async () => { + await writeExecApprovalsConfig({ + version: 1, + defaults: { security: "full", ask: "always", askFallback: "deny" }, + agents: {}, + }); mockNoApprovalRouteRegistration(); const tool = createExecTool({ diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index 41cd26c3510e..db125182adb2 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -596,16 +596,14 @@ export function createExecTool( const approvalDefaults = loadExecApprovals().defaults; const configuredSecurity = - defaults?.security ?? - approvalDefaults?.security ?? - (host === "sandbox" ? "deny" : "allowlist"); + defaults?.security ?? approvalDefaults?.security ?? (host === "sandbox" ? "deny" : "full"); const requestedSecurity = normalizeExecSecurity(params.security); let security = minSecurity(configuredSecurity, requestedSecurity ?? configuredSecurity); if (elevatedRequested && elevatedMode === "full") { security = "full"; } // Keep local exec defaults in sync with exec-approvals.json when tools.exec.* is unset. - const configuredAsk = defaults?.ask ?? approvalDefaults?.ask ?? "on-miss"; + const configuredAsk = defaults?.ask ?? approvalDefaults?.ask ?? "off"; const requestedAsk = normalizeExecAsk(params.ask); let ask = maxAsk(configuredAsk, requestedAsk ?? configuredAsk); const bypassApprovals = elevatedRequested && elevatedMode === "full"; diff --git a/src/commands/doctor-security.ts b/src/commands/doctor-security.ts index 11b709eb69c7..7c0da18716ba 100644 --- a/src/commands/doctor-security.ts +++ b/src/commands/doctor-security.ts @@ -75,8 +75,8 @@ function execAskRank(value: ExecAsk): number { function collectExecPolicyConflictWarnings(cfg: OpenClawConfig): string[] { const warnings: string[] = []; const approvals = loadExecApprovals(); - const defaultRequestedSecuritySource = "OpenClaw default (allowlist)"; - const defaultRequestedAskSource = "OpenClaw default (on-miss)"; + const defaultRequestedSecuritySource = "OpenClaw default (full)"; + const defaultRequestedAskSource = "OpenClaw default (off)"; const maybeWarn = (params: { scopeLabel: string; diff --git a/src/infra/exec-approvals-config.test.ts b/src/infra/exec-approvals-config.test.ts index 19ea2414d2cd..e5cc211121eb 100644 --- a/src/infra/exec-approvals-config.test.ts +++ b/src/infra/exec-approvals-config.test.ts @@ -432,12 +432,12 @@ describe("normalizeExecApprovals strips invalid security/ask enum values (#59006 }, } as unknown as ExecApprovalsFile; const resolved = resolveExecApprovalsFromFile({ file }); - // Invalid "none" in defaults is stripped, so fallback to DEFAULT_SECURITY ("deny") - expect(resolved.defaults.security).toBe("deny"); - // Invalid "never" in defaults is stripped, so fallback to DEFAULT_ASK ("on-miss") - expect(resolved.defaults.ask).toBe("on-miss"); + // Invalid "none" in defaults is stripped, so fallback to DEFAULT_SECURITY ("full") + expect(resolved.defaults.security).toBe("full"); + // Invalid "never" in defaults is stripped, so fallback to DEFAULT_ASK ("off") + expect(resolved.defaults.ask).toBe("off"); // Wildcard agent "none" is stripped, so agent inherits resolved defaults - expect(resolved.agent.security).toBe("deny"); + expect(resolved.agent.security).toBe("full"); // Wildcard agent ask="off" is valid and preserved expect(resolved.agent.ask).toBe("off"); }); diff --git a/src/infra/exec-approvals-effective.ts b/src/infra/exec-approvals-effective.ts index 3e14aa9ec6d3..5433fd393ab6 100644 --- a/src/infra/exec-approvals-effective.ts +++ b/src/infra/exec-approvals-effective.ts @@ -12,8 +12,8 @@ import { type ExecSecurity, } from "./exec-approvals.js"; -const DEFAULT_REQUESTED_SECURITY: ExecSecurity = "allowlist"; -const DEFAULT_REQUESTED_ASK: ExecAsk = "on-miss"; +const DEFAULT_REQUESTED_SECURITY: ExecSecurity = "full"; +const DEFAULT_REQUESTED_ASK: ExecAsk = "off"; const DEFAULT_HOST_PATH = "~/.openclaw/exec-approvals.json"; const REQUESTED_DEFAULT_LABEL = { security: DEFAULT_REQUESTED_SECURITY, diff --git a/src/infra/exec-approvals-policy.test.ts b/src/infra/exec-approvals-policy.test.ts index 0183b723ccaf..850106606a95 100644 --- a/src/infra/exec-approvals-policy.test.ts +++ b/src/infra/exec-approvals-policy.test.ts @@ -463,8 +463,8 @@ describe("exec approvals policy helpers", () => { }); expect(summary.askFallback).toEqual({ - effective: "deny", - source: "OpenClaw default (deny)", + effective: "full", + source: "OpenClaw default (full)", }); }); diff --git a/src/infra/exec-approvals.ts b/src/infra/exec-approvals.ts index ac33778e7d17..f8eb1faac337 100644 --- a/src/infra/exec-approvals.ts +++ b/src/infra/exec-approvals.ts @@ -168,9 +168,9 @@ export type ExecApprovalsResolved = { // Keep CLI + gateway defaults in sync. export const DEFAULT_EXEC_APPROVAL_TIMEOUT_MS = 1_800_000; -const DEFAULT_SECURITY: ExecSecurity = "deny"; -const DEFAULT_ASK: ExecAsk = "on-miss"; -export const DEFAULT_EXEC_APPROVAL_ASK_FALLBACK: ExecSecurity = "deny"; +const DEFAULT_SECURITY: ExecSecurity = "full"; +const DEFAULT_ASK: ExecAsk = "off"; +export const DEFAULT_EXEC_APPROVAL_ASK_FALLBACK: ExecSecurity = "full"; const DEFAULT_AUTO_ALLOW_SKILLS = false; const DEFAULT_SOCKET = "~/.openclaw/exec-approvals.sock"; const DEFAULT_FILE = "~/.openclaw/exec-approvals.json"; @@ -304,9 +304,7 @@ function sanitizeExecApprovalPolicy( const askFallback = toStringOrUndefined(policy?.askFallback)?.trim(); return { security: - security === "deny" || security === "allowlist" || security === "full" - ? security - : undefined, + security === "deny" || security === "allowlist" || security === "full" ? security : undefined, ask: ask === "off" || ask === "on-miss" || ask === "always" ? ask : undefined, askFallback: askFallback === "deny" || askFallback === "allowlist" || askFallback === "full"