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) {