diff --git a/README.md b/README.md index c4db0d5..9e8cc09 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ wp-coding-agents works in two modes with the same setup: Installs without Homeboy still use Data Machine workspaces, flows, jobs, and bridge dispatch. Installs without Kimaki use the selected bridge or terminal runtime; Kimaki-specific routing only applies when Kimaki is selected. +Homeboy setup verification normally proves Homeboy is installed, linked, and advertised through `datamachine_code_homeboy_available`. To prove the full Codebox fleet-cooking path, run the explicit opt-in `scripts/verify-homeboy-codebox-canary.sh` helper from the verification skill. It can call a model, so it requires provider secret environment variable names and validates only read-only status/log/artifact evidence. + ## Quick Start ### Local (macOS / Linux Desktop) diff --git a/lib/homeboy.sh b/lib/homeboy.sh index 83c3a11..7657373 100644 --- a/lib/homeboy.sh +++ b/lib/homeboy.sh @@ -364,4 +364,5 @@ print_homeboy_verification_commands() { echo " homeboy extension show wordpress" echo " homeboy project show " echo " homeboy project components list " + echo " ./scripts/verify-homeboy-codebox-canary.sh --workspace --secret-env --agents-api --agent-runtime --agent-runtime-tools --provider-plugin-path # opt-in model-backed Codebox canary; add --run to execute" } diff --git a/lib/wp-codebox.sh b/lib/wp-codebox.sh index c65cfc2..354b876 100644 --- a/lib/wp-codebox.sh +++ b/lib/wp-codebox.sh @@ -32,8 +32,8 @@ _wp_codebox_latest_tag() { _wp_codebox_header_version() { local main_file="$1" [ -f "$main_file" ] || return 1 - grep -m1 -E '^\s*\*?\s*Version:' "$main_file" \ - | sed -E 's/.*Version:\s*([0-9][0-9.]*).*/\1/' + grep -m1 -E '^[[:space:]]*\*?[[:space:]]*Version:' "$main_file" \ + | sed -E 's/.*Version:[[:space:]]*([0-9][0-9.]*).*/\1/' } update_wp_codebox_plugin_subtree() { diff --git a/scripts/verify-homeboy-codebox-canary.sh b/scripts/verify-homeboy-codebox-canary.sh new file mode 100755 index 0000000..9b36b47 --- /dev/null +++ b/scripts/verify-homeboy-codebox-canary.sh @@ -0,0 +1,384 @@ +#!/bin/bash +# Opt-in Homeboy Codebox fleet canary for wp-coding-agents installs. + +set -euo pipefail + +SCRIPT_NAME="$(basename "$0")" + +RUN=false +WORKSPACE="${HOMEBOY_CANARY_WORKSPACE:-}" +REPO="${HOMEBOY_CANARY_REPO:-wp-coding-agents}" +TASK_URL="${HOMEBOY_CANARY_TASK_URL:-https://github.com/Extra-Chill/wp-coding-agents/issues/190}" +RUN_ID="${HOMEBOY_CANARY_RUN_ID:-homeboy-codebox-canary-$(date -u +%Y%m%d-%H%M%S)}" +MODEL="${HOMEBOY_CANARY_MODEL:-}" +PROVIDER="${HOMEBOY_CANARY_PROVIDER:-openai}" +MAX_TURNS="${HOMEBOY_CANARY_MAX_TURNS:-4}" +CHANNEL="${HOMEBOY_CANARY_CHANNEL:-}" +THREAD="${HOMEBOY_CANARY_THREAD:-}" +ARTIFACT_ROOT="${HOMEBOY_CANARY_ARTIFACT_ROOT:-}" +EVIDENCE_DIR="${HOMEBOY_CANARY_EVIDENCE_DIR:-}" +AGENTS_API="${HOMEBOY_CANARY_AGENTS_API:-}" +AGENT_RUNTIME="${HOMEBOY_CANARY_AGENT_RUNTIME:-}" +AGENT_RUNTIME_TOOLS="${HOMEBOY_CANARY_AGENT_RUNTIME_TOOLS:-}" +HOMEBOY_EXTENSIONS="${HOMEBOY_CANARY_HOMEBOY_EXTENSIONS:-$HOME/.config/homeboy/extensions/wordpress}" +SECRET_ENVS=() +PROVIDER_PLUGIN_PATHS=() + +if [ -n "${HOMEBOY_CANARY_SECRET_ENV:-}" ]; then + IFS=',' read -r -a SECRET_ENVS <<< "${HOMEBOY_CANARY_SECRET_ENV}" +fi + +if [ -n "${HOMEBOY_CANARY_PROVIDER_PLUGIN_PATHS:-}" ]; then + IFS=':' read -r -a PROVIDER_PLUGIN_PATHS <<< "${HOMEBOY_CANARY_PROVIDER_PLUGIN_PATHS}" +fi + +usage() { + cat < --secret-env \\ + --agents-api --agent-runtime --agent-runtime-tools \\ + --provider-plugin-path [options] + +Prints a safe Homeboy Codebox canary command by default. Use --run to execute +the canary and validate terminal status, logs, changed-files, and patch evidence. + +Options: + --run Execute the canary. Default only prints command. + --workspace Existing repo checkout/worktree mounted read-only at /workspace. + --repo Repo slug for Homeboy metadata. Default: wp-coding-agents. + --task-url Tracker URL. Default: issue #190. + --run-id Durable Homeboy run id. Default: generated timestamp id. + --provider Provider id in Codebox config. Default: openai. + --model Optional model override. + --max-turns Low turn cap for the read-only canary. Default: 4. + --secret-env Provider secret env var name to hydrate. Repeatable. + --agents-api Bundled Agents API path from Data Machine. + --agent-runtime Data Machine plugin path. + --agent-runtime-tools Data Machine Code plugin path. + --provider-plugin-path Provider plugin path. Repeatable. + --homeboy-extensions Homeboy WordPress extension path. + --channel Optional Discord channel id for provider routing. + --thread Optional Discord thread id for provider routing. + --artifact-root Optional artifact copy root for Homeboy. + --evidence-dir Optional directory for local validator JSON output. + --help Show this help. + +Secret values are never printed. Pass secret environment variable names only. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --run) + RUN=true + shift + ;; + --workspace) + WORKSPACE="$2" + shift 2 + ;; + --repo) + REPO="$2" + shift 2 + ;; + --task-url) + TASK_URL="$2" + shift 2 + ;; + --run-id) + RUN_ID="$2" + shift 2 + ;; + --provider) + PROVIDER="$2" + shift 2 + ;; + --model) + MODEL="$2" + shift 2 + ;; + --max-turns) + MAX_TURNS="$2" + shift 2 + ;; + --secret-env) + SECRET_ENVS+=("$2") + shift 2 + ;; + --agents-api) + AGENTS_API="$2" + shift 2 + ;; + --agent-runtime) + AGENT_RUNTIME="$2" + shift 2 + ;; + --agent-runtime-tools) + AGENT_RUNTIME_TOOLS="$2" + shift 2 + ;; + --provider-plugin-path) + PROVIDER_PLUGIN_PATHS+=("$2") + shift 2 + ;; + --homeboy-extensions) + HOMEBOY_EXTENSIONS="$2" + shift 2 + ;; + --channel) + CHANNEL="$2" + shift 2 + ;; + --thread) + THREAD="$2" + shift 2 + ;; + --artifact-root) + ARTIFACT_ROOT="$2" + shift 2 + ;; + --evidence-dir) + EVIDENCE_DIR="$2" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "ERROR: unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +fail() { + echo "ERROR: $1" >&2 + exit 1 +} + +require_dir() { + local label="$1" path="$2" + [ -n "$path" ] || fail "$label is required" + [ -d "$path" ] || fail "$label does not exist or is not a directory: $path" +} + +validate_env_name() { + case "$1" in + ''|*[!A-Za-z0-9_]*|[0-9]*) + fail "secret env names must be environment variable names, got: $1" + ;; + esac +} + +[ -n "$WORKSPACE" ] || fail "--workspace is required" +[ -d "$WORKSPACE" ] || fail "workspace does not exist or is not a directory: $WORKSPACE" +[ -n "$REPO" ] || fail "--repo must not be empty" +[ -n "$RUN_ID" ] || fail "--run-id must not be empty" +[[ "$MAX_TURNS" =~ ^[0-9]+$ ]] || fail "--max-turns must be a positive integer" +[ "$MAX_TURNS" -gt 0 ] || fail "--max-turns must be a positive integer" +[ ${#SECRET_ENVS[@]} -gt 0 ] || fail "at least one --secret-env name is required so provider auth is explicit" + +for secret_env in "${SECRET_ENVS[@]}"; do + validate_env_name "$secret_env" +done + +require_dir "--agents-api" "$AGENTS_API" +require_dir "--agent-runtime" "$AGENT_RUNTIME" +require_dir "--agent-runtime-tools" "$AGENT_RUNTIME_TOOLS" +require_dir "--homeboy-extensions" "$HOMEBOY_EXTENSIONS" +[ ${#PROVIDER_PLUGIN_PATHS[@]} -gt 0 ] || fail "at least one --provider-plugin-path is required" +for provider_plugin_path in "${PROVIDER_PLUGIN_PATHS[@]}"; do + require_dir "--provider-plugin-path" "$provider_plugin_path" +done + +if [ "$RUN" = true ] && ! command -v homeboy >/dev/null 2>&1; then + fail "homeboy command was not found" +fi + +if [ -n "$EVIDENCE_DIR" ]; then + TMP_DIR="$EVIDENCE_DIR" + mkdir -p "$TMP_DIR" +else + TMP_DIR="$(mktemp -d)" +fi + +if [ "$RUN" != true ] && [ -z "$EVIDENCE_DIR" ]; then + trap 'rm -rf "$TMP_DIR"' EXIT +fi + +CONFIG_FILE="$TMP_DIR/provider-config.json" +DISPATCH_OUT="$TMP_DIR/dispatch.json" +DISPATCH_STDOUT="$TMP_DIR/dispatch-output.json" +STATUS_OUT="$TMP_DIR/status.json" +LOGS_OUT="$TMP_DIR/logs.json" +ARTIFACTS_OUT="$TMP_DIR/artifacts.json" + +export CANARY_WORKSPACE="$WORKSPACE" +export CANARY_REPO="$REPO" +export CANARY_PROVIDER="$PROVIDER" +export CANARY_MODEL="$MODEL" +export CANARY_MAX_TURNS="$MAX_TURNS" +export CANARY_AGENTS_API="$AGENTS_API" +export CANARY_AGENT_RUNTIME="$AGENT_RUNTIME" +export CANARY_AGENT_RUNTIME_TOOLS="$AGENT_RUNTIME_TOOLS" +export CANARY_HOMEBOY_EXTENSIONS="$HOMEBOY_EXTENSIONS" +export CANARY_CHANNEL="$CHANNEL" +export CANARY_THREAD="$THREAD" +export CANARY_PROVIDER_PLUGIN_PATHS="$(printf '%s\n' "${PROVIDER_PLUGIN_PATHS[@]}")" +export CANARY_SECRET_ENVS="$(printf '%s\n' "${SECRET_ENVS[@]}")" + +python3 - "$CONFIG_FILE" <<'PY' +import json +import os +import sys + +provider_plugin_paths = [ + path for path in os.environ["CANARY_PROVIDER_PLUGIN_PATHS"].splitlines() if path +] +secret_env = [name for name in os.environ["CANARY_SECRET_ENVS"].splitlines() if name] + +config = { + "agents_api": os.environ["CANARY_AGENTS_API"], + "homeboy_extensions": os.environ["CANARY_HOMEBOY_EXTENSIONS"], + "max_turns": int(os.environ["CANARY_MAX_TURNS"]), + "mounts": [ + { + "source": os.environ["CANARY_WORKSPACE"], + "target": "/workspace", + "mode": "readonly", + "metadata": {"kind": "repo-workspace"}, + } + ], + "provider": os.environ["CANARY_PROVIDER"], + "provider_plugin_paths": provider_plugin_paths, + "repo": os.environ["CANARY_REPO"], + "runtime_component_paths": { + "agent_runtime": os.environ["CANARY_AGENT_RUNTIME"], + "agent_runtime_tools": os.environ["CANARY_AGENT_RUNTIME_TOOLS"], + }, + "task_kind": "repo-cooking-canary", + "secret_env": secret_env, + "workspace_root": os.environ["CANARY_WORKSPACE"], +} + +if os.environ.get("CANARY_MODEL"): + config["model"] = os.environ["CANARY_MODEL"] + +routing = {} +if os.environ.get("CANARY_CHANNEL"): + routing["channel"] = os.environ["CANARY_CHANNEL"] +if os.environ.get("CANARY_THREAD"): + routing["thread"] = os.environ["CANARY_THREAD"] +if routing: + routing.update({"client": "discord", "ui": "kimaki", "user_required": False}) + config["routing"] = routing + +with open(sys.argv[1], "w", encoding="utf-8") as handle: + json.dump(config, handle, indent=2) + handle.write("\n") +PY + +PROMPT="Read-only Homeboy Codebox canary for wp-coding-agents. Inspect /workspace only. Do not edit files, do not commit, do not push, and do not create PRs. Return current directory, git branch/status summary, one repo purpose fact, and whether any files changed." + +COMMAND=( + homeboy agent-task dispatch + --run-id "$RUN_ID" + --repo "$REPO" + --cwd "$WORKSPACE" + --backend codebox + --concurrency 1 + --attempts 1 + --task-url "$TASK_URL" + --provider-config "@$CONFIG_FILE" + --prompt "$PROMPT" +) + +for secret_env in "${SECRET_ENVS[@]}"; do + COMMAND+=(--secret-env "$secret_env") +done + +if [ -n "$CHANNEL" ]; then + COMMAND+=(--channel "$CHANNEL") +fi + +if [ -n "$THREAD" ]; then + COMMAND+=(--thread "$THREAD") +fi + +if [ -n "$MODEL" ]; then + COMMAND+=(--model "$MODEL") +fi + +if [ -n "$ARTIFACT_ROOT" ]; then + COMMAND+=(--artifact-root "$ARTIFACT_ROOT") +fi + +print_command() { + printf 'Provider config: %s\n' "$CONFIG_FILE" + printf 'Canary command:' + printf ' %q' "${COMMAND[@]}" + printf '\n' +} + +if [ "$RUN" != true ]; then + print_command + echo "Dry run only. Re-run with --run to execute the model-backed Codebox canary." + exit 0 +fi + +echo "Dispatching Homeboy Codebox canary run: $RUN_ID" +"${COMMAND[@]}" --output "$DISPATCH_OUT" > "$DISPATCH_STDOUT" + +homeboy agent-task status "$RUN_ID" --output "$STATUS_OUT" >/dev/null +homeboy agent-task logs "$RUN_ID" --output "$LOGS_OUT" >/dev/null +homeboy agent-task artifacts "$RUN_ID" --output "$ARTIFACTS_OUT" >/dev/null + +python3 - "$STATUS_OUT" "$LOGS_OUT" "$ARTIFACTS_OUT" <<'PY' +import json +import sys + +status_path, logs_path, artifacts_path = sys.argv[1:] + +with open(status_path, encoding="utf-8") as handle: + status_payload = json.load(handle) +with open(logs_path, encoding="utf-8") as handle: + logs_payload = json.load(handle) +with open(artifacts_path, encoding="utf-8") as handle: + artifacts_payload = json.load(handle) + +status = status_payload.get("data", {}) +if not status_payload.get("success") or status.get("state") != "succeeded": + raise SystemExit(f"canary did not reach succeeded state: {status.get('state')!r}") + +events = logs_payload.get("data", {}).get("events", []) +if not any(event.get("state") == "succeeded" for event in events): + raise SystemExit("canary logs do not include a succeeded event") + +artifacts = artifacts_payload.get("data", {}).get("artifacts", []) +changed = next((item for item in artifacts if item.get("kind") == "codebox-changed-files"), None) +patch = next((item for item in artifacts if item.get("kind") == "codebox-patch"), None) +if changed is None: + raise SystemExit("missing codebox-changed-files artifact") +if patch is None: + raise SystemExit("missing codebox-patch artifact") + +changed_count = changed.get("metadata", {}).get("count") +if changed_count != 0: + raise SystemExit(f"read-only canary changed files: {changed_count}") + +patch_bytes = patch.get("metadata", {}).get("bytes", patch.get("size_bytes")) +if patch_bytes != 0: + raise SystemExit(f"read-only canary produced a non-empty patch: {patch_bytes} bytes") + +print("Canary validation passed") +print(f"Run ID: {status.get('run_id')}") +print(f"State: {status.get('state')}") +print("Changed files: 0") +print("Patch bytes: 0") +PY + +echo "Status evidence: $STATUS_OUT" +echo "Log evidence: $LOGS_OUT" +echo "Artifact evidence: $ARTIFACTS_OUT" +echo "Dispatch output: $DISPATCH_STDOUT" diff --git a/skills/wp-coding-agents-setup-verify/SKILL.md b/skills/wp-coding-agents-setup-verify/SKILL.md index b8c7ab0..5d39a36 100644 --- a/skills/wp-coding-agents-setup-verify/SKILL.md +++ b/skills/wp-coding-agents-setup-verify/SKILL.md @@ -130,6 +130,8 @@ Verify the selected runtime can be started manually in the terminal or over SSH. ### `verify-homeboy` +This overlay proves Homeboy is installed, linked, and advertised to Data Machine. It does **not** prove the repo-aware Homeboy Codebox `agent-task` path can launch a sandbox, hydrate provider auth, mount the workspace at `/workspace`, and return patch/change evidence. + ```bash homeboy --version homeboy extension list @@ -156,6 +158,60 @@ DMC repo@branch worktrees = skipped by default Do not create `homeboy.json` in the WordPress site root to fix a missing Homeboy project. +### `verify-homeboy-codebox-canary` + +Use this explicit opt-in overlay when the install should prove the advertised Homeboy fleet-cooking path actually runs through Codebox. This can call a model and requires provider secret **environment variable names**, so do not run it as part of a casual setup smoke check and never paste secret values into the command. + +The canary is intentionally bounded: + +- read-only `/workspace` mount +- no file edits, commits, pushes, or PRs in the prompt +- one task, one attempt, concurrency `1` +- low `--max-turns` +- status/log/artifact validation only + +Example for a WordPress Studio install with Data Machine-bundled Agents API: + +```bash +./scripts/verify-homeboy-codebox-canary.sh \ + --workspace /path/to/existing/repo-or-dmc-worktree \ + --repo wp-coding-agents \ + --task-url https://github.com/Extra-Chill/wp-coding-agents/issues/190 \ + --secret-env OPENAI_API_KEY \ + --agents-api /path/to/wp-content/plugins/data-machine/vendor/wordpress/agents-api \ + --agent-runtime /path/to/wp-content/plugins/data-machine \ + --agent-runtime-tools /path/to/wp-content/plugins/data-machine-code \ + --provider-plugin-path /path/to/wp-content/plugins/ai-provider-for-openai \ + --homeboy-extensions "$HOME/.config/homeboy/extensions/wordpress" \ + --model gpt-4.1-mini \ + --max-turns 4 +``` + +The command above prints the resolved dispatch shape without executing it. Add `--run` only when the operator intentionally wants to spend a model call and has the named secret env var available to Homeboy: + +```bash +./scripts/verify-homeboy-codebox-canary.sh ... --run +``` + +Passing criteria: + +- `homeboy agent-task status ` reports terminal `succeeded` +- `homeboy agent-task logs ` includes a succeeded task event +- `homeboy agent-task artifacts ` includes `codebox-changed-files` +- `codebox-changed-files.metadata.count` is `0` +- artifacts include `codebox-patch` +- `codebox-patch` is empty (`metadata.bytes` or `size_bytes` is `0`) + +Actionable failure interpretation: + +- Missing Homeboy extension/provider: fix `homeboy extension list` / `homeboy agent-task providers` before rerunning. +- Missing runtime component defaults: pass explicit `--agents-api`, `--agent-runtime`, and `--agent-runtime-tools` paths instead of relying on discovery. +- Stale standalone Agents API mismatch: point `--agents-api` at Data Machine's bundled `vendor/wordpress/agents-api` path and avoid old standalone plugin paths. +- Missing provider secret env names: pass `--secret-env NAME`; pass only env var names, never token values. +- Missing `/workspace` mount: verify the provider config contains a read-only mount with `target: "/workspace"` and `source` set to the existing repo/worktree path. + +This canary is stronger than `datamachine_code_homeboy_available=1`: that option means Data Machine should advertise Homeboy guidance, while the canary proves the Codebox executor returned durable run, log, changed-files, and patch evidence for a repo-aware task. + ### `verify-codex-codebox-provider` Use this only for the Codebox minion provider-auth path. Do not configure WP AI Gateway just because Codex is involved. diff --git a/tests/homeboy-codebox-canary.sh b/tests/homeboy-codebox-canary.sh new file mode 100755 index 0000000..891d3ae --- /dev/null +++ b/tests/homeboy-codebox-canary.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# tests/homeboy-codebox-canary.sh — unit test for Homeboy Codebox canary command construction. +set -eu + +SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +mkdir -p \ + "$TMP/bin" \ + "$TMP/workspace" \ + "$TMP/agents-api" \ + "$TMP/data-machine" \ + "$TMP/data-machine-code" \ + "$TMP/homeboy-wordpress" \ + "$TMP/provider-openai" + +cat > "$TMP/bin/homeboy" <<'SH' +#!/bin/sh +set -eu + +write_json_output() { + file="" + while [ "$#" -gt 0 ]; do + if [ "$1" = "--output" ]; then + file="$2" + shift 2 + continue + fi + shift + done + if [ -n "$file" ]; then + cat > "$file" + else + cat >/dev/null + fi +} + +if [ "$1 $2" = "agent-task dispatch" ]; then + printf '%s\n' "$@" > "$HOMEBOY_CAPTURE_ARGS" + config="" + while [ "$#" -gt 0 ]; do + if [ "$1" = "--provider-config" ]; then + config="$2" + break + fi + shift + done + config="${config#@}" + cp "$config" "$HOMEBOY_CAPTURE_CONFIG" + write_json_output "$@" <<'JSON' +{"success":true,"data":{"run_id":"canary-test-run"}} +JSON + exit 0 +fi + +if [ "$1 $2" = "agent-task status" ]; then + write_json_output "$@" <<'JSON' +{"success":true,"data":{"run_id":"canary-test-run","state":"succeeded"}} +JSON + exit 0 +fi + +if [ "$1 $2" = "agent-task logs" ]; then + write_json_output "$@" <<'JSON' +{"success":true,"data":{"events":[{"state":"succeeded","task_id":"canary"}]}} +JSON + exit 0 +fi + +if [ "$1 $2" = "agent-task artifacts" ]; then + write_json_output "$@" <<'JSON' +{"success":true,"data":{"artifacts":[{"kind":"codebox-changed-files","metadata":{"count":0}},{"kind":"codebox-patch","metadata":{"bytes":0}}]}} +JSON + exit 0 +fi + +exit 2 +SH +chmod +x "$TMP/bin/homeboy" + +HOMEBOY_CAPTURE_ARGS="$TMP/args.log" +HOMEBOY_CAPTURE_CONFIG="$TMP/provider-config.json" +export HOMEBOY_CAPTURE_ARGS HOMEBOY_CAPTURE_CONFIG +PATH="$TMP/bin:$PATH" + +assert_contains() { + local needle="$1" file="$2" + if ! grep -qF -- "$needle" "$file"; then + echo "FAIL: expected '$needle' in $file" + cat "$file" + exit 1 + fi +} + +assert_json() { + python3 - "$HOMEBOY_CAPTURE_CONFIG" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as handle: + config = json.load(handle) + +assert config["agents_api"].endswith("/agents-api") +assert config["runtime_component_paths"]["agent_runtime"].endswith("/data-machine") +assert config["runtime_component_paths"]["agent_runtime_tools"].endswith("/data-machine-code") +assert config["homeboy_extensions"].endswith("/homeboy-wordpress") +assert config["provider"] == "openai" +assert config["model"] == "gpt-4.1-mini" +assert config["max_turns"] == 2 +assert config["secret_env"] == ["OPENAI_API_KEY"] +assert config["mounts"] == [ + { + "source": config["workspace_root"], + "target": "/workspace", + "mode": "readonly", + "metadata": {"kind": "repo-workspace"}, + } +] +assert config["provider_plugin_paths"] and config["provider_plugin_paths"][0].endswith("/provider-openai") +PY +} + +OUTPUT="$TMP/output.log" +"$SCRIPT_DIR/scripts/verify-homeboy-codebox-canary.sh" \ + --run \ + --workspace "$TMP/workspace" \ + --repo wp-coding-agents \ + --task-url https://github.com/Extra-Chill/wp-coding-agents/issues/190 \ + --run-id canary-test-run \ + --secret-env OPENAI_API_KEY \ + --agents-api "$TMP/agents-api" \ + --agent-runtime "$TMP/data-machine" \ + --agent-runtime-tools "$TMP/data-machine-code" \ + --homeboy-extensions "$TMP/homeboy-wordpress" \ + --provider-plugin-path "$TMP/provider-openai" \ + --model gpt-4.1-mini \ + --max-turns 2 \ + > "$OUTPUT" + +assert_contains "--secret-env" "$HOMEBOY_CAPTURE_ARGS" +assert_contains "OPENAI_API_KEY" "$HOMEBOY_CAPTURE_ARGS" +assert_contains "--backend" "$HOMEBOY_CAPTURE_ARGS" +assert_contains "codebox" "$HOMEBOY_CAPTURE_ARGS" +assert_contains "Canary validation passed" "$OUTPUT" +assert_contains "Changed files: 0" "$OUTPUT" +assert_json + +if "$SCRIPT_DIR/scripts/verify-homeboy-codebox-canary.sh" \ + --workspace "$TMP/workspace" \ + --repo wp-coding-agents \ + --run-id invalid-secret-test \ + --secret-env 'OPENAI_API_KEY=secret' \ + --agents-api "$TMP/agents-api" \ + --agent-runtime "$TMP/data-machine" \ + --agent-runtime-tools "$TMP/data-machine-code" \ + --homeboy-extensions "$TMP/homeboy-wordpress" \ + --provider-plugin-path "$TMP/provider-openai" \ + > "$TMP/invalid.log" 2>&1; then + echo "FAIL: invalid secret env value should be rejected" + exit 1 +fi +assert_contains "secret env names must be environment variable names" "$TMP/invalid.log" + +echo "OK: Homeboy Codebox canary command construction"