Skip to content

Commit d06b028

Browse files
authored
Add preview-only browser Playground sessions (#1789)
1 parent 81d32d9 commit d06b028

9 files changed

Lines changed: 264 additions & 28 deletions

docs/sandbox-session-contract.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ registers runtime-principal authorization. If the runner is copied into a normal
5252
host WordPress install, it fails with `wp_codebox_browser_runner_not_playground`
5353
instead of executing the requested sandbox invocation.
5454

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

5767
Browser session, materializer, and task contracts include an additive durable

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
"test:php-artifact-import-idempotency": "php scripts/php-artifact-import-idempotency-smoke.php",
169169
"test:php-browser-runtime-local-package": "php scripts/php-browser-runtime-local-package-smoke.php",
170170
"test:php-browser-runtime-agent-substrate": "php scripts/php-browser-runtime-agent-substrate-smoke.php",
171+
"test:php-browser-preview-only-session": "php -d zend.assertions=1 -d assert.exception=1 scripts/php-browser-preview-only-session-smoke.php",
171172
"test:mcp-client-configs": "tsx tests/mcp-client-configs.test.ts",
172173
"test:runtime-tool-policy": "tsx tests/runtime-tool-policy.test.ts",
173174
"test:primitive-contract-parity": "tsx tests/primitive-contract-parity.test.ts",

packages/wordpress-plugin/src/class-wp-codebox-browser-ability-descriptors.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public static function descriptors( array $context ): array {
8080
),
8181
'wp-codebox/create-browser-playground-session' => array(
8282
'label' => 'Create Browser Sandbox Session',
83-
'description' => 'Prepare a WP Codebox browser sandbox session without requiring the host to run the WP Codebox CLI or Node.',
83+
'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.',
8484
'category' => 'wp-codebox',
8585
'input_schema' => array(
8686
'type' => 'object',

packages/wordpress-plugin/src/class-wp-codebox-browser-contained-site-service.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,12 @@ public function destroy_browser_contained_site_session( array $input ): array|WP
390390
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 {
391391
$prepared_runtime = is_array( $runtime['prepared_runtime'] ?? null ) ? $runtime['prepared_runtime'] : array();
392392
$contained_site = $this->browser_contained_site_envelope( $input, $session_id, $playground, $runtime, $prepared_runtime, 'blocked' );
393+
$preview_only = true === ( $input['preview_only'] ?? false );
393394

394-
return array(
395+
$session = array(
395396
'success' => false,
396397
'schema' => 'wp-codebox/browser-playground-session/v1',
398+
'preview_only' => $preview_only,
397399
'execution' => 'browser-playground',
398400
'execution_scope' => 'disposable-playground',
399401
'permission_model' => 'runtime-principal',
@@ -406,12 +408,12 @@ public function blocked_browser_playground_session( string $session_id, array $i
406408
'session' => $this->browser_session_envelope( $session_id, 'blocked', $input ),
407409
'task' => (string) $task_input['goal'],
408410
'task_input' => $task_input,
409-
'agent' => (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
411+
'agent' => $preview_only ? '' : (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
410412
'plugins' => $browser_plugins,
411413
'runtime' => $runtime,
412414
'contained_site' => $contained_site,
413415
'site_blueprint_artifact' => $site_blueprint_artifact,
414-
'materialization' => array(
416+
'materialization' => $preview_only ? array() : array(
415417
'schema' => 'wp-codebox/browser-materialization/v1',
416418
'status' => 'blocked',
417419
'captures' => array(),
@@ -445,6 +447,11 @@ public function blocked_browser_playground_session( string $session_id, array $i
445447
'expected_artifacts' => $task_input['expected_artifacts'],
446448
),
447449
);
450+
if ( $preview_only ) {
451+
unset( $session['agent'], $session['materialization'] );
452+
}
453+
454+
return $session;
448455
}
449456

450457
public function browser_session_response_for_input( array $session, array $input ): array|WP_Error {

packages/wordpress-plugin/src/class-wp-codebox-browser-task-builder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ public static function product_browser_session_dto( array $session ): array {
290290
'dto_schema' => 'wp-codebox/browser-preview-boot-config/v1',
291291
'source_schema' => (string) ( $session['schema'] ?? '' ),
292292
'success' => (bool) ( $session['success'] ?? false ),
293+
'preview_only' => true === ( $session['preview_only'] ?? false ),
293294
'status' => (string) ( $session['status'] ?? ( true === ( $session['success'] ?? false ) ? 'ready' : '' ) ),
294295
'execution' => (string) ( $session['execution'] ?? '' ),
295296
'execution_scope' => (string) ( $session['execution_scope'] ?? '' ),

packages/wordpress-plugin/src/class-wp-codebox-browser-task-contract-service.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function __call( string $name, array $args ): mixed {
2525
/** @param array<string,mixed> $input Ability input. @return array<string,mixed>|WP_Error */
2626
public function create_browser_materializer_contract( array $input ): array|WP_Error {
2727
$return_raw = $this->include_raw_browser_contract( $input, 'materializer' );
28+
$input['preview_only'] = false;
2829
$input['include_internal_browser_session'] = true;
2930
$session = $this->create_browser_playground_session( $input );
3031
if ( is_wp_error( $session ) ) {
@@ -111,6 +112,7 @@ private function include_raw_browser_contract( array $input, string $contract ):
111112

112113
/** @param array<string,mixed> $input Ability input. @return array<string,mixed>|WP_Error */
113114
private function prepare_browser_task_contract( array $input ): array|WP_Error {
115+
$input['preview_only'] = false;
114116
$input['include_internal_browser_session'] = true;
115117
$primary = $this->create_browser_playground_session( $input );
116118
if ( is_wp_error( $primary ) ) {

packages/wordpress-plugin/src/trait-wp-codebox-abilities-execution.php

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ public static function request_host_delegation( array $input ): array|WP_Error {
145145

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

164-
$inheritance_payload = self::browser_inheritance_resolution_payload( $input );
165-
if ( is_wp_error( $inheritance_payload ) ) {
166-
return $inheritance_payload;
167-
}
168-
$input = self::browser_input_with_inheritance( $input, $inheritance_payload['inheritance'] );
169-
if ( is_wp_error( $input ) ) {
170-
return $input;
165+
$inheritance_payload = array( 'inheritance' => array( 'connectors' => array(), 'settings' => array() ) );
166+
$dependency_plan = null;
167+
if ( ! $preview_only ) {
168+
$inheritance_payload = self::browser_inheritance_resolution_payload( $input );
169+
if ( is_wp_error( $inheritance_payload ) ) {
170+
return $inheritance_payload;
171+
}
172+
$input = self::browser_input_with_inheritance( $input, $inheritance_payload['inheritance'] );
173+
if ( is_wp_error( $input ) ) {
174+
return $input;
175+
}
176+
$dependency_plan = self::browser_runtime_dependency_plan( $input, $inheritance_payload['inheritance'] );
171177
}
172-
$dependency_plan = self::browser_runtime_dependency_plan( $input, $inheritance_payload['inheritance'] );
173178
$browser_runner = is_array( $input['browser_runner'] ?? null ) ? $input['browser_runner'] : array();
174179
$browser_plugins = self::browser_plugins( $input );
175180
if ( is_wp_error( $browser_plugins ) ) {
176181
return $browser_plugins;
177182
}
178-
$runtime = self::browser_runtime_dependencies( $input, $browser_plugins, $dependency_plan );
183+
$runtime_input = $input;
184+
if ( $preview_only ) {
185+
unset( $runtime_input['browser_runner'], $runtime_input['provider_plugin_paths'], $runtime_input['inherit'], $runtime_input['secret_env'], $runtime_input['runtime_requirements'] );
186+
}
187+
$runtime = self::browser_runtime_dependencies( $runtime_input, $browser_plugins, $dependency_plan );
179188
if ( is_wp_error( $runtime ) ) {
180189
return $runtime;
181190
}
@@ -196,43 +205,50 @@ public static function create_browser_playground_session( array $input ): array|
196205
if ( is_wp_error( $artifacts ) ) {
197206
return $artifacts;
198207
}
199-
$ready_to_code = self::browser_ready_to_code_signal( $input, $runtime );
208+
$ready_to_code = self::browser_ready_to_code_signal( $runtime_input, $runtime );
200209
if ( false === ( $ready_to_code['emitted'] ?? false ) ) {
201210
$blocked_session = self::blocked_browser_playground_session( $session_id, $input, $task_input, $ready_to_code, $browser_plugins, $runtime, $artifacts, $playground, $blueprint, $site_blueprint_artifact );
202211
return self::browser_session_response_for_input( $blocked_session, $input );
203212
}
204213

205-
$task_payload = self::browser_task_payload( $input, $task_input, $session_id, $artifacts, $inheritance_payload['inheritance'], $dependency_plan );
206-
$recipe = self::browser_agent_recipe( $task_input, $session_id, $browser_runner, $blueprint, $playground, $task_payload );
207-
if ( is_wp_error( $recipe ) ) {
208-
return $recipe;
214+
$task_payload = array();
215+
$recipe = array();
216+
$materialization = array();
217+
$recipe_blueprint = self::browser_playground_blueprint( self::browser_selected_prepared_runtime_blueprint( $prepared_runtime, $blueprint ), $playground );
218+
if ( ! $preview_only ) {
219+
$task_payload = self::browser_task_payload( $input, $task_input, $session_id, $artifacts, $inheritance_payload['inheritance'], $dependency_plan );
220+
$recipe = self::browser_agent_recipe( $task_input, $session_id, $browser_runner, $blueprint, $playground, $task_payload );
221+
if ( is_wp_error( $recipe ) ) {
222+
return $recipe;
223+
}
224+
$recipe_blueprint = is_array( $recipe['runtime']['blueprint'] ?? null ) ? $recipe['runtime']['blueprint'] : $blueprint;
225+
$materialization = self::browser_materialization_contract( $recipe );
209226
}
210-
$recipe_blueprint = is_array( $recipe['runtime']['blueprint'] ?? null ) ? $recipe['runtime']['blueprint'] : $blueprint;
211227
if ( is_array( $runtime['prepared_runtime'] ?? null ) ) {
212228
self::browser_prepared_runtime_cache_store( $runtime['prepared_runtime'], $recipe_blueprint );
213229
}
214230
$blueprint = $recipe_blueprint;
215-
$materialization = self::browser_materialization_contract( $recipe );
216231

217232
$session = array(
218233
'success' => true,
219234
'schema' => 'wp-codebox/browser-playground-session/v1',
235+
'preview_only' => $preview_only,
220236
'execution' => 'browser-playground',
221237
'execution_scope' => 'disposable-playground',
222238
'permission_model' => 'runtime-principal',
223239
'session' => self::browser_session_envelope( $session_id, 'ready', $input ),
224240
'task' => (string) $task_input['goal'],
225241
'task_input' => $task_input,
226-
'task_payload' => $task_payload,
227-
'agent' => (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
228-
'provider' => self::browser_provider( $input, $inheritance_payload['inheritance'] ),
229-
'model' => self::browser_model( $input, $inheritance_payload['inheritance'] ),
230-
'inheritance' => $inheritance_payload['inheritance'],
242+
'task_payload' => $preview_only ? array() : $task_payload,
243+
'agent' => $preview_only ? '' : (string) ( $input['agent'] ?? 'wp-codebox-sandbox' ),
244+
'provider' => $preview_only ? '' : self::browser_provider( $input, $inheritance_payload['inheritance'] ),
245+
'model' => $preview_only ? '' : self::browser_model( $input, $inheritance_payload['inheritance'] ),
246+
'inheritance' => $preview_only ? array() : $inheritance_payload['inheritance'],
231247
'plugins' => $browser_plugins,
232248
'runtime' => $runtime,
233249
'contained_site' => $contained_site,
234250
'site_blueprint_artifact' => $site_blueprint_artifact,
235-
'materialization' => $materialization,
251+
'materialization' => $preview_only ? array() : $materialization,
236252
'runtime_capabilities' => array_values(
237253
array_unique(
238254
array_filter(
@@ -263,7 +279,7 @@ public static function create_browser_playground_session( array $input ): array|
263279
),
264280
'provenance' => $playground['provenance'],
265281
),
266-
'recipe' => $recipe,
282+
'recipe' => $preview_only ? array() : $recipe,
267283
'signals' => array(
268284
'ready_to_code' => $ready_to_code,
269285
),
@@ -274,6 +290,9 @@ public static function create_browser_playground_session( array $input ): array|
274290
'expected_artifacts' => $task_input['expected_artifacts'],
275291
),
276292
);
293+
if ( $preview_only ) {
294+
unset( $session['task_payload'], $session['agent'], $session['provider'], $session['model'], $session['inheritance'], $session['materialization'], $session['recipe'] );
295+
}
277296

278297
return self::browser_session_response_for_input( $session, $input );
279298
}

packages/wordpress-plugin/src/trait-wp-codebox-abilities-schemas.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ private static function browser_playground_session_schema(): array {
287287
'type' => 'object',
288288
'properties' => array(
289289
'success' => array( 'type' => 'boolean' ),
290+
'preview_only' => array( 'type' => 'boolean' ),
290291
'schema' => array( 'type' => 'string' ),
291292
'execution' => array(
292293
'type' => 'string',
@@ -419,6 +420,7 @@ private static function browser_product_dto_schema(): array {
419420
'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.',
420421
'properties' => array(
421422
'success' => array( 'type' => 'boolean' ),
423+
'preview_only' => array( 'type' => 'boolean' ),
422424
'schema' => array( 'type' => 'string' ),
423425
'dto_schema' => array( 'type' => 'string' ),
424426
'source_schema' => array( 'type' => 'string' ),
@@ -1019,7 +1021,7 @@ private static function component_contracts_schema( string $description = '' ):
10191021
* @return array<string,mixed>
10201022
*/
10211023
private static function browser_task_input_properties( array $task_input_schema, array $inherit_schema, array $session_input, bool $detailed = false ): array {
1022-
return self::task_input_alias_properties( $task_input_schema ) + array(
1024+
$properties = self::task_input_alias_properties( $task_input_schema ) + array(
10231025
'agent_workload' => self::agent_workload_schema(),
10241026
'workload' => self::agent_workload_schema(),
10251027
'agent' => self::string_property_schema( $detailed ? 'Codebox agent runtime id for the browser sandbox.' : '' ),
@@ -1050,6 +1052,15 @@ private static function browser_task_input_properties( array $task_input_schema,
10501052
'description' => 'Optional debug fields for diagnostics. Public callers receive the stable product DTO envelope.',
10511053
),
10521054
);
1055+
if ( $detailed ) {
1056+
$properties['preview_only'] = array(
1057+
'type' => 'boolean',
1058+
'default' => false,
1059+
'description' => 'When true, prepare an editable browser preview runtime without resolving agent/provider inheritance or appending the task runner recipe.',
1060+
);
1061+
}
1062+
1063+
return $properties;
10531064
}
10541065

10551066
/** @return array<string,mixed> */

0 commit comments

Comments
 (0)