diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 6a935f9aaf1..60e2c19224e 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"823c4f91addd9fdc240c5fb8b5fca2c36966064b18deef3684e423bd0d384dab","agent_id":"pi"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"cc975ed0d58d799d184035327af359c4a3225a02a95981b5d20c04fae0f857bf","agent_id":"pi","agent_model":"copilot/claude-sonnet-4-20250514"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.35"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.3"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -128,9 +128,9 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "pi" GH_AW_INFO_ENGINE_NAME: "Pi" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_CUSTOM || 'auto' }} - GH_AW_INFO_VERSION: "0.0.1" - GH_AW_INFO_AGENT_VERSION: "0.0.1" + GH_AW_INFO_MODEL: "copilot/claude-sonnet-4-20250514" + GH_AW_INFO_VERSION: "0.72.1" + GH_AW_INFO_AGENT_VERSION: "0.72.1" GH_AW_INFO_WORKFLOW_NAME: "Dev" GH_AW_INFO_EXPERIMENTAL: "true" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" @@ -239,20 +239,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_cd7fc407040203ff_EOF' + cat << 'GH_AW_PROMPT_820d0bab622491f3_EOF' - GH_AW_PROMPT_cd7fc407040203ff_EOF + GH_AW_PROMPT_820d0bab622491f3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_cd7fc407040203ff_EOF' + cat << 'GH_AW_PROMPT_820d0bab622491f3_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_cd7fc407040203ff_EOF + GH_AW_PROMPT_820d0bab622491f3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_cd7fc407040203ff_EOF' + cat << 'GH_AW_PROMPT_820d0bab622491f3_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -281,13 +281,13 @@ jobs: {{/if}} - GH_AW_PROMPT_cd7fc407040203ff_EOF + GH_AW_PROMPT_820d0bab622491f3_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_cd7fc407040203ff_EOF' + cat << 'GH_AW_PROMPT_820d0bab622491f3_EOF' {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/dev.md}} - GH_AW_PROMPT_cd7fc407040203ff_EOF + GH_AW_PROMPT_820d0bab622491f3_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -454,7 +454,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.35 - name: Install Pi CLI - run: npm install --ignore-scripts -g @pi/cli@0.0.1 + run: npm install --ignore-scripts -g @mariozechner/pi-coding-agent@0.72.1 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 @@ -483,9 +483,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_547cc64ea9012ff5_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_96fe11ece7634061_EOF' {"create_issue":{"expires":168,"max":1,"title_prefix":"[Daily Report] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_547cc64ea9012ff5_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_96fe11ece7634061_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -717,10 +717,10 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["api.pi.ai","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["api.githubcopilot.com","api.pi.ai","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # shellcheck disable=SC1003 sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi run --json-log /tmp/gh-aw/pi-streaming.jsonl --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && mkdir -p /tmp/gh-aw/pi-agent-dir && echo eyJwcm92aWRlcnMiOnsiYXctZ2F0ZXdheSI6eyJhcGkiOiJvcGVuYWktY29tcGxldGlvbnMiLCJhcGlLZXkiOiJDT1BJTE9UX0dJVEhVQl9UT0tFTiIsImJhc2VVcmwiOiJodHRwOi8vaG9zdC5kb2NrZXIuaW50ZXJuYWw6MTAwMDIiLCJtb2RlbHMiOlt7ImlkIjoiY2xhdWRlLXNvbm5ldC00LTIwMjUwNTE0In1dfX19 | base64 -d > /tmp/gh-aw/pi-agent-dir/models.json && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi --print --mode json --no-session --model aw-gateway/claude-sonnet-4-20250514 --extension "${RUNNER_TEMP}/gh-aw/actions/pi_provider.cjs" --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs" 2>&1 | tee /tmp/gh-aw/pi-streaming.jsonl' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_PHASE: agent @@ -735,6 +735,7 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] + PI_CODING_AGENT_DIR: /tmp/gh-aw/pi-agent-dir - name: Stop CLI Proxy if: always() continue-on-error: true @@ -792,7 +793,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "api.pi.ai,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "api.githubcopilot.com,api.pi.ai,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -1168,7 +1169,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.35 - name: Install Pi CLI - run: npm install --ignore-scripts -g @pi/cli@0.0.1 + run: npm install --ignore-scripts -g @mariozechner/pi-coding-agent@0.72.1 - name: Execute Pi CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1177,10 +1178,10 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["api.pi.ai","github.com","host.docker.internal","raw.githubusercontent.com","registry.npmjs.org"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["api.githubcopilot.com","api.pi.ai","github.com","host.docker.internal","raw.githubusercontent.com","registry.npmjs.org"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # shellcheck disable=SC1003 sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi run --json-log /tmp/gh-aw/pi-streaming.jsonl --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && mkdir -p /tmp/gh-aw/pi-agent-dir && echo eyJwcm92aWRlcnMiOnsiYXctZ2F0ZXdheSI6eyJhcGkiOiJvcGVuYWktY29tcGxldGlvbnMiLCJhcGlLZXkiOiJDT1BJTE9UX0dJVEhVQl9UT0tFTiIsImJhc2VVcmwiOiJodHRwOi8vaG9zdC5kb2NrZXIuaW50ZXJuYWw6MTAwMDIiLCJtb2RlbHMiOlt7ImlkIjoiY2xhdWRlLXNvbm5ldC00LTIwMjUwNTE0In1dfX19 | base64 -d > /tmp/gh-aw/pi-agent-dir/models.json && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi --print --mode json --no-session --model aw-gateway/claude-sonnet-4-20250514 --extension "${RUNNER_TEMP}/gh-aw/actions/pi_provider.cjs" --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs" 2>&1 | tee /tmp/gh-aw/pi-streaming.jsonl' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_PHASE: detection @@ -1193,6 +1194,7 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] + PI_CODING_AGENT_DIR: /tmp/gh-aw/pi-agent-dir - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -1290,7 +1292,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "pi" - GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_MODEL: "copilot/claude-sonnet-4-20250514" GH_AW_WORKFLOW_ID: "dev" GH_AW_WORKFLOW_NAME: "Dev" outputs: @@ -1348,7 +1350,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "api.pi.ai,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "api.githubcopilot.com,api.pi.ai,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"expires\":168,\"max\":1,\"title_prefix\":\"[Daily Report] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" diff --git a/.github/workflows/dev.md b/.github/workflows/dev.md index 60599cc825d..1d080fbaa09 100644 --- a/.github/workflows/dev.md +++ b/.github/workflows/dev.md @@ -8,7 +8,9 @@ name: Dev description: Daily status report for gh-aw project timeout-minutes: 30 strict: false -engine: pi +engine: + id: pi + model: copilot/claude-sonnet-4-20250514 permissions: contents: read diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml index 5e4fb9099f2..7a85089f74b 100644 --- a/.github/workflows/smoke-pi.lock.yml +++ b/.github/workflows/smoke-pi.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"0dc5e648f5cd2f6c9d0760cc47e2fa3eac2a1c2ef0247b4bef79e7d0281e0135","strict":true,"agent_id":"pi","agent_model":"claude-sonnet-4-20250514"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b1e013b6e840bc7549cb0c9476704b60abe2801552255b4b67f36139050107ac","strict":true,"agent_id":"pi","agent_model":"copilot/claude-sonnet-4-20250514"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.35"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.3"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -136,9 +136,9 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "pi" GH_AW_INFO_ENGINE_NAME: "Pi" - GH_AW_INFO_MODEL: "claude-sonnet-4-20250514" - GH_AW_INFO_VERSION: "0.0.1" - GH_AW_INFO_AGENT_VERSION: "0.0.1" + GH_AW_INFO_MODEL: "copilot/claude-sonnet-4-20250514" + GH_AW_INFO_VERSION: "0.72.1" + GH_AW_INFO_AGENT_VERSION: "0.72.1" GH_AW_INFO_WORKFLOW_NAME: "Smoke Pi" GH_AW_INFO_EXPERIMENTAL: "true" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" @@ -212,7 +212,7 @@ jobs: id: sanitized uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,127.0.0.1,::1,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,127.0.0.1,::1,api.githubcopilot.com,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -249,21 +249,21 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9ffdcee518128d22_EOF' + cat << 'GH_AW_PROMPT_d1e8cc7eaba0b740_EOF' - GH_AW_PROMPT_9ffdcee518128d22_EOF + GH_AW_PROMPT_d1e8cc7eaba0b740_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_9ffdcee518128d22_EOF' + cat << 'GH_AW_PROMPT_d1e8cc7eaba0b740_EOF' Tools: add_comment(max:2), create_issue, add_labels, missing_tool, missing_data, noop - GH_AW_PROMPT_9ffdcee518128d22_EOF + GH_AW_PROMPT_d1e8cc7eaba0b740_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9ffdcee518128d22_EOF' + cat << 'GH_AW_PROMPT_d1e8cc7eaba0b740_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -292,9 +292,9 @@ jobs: {{/if}} - GH_AW_PROMPT_9ffdcee518128d22_EOF + GH_AW_PROMPT_d1e8cc7eaba0b740_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_9ffdcee518128d22_EOF' + cat << 'GH_AW_PROMPT_d1e8cc7eaba0b740_EOF' {{#runtime-import .github/workflows/shared/gh.md}} {{#runtime-import .github/workflows/shared/reporting-otlp.md}} @@ -302,7 +302,7 @@ jobs: {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/smoke-pi.md}} - GH_AW_PROMPT_9ffdcee518128d22_EOF + GH_AW_PROMPT_d1e8cc7eaba0b740_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -497,7 +497,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.35 - name: Install Pi CLI - run: npm install --ignore-scripts -g @pi/cli@0.0.1 + run: npm install --ignore-scripts -g @mariozechner/pi-coding-agent@0.72.1 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 @@ -526,9 +526,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8a18424d83027f38_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_aeb0c0aab247cb02_EOF' {"add_comment":{"hide_older_comments":true,"max":2},"add_labels":{"allowed":["smoke-pi"]},"create_issue":{"close_older_issues":true,"close_older_key":"smoke-pi","expires":2,"labels":["automation","testing"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_8a18424d83027f38_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_aeb0c0aab247cb02_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -741,7 +741,7 @@ jobs: - name: Write MCP Scripts Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-scripts/logs" - cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/tools.json" << 'GH_AW_MCP_SCRIPTS_TOOLS_2d81bc4868bf24e2_EOF' + cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/tools.json" << 'GH_AW_MCP_SCRIPTS_TOOLS_11e6a82f2d0b90d6_EOF' { "serverName": "mcpscripts", "version": "1.0.0", @@ -771,8 +771,8 @@ jobs: } ] } - GH_AW_MCP_SCRIPTS_TOOLS_2d81bc4868bf24e2_EOF - cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/mcp-server.cjs" << 'GH_AW_MCP_SCRIPTS_SERVER_96eba225b44a163f_EOF' + GH_AW_MCP_SCRIPTS_TOOLS_11e6a82f2d0b90d6_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/mcp-server.cjs" << 'GH_AW_MCP_SCRIPTS_SERVER_d883a4e1ef2b6b37_EOF' const path = require("path"); const { startHttpServer } = require("./mcp_scripts_mcp_server_http.cjs"); const configPath = path.join(__dirname, "tools.json"); @@ -786,12 +786,12 @@ jobs: console.error("Failed to start mcp-scripts HTTP server:", error); process.exit(1); }); - GH_AW_MCP_SCRIPTS_SERVER_96eba225b44a163f_EOF + GH_AW_MCP_SCRIPTS_SERVER_d883a4e1ef2b6b37_EOF chmod +x "${RUNNER_TEMP}/gh-aw/mcp-scripts/mcp-server.cjs" - name: Write MCP Scripts Tool Files run: | - cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/gh.sh" << 'GH_AW_MCP_SCRIPTS_SH_GH_388a443ed82141a4_EOF' + cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/gh.sh" << 'GH_AW_MCP_SCRIPTS_SH_GH_2e8255430cd1de16_EOF' #!/bin/bash # Auto-generated mcp-script tool: gh # Execute any gh CLI command. This tool is accessible as 'mcpscripts-gh'. Provide the full command after 'gh' (e.g., args: 'pr list --limit 5'). The tool will run: gh . Use single quotes ' for complex args to avoid shell interpretation issues. @@ -803,7 +803,7 @@ jobs: GH_TOKEN="$GH_AW_GH_TOKEN" gh $INPUT_ARGS - GH_AW_MCP_SCRIPTS_SH_GH_388a443ed82141a4_EOF + GH_AW_MCP_SCRIPTS_SH_GH_2e8255430cd1de16_EOF chmod +x "${RUNNER_TEMP}/gh-aw/mcp-scripts/gh.sh" - name: Generate MCP Scripts Server Config @@ -909,10 +909,10 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","api.pi.ai","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","api.githubcopilot.com","api.pi.ai","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # shellcheck disable=SC1003 sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_AW_GH_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi run --json-log /tmp/gh-aw/pi-streaming.jsonl --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && mkdir -p /tmp/gh-aw/pi-agent-dir && echo eyJwcm92aWRlcnMiOnsiYXctZ2F0ZXdheSI6eyJhcGkiOiJvcGVuYWktY29tcGxldGlvbnMiLCJhcGlLZXkiOiJDT1BJTE9UX0dJVEhVQl9UT0tFTiIsImJhc2VVcmwiOiJodHRwOi8vaG9zdC5kb2NrZXIuaW50ZXJuYWw6MTAwMDIiLCJtb2RlbHMiOlt7ImlkIjoiY2xhdWRlLXNvbm5ldC00LTIwMjUwNTE0In1dfX19 | base64 -d > /tmp/gh-aw/pi-agent-dir/models.json && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi --print --mode json --no-session --model aw-gateway/claude-sonnet-4-20250514 --extension "${RUNNER_TEMP}/gh-aw/actions/pi_provider.cjs" --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs" 2>&1 | tee /tmp/gh-aw/pi-streaming.jsonl' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_PHASE: agent @@ -927,7 +927,7 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - PI_MODEL: claude-sonnet-4-20250514 + PI_CODING_AGENT_DIR: /tmp/gh-aw/pi-agent-dir - name: Stop CLI Proxy if: always() continue-on-error: true @@ -985,7 +985,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,127.0.0.1,::1,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,127.0.0.1,::1,api.githubcopilot.com,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -1398,7 +1398,7 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.35 - name: Install Pi CLI - run: npm install --ignore-scripts -g @pi/cli@0.0.1 + run: npm install --ignore-scripts -g @mariozechner/pi-coding-agent@0.72.1 - name: Execute Pi CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1407,10 +1407,10 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["api.pi.ai","github.com","host.docker.internal","raw.githubusercontent.com","registry.npmjs.org"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.35/awf-config.schema.json","network":{"allowDomains":["api.githubcopilot.com","api.pi.ai","github.com","host.docker.internal","raw.githubusercontent.com","registry.npmjs.org"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.35"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # shellcheck disable=SC1003 sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi run --json-log /tmp/gh-aw/pi-streaming.jsonl --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && mkdir -p /tmp/gh-aw/pi-agent-dir && echo eyJwcm92aWRlcnMiOnsiYXctZ2F0ZXdheSI6eyJhcGkiOiJvcGVuYWktY29tcGxldGlvbnMiLCJhcGlLZXkiOiJDT1BJTE9UX0dJVEhVQl9UT0tFTiIsImJhc2VVcmwiOiJodHRwOi8vaG9zdC5kb2NrZXIuaW50ZXJuYWw6MTAwMDIiLCJtb2RlbHMiOlt7ImlkIjoiY2xhdWRlLXNvbm5ldC00LTIwMjUwNTE0In1dfX19 | base64 -d > /tmp/gh-aw/pi-agent-dir/models.json && cat /tmp/gh-aw/aw-prompts/prompt.txt | pi --print --mode json --no-session --model aw-gateway/claude-sonnet-4-20250514 --extension "${RUNNER_TEMP}/gh-aw/actions/pi_provider.cjs" --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs" 2>&1 | tee /tmp/gh-aw/pi-streaming.jsonl' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_PHASE: detection @@ -1423,7 +1423,7 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - PI_MODEL: claude-sonnet-4-20250514 + PI_CODING_AGENT_DIR: /tmp/gh-aw/pi-agent-dir - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -1522,7 +1522,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "pi" - GH_AW_ENGINE_MODEL: "claude-sonnet-4-20250514" + GH_AW_ENGINE_MODEL: "copilot/claude-sonnet-4-20250514" GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🥧 *[{workflow_name}]({run_url}) — Powered by Pi*{effective_tokens_suffix}{history_link}\",\"runStarted\":\"🥧 Pi initializing... [{workflow_name}]({run_url}) begins on this {event_type}...\",\"runSuccess\":\"🚀 [{workflow_name}]({run_url}) **MISSION COMPLETE!** Pi delivered. 🥧\",\"runFailure\":\"⚠️ [{workflow_name}]({run_url}) {status}. Pi encountered unexpected challenges...\"}" GH_AW_WORKFLOW_ID: "smoke-pi" GH_AW_WORKFLOW_NAME: "Smoke Pi" @@ -1585,7 +1585,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,127.0.0.1,::1,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,127.0.0.1,::1,api.githubcopilot.com,api.pi.ai,api.snapcraft.io,app.renovatebot.com,appveyor.com,archive.ubuntu.com,azure.archive.ubuntu.com,badgen.net,circleci.com,codacy.com,codeclimate.com,codecov.io,codeload.github.com,coveralls.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deepsource.io,docs.github.com,drone.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,img.shields.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,localhost,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,readthedocs.io,readthedocs.org,registry.npmjs.org,renovatebot.com,s.symcb.com,s.symcd.com,security.ubuntu.com,semaphoreci.com,shields.io,snyk.io,sonarcloud.io,sonarqube.com,travis-ci.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-pi\"]},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-pi\",\"expires\":2,\"labels\":[\"automation\",\"testing\"],\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" diff --git a/.github/workflows/smoke-pi.md b/.github/workflows/smoke-pi.md index 1e82f630b10..0ff9ec7f499 100644 --- a/.github/workflows/smoke-pi.md +++ b/.github/workflows/smoke-pi.md @@ -14,7 +14,7 @@ permissions: name: Smoke Pi engine: id: pi - model: claude-sonnet-4-20250514 + model: copilot/claude-sonnet-4-20250514 strict: true imports: - shared/gh.md diff --git a/actions/setup/js/pi_provider.cjs b/actions/setup/js/pi_provider.cjs new file mode 100644 index 00000000000..d22b6196b65 --- /dev/null +++ b/actions/setup/js/pi_provider.cjs @@ -0,0 +1,109 @@ +// @ts-check + +/** + * Pi Provider Extension for gh-aw + * + * Calls the AWF API proxy /reflect endpoint at session start to dynamically + * discover the open LLM inference paths configured for this run. This gives + * operators runtime visibility into which provider/model combination is active + * and verifies that the expected gateway port is reachable before the agent + * starts working. + * + * When the model uses provider/model format (e.g. "copilot/claude-sonnet-4"), + * the extension logs the matched endpoint so failures can be diagnosed without + * inspecting container internals. + * + * The extension is automatically added to every Pi agent invocation by the + * gh-aw compiler alongside pi_steering_extension.cjs. No workflow frontmatter + * configuration is required. + * + * Configuration (read from environment variables): + * PI_MODEL The engine.model value; may be "provider/model" or bare "model". + */ + +"use strict"; + +const { fetchAWFReflect, AWF_API_PROXY_REFLECT_URL, AWF_REFLECT_OUTPUT_PATH, AWF_REFLECT_TIMEOUT_MS, AWF_MODELS_URL_TIMEOUT_MS } = require("./awf_reflect.cjs"); + +// Default logger: prefixed with "[gh-aw/pi-provider]" for easy grepping. +// prettier-ignore +const DEFAULT_LOGGER = /** @type {(msg: string) => void} */ (msg => process.stderr.write(`[gh-aw/pi-provider] ${new Date().toISOString()} ${msg}\n`)); + +/** + * Extract the provider prefix from a "provider/model" string. + * Returns an empty string when no slash is present (bare model name). + * + * @param {string} model + * @returns {string} + */ +function extractProviderFromModel(model) { + if (!model) return ""; + const slashIdx = model.indexOf("/"); + if (slashIdx <= 0) return ""; + return model.slice(0, slashIdx).toLowerCase(); +} + +/** + * Resolve the expected LLM gateway base URL for a given provider prefix. + * Returns null when the provider is not one of the well-known AWF sidecar providers. + * + * @param {string} provider - Lowercase provider prefix (e.g. "copilot", "anthropic"). + * @returns {string|null} + */ +function resolveGatewayUrl(provider) { + const GATEWAY_PORTS = /** @type {Record} */ { + copilot: 10002, + anthropic: 10000, + openai: 10001, + codex: 10001, + google: 10003, + }; + const port = GATEWAY_PORTS[provider]; + if (!port) return null; + return `http://host.docker.internal:${port}`; +} + +/** + * Pi provider extension for gh-aw. + * + * Subscribes to the `agent_start` Pi SDK event and calls the AWF /reflect + * endpoint to discover and log the open LLM inference paths before the agent + * begins its first turn. This is best-effort: any network or parse error is + * logged but does not abort the agent session. + * + * @param {any} pi - Pi ExtensionAPI instance + * @returns {void} + */ +function piProviderExtension(pi) { + const log = DEFAULT_LOGGER; + + pi.on("agent_start", async () => { + const model = process.env.PI_MODEL || ""; + const provider = extractProviderFromModel(model); + + if (provider) { + const gatewayUrl = resolveGatewayUrl(provider); + if (gatewayUrl) { + log(`provider=${provider} model=${model} gateway=${gatewayUrl}`); + } else { + log(`provider=${provider} model=${model} (no known AWF gateway port for this provider)`); + } + } else { + log(`model=${model || "(not set)"} (no provider prefix — defaulting to Copilot gateway)`); + } + + // Fetch AWF API proxy reflection data and persist to disk so the post-run + // step summary (awf_reflect_summary.cjs) can include provider and model info. + await fetchAWFReflect({ + reflectUrl: AWF_API_PROXY_REFLECT_URL, + outputPath: AWF_REFLECT_OUTPUT_PATH, + timeoutMs: AWF_REFLECT_TIMEOUT_MS, + modelsTimeoutMs: AWF_MODELS_URL_TIMEOUT_MS, + logger: log, + }); + }); +} + +module.exports = piProviderExtension; +module.exports.extractProviderFromModel = extractProviderFromModel; +module.exports.resolveGatewayUrl = resolveGatewayUrl; diff --git a/pkg/constants/version_constants.go b/pkg/constants/version_constants.go index 8f039aab3de..0037051d23c 100644 --- a/pkg/constants/version_constants.go +++ b/pkg/constants/version_constants.go @@ -51,7 +51,7 @@ const DefaultGeminiVersion Version = "0.39.1" const DefaultCrushVersion Version = "0.59.0" // DefaultPiVersion is the default version of the Pi CLI -const DefaultPiVersion Version = "0.0.1" +const DefaultPiVersion Version = "0.72.1" // DefaultOpenCodeVersion is the default version of the OpenCode CLI const DefaultOpenCodeVersion Version = "1.2.14" diff --git a/pkg/workflow/domains.go b/pkg/workflow/domains.go index cd205580ca4..13a74c847b9 100644 --- a/pkg/workflow/domains.go +++ b/pkg/workflow/domains.go @@ -112,10 +112,36 @@ var GeminiDefaultDomains = []string{ "registry.npmjs.org", } -// PiDefaultDomains are the default domains required for the Pi CLI to operate. -// Pi routes its API calls through the AWF LLM gateway (host.docker.internal) when -// the firewall is enabled. The api.pi.ai domain covers the Pi API endpoint. +// PiBaseDefaultDomains are the base domains required for the Pi CLI to operate, +// independent of the chosen LLM provider. When a model uses provider/model format, +// provider-specific API domains are added on top via GetPiDefaultDomains(). +var PiBaseDefaultDomains = []string{ + "api.pi.ai", // Pi CLI telemetry / update checks + "host.docker.internal", // MCP gateway / API proxy access + "github.com", + "raw.githubusercontent.com", + "registry.npmjs.org", // npm package downloads +} + +// piProviderDomains maps provider prefixes to their API domains. +// Mirrors crushProviderDomains / openCodeProviderDomains for the same set of +// providers that Pi can route through via the AWF LLM gateway. +// Note: "google" is intentionally omitted — Pi backend resolution only supports +// copilot, anthropic, openai, and codex; adding google here without backend +// support would produce an inconsistent routing configuration. +var piProviderDomains = map[string]string{ + "copilot": "api.githubcopilot.com", + "github-copilot": "api.githubcopilot.com", + "anthropic": "api.anthropic.com", + "openai": "api.openai.com", + "codex": "api.openai.com", +} + +// PiDefaultDomains are the static default domains for backward compatibility when +// no model provider prefix is given. When a provider/model format is used, the +// dynamic path (GetPiDefaultDomains) resolves provider-specific domains instead. var PiDefaultDomains = []string{ + "api.githubcopilot.com", // Default provider (Copilot routing) "api.pi.ai", "host.docker.internal", "github.com", @@ -270,6 +296,40 @@ func GetCrushAllowedDomainsWithToolsAndRuntimes(model string, network *NetworkPe return GetAllowedDomainsForEngineWithModel(constants.CrushEngine, model, network, tools, runtimes) } +// GetPiDefaultDomains returns the default domains for Pi based on the model provider. +// It starts with PiBaseDefaultDomains and adds the provider-specific API domain when +// the model uses provider/model format (e.g. "copilot/claude-sonnet-4-20250514"). +// When no provider prefix is present the default Copilot API domain is included for +// backward compatibility. +// Returns an error if the model string is malformed (e.g. a leading slash). +func GetPiDefaultDomains(model string) ([]string, error) { + provider, err := extractProviderFromModel(model) + if err != nil { + return nil, err + } + domains := make([]string, 0, len(PiBaseDefaultDomains)+1) + domains = append(domains, PiBaseDefaultDomains...) + + if domain, ok := piProviderDomains[provider]; ok { + domains = append(domains, domain) + } else if provider == "" { + // No provider prefix → default to Copilot routing for backward compatibility. + domains = append(domains, piProviderDomains["copilot"]) + } + + return domains, nil +} + +// GetPiAllowedDomainsWithModel merges Pi default domains with NetworkPermissions, HTTP MCP +// server domains, and runtime ecosystem domains. +// Pass the selected model (e.g. "copilot/claude-sonnet-4-20250514") so provider-specific +// API domains are included. Returns a deduplicated, sorted, comma-separated string suitable +// for AWF's --allow-domains flag. +// Returns an error if the model string is malformed (e.g. a leading slash). +func GetPiAllowedDomainsWithModel(model string, network *NetworkPermissions, tools map[string]any, runtimes map[string]any) (string, error) { + return GetAllowedDomainsForEngineWithModel(constants.PiEngine, model, network, tools, runtimes) +} + // PlaywrightDomains are the domains required for Playwright browser downloads // These domains are needed when Playwright MCP server initializes in the Docker container var PlaywrightDomains = []string{ @@ -695,20 +755,19 @@ func mergeDomainsWithNetworkToolsAndRuntimes(defaultDomains []string, network *N } // engineDefaultDomains maps each engine to its static default required domains. -// Engines with model-specific defaults (for example, Crush) are resolved in +// Engines with model-specific defaults (for example, Crush, OpenCode, Pi) are resolved in // getDefaultDomainsForEngine instead of being stored directly in this map. var engineDefaultDomains = map[constants.EngineName][]string{ constants.CopilotEngine: CopilotDefaultDomains, constants.ClaudeEngine: ClaudeDefaultDomains, constants.CodexEngine: CodexDefaultDomains, constants.GeminiEngine: GeminiDefaultDomains, - constants.PiEngine: PiDefaultDomains, } // getDefaultDomainsForEngine returns the engine's default required domains. -// OpenCode and Crush domains are model/provider-specific, so they must be -// resolved via GetOpenCodeDefaultDomains(model) / GetCrushDefaultDomains(model) -// rather than the static engineDefaultDomains map. +// OpenCode, Crush, and Pi domains are model/provider-specific, so they must be +// resolved via their respective Get*DefaultDomains(model) functions rather than +// the static engineDefaultDomains map. // Falls back to an empty default domain list for unknown engines. // Returns an error if the model string is malformed (e.g. a leading slash). func getDefaultDomainsForEngine(engine constants.EngineName, model string) ([]string, error) { @@ -718,6 +777,9 @@ func getDefaultDomainsForEngine(engine constants.EngineName, model string) ([]st if engine == constants.CrushEngine { return GetCrushDefaultDomains(model) } + if engine == constants.PiEngine { + return GetPiDefaultDomains(model) + } return engineDefaultDomains[engine], nil } @@ -788,10 +850,13 @@ func GetGeminiAllowedDomainsWithToolsAndRuntimes(network *NetworkPermissions, to } // GetPiAllowedDomains merges Pi default domains with NetworkPermissions, HTTP MCP server domains, -// and runtime ecosystem domains. +// and runtime ecosystem domains. Uses backward-compatible Copilot routing when no model is given. +// For model-aware resolution, prefer GetPiAllowedDomainsWithModel. // Returns a deduplicated, sorted, comma-separated string suitable for AWF's --allow-domains flag. func GetPiAllowedDomains(network *NetworkPermissions, tools map[string]any, runtimes map[string]any) string { - return GetAllowedDomainsForEngine(constants.PiEngine, network, tools, runtimes) + // Empty model → backward-compatible Copilot routing; no malformed-model error possible. + result, _ := GetPiAllowedDomainsWithModel("", network, tools, runtimes) + return result } // GetBlockedDomains returns the blocked domains from network permissions @@ -934,7 +999,15 @@ func (c *Compiler) computeAllowedDomainsForSanitization(data *WorkflowData) (str case "gemini": base = GetGeminiAllowedDomainsWithToolsAndRuntimes(data.NetworkPermissions, data.Tools, data.Runtimes) case "pi": - base = GetPiAllowedDomains(data.NetworkPermissions, data.Tools, data.Runtimes) + model := "" + if data.EngineConfig != nil { + model = data.EngineConfig.Model + } + var err error + base, err = GetPiAllowedDomainsWithModel(model, data.NetworkPermissions, data.Tools, data.Runtimes) + if err != nil { + return "", err + } case "opencode": model := "" if data.EngineConfig != nil { diff --git a/pkg/workflow/pi_engine.go b/pkg/workflow/pi_engine.go index a3db4bf0f16..ce6fe21a9f5 100644 --- a/pkg/workflow/pi_engine.go +++ b/pkg/workflow/pi_engine.go @@ -1,8 +1,11 @@ package workflow import ( + "encoding/base64" + "encoding/json" "fmt" "maps" + "strings" "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" @@ -11,8 +14,12 @@ import ( var piLog = logger.New("workflow:pi_engine") // PiEngine represents the Pi AI coding agent (experimental). -// Pi is an agentic coding assistant that communicates via stdin/stdout and -// emits a streaming JSONL log for structured event capture. +// Pi is a provider-agnostic agentic coding assistant that communicates via stdin/stdout +// and emits a streaming JSONL log for structured event capture. When engine.model uses +// provider/model format (e.g. "copilot/claude-sonnet-4-20250514"), Pi borrows the +// matching engine's AWF configuration (secrets, gateway port, allowed domains) so the +// firewall can route LLM traffic through the correct sidecar port. Without a provider +// prefix Pi defaults to the Copilot gateway. // // Requirements: // - tools.github.mode: gh-proxy must be enabled (pre-authenticated gh CLI). @@ -46,22 +53,117 @@ func (e *PiEngine) GetModelEnvVarName() string { return constants.PiCLIModelEnvVar } +// resolvePiBackend extracts the provider prefix from the engine model (if any) and maps +// it to the matching UniversalLLMBackend. A model without a slash (e.g. "claude-sonnet-4") +// defaults to the Copilot backend for backward compatibility. +// +// "github-copilot/" is accepted as an alias for "copilot/" since that is the +// provider name used by Pi CLI's built-in model registry. +func resolvePiBackend(workflowData *WorkflowData) UniversalLLMBackend { + if workflowData == nil || workflowData.EngineConfig == nil || workflowData.EngineConfig.Model == "" { + return UniversalLLMBackendCopilot + } + model := workflowData.EngineConfig.Model + if !strings.Contains(model, "/") { + // No provider prefix — default to Copilot (backward compatibility). + return UniversalLLMBackendCopilot + } + // "github-copilot" is Pi CLI's internal name for GitHub Copilot. Accept it as + // an alias so workflows can use either "copilot/..." or "github-copilot/...". + parts := strings.SplitN(model, "/", 2) + if strings.EqualFold(parts[0], "github-copilot") { + return UniversalLLMBackendCopilot + } + backend, err := resolveUniversalLLMBackendFromModel(model) + if err != nil { + piLog.Printf("Could not resolve backend for Pi model %q, defaulting to copilot: %v", model, err) + return UniversalLLMBackendCopilot + } + return backend +} + +// extractPiModelID returns the model ID portion of a provider/model string. +// For "copilot/claude-sonnet-4" it returns "claude-sonnet-4". +// For a bare model name (no slash) the whole string is returned unchanged. +func extractPiModelID(model string) string { + if _, after, found := strings.Cut(model, "/"); found { + return after + } + return model +} + +// piNativeProviderName maps an AWF UniversalLLMBackend to the corresponding +// Pi CLI built-in provider name. Used when there is no AWF gateway to proxy +// through (firewall disabled) so Pi can call the provider's API directly. +func piNativeProviderName(backend UniversalLLMBackend) string { + switch backend { + case UniversalLLMBackendAnthropic: + return "anthropic" + case UniversalLLMBackendCodex: + return "openai" + default: + return "github-copilot" + } +} + +// buildPiModelsJSON returns a minimal Pi models.json payload that registers a +// single custom provider named "aw-gateway" pointing at the AWF LLM gateway +// sidecar. Pi's resolveConfigValue() resolves the "apiKey" value by looking +// up process.env[apiKey], so passing the secret env-var name (e.g. +// "COPILOT_GITHUB_TOKEN") causes Pi to automatically use the value that is +// already present in the container environment. +// +// All dynamic values are marshaled via encoding/json to prevent JSON injection. +func buildPiModelsJSON(gatewayPort int, secretEnvVarName, modelID string) string { + payload := map[string]any{ + "providers": map[string]any{ + "aw-gateway": map[string]any{ + "baseUrl": fmt.Sprintf("http://host.docker.internal:%d", gatewayPort), + "api": "openai-completions", + "apiKey": secretEnvVarName, + "models": []map[string]any{{"id": modelID}}, + }, + }, + } + b, err := json.Marshal(payload) + if err != nil { + // json.Marshal only fails for non-serialisable types; our map is always + // serialisable, so this branch is unreachable in practice. + panic(fmt.Sprintf("BUG: buildPiModelsJSON failed to marshal JSON: %v", err)) + } + return string(b) +} + +// encodeBase64 returns the standard base64 encoding of s. Used to safely +// embed arbitrary content in shell commands without shell-injection risks. +func encodeBase64(s string) string { + return base64.StdEncoding.EncodeToString([]byte(s)) +} + // GetRequiredSecretNames returns the list of secrets required by the Pi engine. -// Pi routes through the Copilot LLM gateway and reuses COPILOT_GITHUB_TOKEN -// rather than a dedicated PI_API_KEY. +// When the model uses provider/model format the provider-specific secret is required +// (e.g. ANTHROPIC_API_KEY for "anthropic/..."); otherwise Pi routes through the +// Copilot LLM gateway and reuses COPILOT_GITHUB_TOKEN. func (e *PiEngine) GetRequiredSecretNames(workflowData *WorkflowData) []string { piLog.Print("Collecting required secrets for Pi engine") - secrets := []string{"COPILOT_GITHUB_TOKEN"} + backend := resolvePiBackend(workflowData) + profile := getUniversalLLMBackendProfile(backend, isFeatureEnabled(constants.CopilotRequestsFeatureFlag, workflowData)) + secrets := append([]string{}, profile.coreSecretNames...) secrets = append(secrets, collectCommonMCPSecrets(workflowData)...) return secrets } // GetSecretValidationStep returns the secret validation step for the Pi engine. -// Pi reuses COPILOT_GITHUB_TOKEN (no dedicated PI_API_KEY). +// The validated secret depends on the resolved provider backend. func (e *PiEngine) GetSecretValidationStep(workflowData *WorkflowData) GitHubActionStep { + backend := resolvePiBackend(workflowData) + profile := getUniversalLLMBackendProfile(backend, isFeatureEnabled(constants.CopilotRequestsFeatureFlag, workflowData)) + if len(profile.coreSecretNames) == 0 { + return GitHubActionStep{} + } return BuildDefaultSecretValidationStep( workflowData, - []string{"COPILOT_GITHUB_TOKEN"}, + profile.coreSecretNames, "Pi", "https://github.github.com/gh-aw/reference/engines/#pi", ) @@ -84,7 +186,7 @@ func (e *PiEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubActi } npmSteps := BuildStandardNpmEngineInstallSteps( - "@pi/cli", + "@mariozechner/pi-coding-agent", version, "Install Pi CLI", "pi", @@ -156,30 +258,74 @@ func (e *PiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) commandName = workflowData.EngineConfig.Command } - // Build the pi run command. Prompt is piped via stdin. - piArgs := []string{"run", "--json-log", PiStreamingLogFile} + // Resolve backend and profile early so we can use them when building piArgs. + modelConfigured := workflowData.EngineConfig != nil && workflowData.EngineConfig.Model != "" + backend := resolvePiBackend(workflowData) + profile := getUniversalLLMBackendProfile(backend, isFeatureEnabled(constants.CopilotRequestsFeatureFlag, workflowData)) + firewallEnabled := isFirewallEnabled(workflowData) + + // Build the pi command. Pi v0.72+ uses flags-only syntax (no "run" subcommand). + // --print: non-interactive, process prompt from stdin and exit. + // --mode json: emit structured JSONL events to stdout. + // --no-session: don't persist a session file (appropriate for CI). + piArgs := []string{"--print", "--mode", "json", "--no-session"} // Append any user-supplied extra args from engine.args if workflowData.EngineConfig != nil { piArgs = append(piArgs, workflowData.EngineConfig.Args...) } + // Pi v0.72+ does not support a PI_MODEL env var; the model must be passed as + // the --model CLI flag. When the firewall is enabled we route LLM traffic + // through the AWF gateway sidecar by generating a temporary models.json that + // registers a custom "aw-gateway" provider pointing at the gateway port. When + // the firewall is disabled we use Pi's built-in provider directly. + var piModelsJSONSetup string // shell fragment prepended to piCommand when needed + if modelConfigured { + modelID := extractPiModelID(workflowData.EngineConfig.Model) + if firewallEnabled && len(profile.coreSecretNames) > 0 { + // Firewall case: write a models.json that redirects Pi's LLM calls to the + // AWF gateway sidecar port. The "apiKey" field value is the name of the env + // var that holds the secret; Pi's resolveConfigValue() looks up + // process.env[apiKey] to obtain the actual token value at runtime. + // + // The JSON is base64-encoded before embedding in the shell command so that + // the content is injection-safe regardless of what characters it contains. + modelsJSON := buildPiModelsJSON(profile.gatewayPort, profile.coreSecretNames[0], modelID) + modelsJSONBase64 := encodeBase64(modelsJSON) + piModelsJSONSetup = fmt.Sprintf( + `mkdir -p /tmp/gh-aw/pi-agent-dir && echo %s | base64 -d > /tmp/gh-aw/pi-agent-dir/models.json && `, + modelsJSONBase64) + piArgs = append(piArgs, "--model", "aw-gateway/"+modelID) + piLog.Printf("Pi: using models.json gateway routing for model %q via aw-gateway (port %d)", modelID, profile.gatewayPort) + } else { + // No firewall: use Pi's built-in provider so it can reach the real LLM API. + nativeProvider := piNativeProviderName(backend) + piArgs = append(piArgs, "--model", nativeProvider+"/"+modelID) + piLog.Printf("Pi: using native provider %q for model %q (no firewall)", nativeProvider, modelID) + } + } + // The prompt is piped from a file via stdin substitution. - // The built-in steering extension is automatically loaded so that every Pi session - // receives time-pressure steering messages without requiring workflow configuration. - // Pi CLI supports multiple --extension flags; user-specified extensions (via engine.args) - // are appended before this flag so the built-in extension loads last, consistent with the - // aw-harness spec's "built-in extensions after user extensions" ordering. - // ${RUNNER_TEMP} is a Linux shell variable expanded by bash at runtime; gh-aw container - // environments are Linux-only so this is safe across all supported runner configurations. + // Two extensions are automatically loaded (in order): + // 1. pi_provider.cjs — calls /reflect to discover the open LLM inference paths + // 2. pi_steering_extension.cjs — injects time-pressure steering messages + // Pi CLI supports multiple --extension flags; built-in extensions load after any + // user-specified extensions (via engine.args) so the built-in behaviour wins. + // ${RUNNER_TEMP} is a Linux shell variable expanded by bash at runtime; gh-aw + // container environments are Linux-only so this is safe across all runners. + // stdout (JSONL) and stderr are both piped through tee so that PiStreamingLogFile + // captures all structured events while agent-stdio.log captures the same output. piCommand := fmt.Sprintf( - `cat /tmp/gh-aw/aw-prompts/prompt.txt | %s %s --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs"`, - commandName, shellJoinArgs(piArgs)) + `cat /tmp/gh-aw/aw-prompts/prompt.txt | %s %s --extension "${RUNNER_TEMP}/gh-aw/actions/pi_provider.cjs" --extension "${RUNNER_TEMP}/gh-aw/actions/pi_steering_extension.cjs" 2>&1 | tee %s`, + commandName, shellJoinArgs(piArgs), PiStreamingLogFile) - modelConfigured := workflowData.EngineConfig != nil && workflowData.EngineConfig.Model != "" + // Prepend models.json generation when the gateway-routing approach is used. + if piModelsJSONSetup != "" { + piCommand = piModelsJSONSetup + piCommand + } var command string - firewallEnabled := isFirewallEnabled(workflowData) if firewallEnabled { // Get allowed domains: prefer the pre-warmed cache on WorkflowData to avoid // re-running the expensive map+sort operation. @@ -187,7 +333,17 @@ func (e *PiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) if workflowData.CachedAllowedDomainsComputed { allowedDomains = workflowData.CachedAllowedDomainsStr } else { - allowedDomains = GetPiAllowedDomains(workflowData.NetworkPermissions, workflowData.Tools, workflowData.Runtimes) + model := "" + if modelConfigured { + model = workflowData.EngineConfig.Model + } + // The model was validated before reaching here; a malformed model (leading slash) + // must never occur at this point — panic is the correct invariant guard. + var err error + allowedDomains, err = GetPiAllowedDomainsWithModel(model, workflowData.NetworkPermissions, workflowData.Tools, workflowData.Runtimes) + if err != nil { + panic(fmt.Sprintf("BUG: invalid Pi model %q reached domain computation (should have been caught by validation): %v", model, err)) + } } if workflowData.EngineConfig != nil && workflowData.EngineConfig.APITarget != "" { allowedDomains = mergeAPITargetDomains(allowedDomains, workflowData.EngineConfig.APITarget) @@ -207,7 +363,7 @@ func (e *PiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) UsesTTY: false, AllowedDomains: allowedDomains, PathSetup: "touch " + AgentStepSummaryPath, - ExcludeEnvVarNames: ComputeAWFExcludeEnvVarNames(workflowData, []string{"COPILOT_GITHUB_TOKEN"}), + ExcludeEnvVarNames: ComputeAWFExcludeEnvVarNames(workflowData, profile.coreSecretNames), }) } else { command = fmt.Sprintf(`set -o pipefail @@ -216,14 +372,34 @@ touch %s %s 2>&1 | tee -a %s`, AgentStepSummaryPath, logFile, piCommand, logFile) } - // #nosec G101 -- This is NOT a hardcoded credential. It is a GitHub Actions expression - // template that the runtime replaces with the actual secret value. + // Build the environment map. Provider-specific credentials are injected via + // the backend profile. The base URL env var (e.g. GITHUB_COPILOT_BASE_URL) is + // NOT set for Pi because Pi v0.72+ does not read provider-specific base URL env + // vars; routing is instead handled through models.json (firewall case) or by Pi's + // native provider (no-firewall case). env := map[string]string{ - "COPILOT_GITHUB_TOKEN": "${{ secrets.COPILOT_GITHUB_TOKEN }}", - "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", - "GITHUB_AW": "true", - "GITHUB_WORKSPACE": "${{ github.workspace }}", - "GITHUB_STEP_SUMMARY": AgentStepSummaryPath, + "GH_AW_PROMPT": "/tmp/gh-aw/aw-prompts/prompt.txt", + "GITHUB_AW": "true", + "GITHUB_WORKSPACE": "${{ github.workspace }}", + "GITHUB_STEP_SUMMARY": AgentStepSummaryPath, + } + + // Inject provider-specific credentials from the backend profile. + maps.Copy(env, profile.env) + + // Pi CLI reads OPENAI_API_KEY and routes traffic to api.openai.com when the env + // var is present, bypassing the github-copilot provider and the AWF gateway. + // For the Copilot backend Pi authenticates via COPILOT_GITHUB_TOKEN directly + // (either through the native github-copilot provider or via models.json apiKey + // resolution), so OPENAI_API_KEY must not be exposed in the container env. + if backend == UniversalLLMBackendCopilot { + delete(env, "OPENAI_API_KEY") + } + + // When the models.json gateway approach is used, tell Pi where to find it. + if piModelsJSONSetup != "" { + env["PI_CODING_AGENT_DIR"] = "/tmp/gh-aw/pi-agent-dir" + piLog.Printf("Pi: setting PI_CODING_AGENT_DIR for models.json gateway config") } if workflowData.IsDetectionRun { @@ -238,18 +414,11 @@ touch %s } // When the AWF firewall is enabled, set git identity environment variables - // for commit authorship. Pi uses the copilot/claude/codex LLM gateway ports - // directly (no dedicated Pi gateway port). + // for commit authorship. if firewallEnabled { maps.Copy(env, getGitIdentityEnvVars()) } - // Apply native model env var only when explicitly configured. - if modelConfigured { - piLog.Printf("Setting %s env var for model: %s", constants.PiCLIModelEnvVar, workflowData.EngineConfig.Model) - env[constants.PiCLIModelEnvVar] = workflowData.EngineConfig.Model - } - // Apply safe-outputs env applySafeOutputEnvToMap(env, workflowData) diff --git a/pkg/workflow/pi_engine_test.go b/pkg/workflow/pi_engine_test.go index 22adce595a2..61407f486a5 100644 --- a/pkg/workflow/pi_engine_test.go +++ b/pkg/workflow/pi_engine_test.go @@ -33,6 +33,48 @@ func TestPiEngine_GetRequiredSecretNames(t *testing.T) { assert.NotContains(t, secrets, "PI_API_KEY", "Required secrets should not include PI_API_KEY") } +func TestPiEngine_GetRequiredSecretNames_CopilotProvider(t *testing.T) { + engine := NewPiEngine() + workflowData := &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "pi", Model: "copilot/claude-sonnet-4-20250514"}, + } + secrets := engine.GetRequiredSecretNames(workflowData) + assert.Contains(t, secrets, "COPILOT_GITHUB_TOKEN", "copilot/ prefix should require COPILOT_GITHUB_TOKEN") +} + +func TestPiEngine_GetRequiredSecretNames_AnthropicProvider(t *testing.T) { + engine := NewPiEngine() + workflowData := &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "pi", Model: "anthropic/claude-sonnet-4-20250514"}, + } + secrets := engine.GetRequiredSecretNames(workflowData) + assert.Contains(t, secrets, "ANTHROPIC_API_KEY", "anthropic/ prefix should require ANTHROPIC_API_KEY") + assert.NotContains(t, secrets, "COPILOT_GITHUB_TOKEN", "anthropic/ prefix should not require COPILOT_GITHUB_TOKEN") +} + +func TestPiEngine_GetRequiredSecretNames_CodexProvider(t *testing.T) { + engine := NewPiEngine() + workflowData := &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "pi", Model: "codex/gpt-4o"}, + } + secrets := engine.GetRequiredSecretNames(workflowData) + assert.Contains(t, secrets, "CODEX_API_KEY", "codex/ prefix should require CODEX_API_KEY") + assert.Contains(t, secrets, "OPENAI_API_KEY", "codex/ prefix should also require OPENAI_API_KEY (from Codex backend profile)") +} + +func TestPiEngine_GetRequiredSecretNames_NoPrefix(t *testing.T) { + engine := NewPiEngine() + workflowData := &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "pi", Model: "claude-sonnet-4-20250514"}, + } + secrets := engine.GetRequiredSecretNames(workflowData) + assert.Contains(t, secrets, "COPILOT_GITHUB_TOKEN", "bare model (no prefix) should default to COPILOT_GITHUB_TOKEN") +} + func TestPiEngine_GetLogParserScriptId(t *testing.T) { engine := NewPiEngine() assert.Equal(t, "parse_pi_log", engine.GetLogParserScriptId(), "Log parser script ID should be parse_pi_log") @@ -71,17 +113,17 @@ func TestPiEngine_GetInstallationSteps_NoCustomCommand(t *testing.T) { steps := engine.GetInstallationSteps(workflowData) assert.NotEmpty(t, steps, "Installation steps should not be empty") - // The steps should reference @pi/cli + // The steps should reference @mariozechner/pi-coding-agent found := false for _, step := range steps { for _, line := range step { - if strings.Contains(line, "@pi/cli") { + if strings.Contains(line, "@mariozechner/pi-coding-agent") { found = true break } } } - assert.True(t, found, "Installation steps should install @pi/cli") + assert.True(t, found, "Installation steps should install @mariozechner/pi-coding-agent") } func TestPiEngine_GetInstallationSteps_WithCustomCommand(t *testing.T) { @@ -131,9 +173,12 @@ func TestPiEngine_GetExecutionSteps_Basic(t *testing.T) { stepText := strings.Join(steps[0], "\n") assert.Contains(t, stepText, "Execute Pi CLI", "Step should be named 'Execute Pi CLI'") - assert.Contains(t, stepText, "pi run", "Step should run `pi run`") - assert.Contains(t, stepText, "json-log", "Step should include JSON log flag") + assert.Contains(t, stepText, "--print", "Step should use --print flag (non-interactive mode)") + assert.Contains(t, stepText, "--mode json", "Step should use --mode json for structured JSONL output") + assert.NotContains(t, stepText, "pi run", "Step should not use the removed 'pi run' subcommand") + assert.NotContains(t, stepText, "--json-log", "Step should not use the removed --json-log flag") assert.Contains(t, stepText, "agentic_execution", "Step should have agentic_execution id") + assert.Contains(t, stepText, "pi_provider.cjs", "Step should load the provider extension") assert.Contains(t, stepText, "pi_steering_extension.cjs", "Step should automatically load the steering extension") } @@ -141,15 +186,53 @@ func TestPiEngine_GetExecutionSteps_WithModel(t *testing.T) { engine := NewPiEngine() workflowData := &WorkflowData{ Name: "test-workflow", - EngineConfig: &EngineConfig{ID: "pi", Model: "pi-3"}, + EngineConfig: &EngineConfig{ID: "pi", Model: "copilot/claude-sonnet-4"}, ParsedTools: NewTools(map[string]any{}), } steps := engine.GetExecutionSteps(workflowData, "/tmp/gh-aw/agent-stdio.log") require.NotEmpty(t, steps, "Steps should not be empty") stepText := strings.Join(steps[0], "\n") - assert.Contains(t, stepText, "PI_MODEL", "Step env should include PI_MODEL when model is configured") - assert.Contains(t, stepText, "pi-3", "Step env should include the model value") + // When firewall is not enabled, Pi is invoked with the --model flag using the + // native github-copilot provider (Pi's built-in provider for GitHub Copilot). + assert.Contains(t, stepText, "--model", "Step should pass --model flag to Pi CLI") + assert.Contains(t, stepText, "github-copilot", "Non-firewall copilot model should use github-copilot/ provider prefix") + assert.Contains(t, stepText, "claude-sonnet-4", "Step should include the model ID portion") + assert.NotContains(t, stepText, "PI_MODEL", "Step should not set the unsupported PI_MODEL env var") +} + +func TestPiEngine_GetExecutionSteps_ProviderPrefixCopilot(t *testing.T) { + engine := NewPiEngine() + workflowData := &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "pi", Model: "copilot/claude-sonnet-4-20250514"}, + ParsedTools: NewTools(map[string]any{}), + } + steps := engine.GetExecutionSteps(workflowData, "/tmp/gh-aw/agent-stdio.log") + require.Len(t, steps, 1, "Should produce exactly one execution step") + + stepText := strings.Join(steps[0], "\n") + assert.Contains(t, stepText, "COPILOT_GITHUB_TOKEN", "copilot/ prefix should inject COPILOT_GITHUB_TOKEN") + // OPENAI_API_KEY must not be injected: Pi reads it and routes to api.openai.com, + // bypassing the github-copilot provider and the AWF firewall. + assert.NotContains(t, stepText, "OPENAI_API_KEY", "copilot/ prefix must not inject OPENAI_API_KEY (causes Pi to use OpenAI instead of github-copilot)") + assert.Contains(t, stepText, "pi_provider.cjs", "Step should load the provider extension") + assert.Contains(t, stepText, "--model", "Step should pass --model flag to Pi CLI") +} + +func TestPiEngine_GetExecutionSteps_ProviderPrefixAnthropic(t *testing.T) { + engine := NewPiEngine() + workflowData := &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "pi", Model: "anthropic/claude-sonnet-4-20250514"}, + ParsedTools: NewTools(map[string]any{}), + } + steps := engine.GetExecutionSteps(workflowData, "/tmp/gh-aw/agent-stdio.log") + require.Len(t, steps, 1, "Should produce exactly one execution step") + + stepText := strings.Join(steps[0], "\n") + assert.Contains(t, stepText, "ANTHROPIC_API_KEY", "anthropic/ prefix should inject ANTHROPIC_API_KEY") + assert.NotContains(t, stepText, "COPILOT_GITHUB_TOKEN", "anthropic/ prefix should not inject COPILOT_GITHUB_TOKEN") } func TestPiEngine_ImplementsCodingAgentEngine(t *testing.T) {