Skip to content

Commit 1d3623b

Browse files
committed
Test imported runtime package selection
1 parent 5c2ed4f commit 1d3623b

2 files changed

Lines changed: 69 additions & 47 deletions

File tree

.github/workflows/agent-task-contracts.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ jobs:
5555
- run: npm run build
5656
- run: npm run test:agent-task-contracts
5757
- run: npm run test:runtime-sources-playground-integration
58-
env:
59-
WP_CODEBOX_RUN_NETWORK_INTEGRATION: "1"
6058
- run: npm run test:redaction
6159
- run: npm run test:production-boundary-enforcement
6260
- run: npm run test:runtime-tool-policy

tests/runtime-sources-playground-integration.test.ts

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import assert from "node:assert/strict"
22
import { execFile } from "node:child_process"
33
import { 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"
55
import { tmpdir } from "node:os"
66
import { join } from "node:path"
77
import { promisify } from "node:util"
@@ -11,11 +11,6 @@ import { normalizeTaskInput } from "../packages/runtime-core/src/task-input.js"
1111

1212
const 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-
1914
const fixture = JSON.parse(await readFile(new URL("../fixtures/agent-task-runtime-sources-run-29299109269.json", import.meta.url), "utf8"))
2015
const policy = parseExternalPackageSourcePolicy(JSON.stringify({
2116
version: 1,
@@ -28,7 +23,31 @@ const root = await mkdtemp(join(tmpdir(), "wp-codebox-runtime-sources-playground
2823
try {
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",
@@ -38,7 +57,7 @@ try {
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 } }))), /not declared/, "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, /WP Codebox Sandbox|Default sandbox agent/, "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, /^https:\/\/api\.openai\.com\/v1\/responses$/, "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), /ENOENT/, "private source package must be removed with its materialization root")
153177
console.log(`runtime sources Playground integration ok: ${JSON.stringify(checks)}`)

0 commit comments

Comments
 (0)