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
11 changes: 11 additions & 0 deletions packages/wordpress-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,17 @@ an external adapter can consume the verified artifact payload and record adapter
metadata, PR URL, branch, commit, and artifact digest without WP Codebox calling
any product-specific apply-back system.

## Agent Context Section

The plugin registers a `wp-codebox` section for the `agents-md` context on the
upstream context section registry (`WP_Agent_Context_Section_Registry`), so any
host that composes `AGENTS.md` from registered sections surfaces Codebox routing,
safety, and discovery guidance to coding agents. Registration is a no-op when the
registry class is not loaded; no host-specific composer is referenced.

The rendered WP-CLI prefix defaults to `wp --path=<ABSPATH>` and can be adjusted
through the `wp_codebox_agents_md_wp_cli_cmd` filter.

## Configuration

Runtime components can be supplied by ability input, the
Expand Down
104 changes: 104 additions & 0 deletions packages/wordpress-plugin/src/class-wp-codebox-agents-md-section.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php
/**
* Composable agent-context section describing WP Codebox.
*
* Registers a `WP Codebox` section for the `agents-md` context on the
* Agents API context section registry so host composers (any consumer of
* `WP_Agent_Context_Section_Registry`) surface Codebox routing, safety, and
* discovery guidance to coding agents. The plugin owns the surface, so it owns
* the guidance; no host-specific composer is referenced here.
*
* @package WP_Codebox
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

final class WP_Codebox_Agents_Md_Section {

public const CONTEXT_SLUG = 'agents-md';
public const SECTION_SLUG = 'wp-codebox';
public const PRIORITY = 40;

/**
* Hook the registration onto the Agents API context extension point.
*/
public static function register(): void {
add_action( 'agents_api_context_sections', array( self::class, 'register_section' ) );
}

/**
* Register the section when the substrate registry is loaded.
*/
public static function register_section(): void {
if ( ! class_exists( 'WP_Agent_Context_Section_Registry' ) ) {
return;
}

WP_Agent_Context_Section_Registry::register(
self::CONTEXT_SLUG,
self::SECTION_SLUG,
self::PRIORITY,
static function (): string {
return self::render();
},
array(
'label' => 'WP Codebox',
'description' => 'Disposable WordPress sandbox CLI and abilities.',
'meta' => array(
'owner' => 'wp-codebox',
'freshness' => 'generated',
'conditions' => 'Registered when wp-codebox is active and the Agents API context section registry is loaded.',
),
)
);
}

/**
* WP-CLI invocation prefix used in rendered guidance.
*
* Defaults to a path-pinned invocation. Hosts that need a different
* prefix (flags, wrapper binaries, remote routing) adjust it through the
* `wp_codebox_agents_md_wp_cli_cmd` filter.
*/
public static function wp_cli_cmd(): string {
$default = 'wp --path=' . rtrim( (string) ABSPATH, '/\\' );

$cmd = apply_filters( 'wp_codebox_agents_md_wp_cli_cmd', $default );

return is_string( $cmd ) && '' !== trim( $cmd ) ? trim( $cmd ) : $default;
}

/**
* Render the section body.
*/
public static function render(): string {
$wp = self::wp_cli_cmd();

$lines = array(
'## WP Codebox',
'',
'WP Codebox launches disposable, isolated WordPress sandboxes for coding-agent tasks, runtime workloads, and fuzzing. Sandboxes cannot touch the host site; they produce artifact bundles (changed files, patches, evidence) that the host reviews and applies back explicitly. Abilities live under `wp-codebox/*`; the CLI mirrors the same PHP service layer.',
'',
'**Default routing**',
'- Run a bounded coding task in a sandbox: `' . $wp . ' codebox run-agent-task --goal=\'...\' --format=json`',
'- Independent task waves: `' . $wp . ' codebox run-agent-task-batch --input-file=<batch.json>` or `' . $wp . ' codebox run-agent-task-fanout --input-file=<fanout.json>`',
'- Run a runtime task or WordPress workload: `' . $wp . ' codebox run-runtime-task --input-json=\'{...}\'` or `' . $wp . ' codebox run-wordpress-workload --input-file=<workload.json>`',
'- Check runtime/provider readiness before dispatch: `' . $wp . ' codebox resolve-runtime-requirements --format=json`',
'- Review what a sandbox produced: `' . $wp . ' codebox artifacts list --format=json`, then `' . $wp . ' codebox artifacts inspect <artifact_id> --format=json`',
'- Apply reviewed changes to the host: `' . $wp . ' codebox artifacts preflight-apply <artifact_id>` → `' . $wp . ' codebox artifacts stage-apply <artifact_id> --approved-files=...` → `' . $wp . ' codebox artifacts apply <artifact_id> --approved-files=...`',
'- Browser-executed Playground session: `' . $wp . ' codebox browser-session create --goal=\'...\' --format=json`',
'',
'**Safety**',
'- `artifacts apply` mutates the host through the configured apply-back adapter. Always `preflight-apply` and review `inspect` output first; pass only the files you approved via `--approved-files`.',
'- Provider credentials reach sandboxes through `secret_env` names. Never print secret values in prompts, task payloads, logs, or artifacts.',
'- WP-CLI runs in trusted operator context and bypasses ability permission callbacks; shell access is the permission boundary.',
'',
'**Discovery**',
'Use `' . $wp . ' codebox --help` and `' . $wp . ' codebox <command> --help` for the live command contract; `--input-json` / `--input-file` carry complex payloads and CLI flags override payload fields. Inspect `' . $wp . ' codebox runtime descriptor --format=json` for the registered runtime profile. Live `--help` output is authoritative.',
);

return implode( "\n", $lines ) . "\n";
}
}
2 changes: 2 additions & 0 deletions packages/wordpress-plugin/wp-codebox.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
require_once __DIR__ . '/src/class-wp-codebox-runtime-package-executor.php';
require_once __DIR__ . '/src/class-wp-codebox-api.php';
require_once __DIR__ . '/src/class-wp-codebox-abilities.php';
require_once __DIR__ . '/src/class-wp-codebox-agents-md-section.php';

