Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,23 @@ For Homeboy executor integration, `request.json` may use this shape:

WP Codebox normalizes the task input, writes the private temporary recipe, runs `wp-codebox recipe-run`, then deletes temporary recipe/seed files. The JSON response keeps `schema: "wp-codebox/agent-task-run/v1"` and includes stable top-level `status`, `session`, `artifacts`, `diagnostics`, `evidence_refs`, `run_metadata`, `completion_outcome`, and raw `run` fields. Secret values are never accepted in the request or returned in the response; `secret_env` carries names only.

`runtime_overlay_profiles` is a convenience layer for reusable runtime stack examples. The raw primitives remain `provider_plugin_paths`, `runtime_stack_mounts`, `runtime_overlays`, `secret_env`, and `runtime_component_paths`; profiles only expand those fields before the private recipe is written.

The built-in `codex-subscription` profile is an example of packaging a provider plugin plus a scoped bundled-library overlay for subscription-backed model access. It expands to:

- `provider_plugin_paths`: the OpenAI/Codex provider plugin checkout from `WP_CODEBOX_CODEX_PROVIDER_PLUGIN_PATH`
- `runtime_overlays`: a `php-ai-client` bundled-library overlay from `WP_CODEBOX_PHP_AI_CLIENT_PATH` using `strategy: "wordpress-scoped-bundle"`

Prepare the profile checkouts before running it:

```bash
export WP_CODEBOX_CODEX_PROVIDER_PLUGIN_PATH=/path/to/ai-provider-for-openai
export WP_CODEBOX_PHP_AI_CLIENT_PATH=/path/to/php-ai-client
composer install --working-dir "$WP_CODEBOX_PHP_AI_CLIENT_PATH"
```

Callers can skip the profile and pass the expanded fields directly when they own a different provider/library stack. Parent orchestrators such as Homeboy select the profile through the generic `runtime_overlay_profiles` field; WP Codebox still owns only recipe expansion, sandbox lifecycle, and artifact capture.

Consumers that need a stable interpretation layer can import `normalizeAgentTaskRunResult()` from `@automattic/wp-codebox-core`. It accepts the current `agent-task-run` response and compatibility aliases from older orchestrator integrations, including `agentResult`, `agent_result`, `completionOutcome`, `completion_outcome`, and nested `metadata.recipe_run` records. The returned `wp-codebox/agent-task-run-result/v1` envelope normalizes `completed`/`success` into `succeeded` or `failed`, exposes terminal statuses such as `no_op`, `timeout`, `provider_error`, and `unable_to_remediate`, groups artifact bundle, changed-files, patch, transcript, log, and runtime refs, and includes no-op/failure metadata for parent schedulers.

Apply-back is intentionally not part of `run-agent-task`. Sandbox execution returns proposed outputs and evidence. `list-artifacts`, `get-artifact`, and `discard-artifact` manage captured artifact bundles under the configured artifact root. `apply-approved-artifact` is the lower-level adapter/test API: it validates `artifact_id` plus an explicit `approved_files[]` list against canonical `changed-files.json`, recomputes the artifact content digest from `changed-files.json` and the exact `patch.diff` the reviewer approved, delegates to the `wp_codebox_apply_approved_artifact` filter, and requires the adapter to return `wp-codebox/apply-result/v1`. PR creation, direct deploy, package export, and bot identity policy live in adapters behind that reviewed boundary.
Expand Down
30 changes: 23 additions & 7 deletions packages/cli/src/commands/agent-task-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,24 +206,28 @@ interface RuntimeOverlayProfileDefaults {
runtimeOverlays: Array<Record<string, unknown>>
}

const CODEX_SUBSCRIPTION_PROFILE = "codex-subscription"
const CODEX_PROVIDER_PLUGIN_ENV = "WP_CODEBOX_CODEX_PROVIDER_PLUGIN_PATH"
const CODEX_PHP_AI_CLIENT_ENV = "WP_CODEBOX_PHP_AI_CLIENT_PATH"

