11import assert from "node:assert/strict"
22import { execFile } from "node:child_process"
33import { createHash } from "node:crypto"
4- import { access , mkdtemp , readFile , rm , writeFile } from "node:fs/promises"
4+ import { access , mkdir , mkdtemp , readFile , rm , writeFile } from "node:fs/promises"
55import { tmpdir } from "node:os"
66import { join } from "node:path"
77import { promisify } from "node:util"
@@ -11,11 +11,6 @@ import { normalizeTaskInput } from "../packages/runtime-core/src/task-input.js"
1111
1212const execFileAsync = promisify ( execFile )
1313
14- if ( process . env . WP_CODEBOX_RUN_NETWORK_INTEGRATION !== "1" ) {
15- console . log ( "runtime sources Playground integration skipped: set WP_CODEBOX_RUN_NETWORK_INTEGRATION=1; CI enables this pinned-public-source test" )
16- process . exit ( 0 )
17- }
18-
1914const fixture = JSON . parse ( await readFile ( new URL ( "../fixtures/agent-task-runtime-sources-run-29299109269.json" , import . meta. url ) , "utf8" ) )
2015const policy = parseExternalPackageSourcePolicy ( JSON . stringify ( {
2116 version : 1 ,
@@ -28,7 +23,31 @@ const root = await mkdtemp(join(tmpdir(), "wp-codebox-runtime-sources-playground
2823try {
2924 const materialized = await materializeRuntimeSources ( fixture . runtime_sources , { policy, tempRoot : root , forbiddenRoots : [ join ( root , "artifacts" ) ] } )
3025 const privatePackage = join ( materialized . root , "flat-runtime-agent.agent.json" )
26+ const interceptorPlugin = join ( materialized . root , "openai-interceptor" )
3127 const packageInstruction = "PACKAGE SYSTEM INSTRUCTION: selected imported agent"
28+ const packageTool = "workspace_read"
29+ const genericSandboxInstruction = "Default sandbox agent"
30+ const genericSandboxTool = "deny-all"
31+ await mkdir ( interceptorPlugin , { recursive : true } )
32+ await writeFile ( join ( interceptorPlugin , "openai-interceptor.php" ) , `<?php
33+ /**
34+ * Plugin Name: OpenAI Runtime Package Test Interceptor
35+ */
36+ add_filter( 'pre_http_request', static function( $preempt, $args, $url ) {
37+ if ( ! str_starts_with( $url, 'https://api.openai.com/v1/' ) ) {
38+ return $preempt;
39+ }
40+ $capture = WP_CONTENT_DIR . '/openai-runtime-package-requests.json';
41+ $requests = is_readable( $capture ) ? json_decode( (string) file_get_contents( $capture ), true ) : array();
42+ $requests = is_array( $requests ) ? $requests : array();
43+ $requests[] = array( 'url' => $url, 'body' => $args['body'] ?? null );
44+ file_put_contents( $capture, wp_json_encode( $requests ) );
45+ $body = str_ends_with( $url, '/models' )
46+ ? array( 'object' => 'list', 'data' => array( array( 'id' => 'gpt-5.5', 'object' => 'model', 'created' => 0, 'owned_by' => 'openai' ) ) )
47+ : array( 'id' => 'resp-fixture', 'object' => 'response', 'status' => 'completed', 'output' => array( array( 'id' => 'msg-fixture', 'type' => 'message', 'status' => 'completed', 'role' => 'assistant', 'content' => array( array( 'type' => 'output_text', 'text' => 'Mocked runtime-package reply', 'annotations' => array() ) ) ) ), 'usage' => array( 'input_tokens' => 1, 'output_tokens' => 1, 'total_tokens' => 2 ) );
48+ return array( 'headers' => array( 'content-type' => 'application/json' ), 'body' => wp_json_encode( $body ), 'response' => array( 'code' => 200, 'message' => 'OK' ), 'cookies' => array(), 'filename' => null );
49+ }, 1000, 3 );
50+ ` )
3251 await writeFile ( privatePackage , JSON . stringify ( {
3352 schema_version : 1 ,
3453 bundle_slug : "flat-runtime-agent" ,
3857 description : "Playground imported-agent selection fixture." ,
3958 agent_config : {
4059 instructions : packageInstruction ,
41- enabled_tools : [ ] ,
60+ enabled_tools : [ packageTool ] ,
4261 modes : [ "chat" ] ,
4362 } ,
4463 } ,
@@ -87,12 +106,13 @@ echo wp_json_encode( array( 'imported_slug' => $imports[0]['agent_slug'] ?? '',
87106 provider : "openai" ,
88107 model : "gpt-5.5" ,
89108 runtime_env : { OPENAI_API_KEY : "dummy-key" } ,
109+ extra_plugins : [ { source : interceptorPlugin , slug : "openai-runtime-package-interceptor" , pluginFile : "openai-runtime-package-interceptor/openai-interceptor.php" , activate : true , loadAs : "plugin" } ] ,
90110 ...lowered ,
91111 runtime_task : {
92112 ability : "wp-codebox/run-runtime-package" ,
93113 input : {
94114 schema : "wp-codebox/runtime-package-task/v1" ,
95- package : { slug : "flat-runtime-agent" , source : "public-external-package" , bootstrap : { encoding : "base64" , bytes : packageBytes . toString ( "base64" ) , digest : packageDigest } } ,
115+ package : { slug : "flat-runtime-agent" , source : "public-external-package" , external_source : { digest : packageDigest } , bootstrap : { encoding : "base64" , bytes : packageBytes . toString ( "base64" ) , digest : packageDigest } } ,
96116 workflow : { id : "agents/chat" } ,
97117 input : { prompt : "Return the mocked response." , provider : "openai" , model : "gpt-5.5" } ,
98118 artifact_declarations : [ ] ,
@@ -106,48 +126,52 @@ echo wp_json_encode( array( 'imported_slug' => $imports[0]['agent_slug'] ?? '',
106126 assert . deepEqual ( runtimeTask . input . input , { prompt : "Return the mocked response." , provider : "openai" , model : "gpt-5.5" } )
107127 assert . throws ( ( ) => validateRuntimeSourceModel ( { provider : "undeclared" , name : "gpt-5.5" } , materialized . descriptors . map ( ( descriptor : Record < string , unknown > ) => ( { ...descriptor , metadata : { providers : descriptor . providers } } ) ) ) , / n o t d e c l a r e d / , "an undeclared provider must be rejected before a chat turn is constructed" )
108128
109- recipe . workflow = {
110- steps : [ {
111- command : "wordpress.run-php" ,
112- args : [ "code=" + String . raw `$imports = wp_agent_import_runtime_bundles( array( array( 'source' => '/tmp/flat-runtime-agent.agent.json', 'slug' => 'flat-runtime-agent', 'on_conflict' => 'upgrade' ) ), array( 'owner_id' => 1 ) );
113- if ( ! is_array( $imports ) || empty( $imports[0]['success'] ) ) { throw new RuntimeException( 'Canonical importer did not import the flat package.' ); }
114- $GLOBALS['wp_codebox_provider_http_requests'] = array();
115- add_filter( 'agents_chat_permission', static fn() => true, 1000, 2 );
116- add_filter( 'pre_http_request', static function( $preempt, $args, $url ) {
117- $GLOBALS['wp_codebox_provider_http_requests'][] = array( 'url' => $url, 'body' => $args['body'] ?? null );
118- $body = str_contains( $url, '/models' )
119- ? array( 'object' => 'list', 'data' => array( array( 'id' => 'gpt-5.5', 'object' => 'model', 'created' => 0, 'owned_by' => 'openai' ) ) )
120- : array( 'id' => 'resp-fixture', 'object' => 'response', 'status' => 'completed', 'output' => array( array( 'id' => 'msg-fixture', 'type' => 'message', 'status' => 'completed', 'role' => 'assistant', 'content' => array( array( 'type' => 'output_text', 'text' => 'Mocked terminal reply', 'annotations' => array() ) ) ) ), 'usage' => array( 'input_tokens' => 1, 'output_tokens' => 1, 'total_tokens' => 2 ) );
121- return array(
122- 'headers' => array( 'content-type' => 'application/json' ),
123- 'body' => wp_json_encode( $body ),
124- 'response' => array( 'code' => 200, 'message' => 'OK' ),
125- 'cookies' => array(),
126- 'filename' => null,
127- );
128- }, 1000, 3 );
129- $chat = wp_get_ability( 'agents/chat' );
130- if ( ! is_object( $chat ) || ! method_exists( $chat, 'execute' ) ) { throw new RuntimeException( 'Actual Agents API agents/chat ability is unavailable.' ); }
131- $result = $chat->execute( array( 'agent' => 'flat-runtime-agent', 'message' => 'Return the mocked response.', 'provider' => 'openai', 'model' => 'gpt-5.5' ) );
132- if ( is_wp_error( $result ) ) { throw new RuntimeException( $result->get_error_code() . ': ' . $result->get_error_message() ); }
133- echo wp_json_encode( array( 'reply' => $result['reply'] ?? '', 'completed' => $result['completed'] ?? false, 'http_requests' => $GLOBALS['wp_codebox_provider_http_requests'] ) );` ] ,
134- } ] ,
129+ const readCapturedRequests = { command : "wordpress.run-php" , args : [ "code=echo is_readable( WP_CONTENT_DIR . '/openai-runtime-package-requests.json' ) ? file_get_contents( WP_CONTENT_DIR . '/openai-runtime-package-requests.json' ) : '[]';" ] }
130+ const runRuntimePackage = async ( mutateTask : ( task : Record < string , any > ) => void = ( ) => { } ) => {
131+ const candidate = structuredClone ( providerRecipe )
132+ const taskIndex = candidate . workflow . steps [ 0 ] . args . findIndex ( ( arg : string ) => arg . startsWith ( "runtime-task-json=" ) )
133+ const task = JSON . parse ( candidate . workflow . steps [ 0 ] . args [ taskIndex ] . slice ( "runtime-task-json=" . length ) )
134+ mutateTask ( task )
135+ candidate . workflow . steps [ 0 ] . args [ taskIndex ] = `runtime-task-json=${ JSON . stringify ( task ) } `
136+ candidate . workflow . steps . push ( readCapturedRequests )
137+ await writeFile ( recipePath , `${ JSON . stringify ( candidate ) } \n` )
138+ try {
139+ const result = await execFileAsync ( process . execPath , [ "packages/cli/dist/index.js" , "recipe-run" , "--recipe" , recipePath , "--json" ] , {
140+ cwd : process . cwd ( ) , timeout : 180_000 , env : { ...process . env , OPENAI_API_KEY : "dummy-key" } , maxBuffer : 2 * 1024 * 1024 ,
141+ } )
142+ return JSON . parse ( result . stdout )
143+ } catch ( error : any ) {
144+ return JSON . parse ( error . stdout )
145+ }
135146 }
136- await writeFile ( recipePath , `${ JSON . stringify ( recipe ) } \n` )
137- const chatResult = await execFileAsync ( process . execPath , [ "packages/cli/dist/index.js" , "recipe-run" , "--recipe" , recipePath , "--json" ] , {
138- cwd : process . cwd ( ) , timeout : 300_000 , env : { ...process . env , OPENAI_API_KEY : "dummy-key" } , maxBuffer : 2 * 1024 * 1024 ,
139- } )
140- const chatOutput = JSON . parse ( chatResult . stdout )
141- const chatStdout = chatOutput . executions ?. filter ( ( execution : { command ?: string } ) => execution . command === "wordpress.run-php" ) . at ( - 1 ) ?. stdout ?? ""
142- const chat = JSON . parse ( chatStdout )
143- assert . equal ( chat . reply , "Mocked terminal reply" , JSON . stringify ( chat ) )
144- assert . equal ( chat . completed , true )
145- const providerTurn = chat . http_requests . find ( ( request : { url : string } ) => request . url . endsWith ( "/responses" ) )
147+
148+ const runtimePackageOutput = await runRuntimePackage ( )
149+ const runtimeExecution = runtimePackageOutput . executions ?. find ( ( execution : { stdout ?: string } ) => execution . stdout ?. includes ( "agent_runtime" ) )
150+ const runtime = JSON . parse ( JSON . parse ( runtimeExecution ?. stdout ?? "{}" ) . output ?? "{}" )
151+ assert . equal ( runtime . agent_runtime ?. success , true , JSON . stringify ( runtimePackageOutput . executions ?. map ( ( execution : { command ?: string , stdout ?: string } ) => ( { command : execution . command , stdout : execution . stdout } ) ) ) )
152+ assert . equal ( runtime . agent_runtime . result . package . slug , "flat-runtime-agent" , "the generated runtime must execute the imported agent identity" )
153+ const requests = JSON . parse ( runtimePackageOutput . executions ?. filter ( ( execution : { command ?: string } ) => execution . command === "wordpress.run-php" ) . at ( - 1 ) ?. stdout ?? "[]" )
154+ const providerTurn = requests . find ( ( request : { url : string } ) => request . url . endsWith ( "/responses" ) )
146155 assert . ok ( providerTurn , "the OpenAI provider transport must execute through the local interception fixture" )
147156 assert . equal ( JSON . parse ( providerTurn . body ) . model , "gpt-5.5" , "the selected OpenAI model must reach the provider transport" )
148157 assert . match ( providerTurn . body , new RegExp ( packageInstruction ) , "the selected imported agent instruction must reach the provider transport" )
149- assert . doesNotMatch ( providerTurn . body , / W P C o d e b o x S a n d b o x | D e f a u l t s a n d b o x a g e n t / , "the generic sandbox instruction must not reach an imported-agent model request" )
158+ assert . match ( providerTurn . body , new RegExp ( packageTool ) , "the selected imported agent tool schema must reach the provider transport" )
159+ assert . doesNotMatch ( providerTurn . body , new RegExp ( genericSandboxInstruction ) , "the generic sandbox instruction must not reach an imported-agent model request" )
160+ assert . doesNotMatch ( providerTurn . body , new RegExp ( genericSandboxTool ) , "the generic sandbox tool must not reach an imported-agent model request" )
150161 assert . match ( providerTurn . url , / ^ h t t p s : \/ \/ a p i \. o p e n a i \. c o m \/ v 1 \/ r e s p o n s e s $ / , "the selected OpenAI provider must reach its provider transport" )
162+
163+ for ( const [ label , mutateTask ] of [
164+ [ "package" , ( task : Record < string , any > ) => { task . input . package . slug = "spoofed-agent" } ] ,
165+ [ "chat" , ( task : Record < string , any > ) => { task . input . input . agent = "spoofed-agent" } ] ,
166+ [ "metadata" , ( task : Record < string , any > ) => { task . input . metadata = { imported_agent : { slug : "spoofed-agent" } } } ] ,
167+ ] as const ) {
168+ const spoofOutput = await runRuntimePackage ( mutateTask )
169+ const spoofExecution = spoofOutput . executions ?. find ( ( execution : { stdout ?: string } ) => execution . stdout ?. includes ( "agent_runtime" ) )
170+ const spoofRuntime = JSON . parse ( JSON . parse ( spoofExecution ?. stdout ?? "{}" ) . output ?? "{}" )
171+ assert . equal ( spoofRuntime . agent_runtime ?. success , false , `${ label } agent identity spoof must fail` )
172+ const spoofRequests = JSON . parse ( spoofOutput . executions ?. filter ( ( execution : { command ?: string } ) => execution . command === "wordpress.run-php" ) . at ( - 1 ) ?. stdout ?? "[]" )
173+ assert . equal ( spoofRequests . length , 0 , `${ label } agent identity spoof must fail before an outbound provider request` )
174+ }
151175 await rm ( materialized . root , { recursive : true , force : true } )
152176 await assert . rejects ( access ( privatePackage ) , / E N O E N T / , "private source package must be removed with its materialization root" )
153177 console . log ( `runtime sources Playground integration ok: ${ JSON . stringify ( checks ) } ` )
0 commit comments