diff --git a/actions/setup/js/pi_provider.cjs b/actions/setup/js/pi_provider.cjs index 4238479a69d..936e8e22d98 100644 --- a/actions/setup/js/pi_provider.cjs +++ b/actions/setup/js/pi_provider.cjs @@ -82,6 +82,18 @@ function resolveGatewayUrl(provider) { return `http://api-proxy:${port}`; } +/** + * Resolve the AWF /reflect URL. + * + * /reflect is exposed on the api-proxy management port and is not provider-specific. + * + * @param {string} model + * @returns {string} + */ +function resolveReflectUrl(model) { + return AWF_API_PROXY_REFLECT_URL; +} + /** * Register a Pi provider and any aliases. * @@ -180,6 +192,7 @@ function piProviderExtension(pi) { pi.on("agent_start", async () => { const model = getConfiguredModel(); const provider = extractProviderFromModel(model); + const reflectUrl = resolveReflectUrl(model); if (provider) { const gatewayUrl = resolveGatewayUrl(provider); @@ -195,7 +208,7 @@ function piProviderExtension(pi) { // Fetch AWF API proxy reflection data before the agent runs to capture initial proxy state. // This is best-effort: failures are logged but do not affect the agent session. await fetchAWFReflect({ - reflectUrl: AWF_API_PROXY_REFLECT_URL, + reflectUrl, outputPath: AWF_REFLECT_OUTPUT_PATH, timeoutMs: AWF_REFLECT_TIMEOUT_MS, modelsTimeoutMs: AWF_MODELS_URL_TIMEOUT_MS, @@ -204,10 +217,13 @@ function piProviderExtension(pi) { }); pi.on("agent_end", async () => { + const model = getConfiguredModel(); + const reflectUrl = resolveReflectUrl(model); + // Fetch AWF API proxy reflection data after the agent finishes for the post-run step summary. // This is best-effort: failures are logged but do not affect the agent exit code. await fetchAWFReflect({ - reflectUrl: AWF_API_PROXY_REFLECT_URL, + reflectUrl, outputPath: AWF_REFLECT_OUTPUT_PATH, timeoutMs: AWF_REFLECT_TIMEOUT_MS, modelsTimeoutMs: AWF_MODELS_URL_TIMEOUT_MS, @@ -220,4 +236,5 @@ module.exports = piProviderExtension; module.exports.getConfiguredModel = getConfiguredModel; module.exports.extractProviderFromModel = extractProviderFromModel; module.exports.resolveGatewayUrl = resolveGatewayUrl; +module.exports.resolveReflectUrl = resolveReflectUrl; module.exports.registerConfiguredProviders = registerConfiguredProviders; diff --git a/actions/setup/js/pi_provider.test.cjs b/actions/setup/js/pi_provider.test.cjs index d2adeb24dbf..6249f0caabb 100644 --- a/actions/setup/js/pi_provider.test.cjs +++ b/actions/setup/js/pi_provider.test.cjs @@ -58,6 +58,13 @@ describe("pi_provider.cjs", () => { ]); }); + it("resolves reflect URL from provider model", () => { + expect(module.resolveReflectUrl("copilot/claude-sonnet-4")).toBe("http://api-proxy:10000/reflect"); + expect(module.resolveReflectUrl("openai/gpt-5")).toBe("http://api-proxy:10000/reflect"); + expect(module.resolveReflectUrl("custom/provider-model")).toBe("http://api-proxy:10000/reflect"); + expect(module.resolveReflectUrl("claude-sonnet-4")).toBe("http://api-proxy:10000/reflect"); + }); + it("logs the configured provider using GH_AW_PI_MODEL during agent_start", async () => { process.env.GH_AW_PI_MODEL = "copilot/claude-sonnet-4"; global.fetch = vi.fn().mockRejectedValue(new Error("network disabled")); @@ -74,5 +81,6 @@ describe("pi_provider.cjs", () => { await handlers.agent_start(); expect(stderrOutput.some(line => line.includes("provider=copilot model=copilot/claude-sonnet-4"))).toBe(true); + expect(global.fetch).toHaveBeenCalledWith("http://api-proxy:10000/reflect", expect.any(Object)); }); }); diff --git a/scratchpad/smoke-copilot-vs-pi-awf-config-diff.md b/scratchpad/smoke-copilot-vs-pi-awf-config-diff.md new file mode 100644 index 00000000000..2cb067493f0 --- /dev/null +++ b/scratchpad/smoke-copilot-vs-pi-awf-config-diff.md @@ -0,0 +1,71 @@ +# AWF configuration diff: smoke-copilot vs smoke-pi (`*.lock.yml`) + +This note compares: + +- `/home/runner/work/gh-aw/gh-aw/.github/workflows/smoke-copilot.lock.yml` +- `/home/runner/work/gh-aw/gh-aw/.github/workflows/smoke-pi.lock.yml` + +Line references below are from the current lockfile snapshot in this branch and may shift after future workflow recompiles. + +## Top-level workflow env + +No differences were found in top-level `env:`. Both workflows set the same OTEL/GH_AW telemetry variables. + +## AWF execution env differences (agent job) + +Copilot-specific env present in `smoke-copilot.lock.yml` but not in `smoke-pi.lock.yml`: + +- `GH_AW_MCP_CONFIG` (`smoke-copilot.lock.yml:1801`) +- `GITHUB_MCP_SERVER_TOKEN` (`smoke-copilot.lock.yml:1811`) +- `COPILOT_AGENT_RUNNER_TYPE` (`smoke-copilot.lock.yml:1797`) +- `GITHUB_COPILOT_INTEGRATION_ID` (`smoke-copilot.lock.yml:1809`) +- Also present only for copilot execute path: + - `AWF_REFLECT_ENABLED` + - `COPILOT_API_KEY` + - `COPILOT_MODEL` + - `GITHUB_API_URL` + - `GITHUB_HEAD_REF` + - `GITHUB_REF_NAME` + - `GITHUB_SERVER_URL` + - `XDG_CONFIG_HOME` + +Pi-specific env present in `smoke-pi.lock.yml` but not in `smoke-copilot.lock.yml`: + +- `GH_AW_PI_MODEL` (`smoke-pi.lock.yml:848`) +- `PI_CODING_AGENT_DIR` (`smoke-pi.lock.yml:860`) + +## AWF command/CLI argument differences + +`awf` invocation in copilot includes an extra artifact mount not present in pi: + +- Copilot has `--mount "${RUNNER_TEMP}/gh-aw/safeoutputs/upload-artifacts:...:rw"` (`smoke-copilot.lock.yml:1793`) +- Pi does not include this mount (`smoke-pi.lock.yml:843`) + +Inner engine command executed via `awf` differs significantly: + +- Copilot runs `copilot_harness.cjs` + `/usr/local/bin/copilot` with copilot flags: + - `--disable-builtin-mcps` + - `--autopilot` + - `--max-autopilot-continues 2` + - `--allow-all-tools` + - `--allow-all-paths` + - `--no-custom-instructions` + - (`smoke-copilot.lock.yml:1794`) +- Pi runs `pi --print --mode json --no-session --model ... --extension ...`: + - no copilot-equivalent autopilot/allow-all-paths/no-custom-instructions flags + - (`smoke-pi.lock.yml:844`) + +## Guard/integrity policy differences around AWF execution + +- Copilot path configures `GH_AW_MIN_INTEGRITY: approved` (`smoke-copilot.lock.yml:619`) and `CLI_PROXY_POLICY` with `"min-integrity":"approved"` (`smoke-copilot.lock.yml:1772`) +- Pi path configures `GH_AW_MIN_INTEGRITY: none` (`smoke-pi.lock.yml:480`) and `CLI_PROXY_POLICY` with `"min-integrity":"none"` (`smoke-pi.lock.yml:826`) + +## Conclusion (focused on env/CLI mismatch) + +The strongest configuration mismatches tied to AWF behavior are: + +1. Pi execute env lacks copilot-path variables used for MCP wiring/session integration (`GH_AW_MCP_CONFIG`, `GITHUB_MCP_SERVER_TOKEN`, integration vars). +2. Pi executes a different engine command (`pi ...`) with a different CLI contract than copilot harness flags. +3. Pi runs under a less strict integrity policy (`none` vs `approved`), which changes guard behavior around tool access/proxying. + +These are the primary environment/CLI differences that explain why AWF can be correctly wired for copilot while pi behaves differently.