diff --git a/README.md b/README.md index ff5e2f8..4204c3d 100644 --- a/README.md +++ b/README.md @@ -645,7 +645,7 @@ Redaction is part of the contract. The default `redaction: "safe"` avoids user identity fields and requires explicit allowlists for options and users. `redaction: "none"` is an opt-in export mode for trusted callers that need identity fields. Consumers should verify artifact SHA-256 values before replay and should branch -on `schema` and `version` instead of projecting per-consumer legacy state shapes. +on `schema` and `version` instead of projecting per-consumer state shapes. Replay is bounded to the generic runtime contract. A consumer can replay a step by creating a compatible backend runtime, applying the same mounts/artifact @@ -696,7 +696,7 @@ The canonical schema source is `createWorkspaceRecipeJsonSchema()` in `@automatt npm run wp-codebox -- schema recipe --json ``` -The schema covers `runtime`, `inputs.mounts`, `inputs.workspaces`, `inputs.extra_plugins` / `inputs.extraPlugins`, `inputs.secretEnv`, `workflow.steps`, and `artifacts`. +The schema covers `runtime`, `inputs.mounts`, `inputs.workspaces`, `inputs.extra_plugins`, `inputs.secretEnv`, `workflow.steps`, and `artifacts`. ### `run` @@ -734,7 +734,7 @@ Supported runtime commands today: For synthetic web performance investigations, enable `capture=performance` and use `profile=low-end-mobile-slow-4g` or `throttle=low-end-mobile-slow-4g` to apply a deterministic Chromium CPU/network profile before navigation. Performance artifacts include navigation timing, TTFB, FCP, LCP, resource timing, long tasks, CLS, and DOM metrics. Budget assertions may use promoted `browser_*` metric names or short web-performance aliases, for example `assert=lcp_ms<=2500`, `assert=fcp_ms<=1800`, `assert=ttfb_ms<=800`, and `assert=nav_duration_ms<=5000`. -`wordpress.browser-actions` drives the preview with an ordered interaction script so Codebox can prove a plugin still *works* under interaction, not just that it renders. Pass the script as `steps-json=` (inline JSON, or `@` to read it from a file); the legacy `actions-json=` shape is still accepted and normalized to steps. Each step is a thin, stable mapping over a Playwright locator action — this is not a test-runner DSL. +`wordpress.browser-actions` drives the preview with an ordered interaction script so Codebox can prove a plugin still *works* under interaction, not just that it renders. Pass the script as `steps-json=` (inline JSON, or `@` to read it from a file). Each step is a thin, stable mapping over a Playwright locator action — this is not a test-runner DSL. Step kinds: `navigate` (`url`, optional `waitFor=domcontentloaded|load|networkidle`), `click`/`hover` (`selector` or `text`), `fill`/`type` (`selector`, `value`), `press` (`key`, optional `selector`), `drag` (`from` selector, `to` as `{ "selector": ... }` or `{ "x": n, "y": n }`), `select` (`selector`, `value` or `values`), `waitFor` (`selector` or `waitFor=domcontentloaded|load|networkidle|duration|selector:`), `evaluate` (`expression`, optional `assert` to deep-equal the result), `expect` (`selector`, optional `state=visible|hidden|attached|detached|enabled|disabled|checked|unchecked|editable`), and `screenshot` (optional `name` for a named capture). Every step may set its own `timeout=s`; the command also accepts a global `step-timeout=s` (per step) and `timeout=s` (total-script budget). Both are bounded and deterministic — the run stops cleanly on the first failing step, with no silent partial success. @@ -881,7 +881,7 @@ External sources are explicit and CI-safe. WP Codebox validates URL-shaped sourc ```json { "inputs": { - "extraPlugins": [ + "extra_plugins": [ { "source": "https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip", "pluginFile": "bbpress/bbpress.php", diff --git a/docs/portable-wp-codebox.md b/docs/portable-wp-codebox.md index b2d2dd5..9e820aa 100644 --- a/docs/portable-wp-codebox.md +++ b/docs/portable-wp-codebox.md @@ -83,7 +83,7 @@ Recipes can declare generic heavyweight plugin runtime needs with `inputs.plugin "schema": "wp-codebox/workspace-recipe/v1", "runtime": { "wp": "7.0" }, "inputs": { - "extraPlugins": [ + "extra_plugins": [ { "source": "./plugins/page-builder", "slug": "page-builder", @@ -119,9 +119,9 @@ Recipes can declare generic heavyweight plugin runtime needs with `inputs.plugin Execution order is stable: -1. Mount recipe workspaces and `extraPlugins`. +1. Mount recipe workspaces and `extra_plugins`. 2. Install recipe-declared mu-plugin loaders. -3. Activate `extraPlugins` in recipe order. +3. Activate `extra_plugins` in recipe order. 4. Run `pluginRuntime.setup` steps in declared order. 5. Run `pluginRuntime.healthProbes` in declared order. 6. Import site seeds, then run normal workflow steps. diff --git a/docs/transfer-readiness-checklist.md b/docs/transfer-readiness-checklist.md index 0550b7e..b1fd03a 100644 --- a/docs/transfer-readiness-checklist.md +++ b/docs/transfer-readiness-checklist.md @@ -124,7 +124,7 @@ generic substrate boundary. Transfer checklist: -- Support caller-provided plugins through `extraPlugins` with explicit source, +- Support caller-provided plugins through `extra_plugins` with explicit source, slug, plugin file, activation behavior, optional hash, and `loadAs` mode. - Support caller-provided MU plugins through the same generic runtime dependency path; prove load order and invocation through issue #317. diff --git a/examples/recipes/bench-plugin.json b/examples/recipes/bench-plugin.json index 728d035..dea0511 100644 --- a/examples/recipes/bench-plugin.json +++ b/examples/recipes/bench-plugin.json @@ -7,7 +7,7 @@ } }, "inputs": { - "extraPlugins": [ + "extra_plugins": [ { "source": "../bench-plugin", "slug": "bench-plugin", diff --git a/examples/recipes/cookbook/codex-agent-smoke.json b/examples/recipes/cookbook/codex-agent-smoke.json index a12ed1a..14ec60e 100644 --- a/examples/recipes/cookbook/codex-agent-smoke.json +++ b/examples/recipes/cookbook/codex-agent-smoke.json @@ -22,7 +22,7 @@ ] }, "inputs": { - "extraPlugins": [ + "extra_plugins": [ { "source": "/path/to/agents-api", "slug": "agents-api", diff --git a/examples/recipes/cookbook/headless-browser-agent-task.json b/examples/recipes/cookbook/headless-browser-agent-task.json index eef4679..14103f7 100644 --- a/examples/recipes/cookbook/headless-browser-agent-task.json +++ b/examples/recipes/cookbook/headless-browser-agent-task.json @@ -15,7 +15,7 @@ } }, "inputs": { - "extraPlugins": [ + "extra_plugins": [ { "source": "./headless-browser-agent-task-plugin", "pluginFile": "headless-browser-agent-task-plugin/headless-browser-agent-task.php", diff --git a/packages/cli/src/commands/agent-task-run.ts b/packages/cli/src/commands/agent-task-run.ts index dcec414..a16b45e 100644 --- a/packages/cli/src/commands/agent-task-run.ts +++ b/packages/cli/src/commands/agent-task-run.ts @@ -15,7 +15,6 @@ export interface AgentTaskRunOptions { export interface AgentTaskRunInput { goal?: string - task?: string agent?: string mode?: string provider?: string @@ -185,7 +184,7 @@ export function buildAgentTaskRecipe(input: AgentTaskRunInput, taskInput: Return inputs: stripUndefined({ mounts: Array.isArray(input.mounts) ? input.mounts : [], workspaces: Array.isArray(input.workspaces) ? input.workspaces : [], - extraPlugins: [ + extra_plugins: [ ...componentPlugins(input.component_contracts, artifacts), ...providerPlugins, ].filter(Boolean), diff --git a/packages/cli/src/commands/recipe-build.ts b/packages/cli/src/commands/recipe-build.ts index 8239685..92fca45 100644 --- a/packages/cli/src/commands/recipe-build.ts +++ b/packages/cli/src/commands/recipe-build.ts @@ -30,7 +30,7 @@ interface WordPressBenchBuilderOptions { blueprint?: unknown wordpressVersion?: string mounts?: WorkspaceRecipeMount[] - extraPlugins?: WorkspaceRecipeExtraPlugin[] + extra_plugins?: WorkspaceRecipeExtraPlugin[] componentId?: string pluginSlug: string iterations?: number @@ -83,7 +83,7 @@ function buildRecipe(recipeType: RecipeBuildOptions["recipeType"], options: Word blueprint: options.blueprint, wordpressVersion: stringOrUndefined(options.wordpressVersion), mounts: Array.isArray(options.mounts) ? options.mounts : [], - extraPlugins: Array.isArray((options as WordPressBenchBuilderOptions).extraPlugins) ? (options as WordPressBenchBuilderOptions).extraPlugins : [], + extra_plugins: Array.isArray((options as WordPressBenchBuilderOptions).extra_plugins) ? (options as WordPressBenchBuilderOptions).extra_plugins : [], componentId: stringOrUndefined((options as WordPressBenchBuilderOptions).componentId), pluginSlug: requiredString(options.pluginSlug, "pluginSlug"), iterations: integerOrUndefined((options as WordPressBenchBuilderOptions).iterations), diff --git a/packages/cli/src/recipe-sources.ts b/packages/cli/src/recipe-sources.ts index 7553a87..31e1892 100644 --- a/packages/cli/src/recipe-sources.ts +++ b/packages/cli/src/recipe-sources.ts @@ -922,7 +922,7 @@ function titleFromSlug(slug: string): string { } export function recipeExtraPlugins(recipe: WorkspaceRecipe): WorkspaceRecipeExtraPlugin[] { - return recipe.inputs?.extra_plugins ?? recipe.inputs?.extraPlugins ?? [] + return recipe.inputs?.extra_plugins ?? [] } export function recipeSource(sourceRef: string, expectedSha256?: string): ParsedRecipeSource { diff --git a/packages/cli/src/recipe-validation.ts b/packages/cli/src/recipe-validation.ts index dad8812..c42bfb2 100644 --- a/packages/cli/src/recipe-validation.ts +++ b/packages/cli/src/recipe-validation.ts @@ -38,11 +38,6 @@ export function parseWorkspaceRecipeJson(raw: string): WorkspaceRecipe { } export function normalizeWorkspaceRecipeCompatibility(recipe: WorkspaceRecipe): WorkspaceRecipe { - if (recipe.inputs?.extraPlugins !== undefined) { - recipe.inputs.extra_plugins ??= recipe.inputs.extraPlugins - delete recipe.inputs.extraPlugins - } - return recipe } @@ -99,6 +94,10 @@ export function validateWorkspaceRecipeShape(recipe: WorkspaceRecipe, recipePath validateRecipeRuntimePreview(recipe.runtime?.preview, recipePath) validateRecipeMounts(recipe.inputs?.mounts, "mounts", recipePath) + if (recipe.inputs && "extraPlugins" in recipe.inputs) { + throw new Error(`Recipe inputs.extraPlugins is unsupported; use inputs.extra_plugins: ${recipePath}`) + } + const workspaces = recipe.inputs?.workspaces ?? [] if (!Array.isArray(workspaces)) { throw new Error(`Recipe workspaces must be an array: ${recipePath}`) @@ -1086,10 +1085,9 @@ async function validateRecipeStepArgs(step: WorkspaceRecipe["workflow"]["steps"] if (step.command === "wordpress.browser-actions") { const stepsJson = recipeStepArgValue(step.args ?? [], "steps-json") - const actionsJson = recipeStepArgValue(step.args ?? [], "actions-json") const url = recipeStepArgValue(step.args ?? [], "url")?.trim() - if (!stepsJson && !actionsJson && !url) { - addIssue("missing-steps", `${path}.args`, "wordpress.browser-actions requires steps-json= (or actions-json=) or url=.") + if (!stepsJson && !url) { + addIssue("missing-steps", `${path}.args`, "wordpress.browser-actions requires steps-json= or url=.") } if (stepsJson && !stepsJson.startsWith("@")) { diff --git a/packages/runtime-core/src/command-registry.ts b/packages/runtime-core/src/command-registry.ts index 299e9f5..7225661 100644 --- a/packages/runtime-core/src/command-registry.ts +++ b/packages/runtime-core/src/command-registry.ts @@ -230,7 +230,6 @@ export const commandRegistry = [ acceptedArgs: [ { name: "url", description: "Initial preview path or absolute URL to visit when the script omits an initial navigate step.", format: "path or URL" }, { name: "steps-json", description: "Ordered interaction script: navigate, click, fill, type, press, drag, hover, select, waitFor, evaluate, expect, screenshot, and capture steps. waitFor and screenshot steps support generic painted-readiness waits: painted, frame-painted:, and frame-url-painted:.", format: "JSON array (inline or @)" }, - { name: "actions-json", description: "Back-compat alias for steps-json accepting the legacy navigate/click/fill/press/wait/capture action shape.", format: "JSON array" }, { name: "step-timeout", description: "Per-step timeout applied to each interaction step.", format: "duration, e.g. 5s or 500ms" }, { name: "timeout", description: "Total-script timeout bounding the whole interaction run.", format: "duration, e.g. 30s or 1500ms" }, { name: "auth", description: "Optional in-memory browser authentication mode. Use wordpress-admin to bootstrap WordPress admin cookies from PHP without writing token-bearing storage-state artifacts.", format: "wordpress-admin" }, diff --git a/packages/runtime-core/src/recipe-builders.ts b/packages/runtime-core/src/recipe-builders.ts index 9327ade..1cee21a 100644 --- a/packages/runtime-core/src/recipe-builders.ts +++ b/packages/runtime-core/src/recipe-builders.ts @@ -30,7 +30,7 @@ export interface WordPressBenchRecipeOptions { wordpressVersion?: string blueprint?: unknown mounts?: WorkspaceRecipeMount[] - extraPlugins?: WorkspaceRecipeExtraPlugin[] + extra_plugins?: WorkspaceRecipeExtraPlugin[] componentId?: string pluginSlug: string iterations?: number @@ -116,7 +116,7 @@ export function buildWordPressBenchRecipe(options: WordPressBenchRecipeOptions): blueprint: blueprintWithWpConfigDefines(options.blueprint ?? {}, options.wpConfigDefines ?? {}), }, inputs: { - extraPlugins: normalizeExtraPlugins(options.extraPlugins), + extra_plugins: normalizeExtraPlugins(options.extra_plugins), mounts: normalizeRecipeMounts(options.mounts, { defaultMode: "readonly" }), }, workflow: { diff --git a/packages/runtime-core/src/recipe-schema.ts b/packages/runtime-core/src/recipe-schema.ts index f83d707..3c0cb3d 100644 --- a/packages/runtime-core/src/recipe-schema.ts +++ b/packages/runtime-core/src/recipe-schema.ts @@ -63,10 +63,6 @@ export function createWorkspaceRecipeJsonSchema(options: WorkspaceRecipeJsonSche type: "array", items: { $ref: "#/$defs/extraPlugin" }, }, - extraPlugins: { - type: "array", - items: { $ref: "#/$defs/extraPlugin" }, - }, secretEnv: { type: "array", items: { type: "string", pattern: "^[A-Z_][A-Z0-9_]*$" }, diff --git a/packages/runtime-core/src/runtime-action-adapter.ts b/packages/runtime-core/src/runtime-action-adapter.ts index 89b1815..1f12b2d 100644 --- a/packages/runtime-core/src/runtime-action-adapter.ts +++ b/packages/runtime-core/src/runtime-action-adapter.ts @@ -278,7 +278,7 @@ async function runRuntimeFilesystemAction( } async function runRuntimeBrowserAction(episode: RuntimeEpisode, action: RuntimeBrowserAction): Promise { - const args = [`actions-json=${JSON.stringify([runtimeBrowserCommandAction(action)])}`] + const args = [`steps-json=${JSON.stringify([runtimeBrowserCommandStep(action)])}`] if (action.url && action.operation !== "navigate") { args.unshift(`url=${action.url}`) } @@ -324,8 +324,8 @@ async function runRuntimeBrowserAction(episode: RuntimeEpisode, action: RuntimeB }) } -function runtimeBrowserCommandAction(action: RuntimeBrowserAction): Record { - const commandAction: Record = { type: action.operation } +function runtimeBrowserCommandStep(action: RuntimeBrowserAction): Record { + const commandAction: Record = { kind: action.operation === "wait" ? "waitFor" : action.operation } for (const key of ["url", "selector", "text", "value", "key", "duration"] as const) { if (typeof action[key] === "string") { commandAction[key] = action[key] diff --git a/packages/runtime-core/src/runtime-contracts.ts b/packages/runtime-core/src/runtime-contracts.ts index 063bb7c..fee88e7 100644 --- a/packages/runtime-core/src/runtime-contracts.ts +++ b/packages/runtime-core/src/runtime-contracts.ts @@ -341,7 +341,6 @@ export interface WorkspaceRecipe { workspaces?: WorkspaceRecipeWorkspace[] mounts?: WorkspaceRecipeMount[] extra_plugins?: WorkspaceRecipeExtraPlugin[] - extraPlugins?: WorkspaceRecipeExtraPlugin[] secretEnv?: string[] pluginRuntime?: WorkspaceRecipePluginRuntime fixtureDatabases?: WorkspaceRecipeFixtureDatabase[] diff --git a/packages/runtime-core/src/sandbox-tool-policy.ts b/packages/runtime-core/src/sandbox-tool-policy.ts index 25b238a..518d5fd 100644 --- a/packages/runtime-core/src/sandbox-tool-policy.ts +++ b/packages/runtime-core/src/sandbox-tool-policy.ts @@ -98,8 +98,9 @@ export function validateSandboxToolPolicySnapshot(input: unknown): SandboxToolPo } const id = typeof tool.id === "string" ? tool.id.trim() : "" const runtimeToolId = typeof tool.runtime_tool_id === "string" ? tool.runtime_tool_id.trim() : "" - const location = typeof tool.execution_location === "string" ? tool.execution_location.trim() : "" - const visibility = typeof tool.transport_visibility === "string" ? tool.transport_visibility.trim() : "" + const runtime = isPlainObject(tool.runtime) ? tool.runtime : {} + const runtimeEnvironment = typeof runtime[AGENTS_API_RUNTIME_ENVIRONMENT] === "string" ? runtime[AGENTS_API_RUNTIME_ENVIRONMENT].trim() : "" + const runtimeCapabilityScope = typeof runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE] === "string" ? runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE].trim() : "" if (!id) { issues.push({ code: "invalid-tool", field: `${field}.id`, message: `${field}.id must be a non-empty string.` }) } else if (seen.has(id)) { @@ -109,11 +110,11 @@ export function validateSandboxToolPolicySnapshot(input: unknown): SandboxToolPo if (!runtimeToolId) { issues.push({ code: "invalid-tool", field: `${field}.runtime_tool_id`, message: `${field}.runtime_tool_id must be a non-empty string.` }) } - if (!location) { - issues.push({ code: "invalid-tool", field: `${field}.execution_location`, message: `${field}.execution_location must be a non-empty string.` }) + if (!runtimeEnvironment) { + issues.push({ code: "invalid-tool", field: `${field}.runtime.environment`, message: `${field}.runtime.environment must be a non-empty string.` }) } - if (!visibility) { - issues.push({ code: "invalid-tool", field: `${field}.transport_visibility`, message: `${field}.transport_visibility must be a non-empty string.` }) + if (!runtimeCapabilityScope) { + issues.push({ code: "invalid-tool", field: `${field}.runtime.capability_scope`, message: `${field}.runtime.capability_scope must be a non-empty string.` }) } if (typeof tool.allowed !== "boolean") { issues.push({ code: "invalid-tool", field: `${field}.allowed`, message: `${field}.allowed must be boolean.` }) @@ -140,21 +141,13 @@ export function sandboxToolRuntimeMetadata(tool: SandboxToolPolicyTool): Require const runtime = isPlainObject(tool.runtime) ? tool.runtime : {} const environment = typeof runtime[AGENTS_API_RUNTIME_ENVIRONMENT] === "string" ? runtime[AGENTS_API_RUNTIME_ENVIRONMENT] - : legacyExecutionEnvironment(tool.execution_location) + : "" const capabilityScope = typeof runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE] === "string" ? runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE] - : legacyCapabilityScope(tool.transport_visibility) + : "" return { environment, capability_scope: capabilityScope, } } - -function legacyExecutionEnvironment(location: string): AgentsApiRuntimeEnvironment { - return location === "sandbox" ? AGENTS_API_RUNTIME_LOCAL : AGENTS_API_CONTROL_PLANE -} - -function legacyCapabilityScope(visibility: string): AgentsApiRuntimeEnvironment { - return ["sandbox", "both"].includes(visibility) ? AGENTS_API_RUNTIME_LOCAL : AGENTS_API_CONTROL_PLANE -} diff --git a/packages/runtime-core/src/task-input.ts b/packages/runtime-core/src/task-input.ts index 3c2e54d..4b74dd6 100644 --- a/packages/runtime-core/src/task-input.ts +++ b/packages/runtime-core/src/task-input.ts @@ -4,7 +4,6 @@ import type { SandboxToolPolicySnapshot } from "./sandbox-tool-policy.js" export type TaskTargetKind = "repo" | "site" | "plugin" | "theme" | (string & {}) export const TASK_INPUT_SCHEMA = "wp-codebox/task-input/v1" as const -export const AGENTS_API_TASK_INPUT_SCHEMA = "agents-api/task-input/v1" as const export const TASK_INPUT_VERSION = 1 as const export interface TaskTarget { @@ -31,7 +30,7 @@ export interface TaskInputAgentBundle { } export interface TaskInput { - schema: typeof TASK_INPUT_SCHEMA | typeof AGENTS_API_TASK_INPUT_SCHEMA + schema: typeof TASK_INPUT_SCHEMA version: typeof TASK_INPUT_VERSION goal: string target: Partial @@ -45,8 +44,6 @@ export interface TaskInput { export type TaskInputRequest = Partial> & { goal?: string - task?: string - sandboxToolPolicy?: SandboxToolPolicySnapshot } export const TASK_INPUT_JSON_SCHEMA = { @@ -54,7 +51,7 @@ export const TASK_INPUT_JSON_SCHEMA = { type: "object", required: ["schema", "version", "goal", "target", "allowed_tools", "expected_artifacts", "agent_bundles", "sandbox_tool_policy", "policy", "context"], properties: { - schema: { enum: [TASK_INPUT_SCHEMA, AGENTS_API_TASK_INPUT_SCHEMA], description: "Task input contract schema id. The legacy WP Codebox schema remains accepted alongside the generic Agents API schema." }, + schema: { const: TASK_INPUT_SCHEMA, description: "Task input contract schema id." }, version: { const: TASK_INPUT_VERSION, description: "Task input contract version." }, goal: { type: "string", description: "User-facing outcome the sandboxed coding agent should accomplish." }, target: { @@ -109,9 +106,8 @@ export const TASK_INPUT_JSON_SCHEMA = { } as const export function normalizeTaskInput(input: TaskInputRequest): TaskInput { - const goal = String(input.goal ?? input.task ?? "").trim() - if (goal === "") throw new Error("goal or task is required.") - const rawPolicy = (input as Record).sandbox_tool_policy ?? (input as Record).sandboxToolPolicy + const goal = String(input.goal ?? "").trim() + if (goal === "") throw new Error("goal is required.") return { schema: TASK_INPUT_SCHEMA, @@ -120,8 +116,8 @@ export function normalizeTaskInput(input: TaskInputRequest): TaskInput { target: isPlainObject(input.target) ? input.target : {}, allowed_tools: stringList(input.allowed_tools), expected_artifacts: stringList(input.expected_artifacts), - agent_bundles: normalizeAgentBundles((input as Record).agent_bundles ?? (input as Record).agentBundles), - sandbox_tool_policy: isPlainObject(rawPolicy) ? rawPolicy as unknown as SandboxToolPolicySnapshot : {}, + agent_bundles: normalizeAgentBundles(input.agent_bundles), + sandbox_tool_policy: isPlainObject(input.sandbox_tool_policy) ? input.sandbox_tool_policy as unknown as SandboxToolPolicySnapshot : {}, policy: isPlainObject(input.policy) ? input.policy : {}, context: isPlainObject(input.context) ? input.context : {}, } diff --git a/packages/runtime-playground/src/browser-actions.ts b/packages/runtime-playground/src/browser-actions.ts index a77e0b4..e3ba933 100644 --- a/packages/runtime-playground/src/browser-actions.ts +++ b/packages/runtime-playground/src/browser-actions.ts @@ -1,9 +1,7 @@ import { readFile } from "node:fs/promises" import { resolve } from "node:path" import { validateBrowserInteractionScript, type BrowserInteractionStep } from "@automattic/wp-codebox-core" -import { argValue, jsonArrayArg } from "./commands.js" - -export type BrowserActionInput = Record & { type: string } +import { argValue } from "./commands.js" export async function browserInteractionStepsFromArgs(args: string[]): Promise { const stepsRaw = argValue(args, "steps-json") @@ -16,8 +14,7 @@ export async function browserInteractionStepsFromArgs(args: string[]): Promise { @@ -33,33 +30,6 @@ async function parseBrowserStepsPayload(raw: string, name: string): Promise { - if (!action || typeof action !== "object" || Array.isArray(action)) { - throw new Error(`wordpress.browser-actions actions-json[${index}] must be an object`) - } - const typedAction = action as BrowserActionInput - if (typeof typedAction.type !== "string" || typedAction.type.length === 0) { - throw new Error(`wordpress.browser-actions actions-json[${index}].type is required`) - } - return typedAction - }) -} - export function browserActionLoadState(waitFor: unknown): "domcontentloaded" | "load" | "networkidle" { if (waitFor === undefined || waitFor === null || waitFor === "") { return "domcontentloaded" diff --git a/packages/runtime-playground/src/browser-command-runners.ts b/packages/runtime-playground/src/browser-command-runners.ts index 36211b5..d8080a6 100644 --- a/packages/runtime-playground/src/browser-command-runners.ts +++ b/packages/runtime-playground/src/browser-command-runners.ts @@ -1526,7 +1526,7 @@ export async function runBrowserActionsCommand({ const steps = await browserInteractionStepsFromArgs(args) const initialUrl = argValue(args, "url")?.trim() if (steps.length === 0 && !initialUrl) { - throw new Error("wordpress.browser-actions requires steps-json= (or actions-json=) or url=") + throw new Error("wordpress.browser-actions requires steps-json= or url=") } if (initialUrl && steps[0]?.kind !== "navigate") { diff --git a/packages/runtime-playground/src/php-bootstrap.ts b/packages/runtime-playground/src/php-bootstrap.ts index 5077fb4..8f56b06 100644 --- a/packages/runtime-playground/src/php-bootstrap.ts +++ b/packages/runtime-playground/src/php-bootstrap.ts @@ -98,20 +98,16 @@ foreach (is_array($wp_codebox_run_php_active_plugins) ? $wp_codebox_run_php_acti function recipeActivePluginMetadata(spec: RuntimeCreateSpec): RecipePluginMetadata[] { const recipe = spec.metadata?.recipe && typeof spec.metadata.recipe === "object" && !Array.isArray(spec.metadata.recipe) - ? spec.metadata.recipe as { inputs?: { extra_plugins?: unknown; extraPlugins?: unknown } } + ? spec.metadata.recipe as { inputs?: { extra_plugins?: unknown } } : undefined const task = spec.metadata?.task && typeof spec.metadata.task === "object" && !Array.isArray(spec.metadata.task) - ? spec.metadata.task as { inputs?: { extra_plugins?: unknown; extraPlugins?: unknown } } + ? spec.metadata.task as { inputs?: { extra_plugins?: unknown } } : undefined const extraPlugins = Array.isArray(recipe?.inputs?.extra_plugins) ? recipe.inputs.extra_plugins - : Array.isArray(recipe?.inputs?.extraPlugins) - ? recipe.inputs.extraPlugins - : Array.isArray(task?.inputs?.extra_plugins) - ? task.inputs.extra_plugins - : Array.isArray(task?.inputs?.extraPlugins) - ? task.inputs.extraPlugins - : [] + : Array.isArray(task?.inputs?.extra_plugins) + ? task.inputs.extra_plugins + : [] const plugins: RecipePluginMetadata[] = extraPlugins .filter((plugin): plugin is RecipePluginMetadata => Boolean(plugin) && typeof plugin === "object" && !Array.isArray(plugin)) diff --git a/packages/wordpress-plugin/README.md b/packages/wordpress-plugin/README.md index 44454c4..38aabfc 100644 --- a/packages/wordpress-plugin/README.md +++ b/packages/wordpress-plugin/README.md @@ -21,10 +21,8 @@ WordPress Playground runtime, mounts the requested runtime components, invokes the configured sandbox-local ability or hook task, and returns artifact metadata. The task ability accepts `wp-codebox/task-input/v1` fields: `goal`, `target`, -`allowed_tools`, `expected_artifacts`, `policy`, and `context`. Legacy callers -may still pass `task` as a string; the runner normalizes it into `goal` and -returns the normalized `task_input` in the ability response. Raw PHP `code` and -`code_file` fields remain rejected on this product ability path. +`allowed_tools`, `expected_artifacts`, `policy`, and `context`. Raw PHP `code` +and `code_file` fields remain rejected on this product ability path. Runtime orchestrators can pass `agent_bundles` plus a generic `runtime_task` payload to run caller-owned bundle logic without injecting PHP code. WP Codebox diff --git a/packages/wordpress-plugin/src/class-wp-codebox-abilities.php b/packages/wordpress-plugin/src/class-wp-codebox-abilities.php index a903f33..ebf20c4 100644 --- a/packages/wordpress-plugin/src/class-wp-codebox-abilities.php +++ b/packages/wordpress-plugin/src/class-wp-codebox-abilities.php @@ -182,10 +182,7 @@ private function register(): void { 'category' => 'wp-codebox', 'input_schema' => array( 'type' => 'object', - 'anyOf' => array( - array( 'type' => 'object', 'required' => array( 'goal' ) ), - array( 'type' => 'object', 'required' => array( 'task' ) ), - ), + 'required' => array( 'goal' ), 'properties' => $host_agent_task_properties, ), 'output_schema' => array( @@ -228,12 +225,7 @@ private function register(): void { 'tasks' => array( 'type' => 'array', 'description' => 'Task descriptions or structured task inputs. Each task runs in its own isolated sandbox.', - 'items' => array( - 'anyOf' => array( - array( 'type' => 'string' ), - $task_input_schema, - ), - ), + 'items' => $task_input_schema, ), ) + $host_agent_batch_properties, ), @@ -411,10 +403,7 @@ private function register(): void { 'category' => 'wp-codebox', 'input_schema' => array( 'type' => 'object', - 'anyOf' => array( - array( 'type' => 'object', 'required' => array( 'goal' ) ), - array( 'type' => 'object', 'required' => array( 'task' ) ), - ), + 'required' => array( 'goal' ), 'properties' => $browser_session_properties, ), 'output_schema' => $browser_session_schema, @@ -432,10 +421,7 @@ private function register(): void { 'category' => 'wp-codebox', 'input_schema' => array( 'type' => 'object', - 'anyOf' => array( - array( 'type' => 'object', 'required' => array( 'goal' ) ), - array( 'type' => 'object', 'required' => array( 'task' ) ), - ), + 'required' => array( 'goal' ), 'properties' => $browser_contract_properties, ), 'output_schema' => self::browser_materializer_contract_schema(), @@ -453,10 +439,7 @@ private function register(): void { 'category' => 'wp-codebox', 'input_schema' => array( 'type' => 'object', - 'anyOf' => array( - array( 'type' => 'object', 'required' => array( 'goal' ) ), - array( 'type' => 'object', 'required' => array( 'task' ) ), - ), + 'required' => array( 'goal' ), 'properties' => $browser_contract_properties + array( 'execute_phases' => array( 'type' => 'boolean', diff --git a/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php b/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php index 3221566..f8af908 100644 --- a/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php +++ b/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php @@ -100,7 +100,7 @@ public function run_fanout( array $input ): array|WP_Error { static fn( array $worker ): array => array( 'id' => (string) $worker['id'], 'agent' => (string) ( $worker['agent'] ?? $input['agent'] ?? '' ), - 'goal' => (string) ( $worker['goal'] ?? $worker['task'] ?? '' ), + 'goal' => (string) ( $worker['goal'] ?? '' ), 'artifact_namespace' => (string) $worker['id'], ), $workers @@ -472,9 +472,9 @@ private function fanout_workers( array $input ): array|WP_Error { return new WP_Error( 'wp_codebox_fanout_worker_id_duplicate', 'Fanout worker ids must be unique.', array( 'status' => 400, 'worker_id' => $id ) ); } - $goal = trim( (string) ( $worker['goal'] ?? $worker['task'] ?? '' ) ); + $goal = trim( (string) ( $worker['goal'] ?? '' ) ); if ( '' === $goal ) { - return new WP_Error( 'wp_codebox_fanout_worker_goal_missing', 'Each fanout worker requires goal or task.', array( 'status' => 400, 'worker_id' => $id ) ); + return new WP_Error( 'wp_codebox_fanout_worker_goal_missing', 'Each fanout worker requires goal.', array( 'status' => 400, 'worker_id' => $id ) ); } $seen[ $id ] = true; @@ -506,7 +506,7 @@ private function fanout_worker_input( array $parent, array $worker, string $pare $this->ensure_directory( $worker_artifacts_path ); $input = array_merge( $parent, $worker ); - unset( $input['workers'], $input['dependencies'], $input['aggregation'], $input['concurrency'], $input['task'] ); + unset( $input['workers'], $input['dependencies'], $input['aggregation'], $input['concurrency'] ); $input['goal'] = (string) $worker['goal']; $input['sandbox_session_id'] = $parent_session_id . ':' . (string) $worker['id']; @@ -874,14 +874,60 @@ private function reject_raw_code_input( array $input ): true|WP_Error { /** @param array $input Ability input. @return array|WP_Error */ private function normalize_parent_task_request( array $input ): array|WP_Error { - if ( function_exists( 'apply_filters' ) ) { - $filtered = apply_filters( 'wp_codebox_normalize_parent_task_request', null, $input ); - if ( is_wp_error( $filtered ) || is_array( $filtered ) ) { - return $filtered; + $request = is_array( $input['parent_request'] ?? null ) ? $input['parent_request'] : $input; + $schema = (string) ( $request['schema'] ?? '' ); + if ( self::TASK_INPUT_SCHEMA !== $schema ) { + return $input; + } + + $goal = trim( (string) ( $request['goal'] ?? '' ) ); + if ( '' === $goal ) { + return new WP_Error( 'wp_codebox_parent_task_missing', 'parent_request.goal is required.', array( 'status' => 400 ) ); + } + + $context = is_array( $request['context'] ?? null ) ? $request['context'] : array(); + foreach ( array( 'sandbox_session_id', 'group_key', 'audit_findings', 'orchestrator' ) as $context_key ) { + if ( array_key_exists( $context_key, $request ) ) { + $context[ $context_key ] = $request[ $context_key ]; } } - return $input; + $normalized = array_merge( + $input, + array_filter( + array( + 'goal' => $goal, + 'target' => is_array( $request['target'] ?? null ) ? $request['target'] : array(), + 'allowed_tools' => is_array( $request['allowed_tools'] ?? null ) ? $request['allowed_tools'] : array(), + 'sandbox_tool_policy' => is_array( $request['sandbox_tool_policy'] ?? null ) ? $request['sandbox_tool_policy'] : array(), + 'expected_artifacts' => is_array( $request['expected_artifacts'] ?? null ) ? $request['expected_artifacts'] : array(), + 'policy' => is_array( $request['policy'] ?? null ) ? $request['policy'] : array(), + 'context' => $context, + 'provider' => (string) ( $input['provider'] ?? $request['provider'] ?? '' ), + 'model' => (string) ( $input['model'] ?? $request['model'] ?? '' ), + 'provider_plugin_paths' => $this->merge_string_lists( $input['provider_plugin_paths'] ?? array(), $request['provider_plugin_paths'] ?? array() ), + 'agent_bundles' => $this->agent_bundles( $input, $request ), + 'runtime_task' => $this->runtime_task( $input, $request ), + 'component_contracts' => $this->merge_array_lists( $input['component_contracts'] ?? array(), $request['component_contracts'] ?? array() ), + 'secret_env' => $this->merge_string_lists( $input['secret_env'] ?? array(), $request['secret_env'] ?? array() ), + 'mounts' => $this->merge_array_lists( $input['mounts'] ?? array(), $request['mounts'] ?? array() ), + 'workspaces' => $this->merge_array_lists( $input['workspaces'] ?? array(), $request['workspaces'] ?? array() ), + 'runtime_stack_mounts' => $this->merge_array_lists( $input['runtime_stack_mounts'] ?? array(), $request['runtime_stack_mounts'] ?? array() ), + 'runtime_overlays' => $this->merge_array_lists( $input['runtime_overlays'] ?? array(), $request['runtime_overlays'] ?? array() ), + 'task_timeout_seconds' => (int) ( $input['task_timeout_seconds'] ?? $request['task_timeout_seconds'] ?? 0 ), + 'max_turns' => (int) ( $input['max_turns'] ?? $request['max_turns'] ?? 0 ), + 'sandbox_session_id' => (string) ( $input['sandbox_session_id'] ?? $request['sandbox_session_id'] ?? '' ), + 'orchestrator' => is_array( $input['orchestrator'] ?? null ) ? $input['orchestrator'] : ( is_array( $request['orchestrator'] ?? null ) ? $request['orchestrator'] : array() ), + 'artifacts_path' => (string) ( $input['artifacts_path'] ?? $request['artifacts'] ?? '' ), + 'wp_codebox_bin' => (string) ( $input['wp_codebox_bin'] ?? $request['wp_codebox_bin'] ?? '' ), + ), + static fn( mixed $value ): bool => '' !== $value && array() !== $value && 0 !== $value + ) + ); + + unset( $normalized['parent_request'] ); + + return $normalized; } private function agent_slug( array $input ): string { @@ -1142,7 +1188,11 @@ private function task_inputs( array $input ): array|WP_Error { $task_inputs = array(); foreach ( $tasks as $task ) { - $normalized = is_array( $task ) ? $this->task_input( $task ) : $this->task_input( array( 'task' => $task ) ); + if ( ! is_array( $task ) ) { + continue; + } + + $normalized = $this->task_input( $task ); if ( is_wp_error( $normalized ) ) { continue; } @@ -1203,7 +1253,7 @@ private function merge_array_lists( mixed ...$lists ): array { /** @param array $input Direct ability input. @param array $request Parent request input. @return array> */ private function agent_bundles( array $input, array $request = array() ): array { - $bundles = $this->merge_array_lists( $input['agent_bundles'] ?? $input['agentBundles'] ?? array(), $request['agent_bundles'] ?? $request['agentBundles'] ?? array() ); + $bundles = $this->merge_array_lists( $input['agent_bundles'] ?? array(), $request['agent_bundles'] ?? array() ); $normalized = array(); foreach ( $bundles as $bundle ) { $source = isset( $bundle['source'] ) ? trim( (string) $bundle['source'] ) : ''; @@ -1684,7 +1734,7 @@ private function provider_error_details( array $run, string $output ): array { /** @param array $input Ability input. @return array|WP_Error */ private function resolved_sandbox_tool_policy( array $input ): array|WP_Error { - $policy = is_array( $input['sandbox_tool_policy'] ?? null ) ? $input['sandbox_tool_policy'] : ( is_array( $input['sandboxToolPolicy'] ?? null ) ? $input['sandboxToolPolicy'] : array() ); + $policy = is_array( $input['sandbox_tool_policy'] ?? null ) ? $input['sandbox_tool_policy'] : array(); if ( empty( $policy ) && function_exists( 'apply_filters' ) ) { $policy = apply_filters( 'wp_codebox_resolved_sandbox_tool_policy', $policy, $input ); } @@ -1732,11 +1782,17 @@ private function sandbox_tool_policy_issues( array $policy ): array { $issues[] = array( 'field' => 'tools[' . $index . '].id', 'message' => 'Duplicate tool id: ' . $id . '.' ); } $seen[ $id ] = true; - foreach ( array( 'runtime_tool_id', 'execution_location', 'transport_visibility' ) as $field ) { + foreach ( array( 'runtime_tool_id' ) as $field ) { if ( '' === trim( (string) ( $tool[ $field ] ?? '' ) ) ) { $issues[] = array( 'field' => 'tools[' . $index . '].' . $field, 'message' => 'Tool ' . $field . ' must be a non-empty string.' ); } } + $runtime = is_array( $tool['runtime'] ?? null ) ? $tool['runtime'] : array(); + foreach ( array( self::AGENTS_API_RUNTIME_ENVIRONMENT, self::AGENTS_API_RUNTIME_CAPABILITY_SCOPE ) as $field ) { + if ( '' === trim( (string) ( $runtime[ $field ] ?? '' ) ) ) { + $issues[] = array( 'field' => 'tools[' . $index . '].runtime.' . $field, 'message' => 'Tool runtime.' . $field . ' must be a non-empty string.' ); + } + } if ( ! is_bool( $tool['allowed'] ?? null ) ) { $issues[] = array( 'field' => 'tools[' . $index . '].allowed', 'message' => 'Tool allowed must be boolean.' ); } @@ -1792,21 +1848,13 @@ private function sandbox_tool_runtime_metadata( array $tool ): array { return array( self::AGENTS_API_RUNTIME_ENVIRONMENT => isset( $runtime[ self::AGENTS_API_RUNTIME_ENVIRONMENT ] ) && '' !== trim( (string) $runtime[ self::AGENTS_API_RUNTIME_ENVIRONMENT ] ) ? trim( (string) $runtime[ self::AGENTS_API_RUNTIME_ENVIRONMENT ] ) - : $this->legacy_execution_environment( (string) ( $tool['execution_location'] ?? '' ) ), + : '', self::AGENTS_API_RUNTIME_CAPABILITY_SCOPE => isset( $runtime[ self::AGENTS_API_RUNTIME_CAPABILITY_SCOPE ] ) && '' !== trim( (string) $runtime[ self::AGENTS_API_RUNTIME_CAPABILITY_SCOPE ] ) ? trim( (string) $runtime[ self::AGENTS_API_RUNTIME_CAPABILITY_SCOPE ] ) - : $this->legacy_capability_scope( (string) ( $tool['transport_visibility'] ?? '' ) ), + : '', ); } - private function legacy_execution_environment( string $location ): string { - return 'sandbox' === $location ? self::AGENTS_API_RUNTIME_LOCAL : self::AGENTS_API_CONTROL_PLANE; - } - - private function legacy_capability_scope( string $visibility ): string { - return in_array( $visibility, array( 'sandbox', 'both' ), true ) ? self::AGENTS_API_RUNTIME_LOCAL : self::AGENTS_API_CONTROL_PLANE; - } - private function default_artifacts_path(): string { $configured = $this->clean_path( (string) $this->config_option( 'wp_codebox_artifacts_root', '' ) ); if ( '' !== $configured ) { @@ -2131,7 +2179,7 @@ private function write_agent_recipe( array $paths, array $input, array $task_pro 'workspaces' => $workspaces, 'inherit' => $this->inheritance_request( $input ), 'inheritance' => $inheritance, - 'extraPlugins' => array_merge( $this->component_plugins( $paths ), $provider_plugins ), + 'extra_plugins' => array_merge( $this->component_plugins( $paths ), $provider_plugins ), 'secretEnv' => $this->secret_env_names( $input, $inheritance ), ); if ( ! empty( $agent_bundles ) ) { diff --git a/packages/wordpress-plugin/src/class-wp-codebox-task-input-contract.php b/packages/wordpress-plugin/src/class-wp-codebox-task-input-contract.php index 2c9884d..e353063 100644 --- a/packages/wordpress-plugin/src/class-wp-codebox-task-input-contract.php +++ b/packages/wordpress-plugin/src/class-wp-codebox-task-input-contract.php @@ -91,9 +91,9 @@ public static function schema(): array { /** @param array $input Ability input. @return array|WP_Error */ public static function normalize( array $input ): array|WP_Error { - $goal = trim( (string) ( $input['goal'] ?? $input['task'] ?? '' ) ); + $goal = trim( (string) ( $input['goal'] ?? '' ) ); if ( '' === $goal ) { - return new WP_Error( 'wp_codebox_task_missing', 'goal or task is required.', array( 'status' => 400 ) ); + return new WP_Error( 'wp_codebox_task_missing', 'goal is required.', array( 'status' => 400 ) ); } return array( @@ -103,8 +103,8 @@ public static function normalize( array $input ): array|WP_Error { 'target' => is_array( $input['target'] ?? null ) ? $input['target'] : array(), 'allowed_tools' => self::string_list( $input['allowed_tools'] ?? array() ), 'expected_artifacts' => self::string_list( $input['expected_artifacts'] ?? array() ), - 'agent_bundles' => self::agent_bundles( $input['agent_bundles'] ?? $input['agentBundles'] ?? array() ), - 'sandbox_tool_policy' => is_array( $input['sandbox_tool_policy'] ?? null ) ? $input['sandbox_tool_policy'] : ( is_array( $input['sandboxToolPolicy'] ?? null ) ? $input['sandboxToolPolicy'] : array() ), + 'agent_bundles' => self::agent_bundles( $input['agent_bundles'] ?? array() ), + 'sandbox_tool_policy' => is_array( $input['sandbox_tool_policy'] ?? null ) ? $input['sandbox_tool_policy'] : array(), 'policy' => is_array( $input['policy'] ?? null ) ? $input['policy'] : array(), 'context' => is_array( $input['context'] ?? null ) ? $input['context'] : array(), ); diff --git a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-agents-api-executors.php b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-agents-api-executors.php index 1850c52..dae2e53 100644 --- a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-agents-api-executors.php +++ b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-agents-api-executors.php @@ -70,7 +70,6 @@ private static function agents_api_executor_target_declarations(): array { 'capabilities' => array( 'wordpress-playground', 'browser-runtime', 'browser-task-contract' ), 'input_schema' => $task_input_schema, 'output_schema' => $browser_task_output_schema, - 'legacy_abilities' => array( 'wp-codebox/create-browser-task-contract' ), ), array( 'schema' => 'agents-api/executor-target/v1', @@ -82,7 +81,6 @@ private static function agents_api_executor_target_declarations(): array { 'capabilities' => array( 'wordpress-playground', 'host-sandbox-runner', 'artifact-capture' ), 'input_schema' => $task_input_schema, 'output_schema' => array( 'type' => 'object' ), - 'legacy_abilities' => array( 'wp-codebox/run-agent-task' ), ), ); } @@ -92,8 +90,7 @@ private static function agents_api_task_input_schema(): array { $schema = self::task_input_schema(); $schema['$id'] = self::AGENTS_API_TASK_INPUT_SCHEMA; $schema['properties']['schema']['const'] = self::AGENTS_API_TASK_INPUT_SCHEMA; - $schema['properties']['schema']['description'] = 'Generic Agents API task input schema id. WP Codebox also accepts legacy wp-codebox/task-input/v1 inputs for compatibility.'; - $schema['x-wp-codebox-accepted-schemas'] = array( self::AGENTS_API_TASK_INPUT_SCHEMA, WP_Codebox_Task_Input_Contract::SCHEMA ); + $schema['properties']['schema']['description'] = 'Generic Agents API task input schema id.'; return $schema; } @@ -122,17 +119,17 @@ private static function agents_api_executor_target_id( mixed $target, mixed $req /** @param array $request Generic task request. @return array */ private static function agents_api_task_request_input( array $request ): array { - foreach ( array( 'input', 'task_input', 'task' ) as $field ) { + foreach ( array( 'input', 'task_input' ) as $field ) { if ( is_array( $request[ $field ] ?? null ) ) { - return self::agents_api_task_input_with_legacy_compat( $request[ $field ], $request ); + return self::agents_api_task_input( $request[ $field ], $request ); } } - return self::agents_api_task_input_with_legacy_compat( $request, $request ); + return self::agents_api_task_input( $request, $request ); } /** @param array $input Task input. @param array $request Full request. @return array */ - private static function agents_api_task_input_with_legacy_compat( array $input, array $request ): array { + private static function agents_api_task_input( array $input, array $request ): array { if ( self::AGENTS_API_TASK_INPUT_SCHEMA === (string) ( $input['schema'] ?? '' ) ) { $input['schema'] = WP_Codebox_Task_Input_Contract::SCHEMA; } diff --git a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php index c6411d4..0c42e9f 100644 --- a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php +++ b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-browser-runtime.php @@ -321,8 +321,8 @@ private static function browser_plugins( array $input ): array|WP_Error { return self::normalize_browser_plugins( $plugins, 'browser_plugins' ); } -/** @param array $input Ability input. @param array> $legacy_plugins Legacy browser_plugins specs. @return array|WP_Error */ -private static function browser_runtime_dependencies( array $input, array $legacy_plugins ): array|WP_Error { +/** @param array $input Ability input. @param array> $browser_plugins Browser plugin specs. @return array|WP_Error */ +private static function browser_runtime_dependencies( array $input, array $browser_plugins ): array|WP_Error { $runtime = is_array( $input['runtime'] ?? null ) ? $input['runtime'] : array(); $runtime_plugin_specs = self::browser_runtime_plugin_specs( array_merge( self::browser_provider_plugin_specs( $input ), is_array( $runtime['plugins'] ?? null ) ? $runtime['plugins'] : array() ) ); if ( is_wp_error( $runtime_plugin_specs ) ) { @@ -350,12 +350,12 @@ private static function browser_runtime_dependencies( array $input, array $legac } $declared_components = is_array( $runtime['components'] ?? null ) ? $runtime['components'] : array(); - $component_plugins = self::browser_component_plugins( $input, array_merge( $legacy_plugins, $runtime_plugins ), $declared_components ); + $component_plugins = self::browser_component_plugins( $input, array_merge( $browser_plugins, $runtime_plugins ), $declared_components ); if ( is_wp_error( $component_plugins ) ) { return $component_plugins; } - $plugins = self::dedupe_browser_plugins( array_merge( $legacy_plugins, $runtime_plugins, $component_plugins ) ); + $plugins = self::dedupe_browser_plugins( array_merge( $browser_plugins, $runtime_plugins, $component_plugins ) ); $prepared = self::browser_prepared_runtime_contract( $runtime, $plugins, $mu_plugins, $themes, $bootstrap, $input ); if ( is_wp_error( $prepared ) ) { return $prepared; @@ -370,7 +370,7 @@ private static function browser_runtime_dependencies( array $input, array $legac 'bootstrap' => $bootstrap, 'prepared_runtime' => $prepared, 'component_plugins' => count( $component_plugins ), - 'legacy_browser_plugins' => count( $legacy_plugins ), + 'browser_plugins' => count( $browser_plugins ), 'summary' => array( 'plugins' => count( $plugins ), 'mu_plugins' => count( $mu_plugins ), diff --git a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-execution.php b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-execution.php index 16ead77..9cb63d2 100644 --- a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-execution.php +++ b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-execution.php @@ -51,11 +51,11 @@ public static function create_browser_playground_session( array $input ): array| } $input = self::browser_input_with_inheritance( $input, $inheritance_payload['inheritance'] ); $browser_runner = is_array( $input['browser_runner'] ?? null ) ? $input['browser_runner'] : array(); - $legacy_plugins = self::browser_plugins( $input ); - if ( is_wp_error( $legacy_plugins ) ) { - return $legacy_plugins; + $browser_plugins = self::browser_plugins( $input ); + if ( is_wp_error( $browser_plugins ) ) { + return $browser_plugins; } - $runtime = self::browser_runtime_dependencies( $input, $legacy_plugins ); + $runtime = self::browser_runtime_dependencies( $input, $browser_plugins ); if ( is_wp_error( $runtime ) ) { return $runtime; } diff --git a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-inheritance.php b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-inheritance.php index b635146..4de1c98 100644 --- a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-inheritance.php +++ b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-inheritance.php @@ -50,7 +50,7 @@ private static function browser_task_payload( array $input, array $task_input, s 'message' => (string) $task_input['goal'], 'session_id' => $session_id, 'task_input' => $task_input, - 'agent_bundles' => self::normalize_agent_bundles( $input['agent_bundles'] ?? $input['agentBundles'] ?? array() ), + 'agent_bundles' => self::normalize_agent_bundles( $input['agent_bundles'] ?? array() ), 'inheritance' => $inheritance, 'secret_env' => self::browser_secret_env_names( $input ), 'artifacts' => array( diff --git a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-schemas.php b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-schemas.php index 6730e19..adf928c 100644 --- a/packages/wordpress-plugin/src/trait-wp-codebox-abilities-schemas.php +++ b/packages/wordpress-plugin/src/trait-wp-codebox-abilities-schemas.php @@ -510,11 +510,6 @@ private static function object_array_property_schema( string $description = '' ) return $schema; } -/** @return array */ -private static function legacy_task_property_schema(): array { - return self::string_property_schema( 'Legacy task description. Prefer goal for new product callers.' ); -} - /** @return array */ private static function approved_files_schema( string $description ): array { return self::string_array_property_schema( $description ); @@ -552,7 +547,7 @@ private static function host_agent_task_input_properties( array $task_input_sche 'provider_plugin_paths' => self::string_array_property_schema( $detailed ? 'AI provider plugin directories to mount and activate inside the sandbox.' : '' ), 'agent_bundles' => self::agent_bundle_schema(), 'runtime_task' => self::object_property_schema( $detailed ? 'Generic runtime task request. WP Codebox forwards input to the requested sandbox-local ability after importing agent_bundles.' : '' ), - 'parent_request' => self::object_property_schema( $detailed ? 'External orchestrator task request normalized by wp_codebox_normalize_parent_task_request adapters into the WP Codebox runner contract.' : '' ), + 'parent_request' => self::object_property_schema( $detailed ? 'Canonical wp-codebox/task-input/v1 parent request normalized into the WP Codebox runner contract.' : '' ), 'component_contracts' => self::component_contracts_schema( $detailed ? 'Caller-declared runtime components WP Codebox should package, mount, or probe.' : '' ), 'mounts' => $mount_schema, 'workspaces' => self::object_array_property_schema( $detailed ? 'Recipe workspace entries to seed as policy-checked writable repositories.' : '' ), @@ -578,7 +573,6 @@ private static function host_agent_task_input_properties( array $task_input_sche if ( ! empty( $options['task_fields'] ) ) { $properties = array( 'goal' => $task_input_schema['properties']['goal'], - 'task' => self::legacy_task_property_schema(), 'target' => $task_input_schema['properties']['target'], 'allowed_tools' => $task_input_schema['properties']['allowed_tools'], 'sandbox_tool_policy' => $task_input_schema['properties']['sandbox_tool_policy'], @@ -652,7 +646,6 @@ private static function component_contracts_schema( string $description = '' ): private static function browser_task_input_properties( array $task_input_schema, array $inherit_schema, array $session_input, bool $detailed = false ): array { return array( 'goal' => $task_input_schema['properties']['goal'], - 'task' => self::legacy_task_property_schema(), 'target' => $task_input_schema['properties']['target'], 'allowed_tools' => $task_input_schema['properties']['allowed_tools'], 'sandbox_tool_policy' => $task_input_schema['properties']['sandbox_tool_policy'], diff --git a/scripts/agent-task-run-runtime-components-smoke.ts b/scripts/agent-task-run-runtime-components-smoke.ts index 1225c44..462b543 100644 --- a/scripts/agent-task-run-runtime-components-smoke.ts +++ b/scripts/agent-task-run-runtime-components-smoke.ts @@ -21,7 +21,7 @@ const input = { } const recipe = buildAgentTaskRecipe(input, normalizeTaskInput(input), "trunk") -const extraPlugins = recipe.inputs?.extraPlugins ?? [] +const extraPlugins = recipe.inputs?.extra_plugins ?? [] assert.equal(extraPlugins.find((plugin) => plugin?.slug === "agents-api")?.source, "/components/agents-api") assert.equal(extraPlugins.find((plugin) => plugin?.slug === "caller-runtime")?.source, "/components/caller-runtime") @@ -94,7 +94,7 @@ const codexProfileInput = { artifacts_path: "/tmp/wp-codebox-artifacts", } const codexProfileRecipe = buildAgentTaskRecipe(codexProfileInput, normalizeTaskInput(codexProfileInput), "trunk") -const codexPlugins = codexProfileRecipe.inputs?.extraPlugins ?? [] +const codexPlugins = codexProfileRecipe.inputs?.extra_plugins ?? [] const codexOverlays = codexProfileRecipe.runtime?.overlays ?? [] const codexProviderPlugin = codexPlugins.find((plugin) => plugin?.slug === "ai-provider-for-openai-codex-oauth-provider") diff --git a/scripts/browser-action-visual-compare-smoke.ts b/scripts/browser-action-visual-compare-smoke.ts index 03e39b3..71fbb57 100644 --- a/scripts/browser-action-visual-compare-smoke.ts +++ b/scripts/browser-action-visual-compare-smoke.ts @@ -40,7 +40,7 @@ const candidateDomSnapshot = join(artifactsRoot, "files", "browser", "dom-snapsh await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-action-visual-fixture", pluginFile: "browser-action-visual-fixture/browser-action-visual-fixture.php", diff --git a/scripts/browser-actions-artifact-smoke.ts b/scripts/browser-actions-artifact-smoke.ts index f4adfab..d86b4c6 100644 --- a/scripts/browser-actions-artifact-smoke.ts +++ b/scripts/browser-actions-artifact-smoke.ts @@ -32,7 +32,7 @@ add_action('wp_footer', function () { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-action-fixture", pluginFile: "browser-action-fixture/browser-action-fixture.php", @@ -162,7 +162,7 @@ assert.equal(review.browser?.probes?.[0]?.assertions?.passed, 2) await writeFile(failingRecipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-action-fixture", pluginFile: "browser-action-fixture/browser-action-fixture.php", diff --git a/scripts/browser-actions-painted-readiness-smoke.ts b/scripts/browser-actions-painted-readiness-smoke.ts index 9001936..1d1cb7d 100644 --- a/scripts/browser-actions-painted-readiness-smoke.ts +++ b/scripts/browser-actions-painted-readiness-smoke.ts @@ -32,7 +32,7 @@ add_action('template_redirect', function () { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-painted-readiness-fixture", pluginFile: "browser-painted-readiness-fixture/browser-painted-readiness-fixture.php", diff --git a/scripts/browser-html-capture-smoke.ts b/scripts/browser-html-capture-smoke.ts index 3f3c98c..6bb9fce 100644 --- a/scripts/browser-html-capture-smoke.ts +++ b/scripts/browser-html-capture-smoke.ts @@ -25,7 +25,7 @@ add_action('wp_footer', function () { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./html-capture-fixture", pluginFile: "html-capture-fixture/html-capture-fixture.php", diff --git a/scripts/browser-probe-artifact-smoke.ts b/scripts/browser-probe-artifact-smoke.ts index f42f1b3..a216de1 100644 --- a/scripts/browser-probe-artifact-smoke.ts +++ b/scripts/browser-probe-artifact-smoke.ts @@ -27,7 +27,7 @@ const prePageScript = 'window.__wpCodeboxPrePageMarker = "before-app-scripts"; w await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-error-fixture", pluginFile: "browser-error-fixture/browser-error-fixture.php", diff --git a/scripts/browser-probe-assertions-smoke.ts b/scripts/browser-probe-assertions-smoke.ts index 94fbf3a..98b8447 100644 --- a/scripts/browser-probe-assertions-smoke.ts +++ b/scripts/browser-probe-assertions-smoke.ts @@ -130,7 +130,7 @@ async function runRecipe(name: string, args: string[]): Promise { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-assertion-fixture", pluginFile: "browser-assertion-fixture/browser-assertion-fixture.php", diff --git a/scripts/browser-probe-web-performance-smoke.ts b/scripts/browser-probe-web-performance-smoke.ts index 406181c..6b05ea9 100644 --- a/scripts/browser-probe-web-performance-smoke.ts +++ b/scripts/browser-probe-web-performance-smoke.ts @@ -114,7 +114,7 @@ async function writeRecipe(name: string, args: string[]): Promise { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-web-performance-fixture", pluginFile: "browser-web-performance-fixture/browser-web-performance-fixture.php", diff --git a/scripts/browser-scenario-artifact-smoke.ts b/scripts/browser-scenario-artifact-smoke.ts index 60057f1..89255cd 100644 --- a/scripts/browser-scenario-artifact-smoke.ts +++ b/scripts/browser-scenario-artifact-smoke.ts @@ -25,7 +25,7 @@ add_action('wp_footer', function () { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./browser-scenario-fixture", pluginFile: "browser-scenario-fixture/browser-scenario-fixture.php", diff --git a/scripts/browser-visual-compare-smoke.ts b/scripts/browser-visual-compare-smoke.ts index 63ebeb1..a9d6400 100644 --- a/scripts/browser-visual-compare-smoke.ts +++ b/scripts/browser-visual-compare-smoke.ts @@ -33,7 +33,7 @@ add_action( 'template_redirect', function () { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./visual-compare-fixture", pluginFile: "visual-compare-fixture/visual-compare-fixture.php", diff --git a/scripts/discovery-command-smoke.ts b/scripts/discovery-command-smoke.ts index 928d775..b14649a 100644 --- a/scripts/discovery-command-smoke.ts +++ b/scripts/discovery-command-smoke.ts @@ -84,7 +84,7 @@ const representativeRecipes = [ { schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [{ + extra_plugins: [{ source: "https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip", pluginFile: "bbpress/bbpress.php", activate: false, diff --git a/scripts/editor-canvas-probe-smoke.ts b/scripts/editor-canvas-probe-smoke.ts index 5e4f8e8..6ab9c1a 100644 --- a/scripts/editor-canvas-probe-smoke.ts +++ b/scripts/editor-canvas-probe-smoke.ts @@ -26,7 +26,7 @@ add_action('wp_footer', function () { await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./editor-canvas-fixture", pluginFile: "editor-canvas-fixture/editor-canvas-fixture.php", diff --git a/scripts/recipe-bench-smoke.ts b/scripts/recipe-bench-smoke.ts index 4f8a0a0..a48de7d 100644 --- a/scripts/recipe-bench-smoke.ts +++ b/scripts/recipe-bench-smoke.ts @@ -26,7 +26,7 @@ writeFileSync(recipePath, `${JSON.stringify({ }, }, inputs: { - extraPlugins: [ + extra_plugins: [ { source: component, slug: "bench-plugin", diff --git a/scripts/recipe-browser-bench-metrics-smoke.ts b/scripts/recipe-browser-bench-metrics-smoke.ts index 93dd99f..36f9375 100644 --- a/scripts/recipe-browser-bench-metrics-smoke.ts +++ b/scripts/recipe-browser-bench-metrics-smoke.ts @@ -19,7 +19,7 @@ await mkdir(join(shapedArtifactsRoot, "files", "browser"), { recursive: true }) await writeFile(recipePath, `${JSON.stringify({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: resolve(repoRoot, "examples", "bench-plugin"), slug: "bench-plugin", diff --git a/scripts/recipe-build-cli-smoke.ts b/scripts/recipe-build-cli-smoke.ts index bfa0d82..23e295d 100644 --- a/scripts/recipe-build-cli-smoke.ts +++ b/scripts/recipe-build-cli-smoke.ts @@ -56,7 +56,7 @@ await writeFile(benchOptionsPath, JSON.stringify({ wordpressVersion: "6.10", blueprint: { steps: [{ step: "login", username: "admin", password: "password" }] }, mounts: [{ source: "/repo/db.php", target: "/wordpress/wp-content/db.php", type: "file" }], - extraPlugins: [{ source: "/repo/plugin", slug: "demo", pluginFile: "demo/demo.php", activate: true }], + extra_plugins: [{ source: "/repo/plugin", slug: "demo", pluginFile: "demo/demo.php", activate: true }], componentId: "demo-component", pluginSlug: "demo", iterations: 7, @@ -82,7 +82,7 @@ assert.deepEqual(benchRecipe.runtime.blueprint, { }) assert.equal(benchRecipe.workflow.steps[0].command, "wordpress.bench") assert.deepEqual(benchRecipe.inputs.mounts, [{ source: "/repo/db.php", target: "/wordpress/wp-content/db.php", mode: "readonly", type: "file" }]) -assert.deepEqual(benchRecipe.inputs.extraPlugins, [{ source: "/repo/plugin", slug: "demo", pluginFile: "demo/demo.php", activate: true }]) +assert.deepEqual(benchRecipe.inputs.extra_plugins, [{ source: "/repo/plugin", slug: "demo", pluginFile: "demo/demo.php", activate: true }]) assert.deepEqual(benchRecipe.workflow.steps[0].args, [ "component-id=demo-component", "plugin-slug=demo", diff --git a/scripts/recipe-dry-run-smoke.ts b/scripts/recipe-dry-run-smoke.ts index 13be36c..0a8e45c 100644 --- a/scripts/recipe-dry-run-smoke.ts +++ b/scripts/recipe-dry-run-smoke.ts @@ -13,7 +13,7 @@ const externalRecipePath = resolve(workspace, "external-recipe.json") const externalDisabledRecipePath = resolve(workspace, "external-disabled-recipe.json") const externalUntrustedHostRecipePath = resolve(workspace, "external-untrusted-host-recipe.json") const externalStrictDigestRecipePath = resolve(workspace, "external-strict-digest-recipe.json") -const legacyExtraPluginsRecipePath = resolve(workspace, "legacy-extra-plugins-recipe.json") +const unsupportedExtraPluginsRecipePath = resolve(workspace, "unsupported-extra-plugins-recipe.json") const distributionRecipePath = resolve(workspace, "distribution-recipe.json") const invalidDistributionRecipePath = resolve(workspace, "invalid-distribution-recipe.json") const invalidSiteSeedRecipePath = resolve(workspace, "invalid-site-seed-recipe.json") @@ -51,7 +51,7 @@ writeFileSync(recipePath, `${JSON.stringify({ }, }, ], - extraPlugins: [ + extra_plugins: [ { source: "../../examples/simple-plugin", slug: "simple-plugin", @@ -157,7 +157,7 @@ const externalRecipe = { wp: "7.0", }, inputs: { - extraPlugins: [ + extra_plugins: [ { source: "https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip", pluginFile: "bbpress/bbpress.php", @@ -183,7 +183,7 @@ const externalRecipe = { writeFileSync(externalRecipePath, `${JSON.stringify(externalRecipe, null, 2)}\n`) writeFileSync(externalDisabledRecipePath, `${JSON.stringify(externalRecipe, null, 2)}\n`) -writeFileSync(legacyExtraPluginsRecipePath, `${JSON.stringify({ +writeFileSync(unsupportedExtraPluginsRecipePath, `${JSON.stringify({ ...externalRecipe, inputs: { extraPlugins: [ @@ -199,7 +199,7 @@ writeFileSync(legacyExtraPluginsRecipePath, `${JSON.stringify({ writeFileSync(externalUntrustedHostRecipePath, `${JSON.stringify({ ...externalRecipe, inputs: { - extraPlugins: [ + extra_plugins: [ { source: "https://evil.example/plugin.zip", slug: "evil-plugin", @@ -211,7 +211,7 @@ writeFileSync(externalUntrustedHostRecipePath, `${JSON.stringify({ writeFileSync(externalStrictDigestRecipePath, `${JSON.stringify({ ...externalRecipe, inputs: { - extraPlugins: [ + extra_plugins: [ { source: "https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip", pluginFile: "bbpress/bbpress.php", @@ -431,20 +431,19 @@ assert.equal(externalOutput.plan.extra_plugins[1].sourceType, "https_zip") assert.equal(externalOutput.plan.extra_plugins[1].provenance.kind, "https_zip") assert.equal(externalOutput.plan.extra_plugins[1].provenance.policy.host, "example.com") -const legacyExtraPluginsResult = spawnSync(process.execPath, [ +const unsupportedExtraPluginsResult = spawnSync(process.execPath, [ cli, "recipe-run", "--recipe", - legacyExtraPluginsRecipePath, + unsupportedExtraPluginsRecipePath, "--dry-run", "--json", ], { cwd: root, encoding: "utf8" }) -assert.equal(legacyExtraPluginsResult.status, 0, legacyExtraPluginsResult.stderr || legacyExtraPluginsResult.stdout) -const legacyExtraPluginsOutput = JSON.parse(legacyExtraPluginsResult.stdout) -assert.equal(legacyExtraPluginsOutput.success, true) -assert.equal(legacyExtraPluginsOutput.plan.extra_plugins.length, 1) -assert.equal(legacyExtraPluginsOutput.plan.extra_plugins[0].slug, "simple-plugin") +assert.equal(unsupportedExtraPluginsResult.status, 1, unsupportedExtraPluginsResult.stderr || unsupportedExtraPluginsResult.stdout) +const unsupportedExtraPluginsOutput = JSON.parse(unsupportedExtraPluginsResult.stdout) +assert.equal(unsupportedExtraPluginsOutput.success, false) +assert.equal(unsupportedExtraPluginsOutput.valid, false) const externalUntrustedHostResult = spawnSync(process.execPath, [ cli, diff --git a/scripts/recipe-heavyweight-plugin-runtime-smoke.ts b/scripts/recipe-heavyweight-plugin-runtime-smoke.ts index 4d80a88..2900a84 100644 --- a/scripts/recipe-heavyweight-plugin-runtime-smoke.ts +++ b/scripts/recipe-heavyweight-plugin-runtime-smoke.ts @@ -49,7 +49,7 @@ register_activation_hook( __FILE__, static function (): void { schema: "wp-codebox/workspace-recipe/v1", runtime: { wp: "7.0" }, inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./heavy-runtime-plugin", slug: "heavy-runtime-plugin", @@ -121,7 +121,7 @@ register_activation_hook( __FILE__, static function (): void { schema: "wp-codebox/workspace-recipe/v1", runtime: { wp: "7.0" }, inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./heavy-runtime-plugin", slug: "heavy-runtime-plugin", @@ -151,7 +151,7 @@ register_activation_hook( __FILE__, static function (): void { schema: "wp-codebox/workspace-recipe/v1", runtime: { wp: "7.0" }, inputs: { - extraPlugins: [ + extra_plugins: [ { source: "./activation-failure-plugin", slug: "activation-failure-plugin", diff --git a/scripts/recipe-run-php-plugin-load-smoke.ts b/scripts/recipe-run-php-plugin-load-smoke.ts index d03eff6..bf3ca7f 100644 --- a/scripts/recipe-run-php-plugin-load-smoke.ts +++ b/scripts/recipe-run-php-plugin-load-smoke.ts @@ -21,7 +21,7 @@ writeFileSync(recipePath, `${JSON.stringify({ blueprint: { steps: [] }, }, inputs: { - extraPlugins: [ + extra_plugins: [ { source: component, slug: "bench-plugin", diff --git a/scripts/recipe-site-seed-smoke.ts b/scripts/recipe-site-seed-smoke.ts index e39677c..44af441 100644 --- a/scripts/recipe-site-seed-smoke.ts +++ b/scripts/recipe-site-seed-smoke.ts @@ -117,7 +117,7 @@ writeFileSync(recipePath, `${JSON.stringify({ mode: "readonly", }, ], - extraPlugins: [ + extra_plugins: [ { source: "./test-seed-importer", slug: "test-seed-importer", diff --git a/scripts/recipe-source-redirect-smoke.ts b/scripts/recipe-source-redirect-smoke.ts index 026eec4..6c4446f 100644 --- a/scripts/recipe-source-redirect-smoke.ts +++ b/scripts/recipe-source-redirect-smoke.ts @@ -55,7 +55,7 @@ try { () => prepareRecipeExtraPlugins({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip", pluginFile: "bbpress/bbpress.php", @@ -74,7 +74,7 @@ try { () => prepareRecipeExtraPlugins({ schema: "wp-codebox/workspace-recipe/v1", inputs: { - extraPlugins: [ + extra_plugins: [ { source: "https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip", pluginFile: "bbpress/bbpress.php", diff --git a/scripts/runtime-action-adapter-smoke.ts b/scripts/runtime-action-adapter-smoke.ts index 8ec24de..879f8ba 100644 --- a/scripts/runtime-action-adapter-smoke.ts +++ b/scripts/runtime-action-adapter-smoke.ts @@ -78,7 +78,7 @@ try { assert.equal(browser.step?.action.kind, "browser") assert.equal(browser.step?.execution.command, "wordpress.browser-actions") assert.deepEqual(browser.step?.execution.args, [ - 'actions-json=[{"type":"navigate","url":"/"}]', + 'steps-json=[{"kind":"navigate","url":"/"}]', "capture=actions,errors", ]) diff --git a/scripts/sandbox-tool-policy-smoke.ts b/scripts/sandbox-tool-policy-smoke.ts index 1954857..e5b476f 100644 --- a/scripts/sandbox-tool-policy-smoke.ts +++ b/scripts/sandbox-tool-policy-smoke.ts @@ -50,6 +50,10 @@ const genericSnapshot = { execution_location: "sandbox", transport_visibility: "both", allowed: true, + runtime: { + environment: "runtime_local", + capability_scope: "runtime_local", + }, risk: "read", action: "workspace.read", }, @@ -59,6 +63,10 @@ const genericSnapshot = { execution_location: "parent", transport_visibility: "hidden", allowed: false, + runtime: { + environment: "control_plane", + capability_scope: "control_plane", + }, risk: "deploy", action: "deploy.production", }, diff --git a/scripts/wordpress-recipe-builders-smoke.ts b/scripts/wordpress-recipe-builders-smoke.ts index 4609e72..9b88081 100644 --- a/scripts/wordpress-recipe-builders-smoke.ts +++ b/scripts/wordpress-recipe-builders-smoke.ts @@ -61,7 +61,7 @@ const benchRecipe = buildWordPressBenchRecipe({ workloads: [{ id: "noop", file: "tests/bench/noop.php" }], lifecycle: { setup: [{ type: "php", code: "update_option('bench_setup', 'yes');" }] }, resetPolicy: { betweenIterations: "object-cache" }, - extraPlugins: [{ source: "/repo/demo-plugin", slug: "demo-plugin", pluginFile: "demo-plugin/demo.php", activate: false }], + extra_plugins: [{ source: "/repo/demo-plugin", slug: "demo-plugin", pluginFile: "demo-plugin/demo.php", activate: false }], mounts: [{ source: "/repo/db.php", target: "/wordpress/wp-content/db.php" }], }) diff --git a/tests/fixtures/task-input-normalization.json b/tests/fixtures/task-input-normalization.json index 1d53d26..f4f0379 100644 --- a/tests/fixtures/task-input-normalization.json +++ b/tests/fixtures/task-input-normalization.json @@ -1,22 +1,4 @@ [ - { - "name": "legacy task maps to canonical goal with empty optionals", - "input": { - "task": " Run a chat-requested sandbox task. " - }, - "normalized": { - "schema": "wp-codebox/task-input/v1", - "version": 1, - "goal": "Run a chat-requested sandbox task.", - "target": {}, - "allowed_tools": [], - "expected_artifacts": [], - "agent_bundles": [], - "sandbox_tool_policy": {}, - "policy": {}, - "context": {} - } - }, { "name": "structured goal deduplicates lists and preserves objects", "input": { diff --git a/tests/smoke-wordpress-plugin.php b/tests/smoke-wordpress-plugin.php index 505dd50..aafd270 100644 --- a/tests/smoke-wordpress-plugin.php +++ b/tests/smoke-wordpress-plugin.php @@ -354,7 +354,7 @@ function get_users( array $args ): array { return array( new WP_User( 11, 'Priva $ability = $GLOBALS['wp_codebox_registered_abilities']['wp-codebox/run-agent-task'] ?? null; $assert( 'run-agent-task ability registered', is_array( $ability ) ); $assert( 'ability is REST visible', true === ( $ability['meta']['show_in_rest'] ?? false ) ); -$assert( 'ability accepts goal or legacy task', array( 'goal' ) === ( $ability['input_schema']['anyOf'][0]['required'] ?? array() ) && array( 'task' ) === ( $ability['input_schema']['anyOf'][1]['required'] ?? array() ) ); +$assert( 'ability requires canonical goal input', array( 'goal' ) === ( $ability['input_schema']['required'] ?? array() ) && ! isset( $ability['input_schema']['properties']['task'] ) ); $assert( 'ability exposes task target schema', isset( $ability['input_schema']['properties']['target']['properties']['kind'] ) ); $assert( 'ability exposes canonical task input metadata schema', 'wp-codebox/task-input/v1' === ( $ability['output_schema']['properties']['task_input']['properties']['schema']['const'] ?? '' ) && 1 === ( $ability['output_schema']['properties']['task_input']['properties']['version']['const'] ?? 0 ) ); $assert( 'ability exposes allowed tools schema', 'array' === ( $ability['input_schema']['properties']['allowed_tools']['type'] ?? '' ) ); @@ -408,7 +408,7 @@ function get_users( array $args ): array { return array( new WP_User( 11, 'Priva $browser_session_ability = $GLOBALS['wp_codebox_registered_abilities']['wp-codebox/create-browser-playground-session'] ?? null; $assert( 'browser Playground session ability registered', is_array( $browser_session_ability ) ); $assert( 'browser Playground session ability is REST visible', true === ( $browser_session_ability['meta']['show_in_rest'] ?? false ) ); -$assert( 'browser Playground session accepts goal or legacy task', array( 'goal' ) === ( $browser_session_ability['input_schema']['anyOf'][0]['required'] ?? array() ) && array( 'task' ) === ( $browser_session_ability['input_schema']['anyOf'][1]['required'] ?? array() ) ); +$assert( 'browser Playground session requires canonical goal input', array( 'goal' ) === ( $browser_session_ability['input_schema']['required'] ?? array() ) && ! isset( $browser_session_ability['input_schema']['properties']['task'] ) ); $assert( 'browser Playground session exposes artifact file schema', 'array' === ( $browser_session_ability['input_schema']['properties']['artifact_files']['type'] ?? '' ) ); $assert( 'browser Playground session exposes site blueprint artifact schema', 'object' === ( $browser_session_ability['input_schema']['properties']['site_blueprint_artifact']['type'] ?? '' ) && 'object' === ( $browser_session_ability['input_schema']['properties']['site_blueprint_artifact']['properties']['blueprint']['type'] ?? '' ) ); $assert( 'browser Playground session declares site blueprint artifact output', 'object' === ( $browser_session_ability['output_schema']['properties']['site_blueprint_artifact']['type'] ?? '' ) ); @@ -421,13 +421,13 @@ function get_users( array $args ): array { return array( new WP_User( 11, 'Priva $browser_materializer_ability = $GLOBALS['wp_codebox_registered_abilities']['wp-codebox/create-browser-materializer-contract'] ?? null; $assert( 'browser materializer contract ability registered', is_array( $browser_materializer_ability ) ); $assert( 'browser materializer contract ability is REST visible', true === ( $browser_materializer_ability['meta']['show_in_rest'] ?? false ) ); -$assert( 'browser materializer contract accepts goal or legacy task', array( 'goal' ) === ( $browser_materializer_ability['input_schema']['anyOf'][0]['required'] ?? array() ) && array( 'task' ) === ( $browser_materializer_ability['input_schema']['anyOf'][1]['required'] ?? array() ) ); +$assert( 'browser materializer contract requires canonical goal input', array( 'goal' ) === ( $browser_materializer_ability['input_schema']['required'] ?? array() ) && ! isset( $browser_materializer_ability['input_schema']['properties']['task'] ) ); $assert( 'browser materializer contract exposes recipe materialization authorization and compact output', 'object' === ( $browser_materializer_ability['output_schema']['properties']['recipe']['type'] ?? '' ) && 'object' === ( $browser_materializer_ability['output_schema']['properties']['materialization']['type'] ?? '' ) && 'object' === ( $browser_materializer_ability['output_schema']['properties']['authorization']['type'] ?? '' ) && 'object' === ( $browser_materializer_ability['output_schema']['properties']['compact']['type'] ?? '' ) ); $browser_task_contract_ability = $GLOBALS['wp_codebox_registered_abilities']['wp-codebox/create-browser-task-contract'] ?? null; $assert( 'browser task contract ability registered', is_array( $browser_task_contract_ability ) ); $assert( 'browser task contract ability is REST visible', true === ( $browser_task_contract_ability['meta']['show_in_rest'] ?? false ) ); -$assert( 'browser task contract accepts goal or legacy task', array( 'goal' ) === ( $browser_task_contract_ability['input_schema']['anyOf'][0]['required'] ?? array() ) && array( 'task' ) === ( $browser_task_contract_ability['input_schema']['anyOf'][1]['required'] ?? array() ) ); +$assert( 'browser task contract requires canonical goal input', array( 'goal' ) === ( $browser_task_contract_ability['input_schema']['required'] ?? array() ) && ! isset( $browser_task_contract_ability['input_schema']['properties']['task'] ) ); $browser_task_phase_kinds = $browser_task_contract_ability['input_schema']['properties']['phases']['items']['properties']['kind']['enum'] ?? array(); $assert( 'browser task contract exposes phase and compact schema', 'array' === ( $browser_task_contract_ability['input_schema']['properties']['phases']['type'] ?? '' ) && in_array( 'materializer', $browser_task_phase_kinds, true ) && in_array( 'agent', $browser_task_phase_kinds, true ) && in_array( 'aggregator', $browser_task_phase_kinds, true ) && in_array( 'host-delegation', $browser_task_phase_kinds, true ) && 'array' === ( $browser_task_contract_ability['output_schema']['properties']['phases']['type'] ?? '' ) && 'object' === ( $browser_task_contract_ability['output_schema']['properties']['compact']['type'] ?? '' ) ); $assert( 'browser task contract exposes explicit host-side phase execution flag', 'boolean' === ( $browser_task_contract_ability['input_schema']['properties']['execute_phases']['type'] ?? '' ) && false === ( $browser_task_contract_ability['input_schema']['properties']['execute_phases']['default'] ?? null ) ); @@ -436,9 +436,9 @@ function get_users( array $args ): array { return array( new WP_User( 11, 'Priva $wp_agent_executor_targets = apply_filters( 'wp_agent_executor_targets', array() ); $assert( 'Agents API executor registry advertises browser and host targets', isset( $agents_api_executor_targets['wp-codebox/browser-playground'] ) && isset( $agents_api_executor_targets['wp-codebox/host-playground'] ) ); $assert( 'WP Agent executor registry alias advertises browser and host targets', isset( $wp_agent_executor_targets['wp-codebox/browser-playground'] ) && isset( $wp_agent_executor_targets['wp-codebox/host-playground'] ) ); -$assert( 'Agents API browser executor target delegates to browser task contract shape', 'wp-codebox/browser-playground' === ( $agents_api_executor_targets['wp-codebox/browser-playground']['id'] ?? '' ) && 'wp-codebox/create-browser-task-contract' === ( $agents_api_executor_targets['wp-codebox/browser-playground']['legacy_abilities'][0] ?? '' ) && 'wp-codebox/browser-task-contract/v1' === ( $agents_api_executor_targets['wp-codebox/browser-playground']['output_schema']['properties']['schema']['const'] ?? '' ) ); -$assert( 'Agents API host executor target delegates to host sandbox result shape', 'wp-codebox/host-playground' === ( $agents_api_executor_targets['wp-codebox/host-playground']['id'] ?? '' ) && 'wp-codebox/run-agent-task' === ( $agents_api_executor_targets['wp-codebox/host-playground']['legacy_abilities'][0] ?? '' ) ); -$assert( 'Agents API executor targets accept generic and legacy task input schemas', 'agents-api/task-input/v1' === ( $agents_api_executor_targets['wp-codebox/browser-playground']['input_schema']['properties']['schema']['const'] ?? '' ) && in_array( 'wp-codebox/task-input/v1', $agents_api_executor_targets['wp-codebox/browser-playground']['input_schema']['x-wp-codebox-accepted-schemas'] ?? array(), true ) ); +$assert( 'Agents API browser executor target delegates to browser task contract shape', 'wp-codebox/browser-playground' === ( $agents_api_executor_targets['wp-codebox/browser-playground']['id'] ?? '' ) && ! isset( $agents_api_executor_targets['wp-codebox/browser-playground']['legacy_abilities'] ) && 'wp-codebox/browser-task-contract/v1' === ( $agents_api_executor_targets['wp-codebox/browser-playground']['output_schema']['properties']['schema']['const'] ?? '' ) ); +$assert( 'Agents API host executor target delegates to host sandbox result shape', 'wp-codebox/host-playground' === ( $agents_api_executor_targets['wp-codebox/host-playground']['id'] ?? '' ) && ! isset( $agents_api_executor_targets['wp-codebox/host-playground']['legacy_abilities'] ) ); +$assert( 'Agents API executor targets accept only generic task input schema', 'agents-api/task-input/v1' === ( $agents_api_executor_targets['wp-codebox/browser-playground']['input_schema']['properties']['schema']['const'] ?? '' ) && ! isset( $agents_api_executor_targets['wp-codebox/browser-playground']['input_schema']['x-wp-codebox-accepted-schemas'] ) ); $sentinel_executor_result = array( 'schema' => 'sentinel/result' ); $assert( 'Agents API executor dispatch leaves unrelated targets untouched', $sentinel_executor_result === apply_filters( 'agents_api_execute_task', $sentinel_executor_result, array( 'goal' => 'Ignore unrelated target.' ), 'other/executor' ) ); @@ -932,23 +932,23 @@ function get_users( array $args ): array { return array( new WP_User( 11, 'Priva $assert( 'browser task compact DTO preserves named materializer phases', ! is_wp_error( $browser_task_contract ) && 'static-site-materializer' === ( $browser_task_contract['compact']['phases'][0]['name'] ?? '' ) && 'materializer' === ( $browser_task_contract['compact']['phases'][0]['kind'] ?? '' ) && 'wp-codebox/browser-materializer-product-dto/v1' === ( $browser_task_contract['compact']['phases'][0]['contract']['schema'] ?? '' ) && '/tmp/materializer-result.json' === ( $browser_task_contract['compact']['phases'][0]['contract']['materialization']['result_path'] ?? '' ) ); $assert( 'browser task compact DTO omits raw runtime payloads', is_string( $browser_task_compact_encoded ) && ! str_contains( $browser_task_compact_encoded, '"pluginData"' ) && ! str_contains( $browser_task_compact_encoded, '"content"' ) && ! str_contains( $browser_task_compact_encoded, '"content_base64"' ) && ! str_contains( $browser_task_compact_encoded, 'data:application/zip;base64' ) && ! str_contains( $browser_task_compact_encoded, 'sk-browser-provider-secret' ) ); -$legacy_materializer_task_contract = call_user_func( +$materializer_task_contract = call_user_func( $browser_task_contract_ability['execute_callback'], array_replace_recursive( $browser_session_input, array( 'materializers' => array( array( - 'goal' => 'Materialize generated browser artifacts through legacy input.', + 'goal' => 'Materialize generated browser artifacts through canonical input.', 'browser_runner' => array( - 'result_path' => '/tmp/legacy-materializer-result.json', + 'result_path' => '/tmp/materializer-result.json', ), ), ), ) ) ); -$assert( 'browser task contract preserves legacy materializers input compatibility', ! is_wp_error( $legacy_materializer_task_contract ) && 1 === count( $legacy_materializer_task_contract['phases'] ?? array() ) && 'materializer' === ( $legacy_materializer_task_contract['phases'][0]['kind'] ?? '' ) && 'wp-codebox/browser-materializer-contract/v1' === ( $legacy_materializer_task_contract['phases'][0]['contract']['schema'] ?? '' ) && '/tmp/legacy-materializer-result.json' === ( $legacy_materializer_task_contract['phases'][0]['contract']['materialization']['result_path'] ?? '' ) ); +$assert( 'browser task contract preserves materializer phase input', ! is_wp_error( $materializer_task_contract ) && 1 === count( $materializer_task_contract['phases'] ?? array() ) && 'materializer' === ( $materializer_task_contract['phases'][0]['kind'] ?? '' ) && 'wp-codebox/browser-materializer-contract/v1' === ( $materializer_task_contract['phases'][0]['contract']['schema'] ?? '' ) && '/tmp/materializer-result.json' === ( $materializer_task_contract['phases'][0]['contract']['materialization']['result_path'] ?? '' ) ); $browser_phase_fanout_bin = $root . '/wp-codebox-browser-phase-fanout-fixture'; file_put_contents( @@ -1159,7 +1159,7 @@ static function ( mixed $result, array $request ): array { ), 'wp-codebox/browser-playground' ); -$assert( 'Agents API browser executor returns the legacy browser task contract shape', ! is_wp_error( $agents_api_browser_executor_result ) && ( $browser_task_contract['schema'] ?? '' ) === ( $agents_api_browser_executor_result['schema'] ?? '' ) && ( $browser_task_contract['session']['id'] ?? '' ) === ( $agents_api_browser_executor_result['session']['id'] ?? '' ) && ( $browser_task_contract['compact']['schema'] ?? '' ) === ( $agents_api_browser_executor_result['compact']['schema'] ?? '' ) ); +$assert( 'Agents API browser executor returns the browser task contract shape', ! is_wp_error( $agents_api_browser_executor_result ) && ( $browser_task_contract['schema'] ?? '' ) === ( $agents_api_browser_executor_result['schema'] ?? '' ) && ( $browser_task_contract['session']['id'] ?? '' ) === ( $agents_api_browser_executor_result['session']['id'] ?? '' ) && ( $browser_task_contract['compact']['schema'] ?? '' ) === ( $agents_api_browser_executor_result['compact']['schema'] ?? '' ) ); $agents_api_browser_executor_metrics_encoded = wp_json_encode( $agents_api_browser_executor_result['execution_metrics'] ?? array() ); $assert( 'Agents API browser executor exposes normalized execution metrics', ! is_wp_error( $agents_api_browser_executor_result ) && 'agents-api/execution-metrics/v1' === ( $agents_api_browser_executor_result['execution_metrics']['schema'] ?? '' ) && 'wp-codebox/browser-playground' === ( $agents_api_browser_executor_result['execution_metrics']['executor'] ?? '' ) && 'contract' === ( $agents_api_browser_executor_result['execution_metrics']['phase'] ?? '' ) && 'pending' === ( $agents_api_browser_executor_result['execution_metrics']['status'] ?? '' ) && isset( $agents_api_browser_executor_result['execution_metrics']['payload_bytes']['task_payload'] ) && '/tmp/wp-codebox-agent-events.jsonl' === ( $agents_api_browser_executor_result['execution_metrics']['diagnostics_refs']['event_stream_path'] ?? '' ) ); $assert( 'Agents API browser executor metrics are secret-safe summaries', is_string( $agents_api_browser_executor_metrics_encoded ) && ! str_contains( $agents_api_browser_executor_metrics_encoded, 'sk-browser-provider-secret' ) && ! str_contains( $agents_api_browser_executor_metrics_encoded, 'data:application/zip;base64' ) && ! str_contains( $agents_api_browser_executor_metrics_encoded, 'pluginData' ) ); @@ -1172,7 +1172,7 @@ static function ( mixed $result, array $request ): array { 'input' => $browser_task_contract_input, ) ); -$assert( 'WP Agent task handler alias returns the legacy browser task contract shape', ! is_wp_error( $wp_agent_browser_task_handler_result ) && ( $browser_task_contract['schema'] ?? '' ) === ( $wp_agent_browser_task_handler_result['schema'] ?? '' ) && ( $browser_task_contract['session']['id'] ?? '' ) === ( $wp_agent_browser_task_handler_result['session']['id'] ?? '' ) ); +$assert( 'WP Agent task handler alias returns the browser task contract shape', ! is_wp_error( $wp_agent_browser_task_handler_result ) && ( $browser_task_contract['schema'] ?? '' ) === ( $wp_agent_browser_task_handler_result['schema'] ?? '' ) && ( $browser_task_contract['session']['id'] ?? '' ) === ( $wp_agent_browser_task_handler_result['session']['id'] ?? '' ) ); $invalid_browser_task_contract = call_user_func( $browser_task_contract_ability['execute_callback'], @@ -2108,7 +2108,7 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $result = $runner->run( array( - 'task' => 'Run a chat-requested sandbox task.', + 'goal' => 'Run a chat-requested sandbox task.', 'sandbox_session_id' => 'parent-job-123', 'session_id' => 'agent-chat-session-456', 'orchestrator' => array( @@ -2152,9 +2152,7 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $assert( 'runner surfaces normalized agent result summary', ! is_wp_error( $result ) && 'wp-codebox/agent-result/v1' === ( $result['agent_result']['schema'] ?? '' ) && false === ( $result['agent_result']['actionable'] ?? true ) && 'no_file_changes' === ( $result['agent_result']['noOpReason'] ?? '' ) && 'files/transcript.json' === ( $result['agent_result']['transcript']['artifact'] ?? '' ) ); $assert( 'runner surfaces single runtime agent task result envelope', ! is_wp_error( $result ) && 'wp-codebox/agent-task-result/v1' === ( $result['agent_task_result']['schema'] ?? '' ) && 614 === ( $result['agent_task_result']['outputs']['issue_number'] ?? 0 ) && ! isset( $result['agent_task_result']['scenarios'] ) && 'files/agent-task-result.json' === ( $result['session']['artifacts']['agent_task_result'] ?? '' ) ); $assert( 'runner surfaces generic completion outcome', ! is_wp_error( $result ) && 'wp-codebox/sandbox-completion-outcome/v1' === ( $result['completion_outcome']['schema'] ?? '' ) && 'partial' === ( $result['completion_outcome']['status'] ?? '' ) && 'files/completion-outcome.json' === ( $result['session']['artifacts']['completion_outcome'] ?? '' ) ); -$assert( 'runner returns normalized task input for legacy task', ! is_wp_error( $result ) && 'wp-codebox/task-input/v1' === ( $result['task_input']['schema'] ?? '' ) && 'Run a chat-requested sandbox task.' === ( $result['task_input']['goal'] ?? '' ) ); -$legacy_task_fixture = $task_input_fixture_by_name['legacy task maps to canonical goal with empty optionals']['normalized'] ?? array(); -$assert( 'runner legacy task matches shared normalization fixture', ! is_wp_error( $result ) && $legacy_task_fixture === ( $result['task_input'] ?? array() ) ); +$assert( 'runner returns normalized canonical task input', ! is_wp_error( $result ) && 'wp-codebox/task-input/v1' === ( $result['task_input']['schema'] ?? '' ) && 'Run a chat-requested sandbox task.' === ( $result['task_input']['goal'] ?? '' ) ); $assert( 'runner invokes recipe-run', str_contains( $captured_command, 'recipe-run' ) ); $assert( 'runner uses node for JS CLI', str_contains( $captured_command, 'node' ) && str_contains( $captured_command, 'wp-codebox.js' ) ); $assert( 'runner passes preview hold to CLI', str_contains( $captured_command, '--preview-hold' ) && str_contains( $captured_command, "'30'" ) ); @@ -2172,43 +2170,6 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $assert( 'runner passes timeout to command runner and recipe', 7200 === $captured_timeout && str_contains( $captured_recipe, 'timeout-seconds=7200' ) ); $assert( 'runner does not pass raw code options', ! str_contains( $captured_command, '--code ' ) && ! str_contains( $captured_command, '--code-file' ) ); -$GLOBALS['wp_codebox_filters']['wp_codebox_normalize_parent_task_request'] = static function ( mixed $normalized, array $input ) use ( $root, $component_contracts ): array { - $request = is_array( $input['parent_request'] ?? null ) ? $input['parent_request'] : array(); - if ( 'caller/sandbox-task-request/v1' !== ( $request['schema'] ?? '' ) ) { - return is_array( $normalized ) ? $normalized : $input; - } - - $task = is_array( $request['task'] ?? null ) ? $request['task'] : array(); - return array_merge( - $input, - array( - 'goal' => (string) ( $task['prompt'] ?? '' ), - 'expected_artifacts' => is_array( $task['expected_artifacts'] ?? null ) ? $task['expected_artifacts'] : array(), - 'policy' => is_array( $task['policy'] ?? null ) ? $task['policy'] : array(), - 'context' => array_merge( is_array( $task['context'] ?? null ) ? $task['context'] : array(), array( 'group_key' => $request['group_key'] ?? '', 'audit_findings' => $request['audit_findings'] ?? array(), 'orchestrator' => $request['orchestrator'] ?? array() ) ), - 'provider' => (string) ( $request['provider'] ?? '' ), - 'model' => (string) ( $request['model'] ?? '' ), - 'provider_plugin_paths' => is_array( $request['provider_plugin_paths'] ?? null ) ? $request['provider_plugin_paths'] : array(), - 'agent_bundles' => is_array( $request['agent_bundles'] ?? null ) ? $request['agent_bundles'] : array(), - 'secret_env' => is_array( $request['secret_env'] ?? null ) ? $request['secret_env'] : array(), - 'mounts' => is_array( $request['mounts'] ?? null ) ? $request['mounts'] : array(), - 'workspaces' => array( - array( 'seed' => array( 'slug' => 'agents-api', 'path' => $root . '/agents-api' ) ), - array( 'seed' => array( 'slug' => 'editable-plugin', 'path' => $root . '/editable-plugin' ) ), - array( 'seed' => array( 'slug' => 'caller-extension', 'path' => $root . '/plugin-root/agents-api' ) ), - ), - 'runtime_stack_mounts' => is_array( $request['runtime_stack_mounts'] ?? null ) ? $request['runtime_stack_mounts'] : array(), - 'runtime_overlays' => is_array( $request['runtime_overlays'] ?? null ) ? $request['runtime_overlays'] : array(), - 'task_timeout_seconds' => (int) ( $request['task_timeout_seconds'] ?? 0 ), - 'max_turns' => (int) ( $request['max_turns'] ?? 0 ), - 'sandbox_session_id' => (string) ( $request['sandbox_session_id'] ?? '' ), - 'orchestrator' => is_array( $request['orchestrator'] ?? null ) ? $request['orchestrator'] : array(), - 'artifacts_path' => (string) ( $request['artifacts'] ?? '' ), - 'component_contracts' => $component_contracts, - ) - ); -}; - $caller_adapter_result = $runner->run( array( 'runtime_task' => array( @@ -2223,8 +2184,14 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p 'owner' => 'caller-adapter', ), ), + 'component_contracts' => $component_contracts, 'parent_request' => array( - 'schema' => 'caller/sandbox-task-request/v1', + 'schema' => 'wp-codebox/task-input/v1', + 'version' => 1, + 'goal' => 'Run the canonical parent Codebox task.', + 'expected_artifacts' => array( 'patch' ), + 'policy' => array( 'kind' => 'audit-remediation' ), + 'context' => array( 'group_key' => 'smoke' ), 'provider' => 'openai', 'model' => 'gpt-5.5', 'provider_plugin_paths' => array( $root . '/ai-provider-test' ), @@ -2280,12 +2247,10 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p 'job_id' => 'caller-job-123', 'agent_task_id' => 'agent-task-123', ), - 'task' => array( - 'prompt' => 'Run the caller-shaped Codebox task.', - 'expected_artifacts' => array( 'patch' ), - 'policy' => array( 'kind' => 'audit-remediation' ), - 'context' => array( 'group_key' => 'smoke' ), + 'workspaces' => array( + array( 'seed' => array( 'slug' => 'editable-plugin', 'path' => $root . '/editable-plugin' ) ), ), + 'component_contracts' => $component_contracts, ), ) ); @@ -2299,17 +2264,17 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p } } $caller_adapter_runtime_task = '' !== $caller_adapter_runtime_task_arg ? json_decode( $caller_adapter_runtime_task_arg, true ) : array(); -$assert( 'runner accepts caller-normalized parent request', ! is_wp_error( $caller_adapter_result ) && true === ( $caller_adapter_result['success'] ?? false ) && 'caller-sandbox-session-123' === ( $caller_adapter_result['session']['id'] ?? '' ) ); -$assert( 'runner maps caller adapter artifacts and orchestrator metadata', ! is_wp_error( $caller_adapter_result ) && $root . '/artifacts/caller-adapter' === ( $caller_adapter_result['artifacts'] ?? '' ) && 'caller-job-123' === ( $caller_adapter_result['session']['orchestrator']['job_id'] ?? '' ) && 'agent-task-123' === ( $caller_adapter_result['session']['orchestrator']['agent_task_id'] ?? '' ) ); -$assert( 'runner returns stable caller adapter diagnostics evidence and metadata refs', ! is_wp_error( $caller_adapter_result ) && in_array( (string) ( $caller_adapter_result['status'] ?? '' ), array( 'completed', 'failed' ), true ) && 'wp-codebox/agent-task-diagnostics/v1' === ( $caller_adapter_result['diagnostics']['schema'] ?? '' ) && 'wp-codebox/agent-task-evidence-refs/v1' === ( $caller_adapter_result['evidence_refs']['schema'] ?? '' ) && 'artifact-bundle-sha256-fixture-2' === ( $caller_adapter_result['evidence_refs']['artifact_bundle_id'] ?? '' ) && 'files/agent-task-result.json' === ( $caller_adapter_result['evidence_refs']['agent_task_result'] ?? '' ) && 'files/transcript.json' === ( $caller_adapter_result['evidence_refs']['transcript'] ?? '' ) && 'wp-codebox/agent-task-run-metadata/v1' === ( $caller_adapter_result['run_metadata']['schema'] ?? '' ) && 'caller-sandbox-session-123' === ( $caller_adapter_result['run_metadata']['sandbox_session_id'] ?? '' ) ); -$assert( 'runner maps caller adapter provider plugins and secrets', in_array( 'provider-plugin-slugs=ai-provider-test', $caller_adapter_step_args, true ) && str_contains( $captured_recipe, 'GITHUB_TOKEN' ) && ! str_contains( $captured_recipe, 'GITHUB_TOKEN=' ) ); +$assert( 'runner accepts canonical parent request', ! is_wp_error( $caller_adapter_result ) && true === ( $caller_adapter_result['success'] ?? false ) && 'caller-sandbox-session-123' === ( $caller_adapter_result['session']['id'] ?? '' ) ); +$assert( 'runner maps canonical parent artifacts and orchestrator metadata', ! is_wp_error( $caller_adapter_result ) && $root . '/artifacts/caller-adapter' === ( $caller_adapter_result['artifacts'] ?? '' ) && 'caller-job-123' === ( $caller_adapter_result['session']['orchestrator']['job_id'] ?? '' ) && 'agent-task-123' === ( $caller_adapter_result['session']['orchestrator']['agent_task_id'] ?? '' ) ); +$assert( 'runner returns stable canonical parent diagnostics evidence and metadata refs', ! is_wp_error( $caller_adapter_result ) && in_array( (string) ( $caller_adapter_result['status'] ?? '' ), array( 'completed', 'failed' ), true ) && 'wp-codebox/agent-task-diagnostics/v1' === ( $caller_adapter_result['diagnostics']['schema'] ?? '' ) && 'wp-codebox/agent-task-evidence-refs/v1' === ( $caller_adapter_result['evidence_refs']['schema'] ?? '' ) && 'artifact-bundle-sha256-fixture-2' === ( $caller_adapter_result['evidence_refs']['artifact_bundle_id'] ?? '' ) && 'files/agent-task-result.json' === ( $caller_adapter_result['evidence_refs']['agent_task_result'] ?? '' ) && 'files/transcript.json' === ( $caller_adapter_result['evidence_refs']['transcript'] ?? '' ) && 'wp-codebox/agent-task-run-metadata/v1' === ( $caller_adapter_result['run_metadata']['schema'] ?? '' ) && 'caller-sandbox-session-123' === ( $caller_adapter_result['run_metadata']['sandbox_session_id'] ?? '' ) ); +$assert( 'runner maps canonical parent provider plugins and secrets', in_array( 'provider-plugin-slugs=ai-provider-test', $caller_adapter_step_args, true ) && str_contains( $captured_recipe, 'GITHUB_TOKEN' ) && ! str_contains( $captured_recipe, 'GITHUB_TOKEN=' ) ); $assert( 'runner preserves multiple runtime agent bundles in recipe inputs and step args', 2 === count( $caller_adapter_recipe['inputs']['agent_bundles'] ?? array() ) && str_contains( implode( "\n", $caller_adapter_step_args ), 'agent-bundles-json=' ) && str_contains( $captured_recipe, 'site-generator-agent.json' ) && str_contains( $captured_recipe, 'repair-agent' ) ); $assert( 'runner passes generic runtime task execution request to sandbox step', is_array( $caller_adapter_runtime_task ) && 'runtime/run-agent-bundle' === ( $caller_adapter_runtime_task['ability'] ?? '' ) && 'static-site-manual-flow' === ( $caller_adapter_runtime_task['input']['flow'] ?? '' ) && true === ( $caller_adapter_runtime_task['input']['wait_for_completion'] ?? false ) && true === ( $caller_adapter_runtime_task['input']['dry_run'] ?? false ) ); -$assert( 'runner maps caller adapter timeout and max turns', 3600 === $captured_timeout && in_array( 'timeout-seconds=3600', $caller_adapter_step_args, true ) && in_array( 'max-turns=8', $caller_adapter_step_args, true ) ); -$assert( 'runner maps caller adapter runtime stack mounts and overlays', '/runtime/agents-api' === ( $caller_adapter_recipe['runtime']['stack']['mounts'][0]['target'] ?? '' ) && 'caller-runtime-overlay' === ( $caller_adapter_recipe['runtime']['overlays'][0]['id'] ?? '' ) ); -$assert( 'runner maps caller adapter workspaces without downstream recipe generation', 3 === count( $caller_adapter_recipe['inputs']['workspaces'] ?? array() ) && ! str_contains( $captured_recipe, 'Use Data Machine Code workspace repos' ) && str_contains( $captured_recipe, 'agents-api' ) ); -$assert( 'runner passes caller adapter task context to sandbox agent', str_contains( $captured_recipe, 'caller-group-key' ) && str_contains( $captured_recipe, 'finding-1' ) && str_contains( $captured_recipe, 'agent-task-123' ) ); -unset( $GLOBALS['wp_codebox_filters']['wp_codebox_normalize_parent_task_request'] ); +$assert( 'runner maps canonical parent timeout and max turns', 3600 === $captured_timeout && in_array( 'timeout-seconds=3600', $caller_adapter_step_args, true ) && in_array( 'max-turns=8', $caller_adapter_step_args, true ) ); +$assert( 'runner maps canonical parent runtime stack mounts and overlays', '/runtime/agents-api' === ( $caller_adapter_recipe['runtime']['stack']['mounts'][0]['target'] ?? '' ) && 'caller-runtime-overlay' === ( $caller_adapter_recipe['runtime']['overlays'][0]['id'] ?? '' ) ); +$assert( 'runner maps canonical parent workspaces without downstream recipe generation', 1 === count( $caller_adapter_recipe['inputs']['workspaces'] ?? array() ) && ! str_contains( $captured_recipe, 'Use Data Machine Code workspace repos' ) ); +$assert( 'runner maps canonical parent component contracts', str_contains( $captured_recipe, 'agents-api' ) && str_contains( $captured_recipe, 'data-machine' ) && str_contains( $captured_recipe, 'data-machine-code' ) ); +$assert( 'runner passes canonical parent task context to sandbox agent', str_contains( $captured_recipe, 'caller-group-key' ) && str_contains( $captured_recipe, 'finding-1' ) && str_contains( $captured_recipe, 'agent-task-123' ) ); $GLOBALS['wp_codebox_options']['blogname'] = 'Parent Seed Site'; $GLOBALS['wp_codebox_options']['active_plugins'] = array( 'agents-api/agents-api.php' ); @@ -2494,7 +2459,7 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $raw_code = $runner->run( array( - 'task' => 'Run a chat-requested sandbox task.', + 'goal' => 'Run a chat-requested sandbox task.', 'artifacts_path' => $root . '/artifacts', 'code' => 'run( array( - 'task' => 'Run a chat-requested sandbox task.', + 'goal' => 'Run a chat-requested sandbox task.', 'artifacts_path' => $root . '/artifacts', 'code_file' => '/tmp/raw.php', ) @@ -2512,7 +2477,7 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $invalid_mount = $runner->run( array( - 'task' => 'Run a chat-requested sandbox task.', + 'goal' => 'Run a chat-requested sandbox task.', 'artifacts_path' => $root . '/artifacts', 'mounts' => array( array( @@ -2526,7 +2491,7 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $invalid_preview_bind = $runner->run( array( - 'task' => 'Run a chat-requested sandbox task.', + 'goal' => 'Run a chat-requested sandbox task.', 'artifacts_path' => $root . '/artifacts', 'preview_bind' => '0.0.0.0', ) @@ -2535,7 +2500,7 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $invalid_preview_url = $runner->run( array( - 'task' => 'Run a chat-requested sandbox task.', + 'goal' => 'Run a chat-requested sandbox task.', 'artifacts_path' => $root . '/artifacts', 'preview_public_url' => 'file:///tmp/preview', ) @@ -2768,7 +2733,10 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p $batch_result = $runner->run_batch( array( - 'tasks' => array( 'Fix issue one.', 'Fix issue two.' ), + 'tasks' => array( + array( 'goal' => 'Fix issue one.' ), + array( 'goal' => 'Fix issue two.' ), + ), 'sandbox_session_id' => 'parent-batch-789', 'artifacts_path' => $root . '/artifacts', 'provider_plugin_paths' => array( $root . '/ai-provider-test' ), @@ -2827,7 +2795,10 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p ); $partial_batch = $failing_batch_runner->run_batch( array( - 'tasks' => array( 'Succeed once.', 'Fail once.' ), + 'tasks' => array( + array( 'goal' => 'Succeed once.' ), + array( 'goal' => 'Fail once.' ), + ), 'artifacts_path' => $root . '/artifacts', ) ); @@ -2997,7 +2968,7 @@ static function ( $result, array $input, array $payload ) use ( $runner_report_p 'wp_codebox_artifacts_root' => $root . '/artifact-network-root', ); -$network_result = $runner->run( array( 'task' => 'Use network-level WP Codebox configuration.' ) ); +$network_result = $runner->run( array( 'goal' => 'Use network-level WP Codebox configuration.' ) ); $assert( 'multisite runner reads network-level options', ! is_wp_error( $network_result ) && str_starts_with( (string) ( $network_result['artifacts'] ?? '' ), $root . '/artifact-network-root' ) ); $GLOBALS['wp_codebox_is_multisite'] = false;