diff --git a/.github/workflows/agent-task-contracts.yml b/.github/workflows/agent-task-contracts.yml index ff1df5df4..6407cec54 100644 --- a/.github/workflows/agent-task-contracts.yml +++ b/.github/workflows/agent-task-contracts.yml @@ -55,8 +55,6 @@ jobs: - run: npm run build - run: npm run test:agent-task-contracts - run: npm run test:runtime-sources-playground-integration - env: - WP_CODEBOX_RUN_NETWORK_INTEGRATION: "1" - run: npm run test:redaction - run: npm run test:production-boundary-enforcement - run: npm run test:runtime-tool-policy diff --git a/packages/cli/src/agent-code.ts b/packages/cli/src/agent-code.ts index 41d60ad06..f52a60729 100644 --- a/packages/cli/src/agent-code.ts +++ b/packages/cli/src/agent-code.ts @@ -110,11 +110,8 @@ $sandbox_external_runtime_package_import = wp_codebox_import_external_runtime_ag $sandbox_stack['external_runtime_package_import'] = $sandbox_external_runtime_package_import; if (!empty($sandbox_external_runtime_package_import['success']) && empty($sandbox_external_runtime_package_import['skipped'])) { $sandbox_imported_agent = (string) ($sandbox_external_runtime_package_import['identity']['slug'] ?? ''); - if (is_array($sandbox_runtime_task) && '' !== $sandbox_imported_agent) { - $sandbox_runtime_task['input']['package']['slug'] = $sandbox_imported_agent; - $sandbox_runtime_task['input']['package']['bootstrap_imported'] = true; - $sandbox_runtime_task['input']['metadata']['imported_agent'] = array('slug' => $sandbox_imported_agent); - } + $sandbox_external_runtime_package_import = wp_codebox_bind_external_runtime_package_identity($sandbox_runtime_task, $sandbox_imported_agent, $sandbox_external_runtime_package_import); + $sandbox_stack['external_runtime_package_import'] = $sandbox_external_runtime_package_import; } add_filter('agents_chat_runtime_principal_permission', static function (bool $allowed, $principal, array $input): bool { @@ -263,6 +260,31 @@ function wp_codebox_import_external_runtime_agent_package(array $runtime_task): } } +function wp_codebox_bind_external_runtime_package_identity(&$runtime_task, string $agent_slug, array $import): array { + if (!is_array($runtime_task) || '' === $agent_slug) { + return array('success' => false, 'error' => array('code' => 'wp_codebox_external_runtime_package_identity_missing', 'message' => 'Canonical runtime package import did not produce one agent identity.')); + } + $task_input = is_array($runtime_task['input'] ?? null) ? $runtime_task['input'] : null; + $package = is_array($task_input['package'] ?? null) ? $task_input['package'] : null; + $chat_input = is_array($task_input['input'] ?? null) ? $task_input['input'] : array(); + $metadata = is_array($task_input['metadata'] ?? null) ? $task_input['metadata'] : array(); + $requested_package_slug = is_array($package) ? (string) ($package['slug'] ?? '') : ''; + $requested_agent_slug = is_array($chat_input) ? (string) ($chat_input['agent'] ?? '') : ''; + $metadata_agent_slug = is_array($metadata['imported_agent'] ?? null) ? (string) ($metadata['imported_agent']['slug'] ?? '') : ''; + if (!is_array($package) || ('' !== $requested_package_slug && !hash_equals($agent_slug, $requested_package_slug)) || ('' !== $requested_agent_slug && !hash_equals($agent_slug, $requested_agent_slug)) || ('' !== $metadata_agent_slug && !hash_equals($agent_slug, $metadata_agent_slug))) { + return array('success' => false, 'error' => array('code' => 'wp_codebox_external_runtime_package_identity_mismatch', 'message' => 'Runtime package execution must use the single agent identity verified during canonical import.')); + } + $package['slug'] = $agent_slug; + $package['bootstrap_imported'] = true; + $chat_input['agent'] = $agent_slug; + $metadata['imported_agent'] = array('slug' => $agent_slug); + $task_input['package'] = $package; + $task_input['input'] = $chat_input; + $task_input['metadata'] = $metadata; + $runtime_task['input'] = $task_input; + return $import; +} + function wp_codebox_json_encode_agent_runtime_payload($value): string { $json = wp_json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE); if (false !== $json) { diff --git a/packages/wordpress-plugin/src/class-wp-codebox-runtime-package-executor.php b/packages/wordpress-plugin/src/class-wp-codebox-runtime-package-executor.php index 3f2013217..e083518f8 100644 --- a/packages/wordpress-plugin/src/class-wp-codebox-runtime-package-executor.php +++ b/packages/wordpress-plugin/src/class-wp-codebox-runtime-package-executor.php @@ -41,15 +41,17 @@ public function run( array $task ): array|WP_Error { return $imports; } - $agent_slug = $this->imported_agent_slug( $task ); + $agent_slug = $this->imported_agent_slug( $task, $imports ); if ( is_wp_error( $agent_slug ) ) { return $agent_slug; } - if ( '' !== $agent_slug ) { - $input = is_array( $task['input'] ?? null ) ? $task['input'] : array(); - $input['agent'] = $agent_slug; - $task['input'] = $input; + $input = is_array( $task['input'] ?? null ) ? $task['input'] : array(); + $requested_agent = $this->string_value( $input['agent'] ?? '' ); + if ( '' !== $requested_agent && ! hash_equals( $agent_slug, $requested_agent ) ) { + return new WP_Error( 'wp_codebox_runtime_package_agent_identity_mismatch', 'Runtime package execution must use the agent identity verified during import.', array( 'status' => 400 ) ); } + $input['agent'] = $agent_slug; + $task['input'] = $input; $result = $this->execute_workflow( $task ); if ( is_wp_error( $result ) ) { @@ -118,9 +120,6 @@ private function execute_workflow( array $task ): array|WP_Error { $ability = 'agents/chat'; $input = is_array( $task['input'] ?? null ) ? $task['input'] : array(); $package = is_array( $task['package'] ?? null ) ? $task['package'] : array(); - if ( ! isset( $input['agent'] ) && '' !== $this->string_value( $package['slug'] ?? '' ) ) { - $input['agent'] = $this->string_value( $package['slug'] ); - } if ( ! isset( $input['message'] ) ) { $input['message'] = $this->string_value( $input['prompt'] ?? '' ); } @@ -149,16 +148,22 @@ private function execute_workflow( array $task ): array|WP_Error { return $result; } - /** @param array $task Runtime package task. @return string|WP_Error */ - private function imported_agent_slug( array $task ): string|WP_Error { + /** @param array $task Runtime package task. @param array> $imports Import results. @return string|WP_Error */ + private function imported_agent_slug( array $task, array $imports ): string|WP_Error { $package = is_array( $task['package'] ?? null ) ? $task['package'] : array(); + $imported_slugs = array_values( array_unique( array_filter( array_map( static fn( mixed $import ): string => is_array( $import ) && ! empty( $import['success'] ) ? trim( (string) ( $import['agent_slug'] ?? '' ) ) : '', $imports ) ) ) ); + if ( 1 !== count( $imports ) || 1 !== count( $imported_slugs ) ) { + return new WP_Error( 'wp_codebox_runtime_package_imported_agent_unresolved', 'Runtime package import must resolve exactly one agent identity.', array( 'status' => 400 ) ); + } + $slug = $imported_slugs[0]; + if ( ! hash_equals( $slug, $this->string_value( $package['slug'] ?? '' ) ) || ! function_exists( 'wp_get_agent' ) || ! wp_get_agent( $slug ) ) { + return new WP_Error( 'wp_codebox_runtime_package_imported_agent_unresolved', 'The imported runtime package agent identity did not resolve exactly.', array( 'status' => 400 ) ); + } if ( empty( $package['bootstrap_imported'] ) ) { - return ''; + return $slug; } - $metadata = is_array( $task['metadata'] ?? null ) ? $task['metadata'] : array(); - $slug = $this->string_value( $metadata['imported_agent']['slug'] ?? '' ); $bootstrap = is_array( $GLOBALS['wp_codebox_private_runtime_package_import'] ?? null ) ? $GLOBALS['wp_codebox_private_runtime_package_import'] : array(); - if ( '' === $slug || ! hash_equals( $slug, $this->string_value( $bootstrap['identity']['slug'] ?? '' ) ) || ! function_exists( 'wp_get_agent' ) || ! wp_get_agent( $slug ) ) { + if ( ! hash_equals( $slug, $this->string_value( $bootstrap['identity']['slug'] ?? '' ) ) ) { return new WP_Error( 'wp_codebox_runtime_package_imported_agent_unresolved', 'The imported runtime package agent identity did not resolve exactly.', array( 'status' => 400 ) ); } diff --git a/scripts/php-runtime-package-public-contract-smoke.php b/scripts/php-runtime-package-public-contract-smoke.php index 40646d97d..63d90003f 100644 --- a/scripts/php-runtime-package-public-contract-smoke.php +++ b/scripts/php-runtime-package-public-contract-smoke.php @@ -164,13 +164,18 @@ function wp_get_ability( string $name ): ?WP_Codebox_Runtime_Package_Smoke_Abili ); $GLOBALS['wp_codebox_runtime_package_registered_agents']['store-idea-agent'] = true; $bootstrap_task = $wpsg_like_task; -$bootstrap_task['package']['slug'] = 'caller-controlled-agent'; $bootstrap_task['package']['bootstrap_imported'] = true; -$bootstrap_task['input']['agent'] = 'caller-controlled-agent'; +$bootstrap_task['input']['agent'] = 'store-idea-agent'; $bootstrap = WP_Codebox_Abilities::run_runtime_package( $bootstrap_task + array( 'runtime_provider' => 'codebox-runtime-package' ) ); assert( ! is_wp_error( $bootstrap ) ); assert( 'store-idea-agent' === $GLOBALS['wp_codebox_runtime_package_smoke_input']['agent'] ); +$spoofed_bootstrap_task = $bootstrap_task; +$spoofed_bootstrap_task['input']['agent'] = 'caller-controlled-agent'; +$spoofed_bootstrap = WP_Codebox_Abilities::run_runtime_package( $spoofed_bootstrap_task + array( 'runtime_provider' => 'codebox-runtime-package' ) ); +assert( is_wp_error( $spoofed_bootstrap ) ); +assert( 'wp_codebox_runtime_package_agent_identity_mismatch' === $spoofed_bootstrap->get_error_code() ); + file_put_contents( $staged_bundle_file, "tampered\n" ); $tampered = WP_Codebox_Abilities::run_runtime_package( $wpsg_like_task + array( 'runtime_provider' => 'codebox-runtime-package' ) ); assert( is_wp_error( $tampered ) ); diff --git a/tests/agent-no-data-machine-loop.test.ts b/tests/agent-no-data-machine-loop.test.ts index 7f8d4c263..14b35550f 100644 --- a/tests/agent-no-data-machine-loop.test.ts +++ b/tests/agent-no-data-machine-loop.test.ts @@ -175,8 +175,11 @@ assert.match(sandboxAgentCode, /'model' => \$configured_model/) const docsAgentDirectory = process.env.DOCS_AGENT_DIR const publicPackageBytes = docsAgentDirectory ? await readFile(join(docsAgentDirectory, "bundles", "technical-docs-agent", "native", "technical-docs-maintenance-agent.agent.json")) - : await readFile(new URL("./fixtures/external-native-package/flat-agent.agent.json", import.meta.url)) + : await readFile(new URL("./fixtures/external-native-package/technical-bootstrap-agent.agent.json", import.meta.url)) const publicAgentSlug = canonicalExternalNativeAgentIdentity(publicPackageBytes).slug +const skillsPackageBytes = await readFile(new URL("./fixtures/external-native-package/skills-agent.agent.json", import.meta.url)) +assert.deepEqual(canonicalExternalNativeAgentIdentity(skillsPackageBytes), { slug: "skills-agent" }) +assert.deepEqual(canonicalExternalNativeAgentIdentity(publicPackageBytes), { slug: docsAgentDirectory ? "technical-docs-maintenance-agent" : "technical-bootstrap-agent" }) const publicPackageDigest = `sha256-bytes-v1:${await import("node:crypto").then(({ createHash }) => createHash("sha256").update(publicPackageBytes).digest("hex"))}` const bootstrapCode = await resolveSandboxTaskCode({ task: "Say hello", @@ -184,7 +187,7 @@ const bootstrapCode = await resolveSandboxTaskCode({ runtimeTask: { ability: "wp-codebox/run-runtime-package", input: { - package: { slug: "caller-controlled-agent", source: "public-external-package", bootstrap: { encoding: "base64", bytes: publicPackageBytes.toString("base64"), digest: publicPackageDigest } }, + package: { slug: publicAgentSlug, source: "public-external-package", bootstrap: { encoding: "base64", bytes: publicPackageBytes.toString("base64"), digest: publicPackageDigest } }, }, }, sandboxToolPolicy: { schema: "wp-codebox/sandbox-tool-policy/v1", version: 1, tools: [] }, @@ -211,6 +214,30 @@ assert.match(bootstrapCode, /wp_codebox_import_external_runtime_agent_package/) assert.ok(bootstrapCode.indexOf("wp_codebox_import_external_runtime_agent_package") < bootstrapCode.indexOf("wp_codebox_resolve_runtime_task_ability")) assert.match(bootstrapCode, /base64_decode\(\$bootstrap\['bytes'\], true\)/) +const spoofedBootstrapCode = await resolveSandboxTaskCode({ + task: "Say hello", + agent: "wp-codebox-sandbox", + runtimeTask: { + ability: "wp-codebox/run-runtime-package", + input: { + package: { slug: publicAgentSlug, source: "public-external-package", bootstrap: { encoding: "base64", bytes: publicPackageBytes.toString("base64"), digest: publicPackageDigest } }, + input: { agent: "caller-controlled-agent" }, + }, + }, + sandboxToolPolicy: { schema: "wp-codebox/sandbox-tool-policy/v1", version: 1, tools: [] }, +}) +const spoofedBootstrapOutput = execFileSync("php", ["-r", `${phpPreamble} +function wp_agent_import_runtime_bundles($bundles, $options) { + $package = json_decode((string) file_get_contents((string) ($bundles[0]['source'] ?? '')), true); + $slug = $package['agent']['agent_slug'] ?? ''; + WP_Agents_Registry::get_instance()->register($slug, array('source' => 'canonical-importer')); + return array(array('success' => true, 'agent_slug' => $slug)); +} +${spoofedBootstrapCode}`], { encoding: "utf8" }) +const spoofedBootstrap = JSON.parse(spoofedBootstrapOutput) as { agent_runtime?: { success?: boolean, error?: { code?: string } } } +assert.equal(spoofedBootstrap.agent_runtime?.success, false) +assert.equal(spoofedBootstrap.agent_runtime?.error?.code, "wp_codebox_external_runtime_package_identity_mismatch") + const failedImportOutput = execFileSync("php", ["-r", `${phpPreamble} function wp_agent_import_runtime_bundles($bundles, $options) { $GLOBALS['external_source'] = $bundles[0]['source']; return array(array('success' => false)); } ${bootstrapCode}`], { diff --git a/tests/fixtures/external-native-package/skills-agent.agent.json b/tests/fixtures/external-native-package/skills-agent.agent.json new file mode 100644 index 000000000..3d898aeb2 --- /dev/null +++ b/tests/fixtures/external-native-package/skills-agent.agent.json @@ -0,0 +1,15 @@ +{ + "schema_version": 1, + "bundle_slug": "skills-agent", + "bundle_version": "1.0.0", + "agent": { + "agent_slug": "skills-agent", + "agent_name": "Skills Agent", + "description": "Fixture for a package that supplies skills through enabled tools.", + "agent_config": { + "instructions": "SKILLS PACKAGE INSTRUCTION", + "enabled_tools": ["workspace_read", "workspace_write"], + "modes": ["chat", "task"] + } + } +} diff --git a/tests/fixtures/external-native-package/technical-bootstrap-agent.agent.json b/tests/fixtures/external-native-package/technical-bootstrap-agent.agent.json new file mode 100644 index 000000000..7290c73cf --- /dev/null +++ b/tests/fixtures/external-native-package/technical-bootstrap-agent.agent.json @@ -0,0 +1,16 @@ +{ + "schema_version": 1, + "bundle_slug": "technical-bootstrap-agent", + "bundle_version": "1.0.0", + "agent": { + "agent_slug": "technical-bootstrap-agent", + "agent_name": "Technical Bootstrap Agent", + "description": "Fixture for a technical documentation bootstrap package.", + "agent_config": { + "instructions": "TECHNICAL BOOTSTRAP PACKAGE INSTRUCTION", + "enabled_tools": ["workspace_ls", "workspace_read", "workspace_write"], + "tool_call_rules": [{"id": "require-write", "require_tool_use": true, "require_one_of": ["workspace_write"]}], + "modes": ["chat"] + } + } +} diff --git a/tests/runtime-sources-playground-integration.test.ts b/tests/runtime-sources-playground-integration.test.ts index a65262a0a..639a8fdd9 100644 --- a/tests/runtime-sources-playground-integration.test.ts +++ b/tests/runtime-sources-playground-integration.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict" import { execFile } from "node:child_process" import { createHash } from "node:crypto" -import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises" +import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" import { promisify } from "node:util" @@ -11,11 +11,6 @@ import { normalizeTaskInput } from "../packages/runtime-core/src/task-input.js" const execFileAsync = promisify(execFile) -if (process.env.WP_CODEBOX_RUN_NETWORK_INTEGRATION !== "1") { - console.log("runtime sources Playground integration skipped: set WP_CODEBOX_RUN_NETWORK_INTEGRATION=1; CI enables this pinned-public-source test") - process.exit(0) -} - const fixture = JSON.parse(await readFile(new URL("../fixtures/agent-task-runtime-sources-run-29299109269.json", import.meta.url), "utf8")) const policy = parseExternalPackageSourcePolicy(JSON.stringify({ version: 1, @@ -28,7 +23,45 @@ const root = await mkdtemp(join(tmpdir(), "wp-codebox-runtime-sources-playground try { const materialized = await materializeRuntimeSources(fixture.runtime_sources, { policy, tempRoot: root, forbiddenRoots: [join(root, "artifacts")] }) const privatePackage = join(materialized.root, "flat-runtime-agent.agent.json") - await writeFile(privatePackage, '{"schema_version":1,"bundle_slug":"flat-runtime-agent","agent":{"agent_slug":"flat-runtime-agent"}}\n') + const interceptorPlugin = join(materialized.root, "openai-interceptor") + const packageInstruction = "PACKAGE SYSTEM INSTRUCTION: selected imported agent" + const packageTool = "workspace_read" + const genericSandboxInstruction = "Default sandbox agent" + const genericSandboxTool = "deny-all" + await mkdir(interceptorPlugin, { recursive: true }) + await writeFile(join(interceptorPlugin, "openai-interceptor.php"), ` $url, 'body' => $args['body'] ?? null ); + file_put_contents( $capture, wp_json_encode( $requests ) ); + $body = str_ends_with( $url, '/models' ) + ? array( 'object' => 'list', 'data' => array( array( 'id' => 'gpt-5.5', 'object' => 'model', 'created' => 0, 'owned_by' => 'openai' ) ) ) + : 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 ) ); + return array( 'headers' => array( 'content-type' => 'application/json' ), 'body' => wp_json_encode( $body ), 'response' => array( 'code' => 200, 'message' => 'OK' ), 'cookies' => array(), 'filename' => null ); +}, 1000, 3 ); +`) + await writeFile(privatePackage, JSON.stringify({ + schema_version: 1, + bundle_slug: "flat-runtime-agent", + agent: { + agent_slug: "flat-runtime-agent", + agent_name: "Flat Runtime Agent", + description: "Playground imported-agent selection fixture.", + agent_config: { + instructions: packageInstruction, + enabled_tools: [packageTool], + modes: ["chat"], + }, + }, + }) + "\n") const lowered = materialized.lowered.reduce((input: Record, source: Record) => { for (const [key, entries] of Object.entries(source)) input[key] = [...(input[key] ?? []), ...entries] return input @@ -73,12 +106,13 @@ echo wp_json_encode( array( 'imported_slug' => $imports[0]['agent_slug'] ?? '', provider: "openai", model: "gpt-5.5", runtime_env: { OPENAI_API_KEY: "dummy-key" }, + extra_plugins: [{ source: interceptorPlugin, slug: "openai-runtime-package-interceptor", pluginFile: "openai-runtime-package-interceptor/openai-interceptor.php", activate: true, loadAs: "plugin" }], ...lowered, runtime_task: { ability: "wp-codebox/run-runtime-package", input: { schema: "wp-codebox/runtime-package-task/v1", - package: { slug: "flat-runtime-agent", source: "public-external-package", bootstrap: { encoding: "base64", bytes: packageBytes.toString("base64"), digest: packageDigest } }, + package: { slug: "flat-runtime-agent", source: "public-external-package", external_source: { digest: packageDigest }, bootstrap: { encoding: "base64", bytes: packageBytes.toString("base64"), digest: packageDigest } }, workflow: { id: "agents/chat" }, input: { prompt: "Return the mocked response.", provider: "openai", model: "gpt-5.5" }, artifact_declarations: [], @@ -92,46 +126,52 @@ echo wp_json_encode( array( 'imported_slug' => $imports[0]['agent_slug'] ?? '', assert.deepEqual(runtimeTask.input.input, { prompt: "Return the mocked response.", provider: "openai", model: "gpt-5.5" }) assert.throws(() => validateRuntimeSourceModel({ provider: "undeclared", name: "gpt-5.5" }, materialized.descriptors.map((descriptor: Record) => ({ ...descriptor, metadata: { providers: descriptor.providers } }))), /not declared/, "an undeclared provider must be rejected before a chat turn is constructed") - recipe.workflow = { - steps: [{ - command: "wordpress.run-php", - 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 ) ); -if ( ! is_array( $imports ) || empty( $imports[0]['success'] ) ) { throw new RuntimeException( 'Canonical importer did not import the flat package.' ); } -$GLOBALS['wp_codebox_provider_http_requests'] = array(); -add_filter( 'agents_chat_permission', static fn() => true, 1000, 2 ); -add_filter( 'pre_http_request', static function( $preempt, $args, $url ) { - $GLOBALS['wp_codebox_provider_http_requests'][] = array( 'url' => $url, 'body' => $args['body'] ?? null ); - $body = str_contains( $url, '/models' ) - ? array( 'object' => 'list', 'data' => array( array( 'id' => 'gpt-5.5', 'object' => 'model', 'created' => 0, 'owned_by' => 'openai' ) ) ) - : 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 ) ); - return array( - 'headers' => array( 'content-type' => 'application/json' ), - 'body' => wp_json_encode( $body ), - 'response' => array( 'code' => 200, 'message' => 'OK' ), - 'cookies' => array(), - 'filename' => null, - ); -}, 1000, 3 ); -$chat = wp_get_ability( 'agents/chat' ); -if ( ! is_object( $chat ) || ! method_exists( $chat, 'execute' ) ) { throw new RuntimeException( 'Actual Agents API agents/chat ability is unavailable.' ); } -$result = $chat->execute( array( 'agent' => 'flat-runtime-agent', 'message' => 'Return the mocked response.', 'provider' => 'openai', 'model' => 'gpt-5.5' ) ); -if ( is_wp_error( $result ) ) { throw new RuntimeException( $result->get_error_code() . ': ' . $result->get_error_message() ); } -echo wp_json_encode( array( 'reply' => $result['reply'] ?? '', 'completed' => $result['completed'] ?? false, 'http_requests' => $GLOBALS['wp_codebox_provider_http_requests'] ) );`], - }], + 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' ) : '[]';"] } + const runRuntimePackage = async (mutateTask: (task: Record) => void = () => {}) => { + const candidate = structuredClone(providerRecipe) + const taskIndex = candidate.workflow.steps[0].args.findIndex((arg: string) => arg.startsWith("runtime-task-json=")) + const task = JSON.parse(candidate.workflow.steps[0].args[taskIndex].slice("runtime-task-json=".length)) + mutateTask(task) + candidate.workflow.steps[0].args[taskIndex] = `runtime-task-json=${JSON.stringify(task)}` + candidate.workflow.steps.push(readCapturedRequests) + await writeFile(recipePath, `${JSON.stringify(candidate)}\n`) + try { + const result = await execFileAsync(process.execPath, ["packages/cli/dist/index.js", "recipe-run", "--recipe", recipePath, "--json"], { + cwd: process.cwd(), timeout: 180_000, env: { ...process.env, OPENAI_API_KEY: "dummy-key" }, maxBuffer: 2 * 1024 * 1024, + }) + return JSON.parse(result.stdout) + } catch (error: any) { + return JSON.parse(error.stdout) + } } - await writeFile(recipePath, `${JSON.stringify(recipe)}\n`) - const chatResult = await execFileAsync(process.execPath, ["packages/cli/dist/index.js", "recipe-run", "--recipe", recipePath, "--json"], { - cwd: process.cwd(), timeout: 300_000, env: { ...process.env, OPENAI_API_KEY: "dummy-key" }, maxBuffer: 2 * 1024 * 1024, - }) - const chatOutput = JSON.parse(chatResult.stdout) - const chatStdout = chatOutput.executions?.filter((execution: { command?: string }) => execution.command === "wordpress.run-php").at(-1)?.stdout ?? "" - const chat = JSON.parse(chatStdout) - assert.equal(chat.reply, "Mocked terminal reply", JSON.stringify(chat)) - assert.equal(chat.completed, true) - const providerTurn = chat.http_requests.find((request: { url: string }) => request.url.endsWith("/responses")) + + const runtimePackageOutput = await runRuntimePackage() + const runtimeExecution = runtimePackageOutput.executions?.find((execution: { stdout?: string }) => execution.stdout?.includes("agent_runtime")) + const runtime = JSON.parse(JSON.parse(runtimeExecution?.stdout ?? "{}").output ?? "{}") + assert.equal(runtime.agent_runtime?.success, true, JSON.stringify(runtimePackageOutput.executions?.map((execution: { command?: string, stdout?: string }) => ({ command: execution.command, stdout: execution.stdout })))) + assert.equal(runtime.agent_runtime.result.package.slug, "flat-runtime-agent", "the generated runtime must execute the imported agent identity") + const requests = JSON.parse(runtimePackageOutput.executions?.filter((execution: { command?: string }) => execution.command === "wordpress.run-php").at(-1)?.stdout ?? "[]") + const providerTurn = requests.find((request: { url: string }) => request.url.endsWith("/responses")) assert.ok(providerTurn, "the OpenAI provider transport must execute through the local interception fixture") assert.equal(JSON.parse(providerTurn.body).model, "gpt-5.5", "the selected OpenAI model must reach the provider transport") + assert.match(providerTurn.body, new RegExp(packageInstruction), "the selected imported agent instruction must reach the provider transport") + assert.match(providerTurn.body, new RegExp(packageTool), "the selected imported agent tool schema must reach the provider transport") + assert.doesNotMatch(providerTurn.body, new RegExp(genericSandboxInstruction), "the generic sandbox instruction must not reach an imported-agent model request") + assert.doesNotMatch(providerTurn.body, new RegExp(genericSandboxTool), "the generic sandbox tool must not reach an imported-agent model request") assert.match(providerTurn.url, /^https:\/\/api\.openai\.com\/v1\/responses$/, "the selected OpenAI provider must reach its provider transport") + + for (const [label, mutateTask] of [ + ["package", (task: Record) => { task.input.package.slug = "spoofed-agent" }], + ["chat", (task: Record) => { task.input.input.agent = "spoofed-agent" }], + ["metadata", (task: Record) => { task.input.metadata = { imported_agent: { slug: "spoofed-agent" } } }], + ] as const) { + const spoofOutput = await runRuntimePackage(mutateTask) + const spoofExecution = spoofOutput.executions?.find((execution: { stdout?: string }) => execution.stdout?.includes("agent_runtime")) + const spoofRuntime = JSON.parse(JSON.parse(spoofExecution?.stdout ?? "{}").output ?? "{}") + assert.equal(spoofRuntime.agent_runtime?.success, false, `${label} agent identity spoof must fail`) + const spoofRequests = JSON.parse(spoofOutput.executions?.filter((execution: { command?: string }) => execution.command === "wordpress.run-php").at(-1)?.stdout ?? "[]") + assert.equal(spoofRequests.length, 0, `${label} agent identity spoof must fail before an outbound provider request`) + } await rm(materialized.root, { recursive: true, force: true }) await assert.rejects(access(privatePackage), /ENOENT/, "private source package must be removed with its materialization root") console.log(`runtime sources Playground integration ok: ${JSON.stringify(checks)}`)