function runtimeOverlayProfileDefaults(input: AgentTaskRunInput): RuntimeOverlayProfileDefaults {
const profiles = stringList(input.runtime_overlay_profiles)
if (profiles.length === 0) return { providerPluginPaths: [], runtimeOverlays: [] }

const defaults: RuntimeOverlayProfileDefaults = { providerPluginPaths: [], runtimeOverlays: [] }
for (const profile of profiles) {
if (profile === "codex-subscription") {
defaults.providerPluginPaths.push(requiredProfilePath("codex-subscription", "WP_CODEBOX_CODEX_PROVIDER_PLUGIN_PATH", [
if (profile === CODEX_SUBSCRIPTION_PROFILE) {
defaults.providerPluginPaths.push(requiredProfilePath(CODEX_SUBSCRIPTION_PROFILE, CODEX_PROVIDER_PLUGIN_ENV, [
"~/Developer/ai-provider-for-openai@codex-oauth-provider",
"~/Developer/ai-provider-for-openai",
]))
], hasComposerPackage))
defaults.runtimeOverlays.push({
kind: "bundled-library",
library: "php-ai-client",
source: requiredProfilePath("codex-subscription", "WP_CODEBOX_PHP_AI_CLIENT_PATH", [
source: requiredProfilePath(CODEX_SUBSCRIPTION_PROFILE, CODEX_PHP_AI_CLIENT_ENV, [
"~/Developer/php-ai-client@custom-provider-auth",
"~/Developer/php-ai-client",
]),
], hasInstalledComposerPackage),
target: "/wordpress/wp-includes/php-ai-client",
strategy: "wordpress-scoped-bundle",
metadata: { profile, component: "php-ai-client", ref: "custom-provider-auth" },
Expand All @@ -235,18 +239,30 @@ function runtimeOverlayProfileDefaults(input: AgentTaskRunInput): RuntimeOverlay
return defaults
}

function requiredProfilePath(profile: string, envName: string, candidates: string[]): string {
const resolved = stringValue(process.env[envName]) || candidates.map(resolveProfilePathCandidate).find((candidate) => existsSync(candidate)) || ""
function requiredProfilePath(profile: string, envName: string, candidates: string[], isUsablePath: (path: string) => boolean = existsSync): string {
const explicit = stringValue(process.env[envName])
const resolved = explicit || candidates.map(resolveProfilePathCandidate).find((candidate) => isUsablePath(candidate)) || ""
if (!resolved) {
throw new Error(`${profile} runtime overlay profile requires ${envName} or one of: ${candidates.join(", ")}`)
}
if (!isUsablePath(resolved)) {
throw new Error(`${profile} runtime overlay profile path from ${envName} is not prepared: ${resolved}`)
}
return resolved
}

function resolveProfilePathCandidate(candidate: string): string {
return candidate.startsWith("~/") ? join(process.env.HOME || "", candidate.slice(2)) : candidate
}

function hasComposerPackage(candidate: string): boolean {
return existsSync(join(candidate, "composer.json"))
}

function hasInstalledComposerPackage(candidate: string): boolean {
return hasComposerPackage(candidate) && existsSync(join(candidate, "vendor"))
}

function runtimeOverlays(input: AgentTaskRunInput, profile: RuntimeOverlayProfileDefaults): Array<Record<string, unknown>> | undefined {
const overlays = [...profile.runtimeOverlays, ...(Array.isArray(input.runtime_overlays) ? input.runtime_overlays : [])]
return overlays.length > 0 ? overlays : undefined
Expand Down
10 changes: 6 additions & 4 deletions scripts/agent-task-run-runtime-components-smoke.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import assert from "node:assert/strict"
import { mkdirSync } from "node:fs"
import { mkdtempSync } from "node:fs"
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"
import { join } from "node:path"
import { tmpdir } from "node:os"
import { normalizeTaskInput } from "@automattic/wp-codebox-core"
Expand Down Expand Up @@ -44,7 +43,9 @@ const profileRoot = mkdtempSync(join(tmpdir(), "wp-codebox-agent-task-profile-")
const codexProviderPath = join(profileRoot, "ai-provider-for-openai@codex-oauth-provider")
const phpAiClientPath = join(profileRoot, "php-ai-client@custom-provider-auth")
mkdirSync(codexProviderPath, { recursive: true })
mkdirSync(phpAiClientPath, { recursive: true })
mkdirSync(join(phpAiClientPath, "vendor"), { recursive: true })
writeFileSync(join(codexProviderPath, "composer.json"), "{}\n")
writeFileSync(join(phpAiClientPath, "composer.json"), "{}\n")
process.env.WP_CODEBOX_CODEX_PROVIDER_PLUGIN_PATH = codexProviderPath
process.env.WP_CODEBOX_PHP_AI_CLIENT_PATH = phpAiClientPath

Expand All @@ -59,7 +60,8 @@ const codexProfileRecipe = buildAgentTaskRecipe(codexProfileInput, normalizeTask
const codexPlugins = codexProfileRecipe.inputs?.extraPlugins ?? []
const codexOverlays = codexProfileRecipe.runtime?.overlays ?? []

assert.equal(codexPlugins.find((plugin) => plugin?.slug === "ai-provider-for-openai-codex-oauth-provider")?.source, codexProviderPath)
const codexProviderPlugin = codexPlugins.find((plugin) => plugin?.slug === "ai-provider-for-openai-codex-oauth-provider")
assert.equal(codexProviderPlugin?.source, "/tmp/wp-codebox-artifacts/prepared-plugins/ai-provider-for-openai-codex-oauth-provider")
assert.equal(codexOverlays[0]?.kind, "bundled-library")
assert.equal(codexOverlays[0]?.library, "php-ai-client")
assert.equal(codexOverlays[0]?.source, phpAiClientPath)
Expand Down