if ( defined( 'WP_CLI' ) && WP_CLI ) {
require_once __DIR__ . '/src/class-wp-codebox-cli-command.php';
Expand All @@ -94,6 +95,7 @@
add_action( 'plugins_loaded', array( WP_Codebox_Php_Ai_Client_Browser_Provider_Adapter::class, 'register' ), 20 );
new WP_Codebox_Abilities();
WP_Codebox_Browser_Provider_Bridge::register();
WP_Codebox_Agents_Md_Section::register();

if ( defined( 'WP_CLI' ) && WP_CLI ) {
WP_Codebox_CLI_Command::register();
Expand Down
103 changes: 103 additions & 0 deletions tests/php-agents-md-section.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import assert from "node:assert/strict"
import { execFileSync } from "node:child_process"

// Proves the plugin registers its own `agents-md` context section on a
// minimal stand-in for WP_Agent_Context_Section_Registry (the Agents API
// substrate), that the render body carries routing/safety/discovery guidance
// with the host-filtered WP-CLI prefix, and that registration is a no-op
// when the substrate class is absent.
const output = execFileSync(
"php",
[
"-r",
String.raw`
define('ABSPATH', '/srv/site/');

$GLOBALS['actions'] = array();
$GLOBALS['filters'] = array();
function add_action(string $hook, callable $cb, int $priority = 10, int $accepted_args = 1): void { $GLOBALS['actions'][$hook][] = $cb; }
function do_action(string $hook, ...$args): void { foreach ($GLOBALS['actions'][$hook] ?? array() as $cb) { $cb(); } }
function add_filter(string $hook, callable $cb, int $priority = 10, int $accepted_args = 1): void { $GLOBALS['filters'][$hook][] = $cb; }
function apply_filters(string $hook, $value, ...$args) { foreach ($GLOBALS['filters'][$hook] ?? array() as $cb) { $value = $cb($value); } return $value; }

require __DIR__ . '/packages/wordpress-plugin/src/class-wp-codebox-agents-md-section.php';

// 1. Without the substrate class, firing the hook registers nothing and does not fatal.
WP_Codebox_Agents_Md_Section::register();
do_action('agents_api_context_sections');
$without_substrate_ok = true;

$registered_before_substrate = isset($GLOBALS['registered']) ? count($GLOBALS['registered']) : 0;

// 2. With a minimal substrate stand-in, the section registers with the expected shape.
// Declared inside a block so PHP does not hoist it above step 1.
if (true) {
final class WP_Agent_Context_Section_Registry {
public static array $registered = array();
public static function register(string $context, string $slug, int $priority, callable $callback, array $args = array()) {
self::$registered[] = compact('context', 'slug', 'priority', 'callback', 'args');
return self::$registered[count(self::$registered) - 1];
}
}
}
do_action('agents_api_context_sections');

add_filter('wp_codebox_agents_md_wp_cli_cmd', static function ($cmd) { return 'wp --allow-root --path=/srv/site'; });

$reg = WP_Agent_Context_Section_Registry::$registered[0] ?? null;
$rendered = $reg ? ($reg['callback'])(array(), array()) : '';

echo json_encode(array(
'without_substrate_ok' => $without_substrate_ok && 0 === $registered_before_substrate,
'count' => count(WP_Agent_Context_Section_Registry::$registered),
'context' => $reg['context'] ?? null,
'slug' => $reg['slug'] ?? null,
'priority' => $reg['priority'] ?? null,
'label' => $reg['args']['label'] ?? null,
'owner' => $reg['args']['meta']['owner'] ?? null,
'freshness' => $reg['args']['meta']['freshness'] ?? null,
'default_cmd' => 'wp --path=/srv/site',
'rendered' => $rendered,
), JSON_UNESCAPED_SLASHES);
`,
],
{ cwd: new URL("..", import.meta.url), encoding: "utf8" }
)

const result = JSON.parse(output)

assert.equal(result.without_substrate_ok, true)
assert.equal(result.count, 1, "registers exactly one section")
assert.equal(result.context, "agents-md")
assert.equal(result.slug, "wp-codebox")
assert.equal(result.priority, 40)
assert.equal(result.label, "WP Codebox")
assert.equal(result.owner, "wp-codebox")
assert.equal(result.freshness, "generated")

const rendered: string = result.rendered
assert.ok(rendered.startsWith("## WP Codebox\n"), "section starts with its heading")
for (const heading of ["**Default routing**", "**Safety**", "**Discovery**"]) {
assert.ok(rendered.includes(heading), `contains ${heading}`)
}
for (const verb of [
"codebox run-agent-task",
"codebox run-agent-task-fanout",
"codebox run-wordpress-workload",
"codebox resolve-runtime-requirements",
"codebox artifacts inspect",
"codebox artifacts preflight-apply",
"codebox artifacts apply",
"codebox browser-session create",
"codebox --help",
]) {
assert.ok(rendered.includes(verb), `routes to ${verb}`)
}
assert.ok(
rendered.includes("wp --allow-root --path=/srv/site codebox"),
"uses the host-filtered WP-CLI prefix"
)
assert.ok(!rendered.includes(result.default_cmd + " codebox"), "filtered prefix replaces the default")
assert.ok(!/datamachine|DataMachine/.test(rendered), "no host composer names leak into generic guidance")

console.log("php-agents-md-section: OK")
Loading