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
10 changes: 10 additions & 0 deletions docs/sandbox-session-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ registers runtime-principal authorization. If the runner is copied into a normal
host WordPress install, it fails with `wp_codebox_browser_runner_not_playground`
instead of executing the requested sandbox invocation.

Callers that only need an editable contained-site preview can set
`preview_only: true`. WP Codebox still compiles caller-declared runtime plugins,
MU plugins, themes, bootstrap operations, and site blueprint artifacts and
returns the normal product DTO, preview boot descriptor, readiness, contained
site metadata, and hydratable prepared-runtime blueprint ref. This mode does not
resolve agent/provider inheritance and does not append a task runner, captures,
or staged task payload. Provider plugins are included only when the caller
declares them as ordinary `browser_plugins` or `runtime.plugins` dependencies.
Browser task and materializer contract creation remains agentic.

## Browser Contained Site Handle

Browser session, materializer, and task contracts include an additive durable
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
"test:php-artifact-import-idempotency": "php scripts/php-artifact-import-idempotency-smoke.php",
"test:php-browser-runtime-local-package": "php scripts/php-browser-runtime-local-package-smoke.php",
"test:php-browser-runtime-agent-substrate": "php scripts/php-browser-runtime-agent-substrate-smoke.php",
"test:php-browser-preview-only-session": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-preview-only-session-smoke.php",
"test:mcp-client-configs": "tsx tests/mcp-client-configs.test.ts",
"test:runtime-tool-policy": "tsx tests/runtime-tool-policy.test.ts",
"test:primitive-contract-parity": "tsx tests/primitive-contract-parity.test.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public static function descriptors( array $context ): array {
),
'wp-codebox/create-browser-playground-session' => array(
'label' => 'Create Browser Sandbox Session',
'description' => 'Prepare a WP Codebox browser sandbox session without requiring the host to run the WP Codebox CLI or Node.',
'description' => 'Prepare a WP Codebox browser sandbox session without requiring the host to run the WP Codebox CLI or Node. Set preview_only to prepare an editable runtime without an agent task runner.',
'category' => 'wp-codebox',
'input_schema' => array(
'type' => 'object',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,12 @@ public function destroy_browser_contained_site_session( array $input ): array|WP
public function blocked_browser_playground_session( string $session_id, array $input, array $task_input, array $ready_to_code, array $browser_plugins, array $runtime, array $artifacts, array $playground, array $blueprint, array $site_blueprint_artifact ): array {
$prepared_runtime = is_array( $runtime['prepared_runtime'] ?? null ) ? $runtime['prepared_runtime'] : array();
$contained_site = $this->browser_contained_site_envelope( $input, $session_id, $playground, $runtime, $prepared_runtime, 'blocked' );
$preview_only = true === ( $input['preview_only'] ?? false );

return array(
$session = array(
'success' => false,
'schema' => 'wp-codebox/browser-playground-session/v1',
'preview_only' => $preview_only,
'execution' => 'browser-playground',
'execution_scope' => 'disposable-playground',
'permission_model' => 'runtime-principal',
Expand All @@ -406,12 +408,12 @@ public function blocked_browser_playground_session( string $session_id, array $i
'session' => $this->browser_session_envelope( $session_id, 'blocked', $input ),
'task' => (string) $task_input['goal'],
'task_input' => $task_input,
'agent' => (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
'agent' => $preview_only ? '' : (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
'plugins' => $browser_plugins,
'runtime' => $runtime,
'contained_site' => $contained_site,
'site_blueprint_artifact' => $site_blueprint_artifact,
'materialization' => array(
'materialization' => $preview_only ? array() : array(
'schema' => 'wp-codebox/browser-materialization/v1',
'status' => 'blocked',
'captures' => array(),
Expand Down Expand Up @@ -445,6 +447,11 @@ public function blocked_browser_playground_session( string $session_id, array $i
'expected_artifacts' => $task_input['expected_artifacts'],
),
);
if ( $preview_only ) {
unset( $session['agent'], $session['materialization'] );
}

return $session;
}

public function browser_session_response_for_input( array $session, array $input ): array|WP_Error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ public static function product_browser_session_dto( array $session ): array {
'dto_schema' => 'wp-codebox/browser-preview-boot-config/v1',
'source_schema' => (string) ( $session['schema'] ?? '' ),
'success' => (bool) ( $session['success'] ?? false ),
'preview_only' => true === ( $session['preview_only'] ?? false ),
'status' => (string) ( $session['status'] ?? ( true === ( $session['success'] ?? false ) ? 'ready' : '' ) ),
'execution' => (string) ( $session['execution'] ?? '' ),
'execution_scope' => (string) ( $session['execution_scope'] ?? '' ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public function __call( string $name, array $args ): mixed {
/** @param array<string,mixed> $input Ability input. @return array<string,mixed>|WP_Error */
public function create_browser_materializer_contract( array $input ): array|WP_Error {
$return_raw = $this->include_raw_browser_contract( $input, 'materializer' );
$input['preview_only'] = false;
$input['include_internal_browser_session'] = true;
$session = $this->create_browser_playground_session( $input );
if ( is_wp_error( $session ) ) {
Expand Down Expand Up @@ -111,6 +112,7 @@ private function include_raw_browser_contract( array $input, string $contract ):

/** @param array<string,mixed> $input Ability input. @return array<string,mixed>|WP_Error */
private function prepare_browser_task_contract( array $input ): array|WP_Error {
$input['preview_only'] = false;
$input['include_internal_browser_session'] = true;
$primary = $this->create_browser_playground_session( $input );
if ( is_wp_error( $primary ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public static function request_host_delegation( array $input ): array|WP_Error {

/** @param array<string,mixed> $input Ability input. @return array<string,mixed>|WP_Error */
public static function create_browser_playground_session( array $input ): array|WP_Error {
$preview_only = true === ( $input['preview_only'] ?? false );
$input = WP_Codebox_Browser_Task_Builder::local_browser_task_input( $input );
$task_input = self::normalize_task_input( $input );
if ( is_wp_error( $task_input ) ) {
Expand All @@ -161,21 +162,29 @@ public static function create_browser_playground_session( array $input ): array|
return $playground;
}

$inheritance_payload = self::browser_inheritance_resolution_payload( $input );
if ( is_wp_error( $inheritance_payload ) ) {
return $inheritance_payload;
}
$input = self::browser_input_with_inheritance( $input, $inheritance_payload['inheritance'] );
if ( is_wp_error( $input ) ) {
return $input;
$inheritance_payload = array( 'inheritance' => array( 'connectors' => array(), 'settings' => array() ) );
$dependency_plan = null;
if ( ! $preview_only ) {
$inheritance_payload = self::browser_inheritance_resolution_payload( $input );
if ( is_wp_error( $inheritance_payload ) ) {
return $inheritance_payload;
}
$input = self::browser_input_with_inheritance( $input, $inheritance_payload['inheritance'] );
if ( is_wp_error( $input ) ) {
return $input;
}
$dependency_plan = self::browser_runtime_dependency_plan( $input, $inheritance_payload['inheritance'] );
}
$dependency_plan = self::browser_runtime_dependency_plan( $input, $inheritance_payload['inheritance'] );
$browser_runner = is_array( $input['browser_runner'] ?? null ) ? $input['browser_runner'] : array();
$browser_plugins = self::browser_plugins( $input );
if ( is_wp_error( $browser_plugins ) ) {
return $browser_plugins;
}
$runtime = self::browser_runtime_dependencies( $input, $browser_plugins, $dependency_plan );
$runtime_input = $input;
if ( $preview_only ) {
unset( $runtime_input['browser_runner'], $runtime_input['provider_plugin_paths'], $runtime_input['inherit'], $runtime_input['secret_env'], $runtime_input['runtime_requirements'] );
}
$runtime = self::browser_runtime_dependencies( $runtime_input, $browser_plugins, $dependency_plan );
if ( is_wp_error( $runtime ) ) {
return $runtime;
}
Expand All @@ -196,43 +205,50 @@ public static function create_browser_playground_session( array $input ): array|
if ( is_wp_error( $artifacts ) ) {
return $artifacts;
}
$ready_to_code = self::browser_ready_to_code_signal( $input, $runtime );
$ready_to_code = self::browser_ready_to_code_signal( $runtime_input, $runtime );
if ( false === ( $ready_to_code['emitted'] ?? false ) ) {
$blocked_session = self::blocked_browser_playground_session( $session_id, $input, $task_input, $ready_to_code, $browser_plugins, $runtime, $artifacts, $playground, $blueprint, $site_blueprint_artifact );
return self::browser_session_response_for_input( $blocked_session, $input );
}

$task_payload = self::browser_task_payload( $input, $task_input, $session_id, $artifacts, $inheritance_payload['inheritance'], $dependency_plan );
$recipe = self::browser_agent_recipe( $task_input, $session_id, $browser_runner, $blueprint, $playground, $task_payload );
if ( is_wp_error( $recipe ) ) {
return $recipe;
$task_payload = array();
$recipe = array();
$materialization = array();
$recipe_blueprint = self::browser_playground_blueprint( self::browser_selected_prepared_runtime_blueprint( $prepared_runtime, $blueprint ), $playground );
if ( ! $preview_only ) {
$task_payload = self::browser_task_payload( $input, $task_input, $session_id, $artifacts, $inheritance_payload['inheritance'], $dependency_plan );
$recipe = self::browser_agent_recipe( $task_input, $session_id, $browser_runner, $blueprint, $playground, $task_payload );
if ( is_wp_error( $recipe ) ) {
return $recipe;
}
$recipe_blueprint = is_array( $recipe['runtime']['blueprint'] ?? null ) ? $recipe['runtime']['blueprint'] : $blueprint;
$materialization = self::browser_materialization_contract( $recipe );
}
$recipe_blueprint = is_array( $recipe['runtime']['blueprint'] ?? null ) ? $recipe['runtime']['blueprint'] : $blueprint;
if ( is_array( $runtime['prepared_runtime'] ?? null ) ) {
self::browser_prepared_runtime_cache_store( $runtime['prepared_runtime'], $recipe_blueprint );
}
$blueprint = $recipe_blueprint;
$materialization = self::browser_materialization_contract( $recipe );

$session = array(
'success' => true,
'schema' => 'wp-codebox/browser-playground-session/v1',
'preview_only' => $preview_only,
'execution' => 'browser-playground',
'execution_scope' => 'disposable-playground',
'permission_model' => 'runtime-principal',
'session' => self::browser_session_envelope( $session_id, 'ready', $input ),
'task' => (string) $task_input['goal'],
'task_input' => $task_input,
'task_payload' => $task_payload,
'agent' => (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
'provider' => self::browser_provider( $input, $inheritance_payload['inheritance'] ),
'model' => self::browser_model( $input, $inheritance_payload['inheritance'] ),
'inheritance' => $inheritance_payload['inheritance'],
'task_payload' => $preview_only ? array() : $task_payload,
'agent' => $preview_only ? '' : (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
'provider' => $preview_only ? '' : self::browser_provider( $input, $inheritance_payload['inheritance'] ),
'model' => $preview_only ? '' : self::browser_model( $input, $inheritance_payload['inheritance'] ),
'inheritance' => $preview_only ? array() : $inheritance_payload['inheritance'],
'plugins' => $browser_plugins,
'runtime' => $runtime,
'contained_site' => $contained_site,
'site_blueprint_artifact' => $site_blueprint_artifact,
'materialization' => $materialization,
'materialization' => $preview_only ? array() : $materialization,
'runtime_capabilities' => array_values(
array_unique(
array_filter(
Expand Down Expand Up @@ -263,7 +279,7 @@ public static function create_browser_playground_session( array $input ): array|
),
'provenance' => $playground['provenance'],
),
'recipe' => $recipe,
'recipe' => $preview_only ? array() : $recipe,
'signals' => array(
'ready_to_code' => $ready_to_code,
),
Expand All @@ -274,6 +290,9 @@ public static function create_browser_playground_session( array $input ): array|
'expected_artifacts' => $task_input['expected_artifacts'],
),
);
if ( $preview_only ) {
unset( $session['task_payload'], $session['agent'], $session['provider'], $session['model'], $session['inheritance'], $session['materialization'], $session['recipe'] );
}

return self::browser_session_response_for_input( $session, $input );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ private static function browser_playground_session_schema(): array {
'type' => 'object',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
'preview_only' => array( 'type' => 'boolean' ),
'schema' => array( 'type' => 'string' ),
'execution' => array(
'type' => 'string',
Expand Down Expand Up @@ -419,6 +420,7 @@ private static function browser_product_dto_schema(): array {
'description' => 'Compact product-facing browser task/session DTO for durable product records and REST responses. It exposes stable ids, preview refs, artifact refs, status, and compact diagnostics while omitting raw playground, runtime, recipe, task payload, sandbox path, and implementation diagnostic contracts.',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
'preview_only' => array( 'type' => 'boolean' ),
'schema' => array( 'type' => 'string' ),
'dto_schema' => array( 'type' => 'string' ),
'source_schema' => array( 'type' => 'string' ),
Expand Down Expand Up @@ -1019,7 +1021,7 @@ private static function component_contracts_schema( string $description = '' ):
* @return array<string,mixed>
*/
private static function browser_task_input_properties( array $task_input_schema, array $inherit_schema, array $session_input, bool $detailed = false ): array {
return self::task_input_alias_properties( $task_input_schema ) + array(
$properties = self::task_input_alias_properties( $task_input_schema ) + array(
'agent_workload' => self::agent_workload_schema(),
'workload' => self::agent_workload_schema(),
'agent' => self::string_property_schema( $detailed ? 'Codebox agent runtime id for the browser sandbox.' : '' ),
Expand Down Expand Up @@ -1050,6 +1052,15 @@ private static function browser_task_input_properties( array $task_input_schema,
'description' => 'Optional debug fields for diagnostics. Public callers receive the stable product DTO envelope.',
),
);
if ( $detailed ) {
$properties['preview_only'] = array(
'type' => 'boolean',
'default' => false,
'description' => 'When true, prepare an editable browser preview runtime without resolving agent/provider inheritance or appending the task runner recipe.',
);
}

return $properties;
}

/** @return array<string,mixed> */
Expand Down
Loading
Loading