Skip to content

Releases: clawnify/agent-permissions

v0.4.0 — operator-scoped allow-always

23 May 13:15

Choose a tag to compare

What's new

When the user clicks 'always' on an approval prompt, the persisted rule is now the pattern of the rule that triggered the ask (not the exact call). Operators control the breadth via their ask rule patterns; one click grants the breadth the operator already declared.

'ask': ['clawnify_action(*_SEND*)']        →  one 'always' → allow all *_SEND*
'ask': ['clawnify_action(GMAIL_*)']        →  one 'always' → allow all Gmail
'ask': ['clawnify_action(GMAIL_SEND_EMAIL)']  →  one 'always' → that slug only

Each prompt description names the rule that will be persisted:

Run clawnify_action (GMAIL_SEND_EMAIL)?

Matched: rule 'clawnify_action(*_SEND*)' from config settings

'Always' will allow: `clawnify_action(*_SEND*)`

The dangerousPatterns check now runs against the rule that would be persisted, so allow-always is refused for rules with dangerous prefixes regardless of which specific call triggered the prompt.

Falls back to the exact call content when no rule matched (only happens under strict mode where everything asks by default).

Forward-compat note

This is the v0.4.0 bridge while OpenClaw's requireApproval protocol only supports a fixed allow-once | allow-always | deny enum. A v0.5.0 release will add a sideband-encoded scope ladder for Clawnify-style dashboards that want to render multi-button 'Always for X / Always for Y / ...' prompts (the Claude Code pattern). When OpenClaw adds allowedDecisions upstream, the sideband becomes redundant and the matched-rule semantic from this release stays as the default button.

Tests

54/54 passing.

— Initiated and maintained by the Clawnify team.

v0.3.0 — paramKeys config

23 May 12:52

Choose a tag to compare

What's new

Operator-configurable per-tool content extraction via the new paramKeys config.

"agent-permissions": {
  "config": {
    "paramKeys": {
      "clawnify_action": "slug",
      "clawnify_call_app_api": "method"
    },
    "ask":   ["clawnify_action(*_DELETE*)", "clawnify_action(*_SEND*)"],
    "allow": ["clawnify_action(GMAIL_EMAIL_LIST)"]
  }
}

When a tool call fires, the engine reads params[paramKeys[toolName]] as the rule content. Wildcard rules like clawnify_action(GMAIL_EMAIL_*) now match without consumer plugins needing to register resolvers. Zero coupling — entirely operator-driven.

Falls back to existing behavior (shell-command extraction for bash/exec, tool-wide matching otherwise) when no paramKeys entry exists.

Tests

54/54 still passing.

— Initiated and maintained by the Clawnify team.

v0.2.0 — resolver-less gating + operator-opt-in default

22 May 20:38

Choose a tag to compare

Breaking semantic changes

Resolverless mode is now the primary path

The before_tool_call hook no longer bails when no resolver is registered for a tool. It falls back to a generic GateRequest:

  • For bash/exec tools: pulls params.command into ruleContent so wildcard rules like Bash(curl *) match the actual shell command
  • For all other tools: tool-wide matching (only Tool / Tool(*) rules apply) with a JSON-preview prompt

This means agent-permissions can now gate ANY tool by name from openclaw.json rules, with no consumer-plugin awareness required. No inter-plugin coupling. Each plugin ships independently.

Consumer plugins can still call registerResolver({ toolName, resolve }) for tool-specific richer prompts (titles/descriptions). Strictly opt-in.

Default mode 'default' now means operator-opt-in

Mode Behavior
default (new) Operator-opt-in: tools pass through unless an ask or deny rule explicitly matches
strict (NEW) Previous default behavior — ask on anything not explicitly allowed (Claude-Code style)
bypassPermissions / dontAsk Allow everything except matching deny rules
acceptEdits Currently same as default. Reserved for future tool-category-aware behavior.

Why: the previous design required every consumer plugin to call registerResolver to participate. That's inter-plugin coupling that scales badly — every Clawnify plugin plus any third-party plugin would need to know about us. The new resolverless path inverts that.

Migration

If you were previously calling registerResolver for each tool you wanted gated, you can:

  • Keep doing that — resolvers still work, give you richer prompts
  • OR drop the calls — add rules to openclaw.json targeting the tool names directly. Generic prompts will be used.

If you relied on default mode asking on every tool call, switch to strict mode explicitly.

Tests

54/54 passing.

— Initiated and maintained by the Clawnify team.

v0.1.0 — initial publish

22 May 20:05

Choose a tag to compare

Initial release of @clawnify/agent-permissions.

OpenClaw plugin — permission and approval engine for any OpenClaw agent.

What it does

  • Gates built-in tool calls (bash, file edit, etc.) and any plugin-registered tool
  • Three-bucket policy model: allow / deny / ask
  • Rule sources walked in priority order: session → workspace → user → config
  • In-chat approval via OpenClaw's native requireApproval mechanism
  • Learning into allow-always rules persisted to a chosen destination
  • Wildcard rule format (Tool(foo) exact, Tool(foo:*) legacy prefix, Tool(foo *) new wildcard)
  • Dangerous-pattern denylist (python:*, node:*, eval, …) that cannot be allow-always-persisted
  • Fail-closed: any internal error returns { block: true } (OpenClaw's hook runner is fail-open by default)

Install

openclaw plugins install @clawnify/agent-permissions --pin

Then in openclaw.json:

{
  "plugins": {
    "allow": ["agent-permissions", "your-consumer-plugin"],
    "entries": {
      "agent-permissions": {
        "enabled": true,
        "config": {
          "defaultMode": "default",
          "ask": ["Bash(*)"]
        }
      }
    }
  }
}

See README for full inter-plugin API + rule format.

— Initiated and maintained by the Clawnify team.