diff --git a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php index 046a515..136a7fe 100644 --- a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php +++ b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php @@ -501,7 +501,29 @@ private static function browser_component_plugins( array $input, array $declared 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' ); + /** + * Filters the set of component slugs the browser runtime always installs. + * + * The generic runtime has no built-in required components; consumer + * integrations (e.g. an agent runtime) register the plugin slugs their + * sandbox depends on. Slugs must have a matching entry in the component + * registry (see the wp_codebox_browser_runtime_component_registry filter) + * to resolve to an installable source. + * + * @param array $slugs Required component slugs. Default empty. + */ + $required = function_exists( 'apply_filters' ) + ? apply_filters( 'wp_codebox_browser_runtime_required_components', array() ) + : array(); + + if ( is_array( $required ) ) { + foreach ( $required as $required_slug ) { + $normalized = self::safe_key( (string) $required_slug ); + if ( '' !== $normalized ) { + $slugs[] = $normalized; + } + } + } } foreach ( $declared_components as $component ) { @@ -520,33 +542,22 @@ private static function browser_runtime_component_key( string $slug ): string { /** @return array> */ private static function browser_runtime_component_registry(): array { - $registry = array( - 'agents-api' => array( - 'resource' => 'url', - 'url' => 'https://github.com/Automattic/agents-api/releases/latest/download/agents-api.zip', - 'targetFolderName' => 'agents-api', - 'activate' => true, - 'provenance' => array( 'source' => 'runtime-component-registry' ), - ), - 'data-machine' => array( - 'resource' => 'url', - 'url' => 'https://github.com/Extra-Chill/data-machine/releases/latest/download/data-machine.zip', - 'targetFolderName' => 'data-machine', - 'activate' => true, - 'provenance' => array( 'source' => 'runtime-component-registry' ), - ), - 'data-machine-code' => array( - 'resource' => 'url', - 'url' => 'https://github.com/Extra-Chill/data-machine-code/releases/latest/download/data-machine-code.zip', - 'targetFolderName' => 'data-machine-code', - 'activate' => true, - 'provenance' => array( 'source' => 'runtime-component-registry' ), - ), - ); - - if ( function_exists( 'apply_filters' ) ) { - $registry = apply_filters( 'wp_codebox_browser_runtime_component_registry', $registry ); - } + /** + * Filters the browser runtime component registry: a map of component slug + * to an installable source descriptor (resource, url, targetFolderName, + * activate, provenance). + * + * The generic runtime ships no built-in components — it must not know which + * downstream plugins or vendor release URLs exist. Consumer integrations + * register their own components (and pair them with the + * wp_codebox_browser_runtime_required_components filter when a component + * should always be installed). + * + * @param array> $registry Component registry. Default empty. + */ + $registry = function_exists( 'apply_filters' ) + ? apply_filters( 'wp_codebox_browser_runtime_component_registry', array() ) + : array(); return is_array( $registry ) ? $registry : array(); } diff --git a/tests/smoke-wordpress-plugin.php b/tests/smoke-wordpress-plugin.php index 5504a6f..0393423 100644 --- a/tests/smoke-wordpress-plugin.php +++ b/tests/smoke-wordpress-plugin.php @@ -544,6 +544,37 @@ function get_users( array $args ): array { return array( new WP_User( 11, 'Priva 'data_machine_code' => $root . '/data-machine-code', 'provider_plugins' => array( $root . '/ai-provider-test' ), ); +// The generic runtime ships no built-in components. A consumer integration +// registers the agent-stack components + their release sources via these +// filters (mirrors how extrachill-roadie wires the Extra Chill platform in). +$GLOBALS['wp_codebox_filters']['wp_codebox_browser_runtime_required_components'] = static function ( array $slugs ): array { + foreach ( array( 'agents-api', 'data-machine', 'data-machine-code' ) as $required ) { + if ( ! in_array( $required, $slugs, true ) ) { + $slugs[] = $required; + } + } + return $slugs; +}; +$GLOBALS['wp_codebox_filters']['wp_codebox_browser_runtime_component_registry'] = static function ( array $registry ): array { + $defaults = array( + 'agents-api' => 'https://github.com/Automattic/agents-api/releases/latest/download/agents-api.zip', + 'data-machine' => 'https://github.com/Extra-Chill/data-machine/releases/latest/download/data-machine.zip', + 'data-machine-code' => 'https://github.com/Extra-Chill/data-machine-code/releases/latest/download/data-machine-code.zip', + ); + foreach ( $defaults as $slug => $url ) { + if ( isset( $registry[ $slug ] ) ) { + continue; + } + $registry[ $slug ] = array( + 'resource' => 'url', + 'url' => $url, + 'targetFolderName' => $slug, + 'activate' => true, + 'provenance' => array( 'source' => 'runtime-component-registry' ), + ); + } + return $registry; +}; $GLOBALS['wp_codebox_filters']['wp_codebox_bin'] = $root . '/wp-codebox.js'; $GLOBALS['wp_codebox_filters']['wp_codebox_default_agent'] = 'site-coder'; $GLOBALS['wp_codebox_filters']['wp_codebox_default_provider'] = 'openai'; @@ -1533,7 +1564,23 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $component_paths = $GLOBALS['wp_codebox_filters']['wp_codebox_component_paths']; $GLOBALS['wp_codebox_filters']['wp_codebox_component_paths'] = array_merge( $component_paths, array( 'data_machine' => '' ) ); $registry_filter = $GLOBALS['wp_codebox_filters']['wp_codebox_browser_runtime_component_registry'] ?? null; +// Register only agents-api + data-machine-code (omit data-machine) so the +// missing-Data-Machine prerequisite path is exercised. The generic default is +// empty, so the consumer-style registration here is the full source of truth. $GLOBALS['wp_codebox_filters']['wp_codebox_browser_runtime_component_registry'] = static function ( array $registry ): array { + $defaults = array( + 'agents-api' => 'https://github.com/Automattic/agents-api/releases/latest/download/agents-api.zip', + 'data-machine-code' => 'https://github.com/Extra-Chill/data-machine-code/releases/latest/download/data-machine-code.zip', + ); + foreach ( $defaults as $slug => $url ) { + $registry[ $slug ] = array( + 'resource' => 'url', + 'url' => $url, + 'targetFolderName' => $slug, + 'activate' => true, + 'provenance' => array( 'source' => 'runtime-component-registry' ), + ); + } unset( $registry['data-machine'] ); return $registry; };