Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@

[Preview in WordPress Playground](https://playground.wordpress.net/?wp=beta&blueprint-url=https://raw.githubusercontent.com/bradvin/clawpress/refs/heads/main/blueprint.json)

```
Contributors: bradvin, welbinator, foo-bender
Tags: ai, assistant, admin
Requires at least: 7.0
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 0.0.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
```

## Quick Start

```bash
Expand Down Expand Up @@ -63,6 +52,7 @@ Current Agent Features:
- `file_read`
- `file_write`
- `file_delete`
- `web_fetch` (read-only remote fetch via the WordPress HTTP API, validated with `wp_http_validate_url()`, logged to the action log table)
- `memory_long_term_add`
- `memory_long_term_update`
- `memory_long_term_delete`
Expand Down
31 changes: 17 additions & 14 deletions docs/policy-helper.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,28 +67,30 @@ Unknown or empty trigger types are normalized to `chat`.

### `chat`

Uses base defaults (least restrictive profile).
Uses base defaults (least restrictive profile), including `allow_network = true`.

### `heartbeat`

More restrictive than chat:

1. `allow_destructive_tools = false`
2. `allow_file_delete = false`
3. `max_tool_rounds = 2`
4. `max_tool_calls_per_round = 3`
5. `max_wall_time_seconds = 45`
6. `allow_background_followups = false`
3. `allow_network = true`
4. `max_tool_rounds = 2`
5. `max_tool_calls_per_round = 3`
6. `max_wall_time_seconds = 45`
7. `allow_background_followups = false`

### `spawned_agent`

Restrictive but less constrained than heartbeat:

1. `allow_destructive_tools = false`
2. `allow_file_delete = false`
3. `max_tool_rounds = 3`
4. `max_tool_calls_per_round = 4`
5. `max_wall_time_seconds = 90`
3. `allow_network = true`
4. `max_tool_rounds = 3`
5. `max_tool_calls_per_round = 4`
6. `max_wall_time_seconds = 90`

## Where It Is Used

Expand All @@ -107,9 +109,10 @@ Implementation: `includes/helpers/class-agent-loop-helper.php`
`Abilities_Helper::execute_tool_call()` enforces policy gates in this order:

1. `allow_tools`
2. `allow_destructive_tools` (for destructive abilities)
3. `allow_file_delete` (for `file_delete`)
4. `require_confirmation_for_destructive` (confirmation workflow gate)
2. `allow_network` (for network-capable abilities such as `web_fetch`)
3. `allow_destructive_tools` (for destructive abilities)
4. `allow_file_delete` (for `file_delete`)
5. `require_confirmation_for_destructive` (confirmation workflow gate)

On policy violation, it returns a structured payload with:

Expand Down Expand Up @@ -200,8 +203,8 @@ $result = Abilities_Helper::get_instance()->execute_tool_call(

## Suggested Future Improvements

1. Wire `allow_network` to network-capable tools or provider request constraints.
2. Add a policy filter hook (for example, `clawpress_runtime_policy_resolved`) if third-party plugins need policy customization without patching core code.
1. Add a policy filter hook (for example, `clawpress_runtime_policy_resolved`) if third-party plugins need policy customization without patching core code.
2. If future network-capable tools need different restrictions than `web_fetch`, split `allow_network` into capability-specific fields without overloading destructive-tool policy.

## Test Coverage Today

Expand All @@ -214,4 +217,4 @@ Current coverage includes:
Recommended additional coverage:

1. Chat loop behavior under non-default `max_tool_rounds` and `max_tool_calls_per_round`.
2. Any new enforcement path added for currently informational fields.
2. Any new enforcement path added for future policy fields beyond the current tool and network gates.
134 changes: 134 additions & 0 deletions includes/abilities/class-web-fetch-ability.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php
/**
* Web fetch ability.
*
* @package ClawPress
*/

declare( strict_types=1 );

namespace ClawPress\Abilities\BuiltIn;

use ClawPress\Abilities\Abilities;
use ClawPress\Helpers\Web_Fetch_Helper;

defined( 'ABSPATH' ) || exit;

/**
* Registers the `web_fetch` ability.
*/
final class Web_Fetch_Ability {
/**
* Ability ID.
*/
private const ABILITY_NAME = 'clawpress/web-fetch';

/**
* Register ability.
*/
public static function register(): void {
Web_Fetch_Helper::register_logging_hook();

wp_register_ability(
self::ABILITY_NAME,
[
'label' => __( 'Web Fetch', 'clawpress' ),
'description' => __( 'Fetch a remote URL using the configured fetcher.', 'clawpress' ),
'category' => Abilities::CATEGORY_SLUG,
'input_schema' => [
'type' => 'object',
'required' => [ 'url' ],
'properties' => [
'url' => [
'type' => 'string',
'description' => __( 'Remote URL to fetch.', 'clawpress' ),
],
'fetcher' => [
'type' => 'string',
'description' => __( 'Fetcher provider slug. Defaults to `wp`.', 'clawpress' ),
],
'method' => [
'type' => 'string',
'enum' => [ 'GET', 'HEAD' ],
'description' => __( 'Read-only HTTP method. Defaults to `GET`.', 'clawpress' ),
],
'headers' => [
'type' => 'object',
'description' => __( 'Optional request headers.', 'clawpress' ),
'additionalProperties' => [
'type' => 'string',
],
],
'timeout' => [
'type' => 'integer',
'description' => __( 'Request timeout in seconds. Defaults to `15`.', 'clawpress' ),
],
'redirection' => [
'type' => 'integer',
'description' => __( 'Maximum redirect count. Defaults to `5`.', 'clawpress' ),
],
'arguments' => [
'type' => 'object',
'description' => __( 'Optional fetcher-specific arguments for future providers.', 'clawpress' ),
'additionalProperties' => true,
],
],
'additionalProperties' => false,
],
'output_schema' => [
'type' => 'object',
'required' => [
'fetcher',
'url',
'method',
'status_code',
'status_message',
'headers',
'content_type',
'body',
'truncated',
'body_bytes',
],
'properties' => [
'fetcher' => [ 'type' => 'string' ],
'url' => [ 'type' => 'string' ],
'method' => [ 'type' => 'string' ],
'status_code' => [ 'type' => 'integer' ],
'status_message' => [ 'type' => 'string' ],
'headers' => [
'type' => 'object',
'additionalProperties' => [
'type' => 'string',
],
],
'content_type' => [ 'type' => 'string' ],
'body' => [ 'type' => 'string' ],
'truncated' => [ 'type' => 'boolean' ],
'body_bytes' => [ 'type' => 'integer' ],
],
'additionalProperties' => false,
],
'execute_callback' => static fn( $input = [] ) => self::execute( is_array( $input ) ? $input : [] ),
'permission_callback' => static fn(): bool => current_user_can( 'read' ),
'meta' => [
'annotations' => [
'readonly' => true,
'destructive' => false,
'idempotent' => true,
'network' => true,
],
],
]
);
}

/**
* Execute ability callback.
*
* @param array<string,mixed> $input Ability input.
* @return array<string,mixed>|\WP_Error
*/
public static function execute( array $input ) {
return Web_Fetch_Helper::get_instance()->fetch( $input );
}
}
2 changes: 2 additions & 0 deletions includes/class-abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use ClawPress\Abilities\BuiltIn\Memory_Short_Term_Add_Ability;
use ClawPress\Abilities\BuiltIn\Memory_Short_Term_Delete_Ability;
use ClawPress\Abilities\BuiltIn\Memory_Short_Term_Update_Ability;
use ClawPress\Abilities\BuiltIn\Web_Fetch_Ability;

defined( 'ABSPATH' ) || exit;

Expand Down Expand Up @@ -60,6 +61,7 @@ public function register_abilities(): void {
File_Write_Ability::register();
File_Delete_Ability::register();
File_List_Ability::register();
Web_Fetch_Ability::register();

Memory_Short_Term_Add_Ability::register();
Memory_Short_Term_Update_Ability::register();
Expand Down
5 changes: 3 additions & 2 deletions includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ function clawpress_render_minimum_wp_version_notice(): void {
}

/* translators: 1: minimum supported WordPress version, 2: current WordPress version. */
$message = sprintf(
__( 'ClawPress requires WordPress %1$s or newer. You are currently running WordPress %2$s.', 'clawpress' ),
$message_template = __( 'ClawPress requires WordPress %1$s or newer. You are currently running WordPress %2$s.', 'clawpress' );
$message = sprintf(
$message_template,
'7.0',
clawpress_get_wp_version() ?: __( 'an unknown version', 'clawpress' )
);
Expand Down
Loading
Loading