Summary
wp-codebox describes itself as a generic substrate — "Secure coding environments inside WordPress... disposable WP Codebox Playground sandboxes." But its WordPress plugin source names specific downstream consumers (agents-api, data-machine, data-machine-code), ships pinned Extra-Chill/ GitHub release-zip URLs, and compiles a feature against a \DataMachine\... interface. A generic sandbox layer should not know its consumers exist.
The grep test (the standard for layer-purity audits) finds the leak:
grep -riE 'data-machine|datamachine' packages/wordpress-plugin/src
Exact locations (v0.5.0, packages/wordpress-plugin/src/)
1. trait-wp-codebox-abilities-browser-runtime.php
Hardcoded required consumer set (browser_runtime_component_slugs, ~L501-505):
private static function browser_runtime_component_slugs( array $declared_components, bool $include_required ): array {
$slugs = array();
if ( $include_required ) {
$slugs = array( 'agents-api', 'data-machine', 'data-machine-code' ); // <-- consumer names in the substrate
}
...
Default registry seeded with consumer names + vendor release URLs (browser_runtime_component_registry, ~L522-545):
$registry = array(
'agents-api' => array( 'url' => 'https://github.com/Automattic/agents-api/releases/latest/download/agents-api.zip', ... ),
'data-machine' => array( 'url' => 'https://github.com/Extra-Chill/data-machine/releases/latest/download/data-machine.zip', ... ),
'data-machine-code' => array( 'url' => 'https://github.com/Extra-Chill/data-machine-code/releases/latest/download/data-machine-code.zip', ... ),
);
if ( function_exists( 'apply_filters' ) ) {
$registry = apply_filters( 'wp_codebox_browser_runtime_component_registry', $registry ); // <-- filter already exists!
}
2. trait-wp-codebox-abilities-browser-runner.php
Event-sink hard-coupled to a Data Machine interface by name (~L334-335, L1035-1039):
if ( interface_exists( "\\DataMachine\\Engine\\AI\\LoopEventSinkInterface" ) && ! class_exists( "WP_Codebox_Browser_Event_File_Sink" ) ) {
class WP_Codebox_Browser_Event_File_Sink implements \DataMachine\Engine\AI\LoopEventSinkInterface { ... }
}
...
if ( interface_exists( "\\DataMachine\\Engine\\AI\\LoopEventSinkInterface" ) && class_exists( "WP_Codebox_Browser_Event_File_Sink" ) ) {
$input['event_sink'] = new WP_Codebox_Browser_Event_File_Sink( $event_path );
}
Why this is a problem
This is the same anti-pattern as a generic transport hardcoding kimaki/discord/telegram: the substrate names the layers above it. Concretely:
- Adding/removing a runtime consumer requires editing the substrate. A new agent stack can't be supported via config — it needs a code change in
wp-codebox source.
- Vendor-org release URLs are baked in. The
Extra-Chill/ URLs are a downstream deployment's concern, not the sandbox's.
- The
\DataMachine\... interface coupling means the substrate compiles a class against a specific consumer's contract. The interface_exists() guard makes it soft (degrades when absent), which is better than a hard dep — but soft-coupling-by-name is still coupling-by-name.
The fix (config, not code)
The mechanism already mostly exists — the wp_codebox_browser_runtime_component_registry filter is right there. Complete the pattern:
- Ship empty/neutral defaults.
browser_runtime_component_registry() should default to array() (or only truly-generic entries), with consumers registering their own slugs + URLs via the existing filter from the integration plugin (e.g. data-machine / wp-coding-agents register data-machine, data-machine-code, agents-api themselves).
- Make the required set filterable too. Replace the hardcoded
array( 'agents-api', 'data-machine', 'data-machine-code' ) in browser_runtime_component_slugs() with a filter (e.g. wp_codebox_browser_runtime_required_components) defaulting to array().
- Decouple the event sink. Instead of
implements \DataMachine\Engine\AI\LoopEventSinkInterface, expose a generic event-sink hook/contract owned by wp-codebox and let Data Machine adapt to it (or register its sink via a filter like wp_codebox_browser_runtime_event_sink). The substrate should define the seam; the consumer implements it.
Net result: grep -riE 'data-machine|datamachine|extra-chill' over packages/wordpress-plugin/src returns clean, and shipping a new agent runtime is a config/filter registration in the consumer plugin — never a code change in wp-codebox.
Scope note
The PHPUnit / run-php / wp-cli / browser-probe / recipe/mount core is already clean and agnostic — no consumer names there. This leak is confined to the agent-runtime browser bridge (the two browser-runtime/browser-runner traits). The fix is well-scoped to those files plus moving the defaults into the consumer plugin.
Summary
wp-codeboxdescribes itself as a generic substrate — "Secure coding environments inside WordPress... disposable WP Codebox Playground sandboxes." But its WordPress plugin source names specific downstream consumers (agents-api,data-machine,data-machine-code), ships pinnedExtra-Chill/GitHub release-zip URLs, and compiles a feature against a\DataMachine\...interface. A generic sandbox layer should not know its consumers exist.The grep test (the standard for layer-purity audits) finds the leak:
Exact locations (v0.5.0,
packages/wordpress-plugin/src/)1.
trait-wp-codebox-abilities-browser-runtime.phpHardcoded required consumer set (
browser_runtime_component_slugs, ~L501-505):Default registry seeded with consumer names + vendor release URLs (
browser_runtime_component_registry, ~L522-545):2.
trait-wp-codebox-abilities-browser-runner.phpEvent-sink hard-coupled to a Data Machine interface by name (~L334-335, L1035-1039):
Why this is a problem
This is the same anti-pattern as a generic transport hardcoding
kimaki/discord/telegram: the substrate names the layers above it. Concretely:wp-codeboxsource.Extra-Chill/URLs are a downstream deployment's concern, not the sandbox's.\DataMachine\...interface coupling means the substrate compiles a class against a specific consumer's contract. Theinterface_exists()guard makes it soft (degrades when absent), which is better than a hard dep — but soft-coupling-by-name is still coupling-by-name.The fix (config, not code)
The mechanism already mostly exists — the
wp_codebox_browser_runtime_component_registryfilter is right there. Complete the pattern:browser_runtime_component_registry()should default toarray()(or only truly-generic entries), with consumers registering their own slugs + URLs via the existing filter from the integration plugin (e.g. data-machine / wp-coding-agents registerdata-machine,data-machine-code,agents-apithemselves).array( 'agents-api', 'data-machine', 'data-machine-code' )inbrowser_runtime_component_slugs()with a filter (e.g.wp_codebox_browser_runtime_required_components) defaulting toarray().implements \DataMachine\Engine\AI\LoopEventSinkInterface, expose a generic event-sink hook/contract owned by wp-codebox and let Data Machine adapt to it (or register its sink via a filter likewp_codebox_browser_runtime_event_sink). The substrate should define the seam; the consumer implements it.Net result:
grep -riE 'data-machine|datamachine|extra-chill'overpackages/wordpress-plugin/srcreturns clean, and shipping a new agent runtime is a config/filter registration in the consumer plugin — never a code change in wp-codebox.Scope note
The PHPUnit / run-php / wp-cli / browser-probe / recipe/mount core is already clean and agnostic — no consumer names there. This leak is confined to the agent-runtime browser bridge (the two browser-runtime/browser-runner traits). The fix is well-scoped to those files plus moving the defaults into the consumer plugin.